1/*
2 * username_providers.c: providers for SVN_AUTH_CRED_USERNAME
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
26
27
28/*** Includes. ***/
29
30#include <apr_pools.h>
31#include "svn_hash.h"
32#include "svn_auth.h"
33#include "svn_error.h"
34#include "svn_utf.h"
35#include "svn_config.h"
36#include "svn_user.h"
37
38
39/*-----------------------------------------------------------------------*/
40/* File provider                                                         */
41/*-----------------------------------------------------------------------*/
42
43/*** Username-only Provider ***/
44static svn_error_t *
45username_first_creds(void **credentials,
46                     void **iter_baton,
47                     void *provider_baton,
48                     apr_hash_t *parameters,
49                     const char *realmstring,
50                     apr_pool_t *pool)
51{
52  const char *config_dir = svn_hash_gets(parameters,
53                                         SVN_AUTH_PARAM_CONFIG_DIR);
54  const char *username = svn_hash_gets(parameters,
55                                       SVN_AUTH_PARAM_DEFAULT_USERNAME);
56  svn_boolean_t may_save = !! username;
57  svn_error_t *err;
58
59  /* If we don't have a usename yet, try the auth cache */
60  if (! username)
61    {
62      apr_hash_t *creds_hash = NULL;
63
64      /* Try to load credentials from a file on disk, based on the
65         realmstring.  Don't throw an error, though: if something went
66         wrong reading the file, no big deal.  What really matters is that
67         we failed to get the creds, so allow the auth system to try the
68         next provider. */
69      err = svn_config_read_auth_data(&creds_hash, SVN_AUTH_CRED_USERNAME,
70                                      realmstring, config_dir, pool);
71      svn_error_clear(err);
72      if (! err && creds_hash)
73        {
74          svn_string_t *str = svn_hash_gets(creds_hash,
75                                            SVN_CONFIG_AUTHN_USERNAME_KEY);
76          if (str && str->data)
77            username = str->data;
78        }
79    }
80
81  /* If that failed, ask the OS for the username */
82  if (! username)
83    username = svn_user_get_name(pool);
84
85  if (username)
86    {
87      svn_auth_cred_simple_t *creds = apr_pcalloc(pool, sizeof(*creds));
88      creds->username = username;
89      creds->may_save = may_save;
90      *credentials = creds;
91    }
92  else
93    *credentials = NULL;
94
95  *iter_baton = NULL;
96
97  return SVN_NO_ERROR;
98}
99
100
101static svn_error_t *
102username_save_creds(svn_boolean_t *saved,
103                    void *credentials,
104                    void *provider_baton,
105                    apr_hash_t *parameters,
106                    const char *realmstring,
107                    apr_pool_t *pool)
108{
109  svn_auth_cred_simple_t *creds = credentials;
110  apr_hash_t *creds_hash = NULL;
111  const char *config_dir;
112  svn_error_t *err;
113
114  *saved = FALSE;
115
116  if (! creds->may_save)
117    return SVN_NO_ERROR;
118
119  config_dir = svn_hash_gets(parameters, SVN_AUTH_PARAM_CONFIG_DIR);
120
121  /* Put the credentials in a hash and save it to disk */
122  creds_hash = apr_hash_make(pool);
123  svn_hash_sets(creds_hash, SVN_CONFIG_AUTHN_USERNAME_KEY,
124                svn_string_create(creds->username, pool));
125  err = svn_config_write_auth_data(creds_hash, SVN_AUTH_CRED_USERNAME,
126                                   realmstring, config_dir, pool);
127  svn_error_clear(err);
128  *saved = ! err;
129
130  return SVN_NO_ERROR;
131}
132
133
134static const svn_auth_provider_t username_provider = {
135  SVN_AUTH_CRED_USERNAME,
136  username_first_creds,
137  NULL,
138  username_save_creds
139};
140
141
142/* Public API */
143void
144svn_auth_get_username_provider(svn_auth_provider_object_t **provider,
145                               apr_pool_t *pool)
146{
147  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
148
149  po->vtable = &username_provider;
150  *provider = po;
151}
152
153
154/*-----------------------------------------------------------------------*/
155/* Prompt provider                                                       */
156/*-----------------------------------------------------------------------*/
157
158/* Baton type for username-only prompting. */
159typedef struct username_prompt_provider_baton_t
160{
161  svn_auth_username_prompt_func_t prompt_func;
162  void *prompt_baton;
163
164  /* how many times to re-prompt after the first one fails */
165  int retry_limit;
166} username_prompt_provider_baton_t;
167
168
169/* Iteration baton type for username-only prompting. */
170typedef struct username_prompt_iter_baton_t
171{
172  /* how many times we've reprompted */
173  int retries;
174
175} username_prompt_iter_baton_t;
176
177
178/*** Helper Functions ***/
179static svn_error_t *
180prompt_for_username_creds(svn_auth_cred_username_t **cred_p,
181                          username_prompt_provider_baton_t *pb,
182                          apr_hash_t *parameters,
183                          const char *realmstring,
184                          svn_boolean_t first_time,
185                          svn_boolean_t may_save,
186                          apr_pool_t *pool)
187{
188  const char *def_username = NULL;
189
190  *cred_p = NULL;
191
192  /* If we're allowed to check for default usernames, do so. */
193  if (first_time)
194    def_username = svn_hash_gets(parameters, SVN_AUTH_PARAM_DEFAULT_USERNAME);
195
196  /* If we have defaults, just build the cred here and return it.
197   *
198   * ### I do wonder why this is here instead of in a separate
199   * ### 'defaults' provider that would run before the prompt
200   * ### provider... Hmmm.
201   */
202  if (def_username)
203    {
204      *cred_p = apr_palloc(pool, sizeof(**cred_p));
205      (*cred_p)->username = apr_pstrdup(pool, def_username);
206      (*cred_p)->may_save = TRUE;
207    }
208  else
209    {
210      SVN_ERR(pb->prompt_func(cred_p, pb->prompt_baton, realmstring,
211                              may_save, pool));
212    }
213
214  return SVN_NO_ERROR;
215}
216
217
218/* Our first attempt will use any default username passed
219   in, and prompt for the remaining stuff. */
220static svn_error_t *
221username_prompt_first_creds(void **credentials_p,
222                            void **iter_baton,
223                            void *provider_baton,
224                            apr_hash_t *parameters,
225                            const char *realmstring,
226                            apr_pool_t *pool)
227{
228  username_prompt_provider_baton_t *pb = provider_baton;
229  username_prompt_iter_baton_t *ibaton = apr_pcalloc(pool, sizeof(*ibaton));
230  const char *no_auth_cache = svn_hash_gets(parameters,
231                                            SVN_AUTH_PARAM_NO_AUTH_CACHE);
232
233  SVN_ERR(prompt_for_username_creds
234          ((svn_auth_cred_username_t **) credentials_p, pb,
235           parameters, realmstring, TRUE, ! no_auth_cache, pool));
236
237  ibaton->retries = 0;
238  *iter_baton = ibaton;
239
240  return SVN_NO_ERROR;
241}
242
243
244/* Subsequent attempts to fetch will ignore the default username
245   value, and simply re-prompt for the username, up to a maximum of
246   ib->pb->retry_limit. */
247static svn_error_t *
248username_prompt_next_creds(void **credentials_p,
249                           void *iter_baton,
250                           void *provider_baton,
251                           apr_hash_t *parameters,
252                           const char *realmstring,
253                           apr_pool_t *pool)
254{
255  username_prompt_iter_baton_t *ib = iter_baton;
256  username_prompt_provider_baton_t *pb = provider_baton;
257  const char *no_auth_cache = svn_hash_gets(parameters,
258                                            SVN_AUTH_PARAM_NO_AUTH_CACHE);
259
260  if ((pb->retry_limit >= 0) && (ib->retries >= pb->retry_limit))
261    {
262      /* give up, go on to next provider. */
263      *credentials_p = NULL;
264      return SVN_NO_ERROR;
265    }
266  ib->retries++;
267
268  return prompt_for_username_creds
269         ((svn_auth_cred_username_t **) credentials_p, pb,
270          parameters, realmstring, FALSE, ! no_auth_cache, pool);
271}
272
273
274static const svn_auth_provider_t username_prompt_provider = {
275  SVN_AUTH_CRED_USERNAME,
276  username_prompt_first_creds,
277  username_prompt_next_creds,
278  NULL,
279};
280
281
282/* Public API */
283void
284svn_auth_get_username_prompt_provider
285  (svn_auth_provider_object_t **provider,
286   svn_auth_username_prompt_func_t prompt_func,
287   void *prompt_baton,
288   int retry_limit,
289   apr_pool_t *pool)
290{
291  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
292  username_prompt_provider_baton_t *pb = apr_pcalloc(pool, sizeof(*pb));
293
294  pb->prompt_func = prompt_func;
295  pb->prompt_baton = prompt_baton;
296  pb->retry_limit = retry_limit;
297
298  po->vtable = &username_prompt_provider;
299  po->provider_baton = pb;
300  *provider = po;
301}
302