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 const char *dont_store_passwords
=
238 apr_hash_get(parameters
,
239 SVN_AUTH_PARAM_DONT_STORE_PASSWORDS
,
240 APR_HASH_KEY_STRING
);
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 password_stored
= TRUE
;
248 if (! creds
->may_save
)
251 config_dir
= apr_hash_get(parameters
,
252 SVN_AUTH_PARAM_CONFIG_DIR
,
253 APR_HASH_KEY_STRING
);
255 /* Put the credentials in a hash and save it to disk */
256 creds_hash
= apr_hash_make(pool
);
257 apr_hash_set(creds_hash
, SVN_AUTH__AUTHFILE_USERNAME_KEY
,
259 svn_string_create(creds
->username
, pool
));
260 if (! dont_store_passwords
)
262 password_stored
= password_set(creds_hash
, realmstring
, creds
->username
,
263 creds
->password
, non_interactive
, pool
);
266 /* Store the password type with the auth data, so that we
267 know which provider owns the password. */
270 apr_hash_set(creds_hash
, SVN_AUTH__AUTHFILE_PASSTYPE_KEY
,
272 svn_string_create(passtype
, pool
));
281 err
= svn_config_write_auth_data(creds_hash
, SVN_AUTH_CRED_SIMPLE
,
282 realmstring
, config_dir
, pool
);
283 svn_error_clear(err
);
290 /* Get cached (unencrypted) credentials from the simple provider's cache. */
292 simple_first_creds(void **credentials
,
294 void *provider_baton
,
295 apr_hash_t
*parameters
,
296 const char *realmstring
,
299 return simple_first_creds_helper(credentials
,
300 iter_baton
, provider_baton
,
301 parameters
, realmstring
,
303 SVN_AUTH__SIMPLE_PASSWORD_TYPE
,
307 /* Save (unencrypted) credentials to the simple provider's cache. */
309 simple_save_creds(svn_boolean_t
*saved
,
311 void *provider_baton
,
312 apr_hash_t
*parameters
,
313 const char *realmstring
,
316 return simple_save_creds_helper(saved
, credentials
, provider_baton
,
317 parameters
, realmstring
,
319 SVN_AUTH__SIMPLE_PASSWORD_TYPE
,
323 static const svn_auth_provider_t simple_provider
= {
324 SVN_AUTH_CRED_SIMPLE
,
333 svn_auth_get_simple_provider(svn_auth_provider_object_t
**provider
,
336 svn_auth_provider_object_t
*po
= apr_pcalloc(pool
, sizeof(*po
));
338 po
->vtable
= &simple_provider
;
343 /*-----------------------------------------------------------------------*/
344 /* Prompt provider */
345 /*-----------------------------------------------------------------------*/
347 /* Baton type for username/password prompting. */
350 svn_auth_simple_prompt_func_t prompt_func
;
353 /* how many times to re-prompt after the first one fails */
355 } simple_prompt_provider_baton_t
;
358 /* Iteration baton type for username/password prompting. */
361 /* how many times we've reprompted */
363 } simple_prompt_iter_baton_t
;
367 /*** Helper Functions ***/
369 prompt_for_simple_creds(svn_auth_cred_simple_t
**cred_p
,
370 simple_prompt_provider_baton_t
*pb
,
371 apr_hash_t
*parameters
,
372 const char *realmstring
,
373 svn_boolean_t first_time
,
374 svn_boolean_t may_save
,
377 const char *def_username
= NULL
, *def_password
= NULL
;
381 /* If we're allowed to check for default usernames and passwords, do
385 def_username
= apr_hash_get(parameters
,
386 SVN_AUTH_PARAM_DEFAULT_USERNAME
,
387 APR_HASH_KEY_STRING
);
389 /* No default username? Try the auth cache. */
392 const char *config_dir
= apr_hash_get(parameters
,
393 SVN_AUTH_PARAM_CONFIG_DIR
,
394 APR_HASH_KEY_STRING
);
395 apr_hash_t
*creds_hash
= NULL
;
399 err
= svn_config_read_auth_data(&creds_hash
, SVN_AUTH_CRED_SIMPLE
,
400 realmstring
, config_dir
, pool
);
401 svn_error_clear(err
);
402 if (! err
&& creds_hash
)
404 str
= apr_hash_get(creds_hash
,
405 SVN_AUTH__AUTHFILE_USERNAME_KEY
,
406 APR_HASH_KEY_STRING
);
407 if (str
&& str
->data
)
408 def_username
= str
->data
;
412 /* Still no default username? Try the UID. */
414 def_username
= svn_user_get_name(pool
);
416 def_password
= apr_hash_get(parameters
,
417 SVN_AUTH_PARAM_DEFAULT_PASSWORD
,
418 APR_HASH_KEY_STRING
);
421 /* If we have defaults, just build the cred here and return it.
423 * ### I do wonder why this is here instead of in a separate
424 * ### 'defaults' provider that would run before the prompt
425 * ### provider... Hmmm.
427 if (def_username
&& def_password
)
429 *cred_p
= apr_palloc(pool
, sizeof(**cred_p
));
430 (*cred_p
)->username
= apr_pstrdup(pool
, def_username
);
431 (*cred_p
)->password
= apr_pstrdup(pool
, def_password
);
432 (*cred_p
)->may_save
= TRUE
;
436 SVN_ERR(pb
->prompt_func(cred_p
, pb
->prompt_baton
, realmstring
,
437 def_username
, may_save
, pool
));
444 /* Our first attempt will use any default username/password passed
445 in, and prompt for the remaining stuff. */
447 simple_prompt_first_creds(void **credentials_p
,
449 void *provider_baton
,
450 apr_hash_t
*parameters
,
451 const char *realmstring
,
454 simple_prompt_provider_baton_t
*pb
= provider_baton
;
455 simple_prompt_iter_baton_t
*ibaton
= apr_pcalloc(pool
, sizeof(*ibaton
));
456 const char *no_auth_cache
= apr_hash_get(parameters
,
457 SVN_AUTH_PARAM_NO_AUTH_CACHE
,
458 APR_HASH_KEY_STRING
);
460 SVN_ERR(prompt_for_simple_creds((svn_auth_cred_simple_t
**) credentials_p
,
461 pb
, parameters
, realmstring
, TRUE
,
462 ! no_auth_cache
, pool
));
465 *iter_baton
= ibaton
;
471 /* Subsequent attempts to fetch will ignore the default values, and
472 simply re-prompt for both, up to a maximum of ib->pb->retry_limit. */
474 simple_prompt_next_creds(void **credentials_p
,
476 void *provider_baton
,
477 apr_hash_t
*parameters
,
478 const char *realmstring
,
481 simple_prompt_iter_baton_t
*ib
= iter_baton
;
482 simple_prompt_provider_baton_t
*pb
= provider_baton
;
483 const char *no_auth_cache
= apr_hash_get(parameters
,
484 SVN_AUTH_PARAM_NO_AUTH_CACHE
,
485 APR_HASH_KEY_STRING
);
487 if (ib
->retries
>= pb
->retry_limit
)
489 /* give up, go on to next provider. */
490 *credentials_p
= NULL
;
495 SVN_ERR(prompt_for_simple_creds((svn_auth_cred_simple_t
**) credentials_p
,
496 pb
, parameters
, realmstring
, FALSE
,
497 ! no_auth_cache
, pool
));
503 static const svn_auth_provider_t simple_prompt_provider
= {
504 SVN_AUTH_CRED_SIMPLE
,
505 simple_prompt_first_creds
,
506 simple_prompt_next_creds
,
513 svn_auth_get_simple_prompt_provider
514 (svn_auth_provider_object_t
**provider
,
515 svn_auth_simple_prompt_func_t prompt_func
,
520 svn_auth_provider_object_t
*po
= apr_pcalloc(pool
, sizeof(*po
));
521 simple_prompt_provider_baton_t
*pb
= apr_pcalloc(pool
, sizeof(*pb
));
523 pb
->prompt_func
= prompt_func
;
524 pb
->prompt_baton
= prompt_baton
;
525 pb
->retry_limit
= retry_limit
;
527 po
->vtable
= &simple_prompt_provider
;
528 po
->provider_baton
= pb
;
534 /*-----------------------------------------------------------------------*/
535 /* Windows simple provider, encrypts the password on Win2k and later. */
536 /*-----------------------------------------------------------------------*/
538 #if defined(WIN32) && !defined(__MINGW32__)
539 #include <wincrypt.h>
540 #include <apr_base64.h>
542 /* The description string that's combined with unencrypted data by the
543 Windows CryptoAPI. Used during decryption to verify that the
544 encrypted data were valid. */
545 static const WCHAR description
[] = L
"auth_svn.simple.wincrypt";
547 /* Dynamically load the address of function NAME in PDLL into
548 PFN. Return TRUE if the function name was found, otherwise
549 FALSE. Equivalent to dlsym(). */
551 get_crypto_function(const char *name
, HINSTANCE
*pdll
, FARPROC
*pfn
)
553 /* In case anyone wonders why we use LoadLibraryA here: This will
554 always work on Win9x/Me, whilst LoadLibraryW may not. */
555 HINSTANCE dll
= LoadLibraryA("Crypt32.dll");
558 FARPROC fn
= GetProcAddress(dll
, name
);
570 /* Implementation of password_set_t that encrypts the incoming
571 password using the Windows CryptoAPI. */
573 windows_password_encrypter(apr_hash_t
*creds
,
574 const char *realmstring
,
575 const char *username
,
577 svn_boolean_t non_interactive
,
580 typedef BOOL (CALLBACK
*encrypt_fn_t
)
581 (DATA_BLOB
*, /* pDataIn */
582 LPCWSTR
, /* szDataDescr */
583 DATA_BLOB
*, /* pOptionalEntropy */
584 PVOID
, /* pvReserved */
585 CRYPTPROTECT_PROMPTSTRUCT
*, /* pPromptStruct */
587 DATA_BLOB
*); /* pDataOut */
591 encrypt_fn_t encrypt
;
594 svn_boolean_t crypted
;
596 if (!get_crypto_function("CryptProtectData", &dll
, &fn
))
598 encrypt
= (encrypt_fn_t
) fn
;
600 blobin
.cbData
= strlen(in
);
601 blobin
.pbData
= (BYTE
*) in
;
602 crypted
= encrypt(&blobin
, description
, NULL
, NULL
, NULL
,
603 CRYPTPROTECT_UI_FORBIDDEN
, &blobout
);
606 char *coded
= apr_palloc(pool
, apr_base64_encode_len(blobout
.cbData
));
607 apr_base64_encode(coded
, blobout
.pbData
, blobout
.cbData
);
608 crypted
= simple_password_set(creds
, realmstring
, username
, coded
,
609 non_interactive
, pool
);
610 LocalFree(blobout
.pbData
);
617 /* Implementation of password_get_t that decrypts the incoming
618 password using the Windows CryptoAPI and verifies its validity. */
620 windows_password_decrypter(const char **out
,
622 const char *realmstring
,
623 const char *username
,
624 svn_boolean_t non_interactive
,
627 typedef BOOL (CALLBACK
* decrypt_fn_t
)
628 (DATA_BLOB
*, /* pDataIn */
629 LPWSTR
*, /* ppszDataDescr */
630 DATA_BLOB
*, /* pOptionalEntropy */
631 PVOID
, /* pvReserved */
632 CRYPTPROTECT_PROMPTSTRUCT
*, /* pPromptStruct */
634 DATA_BLOB
*); /* pDataOut */
641 decrypt_fn_t decrypt
;
642 svn_boolean_t decrypted
;
645 if (!simple_password_get(&in
, creds
, realmstring
, username
,
646 non_interactive
, pool
))
649 if (!get_crypto_function("CryptUnprotectData", &dll
, &fn
))
651 decrypt
= (decrypt_fn_t
) fn
;
653 blobin
.cbData
= strlen(in
);
654 blobin
.pbData
= apr_palloc(pool
, apr_base64_decode_len(in
));
655 apr_base64_decode(blobin
.pbData
, in
);
656 decrypted
= decrypt(&blobin
, &descr
, NULL
, NULL
, NULL
,
657 CRYPTPROTECT_UI_FORBIDDEN
, &blobout
);
660 if (0 == lstrcmpW(descr
, description
))
661 *out
= apr_pstrndup(pool
, blobout
.pbData
, blobout
.cbData
);
664 LocalFree(blobout
.pbData
);
671 /* Get cached encrypted credentials from the simple provider's cache. */
673 windows_simple_first_creds(void **credentials
,
675 void *provider_baton
,
676 apr_hash_t
*parameters
,
677 const char *realmstring
,
680 return simple_first_creds_helper(credentials
,
681 iter_baton
, provider_baton
,
682 parameters
, realmstring
,
683 windows_password_decrypter
,
684 SVN_AUTH__WINCRYPT_PASSWORD_TYPE
,
688 /* Save encrypted credentials to the simple provider's cache. */
690 windows_simple_save_creds(svn_boolean_t
*saved
,
692 void *provider_baton
,
693 apr_hash_t
*parameters
,
694 const char *realmstring
,
697 return simple_save_creds_helper(saved
, credentials
, provider_baton
,
698 parameters
, realmstring
,
699 windows_password_encrypter
,
700 SVN_AUTH__WINCRYPT_PASSWORD_TYPE
,
704 static const svn_auth_provider_t windows_simple_provider
= {
705 SVN_AUTH_CRED_SIMPLE
,
706 windows_simple_first_creds
,
708 windows_simple_save_creds
714 svn_auth_get_windows_simple_provider(svn_auth_provider_object_t
**provider
,
717 svn_auth_provider_object_t
*po
= apr_pcalloc(pool
, sizeof(*po
));
719 po
->vtable
= &windows_simple_provider
;
725 /*-----------------------------------------------------------------------*/
726 /* keychain simple provider, puts passwords in the KeyChain */
727 /*-----------------------------------------------------------------------*/
729 #ifdef SVN_HAVE_KEYCHAIN_SERVICES
730 #include <Security/Security.h>
733 * XXX (2005-12-07): If no GUI is available (e.g. over a SSH session),
734 * you won't be prompted for credentials with which to unlock your
735 * keychain. Apple recognizes lack of TTY prompting as a known
739 * XXX (2005-12-07): SecKeychainSetUserInteractionAllowed(FALSE) does
740 * not appear to actually prevent all user interaction. Specifically,
741 * if the executable changes (for example, if it is rebuilt), the
742 * system prompts the user to okay the use of the new executable.
744 * Worse than that, the interactivity setting is global per app (not
745 * process/thread), meaning that there is a race condition in the
746 * implementation below between calls to
747 * SecKeychainSetUserInteractionAllowed() when multiple instances of
748 * the same Subversion auth provider-based app run concurrently.
751 /* Implementation of password_set_t that stores the password
752 in the OS X KeyChain. */
754 keychain_password_set(apr_hash_t
*creds
,
755 const char *realmstring
,
756 const char *username
,
757 const char *password
,
758 svn_boolean_t non_interactive
,
762 SecKeychainItemRef item
;
765 SecKeychainSetUserInteractionAllowed(FALSE
);
767 status
= SecKeychainFindGenericPassword(NULL
, strlen(realmstring
),
768 realmstring
, strlen(username
),
769 username
, 0, NULL
, &item
);
772 if (status
== errSecItemNotFound
)
773 status
= SecKeychainAddGenericPassword(NULL
, strlen(realmstring
),
774 realmstring
, strlen(username
),
775 username
, strlen(password
),
780 status
= SecKeychainItemModifyAttributesAndData(item
, NULL
,
787 SecKeychainSetUserInteractionAllowed(TRUE
);
792 /* Implementation of password_get_t that retrieves the password
793 from the OS X KeyChain. */
795 keychain_password_get(const char **password
,
797 const char *realmstring
,
798 const char *username
,
799 svn_boolean_t non_interactive
,
807 SecKeychainSetUserInteractionAllowed(FALSE
);
809 status
= SecKeychainFindGenericPassword(NULL
, strlen(realmstring
),
810 realmstring
, strlen(username
),
811 username
, &length
, &data
, NULL
);
814 SecKeychainSetUserInteractionAllowed(TRUE
);
819 *password
= apr_pstrmemdup(pool
, data
, length
);
820 SecKeychainItemFreeContent(NULL
, data
);
824 /* Get cached encrypted credentials from the simple provider's cache. */
826 keychain_simple_first_creds(void **credentials
,
828 void *provider_baton
,
829 apr_hash_t
*parameters
,
830 const char *realmstring
,
833 return simple_first_creds_helper(credentials
,
834 iter_baton
, provider_baton
,
835 parameters
, realmstring
,
836 keychain_password_get
,
837 SVN_AUTH__KEYCHAIN_PASSWORD_TYPE
,
841 /* Save encrypted credentials to the simple provider's cache. */
843 keychain_simple_save_creds(svn_boolean_t
*saved
,
845 void *provider_baton
,
846 apr_hash_t
*parameters
,
847 const char *realmstring
,
850 return simple_save_creds_helper(saved
, credentials
, provider_baton
,
851 parameters
, realmstring
,
852 keychain_password_set
,
853 SVN_AUTH__KEYCHAIN_PASSWORD_TYPE
,
857 static const svn_auth_provider_t keychain_simple_provider
= {
858 SVN_AUTH_CRED_SIMPLE
,
859 keychain_simple_first_creds
,
861 keychain_simple_save_creds
866 svn_auth_get_keychain_simple_provider(svn_auth_provider_object_t
**provider
,
869 svn_auth_provider_object_t
*po
= apr_pcalloc(pool
, sizeof(*po
));
871 po
->vtable
= &keychain_simple_provider
;
875 #endif /* SVN_HAVE_KEYCHAIN_SERVICES */