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