1/*
2 * auth.c: authentication support functions for Subversion
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25#include <apr_pools.h>
26#include <apr_tables.h>
27#include <apr_strings.h>
28
29#include "svn_hash.h"
30#include "svn_types.h"
31#include "svn_string.h"
32#include "svn_error.h"
33#include "svn_auth.h"
34#include "svn_config.h"
35#include "svn_private_config.h"
36#include "svn_dso.h"
37#include "svn_version.h"
38#include "private/svn_dep_compat.h"
39
40#include "auth.h"
41
42/* AN OVERVIEW
43   ===========
44
45   A good way to think of this machinery is as a set of tables.
46
47     - Each type of credentials selects a single table.
48
49     - In a given table, each row is a 'provider' capable of returning
50       the same type of credentials.  Each column represents a
51       provider's repeated attempts to provide credentials.
52
53
54   Fetching Credentials from Providers
55   -----------------------------------
56
57   When the caller asks for a particular type of credentials, the
58   machinery in this file walks over the appropriate table.  It starts
59   with the first provider (first row), and calls first_credentials()
60   to get the first set of credentials (first column).  If the caller
61   is unhappy with the credentials, then each subsequent call to
62   next_credentials() traverses the row from left to right.  If the
63   provider returns error at any point, then we go to the next provider
64   (row).  We continue this way until every provider fails, or
65   until the client is happy with the returned credentials.
66
67   Note that the caller cannot see the table traversal, and thus has
68   no idea when we switch providers.
69
70
71   Storing Credentials with Providers
72   ----------------------------------
73
74   When the server has validated a set of credentials, and when
75   credential caching is enabled, we have the chance to store those
76   credentials for later use.  The provider which provided the working
77   credentials is the first one given the opportunity to (re)cache
78   those credentials.  Its save_credentials() function is invoked with
79   the working credentials.  If that provider reports that it
80   successfully stored the credentials, we're done.  Otherwise, we
81   walk the providers (rows) for that type of credentials in order
82   from the top of the table, allowing each in turn the opportunity to
83   store the credentials.  When one reports that it has done so
84   successfully -- or when we run out of providers (rows) to try --
85   the table walk ends.
86*/
87
88
89
90/* This effectively defines a single table.  Every provider in this
91   array returns the same kind of credentials. */
92typedef struct provider_set_t
93{
94  /* ordered array of svn_auth_provider_object_t */
95  apr_array_header_t *providers;
96
97} provider_set_t;
98
99
100/* The main auth baton. */
101struct svn_auth_baton_t
102{
103  /* a collection of tables.  maps cred_kind -> provider_set */
104  apr_hash_t *tables;
105
106  /* the pool I'm allocated in. */
107  apr_pool_t *pool;
108
109  /* run-time parameters needed by providers. */
110  apr_hash_t *parameters;
111
112  /* run-time credentials cache. */
113  apr_hash_t *creds_cache;
114
115};
116
117/* Abstracted iteration baton */
118struct svn_auth_iterstate_t
119{
120  provider_set_t *table;        /* the table being searched */
121  int provider_idx;             /* the current provider (row) */
122  svn_boolean_t got_first;      /* did we get the provider's first creds? */
123  void *provider_iter_baton;    /* the provider's own iteration context */
124  const char *realmstring;      /* The original realmstring passed in */
125  const char *cache_key;        /* key to use in auth_baton's creds_cache */
126  svn_auth_baton_t *auth_baton; /* the original auth_baton. */
127};
128
129
130
131void
132svn_auth_open(svn_auth_baton_t **auth_baton,
133              const apr_array_header_t *providers,
134              apr_pool_t *pool)
135{
136  svn_auth_baton_t *ab;
137  svn_auth_provider_object_t *provider;
138  int i;
139
140  /* Build the auth_baton. */
141  ab = apr_pcalloc(pool, sizeof(*ab));
142  ab->tables = apr_hash_make(pool);
143  ab->parameters = apr_hash_make(pool);
144  ab->creds_cache = apr_hash_make(pool);
145  ab->pool = pool;
146
147  /* Register each provider in order.  Providers of different
148     credentials will be automatically sorted into different tables by
149     register_provider(). */
150  for (i = 0; i < providers->nelts; i++)
151    {
152      provider_set_t *table;
153      provider = APR_ARRAY_IDX(providers, i, svn_auth_provider_object_t *);
154
155      /* Add it to the appropriate table in the auth_baton */
156      table = svn_hash_gets(ab->tables, provider->vtable->cred_kind);
157      if (! table)
158        {
159          table = apr_pcalloc(pool, sizeof(*table));
160          table->providers
161            = apr_array_make(pool, 1, sizeof(svn_auth_provider_object_t *));
162
163          svn_hash_sets(ab->tables, provider->vtable->cred_kind, table);
164        }
165      APR_ARRAY_PUSH(table->providers, svn_auth_provider_object_t *)
166        = provider;
167    }
168
169  *auth_baton = ab;
170}
171
172
173
174void
175svn_auth_set_parameter(svn_auth_baton_t *auth_baton,
176                       const char *name,
177                       const void *value)
178{
179  svn_hash_sets(auth_baton->parameters, name, value);
180}
181
182const void *
183svn_auth_get_parameter(svn_auth_baton_t *auth_baton,
184                       const char *name)
185{
186  return svn_hash_gets(auth_baton->parameters, name);
187}
188
189
190/* Return the key used to address the in-memory cache of auth
191   credentials of type CRED_KIND and associated with REALMSTRING. */
192static const char *
193make_cache_key(const char *cred_kind,
194               const char *realmstring,
195               apr_pool_t *pool)
196{
197  return apr_pstrcat(pool, cred_kind, ":", realmstring, (char *)NULL);
198}
199
200svn_error_t *
201svn_auth_first_credentials(void **credentials,
202                           svn_auth_iterstate_t **state,
203                           const char *cred_kind,
204                           const char *realmstring,
205                           svn_auth_baton_t *auth_baton,
206                           apr_pool_t *pool)
207{
208  int i = 0;
209  provider_set_t *table;
210  svn_auth_provider_object_t *provider = NULL;
211  void *creds = NULL;
212  void *iter_baton = NULL;
213  svn_boolean_t got_first = FALSE;
214  svn_auth_iterstate_t *iterstate;
215  const char *cache_key;
216
217  /* Get the appropriate table of providers for CRED_KIND. */
218  table = svn_hash_gets(auth_baton->tables, cred_kind);
219  if (! table)
220    return svn_error_createf(SVN_ERR_AUTHN_NO_PROVIDER, NULL,
221                             _("No provider registered for '%s' credentials"),
222                             cred_kind);
223
224  /* First, see if we have cached creds in the auth_baton. */
225  cache_key = make_cache_key(cred_kind, realmstring, pool);
226  creds = svn_hash_gets(auth_baton->creds_cache, cache_key);
227  if (creds)
228    {
229       got_first = FALSE;
230    }
231  else
232    /* If not, find a provider that can give "first" credentials. */
233    {
234      /* Find a provider that can give "first" credentials. */
235      for (i = 0; i < table->providers->nelts; i++)
236        {
237          provider = APR_ARRAY_IDX(table->providers, i,
238                                   svn_auth_provider_object_t *);
239          SVN_ERR(provider->vtable->first_credentials(&creds, &iter_baton,
240                                                      provider->provider_baton,
241                                                      auth_baton->parameters,
242                                                      realmstring,
243                                                      auth_baton->pool));
244
245          if (creds != NULL)
246            {
247              got_first = TRUE;
248              break;
249            }
250        }
251    }
252
253  if (! creds)
254    *state = NULL;
255  else
256    {
257      /* Build an abstract iteration state. */
258      iterstate = apr_pcalloc(pool, sizeof(*iterstate));
259      iterstate->table = table;
260      iterstate->provider_idx = i;
261      iterstate->got_first = got_first;
262      iterstate->provider_iter_baton = iter_baton;
263      iterstate->realmstring = apr_pstrdup(pool, realmstring);
264      iterstate->cache_key = cache_key;
265      iterstate->auth_baton = auth_baton;
266      *state = iterstate;
267
268      /* Put the creds in the cache */
269      svn_hash_sets(auth_baton->creds_cache,
270                    apr_pstrdup(auth_baton->pool, cache_key),
271                    creds);
272    }
273
274  *credentials = creds;
275
276  return SVN_NO_ERROR;
277}
278
279
280svn_error_t *
281svn_auth_next_credentials(void **credentials,
282                          svn_auth_iterstate_t *state,
283                          apr_pool_t *pool)
284{
285  svn_auth_baton_t *auth_baton = state->auth_baton;
286  svn_auth_provider_object_t *provider;
287  provider_set_t *table = state->table;
288  void *creds = NULL;
289
290  /* Continue traversing the table from where we left off. */
291  for (/* no init */;
292       state->provider_idx < table->providers->nelts;
293       state->provider_idx++)
294    {
295      provider = APR_ARRAY_IDX(table->providers,
296                               state->provider_idx,
297                               svn_auth_provider_object_t *);
298      if (! state->got_first)
299        {
300          SVN_ERR(provider->vtable->first_credentials(
301                      &creds, &(state->provider_iter_baton),
302                      provider->provider_baton, auth_baton->parameters,
303                      state->realmstring, auth_baton->pool));
304          state->got_first = TRUE;
305        }
306      else if (provider->vtable->next_credentials)
307        {
308          SVN_ERR(provider->vtable->next_credentials(
309                      &creds, state->provider_iter_baton,
310                      provider->provider_baton, auth_baton->parameters,
311                      state->realmstring, auth_baton->pool));
312        }
313
314      if (creds != NULL)
315        {
316          /* Put the creds in the cache */
317          svn_hash_sets(auth_baton->creds_cache, state->cache_key, creds);
318          break;
319        }
320
321      state->got_first = FALSE;
322    }
323
324  *credentials = creds;
325
326  return SVN_NO_ERROR;
327}
328
329
330svn_error_t *
331svn_auth_save_credentials(svn_auth_iterstate_t *state,
332                          apr_pool_t *pool)
333{
334  int i;
335  svn_auth_provider_object_t *provider;
336  svn_boolean_t save_succeeded = FALSE;
337  const char *no_auth_cache;
338  svn_auth_baton_t *auth_baton;
339  void *creds;
340
341  if (! state || state->table->providers->nelts <= state->provider_idx)
342    return SVN_NO_ERROR;
343
344  auth_baton = state->auth_baton;
345  creds = svn_hash_gets(state->auth_baton->creds_cache, state->cache_key);
346  if (! creds)
347    return SVN_NO_ERROR;
348
349  /* Do not save the creds if SVN_AUTH_PARAM_NO_AUTH_CACHE is set */
350  no_auth_cache = svn_hash_gets(auth_baton->parameters,
351                                SVN_AUTH_PARAM_NO_AUTH_CACHE);
352  if (no_auth_cache)
353    return SVN_NO_ERROR;
354
355  /* First, try to save the creds using the provider that produced them. */
356  provider = APR_ARRAY_IDX(state->table->providers,
357                           state->provider_idx,
358                           svn_auth_provider_object_t *);
359  if (provider->vtable->save_credentials)
360    SVN_ERR(provider->vtable->save_credentials(&save_succeeded,
361                                               creds,
362                                               provider->provider_baton,
363                                               auth_baton->parameters,
364                                               state->realmstring,
365                                               pool));
366  if (save_succeeded)
367    return SVN_NO_ERROR;
368
369  /* Otherwise, loop from the top of the list, asking every provider
370     to attempt a save.  ### todo: someday optimize so we don't
371     necessarily start from the top of the list. */
372  for (i = 0; i < state->table->providers->nelts; i++)
373    {
374      provider = APR_ARRAY_IDX(state->table->providers, i,
375                               svn_auth_provider_object_t *);
376      if (provider->vtable->save_credentials)
377        SVN_ERR(provider->vtable->save_credentials
378                (&save_succeeded, creds,
379                 provider->provider_baton,
380                 auth_baton->parameters,
381                 state->realmstring,
382                 pool));
383
384      if (save_succeeded)
385        break;
386    }
387
388  /* ### notice that at the moment, if no provider can save, there's
389     no way the caller will know. */
390
391  return SVN_NO_ERROR;
392}
393
394
395svn_error_t *
396svn_auth_forget_credentials(svn_auth_baton_t *auth_baton,
397                            const char *cred_kind,
398                            const char *realmstring,
399                            apr_pool_t *scratch_pool)
400{
401  SVN_ERR_ASSERT((cred_kind && realmstring) || (!cred_kind && !realmstring));
402
403  /* If we have a CRED_KIND and REALMSTRING, we clear out just the
404     cached item (if any).  Otherwise, empty the whole hash. */
405  if (cred_kind)
406    {
407      svn_hash_sets(auth_baton->creds_cache,
408                    make_cache_key(cred_kind, realmstring, scratch_pool),
409                    NULL);
410    }
411  else
412    {
413      apr_hash_clear(auth_baton->creds_cache);
414    }
415
416  return SVN_NO_ERROR;
417}
418
419
420svn_auth_ssl_server_cert_info_t *
421svn_auth_ssl_server_cert_info_dup
422  (const svn_auth_ssl_server_cert_info_t *info, apr_pool_t *pool)
423{
424  svn_auth_ssl_server_cert_info_t *new_info
425    = apr_palloc(pool, sizeof(*new_info));
426
427  *new_info = *info;
428
429  new_info->hostname = apr_pstrdup(pool, new_info->hostname);
430  new_info->fingerprint = apr_pstrdup(pool, new_info->fingerprint);
431  new_info->valid_from = apr_pstrdup(pool, new_info->valid_from);
432  new_info->valid_until = apr_pstrdup(pool, new_info->valid_until);
433  new_info->issuer_dname = apr_pstrdup(pool, new_info->issuer_dname);
434  new_info->ascii_cert = apr_pstrdup(pool, new_info->ascii_cert);
435
436  return new_info;
437}
438
439svn_error_t *
440svn_auth_get_platform_specific_provider(svn_auth_provider_object_t **provider,
441                                        const char *provider_name,
442                                        const char *provider_type,
443                                        apr_pool_t *pool)
444{
445  *provider = NULL;
446
447  if (apr_strnatcmp(provider_name, "gnome_keyring") == 0 ||
448      apr_strnatcmp(provider_name, "kwallet") == 0)
449    {
450#if defined(SVN_HAVE_GNOME_KEYRING) || defined(SVN_HAVE_KWALLET)
451      apr_dso_handle_t *dso;
452      apr_dso_handle_sym_t provider_function_symbol, version_function_symbol;
453      const char *library_label, *library_name;
454      const char *provider_function_name, *version_function_name;
455      library_name = apr_psprintf(pool,
456                                  "libsvn_auth_%s-%d.so.%d",
457                                  provider_name,
458                                  SVN_VER_MAJOR, SVN_SOVERSION);
459      library_label = apr_psprintf(pool, "svn_%s", provider_name);
460      provider_function_name = apr_psprintf(pool,
461                                            "svn_auth_get_%s_%s_provider",
462                                            provider_name, provider_type);
463      version_function_name = apr_psprintf(pool,
464                                           "svn_auth_%s_version",
465                                           provider_name);
466      SVN_ERR(svn_dso_load(&dso, library_name));
467      if (dso)
468        {
469          if (apr_dso_sym(&version_function_symbol,
470                          dso,
471                          version_function_name) == 0)
472            {
473              svn_version_func_t version_function
474                = version_function_symbol;
475              svn_version_checklist_t check_list[2];
476
477              check_list[0].label = library_label;
478              check_list[0].version_query = version_function;
479              check_list[1].label = NULL;
480              check_list[1].version_query = NULL;
481              SVN_ERR(svn_ver_check_list(svn_subr_version(), check_list));
482            }
483          if (apr_dso_sym(&provider_function_symbol,
484                          dso,
485                          provider_function_name) == 0)
486            {
487              if (strcmp(provider_type, "simple") == 0)
488                {
489                  svn_auth_simple_provider_func_t provider_function
490                    = provider_function_symbol;
491                  provider_function(provider, pool);
492                }
493              else if (strcmp(provider_type, "ssl_client_cert_pw") == 0)
494                {
495                  svn_auth_ssl_client_cert_pw_provider_func_t provider_function
496                    = provider_function_symbol;
497                  provider_function(provider, pool);
498                }
499            }
500        }
501#endif
502    }
503  else
504    {
505#if defined(SVN_HAVE_GPG_AGENT)
506      if (strcmp(provider_name, "gpg_agent") == 0 &&
507          strcmp(provider_type, "simple") == 0)
508        {
509          svn_auth_get_gpg_agent_simple_provider(provider, pool);
510        }
511#endif
512#ifdef SVN_HAVE_KEYCHAIN_SERVICES
513      if (strcmp(provider_name, "keychain") == 0 &&
514          strcmp(provider_type, "simple") == 0)
515        {
516          svn_auth_get_keychain_simple_provider(provider, pool);
517        }
518      else if (strcmp(provider_name, "keychain") == 0 &&
519               strcmp(provider_type, "ssl_client_cert_pw") == 0)
520        {
521          svn_auth_get_keychain_ssl_client_cert_pw_provider(provider, pool);
522        }
523#endif
524
525#if defined(WIN32) && !defined(__MINGW32__)
526      if (strcmp(provider_name, "windows") == 0 &&
527          strcmp(provider_type, "simple") == 0)
528        {
529          svn_auth_get_windows_simple_provider(provider, pool);
530        }
531      else if (strcmp(provider_name, "windows") == 0 &&
532               strcmp(provider_type, "ssl_client_cert_pw") == 0)
533        {
534          svn_auth_get_windows_ssl_client_cert_pw_provider(provider, pool);
535        }
536      else if (strcmp(provider_name, "windows") == 0 &&
537               strcmp(provider_type, "ssl_server_trust") == 0)
538        {
539          svn_auth_get_windows_ssl_server_trust_provider(provider, pool);
540        }
541#endif
542    }
543
544  return SVN_NO_ERROR;
545}
546
547svn_error_t *
548svn_auth_get_platform_specific_client_providers(apr_array_header_t **providers,
549                                                svn_config_t *config,
550                                                apr_pool_t *pool)
551{
552  svn_auth_provider_object_t *provider;
553  const char *password_stores_config_option;
554  apr_array_header_t *password_stores;
555  int i;
556
557#define SVN__MAYBE_ADD_PROVIDER(list, p) \
558  { if (p) APR_ARRAY_PUSH(list, svn_auth_provider_object_t *) = p; }
559
560#define SVN__DEFAULT_AUTH_PROVIDER_LIST \
561         "gnome-keyring,kwallet,keychain,gpg-agent,windows-cryptoapi"
562
563  *providers = apr_array_make(pool, 12, sizeof(svn_auth_provider_object_t *));
564
565  /* Fetch the configured list of password stores, and split them into
566     an array. */
567  svn_config_get(config,
568                 &password_stores_config_option,
569                 SVN_CONFIG_SECTION_AUTH,
570                 SVN_CONFIG_OPTION_PASSWORD_STORES,
571                 SVN__DEFAULT_AUTH_PROVIDER_LIST);
572  password_stores = svn_cstring_split(password_stores_config_option,
573                                      " ,", TRUE, pool);
574
575  for (i = 0; i < password_stores->nelts; i++)
576    {
577      const char *password_store = APR_ARRAY_IDX(password_stores, i,
578                                                 const char *);
579
580      /* GNOME Keyring */
581      if (apr_strnatcmp(password_store, "gnome-keyring") == 0)
582        {
583          SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
584                                                          "gnome_keyring",
585                                                          "simple",
586                                                          pool));
587          SVN__MAYBE_ADD_PROVIDER(*providers, provider);
588
589          SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
590                                                          "gnome_keyring",
591                                                          "ssl_client_cert_pw",
592                                                          pool));
593          SVN__MAYBE_ADD_PROVIDER(*providers, provider);
594        }
595      /* GPG-AGENT */
596      else if (apr_strnatcmp(password_store, "gpg-agent") == 0)
597        {
598          SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
599                                                          "gpg_agent",
600                                                          "simple",
601                                                          pool));
602          SVN__MAYBE_ADD_PROVIDER(*providers, provider);
603        }
604      /* KWallet */
605      else if (apr_strnatcmp(password_store, "kwallet") == 0)
606        {
607          SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
608                                                          "kwallet",
609                                                          "simple",
610                                                          pool));
611          SVN__MAYBE_ADD_PROVIDER(*providers, provider);
612
613          SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
614                                                          "kwallet",
615                                                          "ssl_client_cert_pw",
616                                                          pool));
617          SVN__MAYBE_ADD_PROVIDER(*providers, provider);
618        }
619      /* Keychain */
620      else if (apr_strnatcmp(password_store, "keychain") == 0)
621        {
622          SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
623                                                          "keychain",
624                                                          "simple",
625                                                          pool));
626          SVN__MAYBE_ADD_PROVIDER(*providers, provider);
627
628          SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
629                                                          "keychain",
630                                                          "ssl_client_cert_pw",
631                                                          pool));
632          SVN__MAYBE_ADD_PROVIDER(*providers, provider);
633        }
634      /* Windows */
635      else if (apr_strnatcmp(password_store, "windows-cryptoapi") == 0)
636        {
637          SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
638                                                          "windows",
639                                                          "simple",
640                                                          pool));
641          SVN__MAYBE_ADD_PROVIDER(*providers, provider);
642
643          SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
644                                                          "windows",
645                                                          "ssl_client_cert_pw",
646                                                          pool));
647          SVN__MAYBE_ADD_PROVIDER(*providers, provider);
648        }
649    }
650
651  return SVN_NO_ERROR;
652}
653