2 * simple_providers.c: providers for SVN_AUTH_CRED_SIMPLE
4 * ====================================================================
5 * Copyright (c) 2000-2004 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
25 #include <apr_pools.h>
27 #include "svn_error.h"
29 #include "svn_config.h"
32 #include "svn_private_config.h"
34 /*-----------------------------------------------------------------------*/
36 /*-----------------------------------------------------------------------*/
38 /* The keys that will be stored on disk */
39 #define SVN_AUTH__AUTHFILE_USERNAME_KEY "username"
40 #define SVN_AUTH__AUTHFILE_PASSWORD_KEY "password"
41 #define SVN_AUTH__AUTHFILE_PASSTYPE_KEY "passtype"
43 #define SVN_AUTH__SIMPLE_PASSWORD_TYPE "simple"
44 #define SVN_AUTH__WINCRYPT_PASSWORD_TYPE "wincrypt"
45 #define SVN_AUTH__KEYCHAIN_PASSWORD_TYPE "keychain"
48 /* A function that stores PASSWORD (or some encrypted version thereof)
49 either directly in CREDS, or externally using REALMSTRING and USERNAME
50 as keys into the external store. If NON_INTERACTIVE is set, the user
51 must not be involved in the storage process. POOL is used for any
52 necessary allocation. */
53 typedef svn_boolean_t (*password_set_t
)(apr_hash_t
*creds
,
54 const char *realmstring
,
57 svn_boolean_t non_interactive
,
60 /* A function that stores in *PASSWORD (potentially after decrypting it)
61 the user's password. It might be obtained directly from CREDS, or
62 from an external store, using REALMSTRING and USERNAME as keys.
63 If NON_INTERACTIVE is set, the user must not be involved in the
64 retrieval process. POOL is used for any necessary allocation. */
65 typedef svn_boolean_t (*password_get_t
)(const char **password
,
67 const char *realmstring
,
69 svn_boolean_t non_interactive
,
74 /* Implementation of password_get_t that retrieves the plaintext password
77 simple_password_get(const char **password
,
79 const char *realmstring
,
81 svn_boolean_t non_interactive
,
85 str
= apr_hash_get(creds
, SVN_AUTH__AUTHFILE_PASSWORD_KEY
,
89 *password
= str
->data
;
95 /* Implementation of password_set_t that store the plaintext password
98 simple_password_set(apr_hash_t
*creds
,
99 const char *realmstring
,
100 const char *username
,
101 const char *password
,
102 svn_boolean_t non_interactive
,
105 apr_hash_set(creds
, SVN_AUTH__AUTHFILE_PASSWORD_KEY
, APR_HASH_KEY_STRING
,
106 svn_string_create(password
, pool
));
110 /* Common implementation for simple_first_creds and
111 windows_simple_first_creds. Uses PARAMETERS, REALMSTRING and the
112 simple auth provider's username and password cache to fill a set of
113 CREDENTIALS. PASSWORD_GET is used to obtain the password value.
114 PASSTYPE identifies the type of the cached password. CREDENTIALS are
115 allocated from POOL. */
117 simple_first_creds_helper(void **credentials
,
119 void *provider_baton
,
120 apr_hash_t
*parameters
,
121 const char *realmstring
,
122 password_get_t password_get
,
123 const char *passtype
,
126 const char *config_dir
= apr_hash_get(parameters
,
127 SVN_AUTH_PARAM_CONFIG_DIR
,
128 APR_HASH_KEY_STRING
);
129 const char *username
= apr_hash_get(parameters
,
130 SVN_AUTH_PARAM_DEFAULT_USERNAME
,
131 APR_HASH_KEY_STRING
);
132 const char *password
= apr_hash_get(parameters
,
133 SVN_AUTH_PARAM_DEFAULT_PASSWORD
,
134 APR_HASH_KEY_STRING
);
135 svn_boolean_t non_interactive
= apr_hash_get(parameters
,
136 SVN_AUTH_PARAM_NON_INTERACTIVE
,
137 APR_HASH_KEY_STRING
) != NULL
;
139 svn_boolean_t may_save
= username
|| password
;
142 /* If we don't have a usename and a password yet, we try the auth cache */
143 if (! (username
&& password
))
145 apr_hash_t
*creds_hash
= NULL
;
147 /* Try to load credentials from a file on disk, based on the
148 realmstring. Don't throw an error, though: if something went
149 wrong reading the file, no big deal. What really matters is that
150 we failed to get the creds, so allow the auth system to try the
152 err
= svn_config_read_auth_data(&creds_hash
, SVN_AUTH_CRED_SIMPLE
,
153 realmstring
, config_dir
, pool
);
154 svn_error_clear(err
);
155 if (! err
&& creds_hash
)
160 str
= apr_hash_get(creds_hash
,
161 SVN_AUTH__AUTHFILE_USERNAME_KEY
,
162 APR_HASH_KEY_STRING
);
163 if (str
&& str
->data
)
164 username
= str
->data
;
169 svn_boolean_t have_passtype
;
170 /* The password type in the auth data must match the
171 mangler's type, otherwise the password must be
172 interpreted by another provider. */
173 str
= apr_hash_get(creds_hash
,
174 SVN_AUTH__AUTHFILE_PASSTYPE_KEY
,
175 APR_HASH_KEY_STRING
);
176 have_passtype
= (str
&& str
->data
);
177 if (have_passtype
&& passtype
178 && 0 != strcmp(str
->data
, passtype
))
182 if (!password_get(&password
, creds_hash
, realmstring
,
183 username
, non_interactive
, pool
))
186 /* If the auth data didn't contain a password type,
187 force a write to upgrade the format of the auth
189 if (password
&& passtype
&& !have_passtype
)
196 /* Ask the OS for the username if we have a password but no
198 if (password
&& ! username
)
199 username
= svn_user_get_name(pool
);
201 if (username
&& password
)
203 svn_auth_cred_simple_t
*creds
= apr_pcalloc(pool
, sizeof(*creds
));
204 creds
->username
= username
;
205 creds
->password
= password
;
206 creds
->may_save
= may_save
;
207 *credentials
= creds
;
218 /* Common implementation for simple_save_creds and
219 windows_simple_save_creds. Uses PARAMETERS and REALMSTRING to save
220 a set of CREDENTIALS to the simple auth provider's username and
221 password cache. PASSWORD_SET is used to store the password.
222 PASSTYPE identifies the type of the cached password. Allocates from POOL. */
224 simple_save_creds_helper(svn_boolean_t
*saved
,
226 void *provider_baton
,
227 apr_hash_t
*parameters
,
228 const char *realmstring
,
229 password_set_t password_set
,
230 const char *passtype
,
233 svn_auth_cred_simple_t
*creds
= credentials
;
234 apr_hash_t
*creds_hash
= NULL
;
235 const char *config_dir
;
237 svn_boolean_t dont_store_passwords
=
238 apr_hash_get(parameters
,
239 SVN_AUTH_PARAM_DONT_STORE_PASSWORDS
,
240 APR_HASH_KEY_STRING
) != NULL
;
241 svn_boolean_t non_interactive
= apr_hash_get(parameters
,
242 SVN_AUTH_PARAM_NON_INTERACTIVE
,
243 APR_HASH_KEY_STRING
) != NULL
;
244 svn_boolean_t no_auth_cache
=
245 (! creds
->may_save
) || (apr_hash_get(parameters
,
246 SVN_AUTH_PARAM_NO_AUTH_CACHE
,
247 APR_HASH_KEY_STRING
) != NULL
);
249 svn_boolean_t username_stored
= FALSE
;
250 svn_boolean_t password_stored
= TRUE
;
257 config_dir
= apr_hash_get(parameters
,
258 SVN_AUTH_PARAM_CONFIG_DIR
,
259 APR_HASH_KEY_STRING
);
261 /* Put the credentials in a hash and save it to disk */
262 creds_hash
= apr_hash_make(pool
);
264 /* Maybe cache the username. */
267 apr_hash_set(creds_hash
, SVN_AUTH__AUTHFILE_USERNAME_KEY
,
269 svn_string_create(creds
->username
, pool
));
270 username_stored
= TRUE
;
273 /* Maybe cache the password. */
274 if (! dont_store_passwords
)
276 password_stored
= password_set(creds_hash
, realmstring
, creds
->username
,
277 creds
->password
, non_interactive
, pool
);
280 /* Store the password type with the auth data, so that we
281 know which provider owns the password. */
284 apr_hash_set(creds_hash
, SVN_AUTH__AUTHFILE_PASSTYPE_KEY
,
286 svn_string_create(passtype
, pool
));
293 /* If we cached anything, write it to disk. */
294 if (username_stored
|| password_stored
)
296 err
= svn_config_write_auth_data(creds_hash
, SVN_AUTH_CRED_SIMPLE
,
297 realmstring
, config_dir
, pool
);
298 svn_error_clear(err
);
305 /* Get cached (unencrypted) credentials from the simple provider's cache. */
307 simple_first_creds(void **credentials
,
309 void *provider_baton
,
310 apr_hash_t
*parameters
,
311 const char *realmstring
,
314 return simple_first_creds_helper(credentials
,
315 iter_baton
, provider_baton
,
316 parameters
, realmstring
,
318 SVN_AUTH__SIMPLE_PASSWORD_TYPE
,
322 /* Save (unencrypted) credentials to the simple provider's cache. */
324 simple_save_creds(svn_boolean_t
*saved
,
326 void *provider_baton
,
327 apr_hash_t
*parameters
,
328 const char *realmstring
,
331 return simple_save_creds_helper(saved
, credentials
, provider_baton
,
332 parameters
, realmstring
,
334 SVN_AUTH__SIMPLE_PASSWORD_TYPE
,
338 static const svn_auth_provider_t simple_provider
= {
339 SVN_AUTH_CRED_SIMPLE
,
348 svn_auth_get_simple_provider(svn_auth_provider_object_t
**provider
,
351 svn_auth_provider_object_t
*po
= apr_pcalloc(pool
, sizeof(*po
));
353 po
->vtable
= &simple_provider
;
358 /*-----------------------------------------------------------------------*/
359 /* Prompt provider */
360 /*-----------------------------------------------------------------------*/
362 /* Baton type for username/password prompting. */
365 svn_auth_simple_prompt_func_t prompt_func
;
368 /* how many times to re-prompt after the first one fails */
370 } simple_prompt_provider_baton_t
;
373 /* Iteration baton type for username/password prompting. */
376 /* how many times we've reprompted */
378 } simple_prompt_iter_baton_t
;
382 /*** Helper Functions ***/
384 prompt_for_simple_creds(svn_auth_cred_simple_t
**cred_p
,
385 simple_prompt_provider_baton_t
*pb
,
386 apr_hash_t
*parameters
,
387 const char *realmstring
,
388 svn_boolean_t first_time
,
389 svn_boolean_t may_save
,
392 const char *def_username
= NULL
, *def_password
= NULL
;
396 /* If we're allowed to check for default usernames and passwords, do
400 def_username
= apr_hash_get(parameters
,
401 SVN_AUTH_PARAM_DEFAULT_USERNAME
,
402 APR_HASH_KEY_STRING
);
404 /* No default username? Try the auth cache. */
407 const char *config_dir
= apr_hash_get(parameters
,
408 SVN_AUTH_PARAM_CONFIG_DIR
,
409 APR_HASH_KEY_STRING
);
410 apr_hash_t
*creds_hash
= NULL
;
414 err
= svn_config_read_auth_data(&creds_hash
, SVN_AUTH_CRED_SIMPLE
,
415 realmstring
, config_dir
, pool
);
416 svn_error_clear(err
);
417 if (! err
&& creds_hash
)
419 str
= apr_hash_get(creds_hash
,
420 SVN_AUTH__AUTHFILE_USERNAME_KEY
,
421 APR_HASH_KEY_STRING
);
422 if (str
&& str
->data
)
423 def_username
= str
->data
;
427 /* Still no default username? Try the UID. */
429 def_username
= svn_user_get_name(pool
);
431 def_password
= apr_hash_get(parameters
,
432 SVN_AUTH_PARAM_DEFAULT_PASSWORD
,
433 APR_HASH_KEY_STRING
);
436 /* If we have defaults, just build the cred here and return it.
438 * ### I do wonder why this is here instead of in a separate
439 * ### 'defaults' provider that would run before the prompt
440 * ### provider... Hmmm.
442 if (def_username
&& def_password
)
444 *cred_p
= apr_palloc(pool
, sizeof(**cred_p
));
445 (*cred_p
)->username
= apr_pstrdup(pool
, def_username
);
446 (*cred_p
)->password
= apr_pstrdup(pool
, def_password
);
447 (*cred_p
)->may_save
= TRUE
;
451 SVN_ERR(pb
->prompt_func(cred_p
, pb
->prompt_baton
, realmstring
,
452 def_username
, may_save
, pool
));
459 /* Our first attempt will use any default username/password passed
460 in, and prompt for the remaining stuff. */
462 simple_prompt_first_creds(void **credentials_p
,
464 void *provider_baton
,
465 apr_hash_t
*parameters
,
466 const char *realmstring
,
469 simple_prompt_provider_baton_t
*pb
= provider_baton
;
470 simple_prompt_iter_baton_t
*ibaton
= apr_pcalloc(pool
, sizeof(*ibaton
));
471 const char *no_auth_cache
= apr_hash_get(parameters
,
472 SVN_AUTH_PARAM_NO_AUTH_CACHE
,
473 APR_HASH_KEY_STRING
);
475 SVN_ERR(prompt_for_simple_creds((svn_auth_cred_simple_t
**) credentials_p
,
476 pb
, parameters
, realmstring
, TRUE
,
477 ! no_auth_cache
, pool
));
480 *iter_baton
= ibaton
;
486 /* Subsequent attempts to fetch will ignore the default values, and
487 simply re-prompt for both, up to a maximum of ib->pb->retry_limit. */
489 simple_prompt_next_creds(void **credentials_p
,
491 void *provider_baton
,
492 apr_hash_t
*parameters
,
493 const char *realmstring
,
496 simple_prompt_iter_baton_t
*ib
= iter_baton
;
497 simple_prompt_provider_baton_t
*pb
= provider_baton
;
498 const char *no_auth_cache
= apr_hash_get(parameters
,
499 SVN_AUTH_PARAM_NO_AUTH_CACHE
,
500 APR_HASH_KEY_STRING
);
502 if (ib
->retries
>= pb
->retry_limit
)
504 /* give up, go on to next provider. */
505 *credentials_p
= NULL
;
510 SVN_ERR(prompt_for_simple_creds((svn_auth_cred_simple_t
**) credentials_p
,
511 pb
, parameters
, realmstring
, FALSE
,
512 ! no_auth_cache
, pool
));
518 static const svn_auth_provider_t simple_prompt_provider
= {
519 SVN_AUTH_CRED_SIMPLE
,
520 simple_prompt_first_creds
,
521 simple_prompt_next_creds
,
528 svn_auth_get_simple_prompt_provider
529 (svn_auth_provider_object_t
**provider
,
530 svn_auth_simple_prompt_func_t prompt_func
,
535 svn_auth_provider_object_t
*po
= apr_pcalloc(pool
, sizeof(*po
));
536 simple_prompt_provider_baton_t
*pb
= apr_pcalloc(pool
, sizeof(*pb
));
538 pb
->prompt_func
= prompt_func
;
539 pb
->prompt_baton
= prompt_baton
;
540 pb
->retry_limit
= retry_limit
;
542 po
->vtable
= &simple_prompt_provider
;
543 po
->provider_baton
= pb
;
549 /*-----------------------------------------------------------------------*/
550 /* Windows simple provider, encrypts the password on Win2k and later. */
551 /*-----------------------------------------------------------------------*/
553 #if defined(WIN32) && !defined(__MINGW32__)
554 #include <wincrypt.h>
555 #include <apr_base64.h>
557 /* The description string that's combined with unencrypted data by the
558 Windows CryptoAPI. Used during decryption to verify that the
559 encrypted data were valid. */
560 static const WCHAR description
[] = L
"auth_svn.simple.wincrypt";
562 /* Dynamically load the address of function NAME in PDLL into
563 PFN. Return TRUE if the function name was found, otherwise
564 FALSE. Equivalent to dlsym(). */
566 get_crypto_function(const char *name
, HINSTANCE
*pdll
, FARPROC
*pfn
)
568 /* In case anyone wonders why we use LoadLibraryA here: This will
569 always work on Win9x/Me, whilst LoadLibraryW may not. */
570 HINSTANCE dll
= LoadLibraryA("Crypt32.dll");
573 FARPROC fn
= GetProcAddress(dll
, name
);
585 /* Implementation of password_set_t that encrypts the incoming
586 password using the Windows CryptoAPI. */
588 windows_password_encrypter(apr_hash_t
*creds
,
589 const char *realmstring
,
590 const char *username
,
592 svn_boolean_t non_interactive
,
595 typedef BOOL (CALLBACK
*encrypt_fn_t
)
596 (DATA_BLOB
*, /* pDataIn */
597 LPCWSTR
, /* szDataDescr */
598 DATA_BLOB
*, /* pOptionalEntropy */
599 PVOID
, /* pvReserved */
600 CRYPTPROTECT_PROMPTSTRUCT
*, /* pPromptStruct */
602 DATA_BLOB
*); /* pDataOut */
606 encrypt_fn_t encrypt
;
609 svn_boolean_t crypted
;
611 if (!get_crypto_function("CryptProtectData", &dll
, &fn
))
613 encrypt
= (encrypt_fn_t
) fn
;
615 blobin
.cbData
= strlen(in
);
616 blobin
.pbData
= (BYTE
*) in
;
617 crypted
= encrypt(&blobin
, description
, NULL
, NULL
, NULL
,
618 CRYPTPROTECT_UI_FORBIDDEN
, &blobout
);
621 char *coded
= apr_palloc(pool
, apr_base64_encode_len(blobout
.cbData
));
622 apr_base64_encode(coded
, blobout
.pbData
, blobout
.cbData
);
623 crypted
= simple_password_set(creds
, realmstring
, username
, coded
,
624 non_interactive
, pool
);
625 LocalFree(blobout
.pbData
);
632 /* Implementation of password_get_t that decrypts the incoming
633 password using the Windows CryptoAPI and verifies its validity. */
635 windows_password_decrypter(const char **out
,
637 const char *realmstring
,
638 const char *username
,
639 svn_boolean_t non_interactive
,
642 typedef BOOL (CALLBACK
* decrypt_fn_t
)
643 (DATA_BLOB
*, /* pDataIn */
644 LPWSTR
*, /* ppszDataDescr */
645 DATA_BLOB
*, /* pOptionalEntropy */
646 PVOID
, /* pvReserved */
647 CRYPTPROTECT_PROMPTSTRUCT
*, /* pPromptStruct */
649 DATA_BLOB
*); /* pDataOut */
656 decrypt_fn_t decrypt
;
657 svn_boolean_t decrypted
;
660 if (!simple_password_get(&in
, creds
, realmstring
, username
,
661 non_interactive
, pool
))
664 if (!get_crypto_function("CryptUnprotectData", &dll
, &fn
))
666 decrypt
= (decrypt_fn_t
) fn
;
668 blobin
.cbData
= strlen(in
);
669 blobin
.pbData
= apr_palloc(pool
, apr_base64_decode_len(in
));
670 apr_base64_decode(blobin
.pbData
, in
);
671 decrypted
= decrypt(&blobin
, &descr
, NULL
, NULL
, NULL
,
672 CRYPTPROTECT_UI_FORBIDDEN
, &blobout
);
675 if (0 == lstrcmpW(descr
, description
))
676 *out
= apr_pstrndup(pool
, blobout
.pbData
, blobout
.cbData
);
679 LocalFree(blobout
.pbData
);
686 /* Get cached encrypted credentials from the simple provider's cache. */
688 windows_simple_first_creds(void **credentials
,
690 void *provider_baton
,
691 apr_hash_t
*parameters
,
692 const char *realmstring
,
695 return simple_first_creds_helper(credentials
,
696 iter_baton
, provider_baton
,
697 parameters
, realmstring
,
698 windows_password_decrypter
,
699 SVN_AUTH__WINCRYPT_PASSWORD_TYPE
,
703 /* Save encrypted credentials to the simple provider's cache. */
705 windows_simple_save_creds(svn_boolean_t
*saved
,
707 void *provider_baton
,
708 apr_hash_t
*parameters
,
709 const char *realmstring
,
712 return simple_save_creds_helper(saved
, credentials
, provider_baton
,
713 parameters
, realmstring
,
714 windows_password_encrypter
,
715 SVN_AUTH__WINCRYPT_PASSWORD_TYPE
,
719 static const svn_auth_provider_t windows_simple_provider
= {
720 SVN_AUTH_CRED_SIMPLE
,
721 windows_simple_first_creds
,
723 windows_simple_save_creds
729 svn_auth_get_windows_simple_provider(svn_auth_provider_object_t
**provider
,
732 svn_auth_provider_object_t
*po
= apr_pcalloc(pool
, sizeof(*po
));
734 po
->vtable
= &windows_simple_provider
;
740 /*-----------------------------------------------------------------------*/
741 /* keychain simple provider, puts passwords in the KeyChain */
742 /*-----------------------------------------------------------------------*/
744 #ifdef SVN_HAVE_KEYCHAIN_SERVICES
745 #include <Security/Security.h>
748 * XXX (2005-12-07): If no GUI is available (e.g. over a SSH session),
749 * you won't be prompted for credentials with which to unlock your
750 * keychain. Apple recognizes lack of TTY prompting as a known
754 * XXX (2005-12-07): SecKeychainSetUserInteractionAllowed(FALSE) does
755 * not appear to actually prevent all user interaction. Specifically,
756 * if the executable changes (for example, if it is rebuilt), the
757 * system prompts the user to okay the use of the new executable.
759 * Worse than that, the interactivity setting is global per app (not
760 * process/thread), meaning that there is a race condition in the
761 * implementation below between calls to
762 * SecKeychainSetUserInteractionAllowed() when multiple instances of
763 * the same Subversion auth provider-based app run concurrently.
766 /* Implementation of password_set_t that stores the password
767 in the OS X KeyChain. */
769 keychain_password_set(apr_hash_t
*creds
,
770 const char *realmstring
,
771 const char *username
,
772 const char *password
,
773 svn_boolean_t non_interactive
,
777 SecKeychainItemRef item
;
780 SecKeychainSetUserInteractionAllowed(FALSE
);
782 status
= SecKeychainFindGenericPassword(NULL
, strlen(realmstring
),
783 realmstring
, strlen(username
),
784 username
, 0, NULL
, &item
);
787 if (status
== errSecItemNotFound
)
788 status
= SecKeychainAddGenericPassword(NULL
, strlen(realmstring
),
789 realmstring
, strlen(username
),
790 username
, strlen(password
),
795 status
= SecKeychainItemModifyAttributesAndData(item
, NULL
,
802 SecKeychainSetUserInteractionAllowed(TRUE
);
807 /* Implementation of password_get_t that retrieves the password
808 from the OS X KeyChain. */
810 keychain_password_get(const char **password
,
812 const char *realmstring
,
813 const char *username
,
814 svn_boolean_t non_interactive
,
822 SecKeychainSetUserInteractionAllowed(FALSE
);
824 status
= SecKeychainFindGenericPassword(NULL
, strlen(realmstring
),
825 realmstring
, strlen(username
),
826 username
, &length
, &data
, NULL
);
829 SecKeychainSetUserInteractionAllowed(TRUE
);
834 *password
= apr_pstrmemdup(pool
, data
, length
);
835 SecKeychainItemFreeContent(NULL
, data
);
839 /* Get cached encrypted credentials from the simple provider's cache. */
841 keychain_simple_first_creds(void **credentials
,
843 void *provider_baton
,
844 apr_hash_t
*parameters
,
845 const char *realmstring
,
848 return simple_first_creds_helper(credentials
,
849 iter_baton
, provider_baton
,
850 parameters
, realmstring
,
851 keychain_password_get
,
852 SVN_AUTH__KEYCHAIN_PASSWORD_TYPE
,
856 /* Save encrypted credentials to the simple provider's cache. */
858 keychain_simple_save_creds(svn_boolean_t
*saved
,
860 void *provider_baton
,
861 apr_hash_t
*parameters
,
862 const char *realmstring
,
865 return simple_save_creds_helper(saved
, credentials
, provider_baton
,
866 parameters
, realmstring
,
867 keychain_password_set
,
868 SVN_AUTH__KEYCHAIN_PASSWORD_TYPE
,
872 static const svn_auth_provider_t keychain_simple_provider
= {
873 SVN_AUTH_CRED_SIMPLE
,
874 keychain_simple_first_creds
,
876 keychain_simple_save_creds
881 svn_auth_get_keychain_simple_provider(svn_auth_provider_object_t
**provider
,
884 svn_auth_provider_object_t
*po
= apr_pcalloc(pool
, sizeof(*po
));
886 po
->vtable
= &keychain_simple_provider
;
890 #endif /* SVN_HAVE_KEYCHAIN_SERVICES */