1/*
2 * auth-cmd.c:  Subversion auth creds cache administration
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/*** Includes. ***/
25
26#include <apr_general.h>
27#include <apr_getopt.h>
28#include <apr_fnmatch.h>
29#include <apr_tables.h>
30
31#include "svn_private_config.h"
32
33#include "svn_private_config.h"
34#include "svn_pools.h"
35#include "svn_error.h"
36#include "svn_opt.h"
37#include "svn_dirent_uri.h"
38#include "svn_hash.h"
39#include "svn_utf.h"
40#include "svn_cmdline.h"
41#include "svn_config.h"
42#include "svn_auth.h"
43#include "svn_sorts.h"
44#include "svn_base64.h"
45#include "svn_x509.h"
46#include "svn_time.h"
47
48#include "private/svn_cmdline_private.h"
49#include "private/svn_token.h"
50#include "private/svn_sorts_private.h"
51
52#include "cl.h"
53
54/* The separator between credentials . */
55#define SEP_STRING \
56  "------------------------------------------------------------------------\n"
57
58static svn_error_t *
59show_cert_failures(const char *failure_string,
60                   apr_pool_t *scratch_pool)
61{
62  unsigned int failures;
63
64  SVN_ERR(svn_cstring_atoui(&failures, failure_string));
65
66  if (0 == (failures & (SVN_AUTH_SSL_NOTYETVALID | SVN_AUTH_SSL_EXPIRED |
67                        SVN_AUTH_SSL_CNMISMATCH | SVN_AUTH_SSL_UNKNOWNCA |
68                        SVN_AUTH_SSL_OTHER)))
69    return SVN_NO_ERROR;
70
71  SVN_ERR(svn_cmdline_printf(
72            scratch_pool, _("Automatic certificate validity check failed "
73                            "because:\n")));
74
75  if (failures & SVN_AUTH_SSL_NOTYETVALID)
76    SVN_ERR(svn_cmdline_printf(
77              scratch_pool, _("  The certificate is not yet valid.\n")));
78
79  if (failures & SVN_AUTH_SSL_EXPIRED)
80    SVN_ERR(svn_cmdline_printf(
81              scratch_pool, _("  The certificate has expired.\n")));
82
83  if (failures & SVN_AUTH_SSL_CNMISMATCH)
84    SVN_ERR(svn_cmdline_printf(
85              scratch_pool, _("  The certificate's Common Name (hostname) "
86                              "does not match the remote hostname.\n")));
87
88  if (failures & SVN_AUTH_SSL_UNKNOWNCA)
89    SVN_ERR(svn_cmdline_printf(
90              scratch_pool, _("  The certificate issuer is unknown.\n")));
91
92  if (failures & SVN_AUTH_SSL_OTHER)
93    SVN_ERR(svn_cmdline_printf(
94              scratch_pool, _("  Unknown verification failure.\n")));
95
96  return SVN_NO_ERROR;
97}
98
99
100/* decodes from format we store certs in for auth creds and
101 * turns parsing errors into warnings if PRINT_WARNING is TRUE
102 * and ignores them otherwise. returns NULL if it couldn't
103 * parse a cert for any reason. */
104static svn_x509_certinfo_t *
105parse_certificate(const svn_string_t *ascii_cert,
106                  svn_boolean_t print_warning,
107                  apr_pool_t *result_pool,
108                  apr_pool_t *scratch_pool)
109{
110  svn_x509_certinfo_t *certinfo;
111  const svn_string_t *der_cert;
112  svn_error_t *err;
113
114  /* Convert header-less PEM to DER by undoing base64 encoding. */
115  der_cert = svn_base64_decode_string(ascii_cert, scratch_pool);
116
117  err = svn_x509_parse_cert(&certinfo, der_cert->data, der_cert->len,
118                            result_pool, scratch_pool);
119  if (err)
120    {
121      /* Just display X.509 parsing errors as warnings and continue */
122      if (print_warning)
123        svn_handle_warning2(stderr, err, "svn: ");
124      svn_error_clear(err);
125      return NULL;
126    }
127
128  return certinfo;
129}
130
131
132struct walk_credentials_baton_t
133{
134  int matches;
135  svn_boolean_t list;
136  svn_boolean_t delete;
137  svn_boolean_t show_passwords;
138  apr_array_header_t *patterns;
139};
140
141static svn_boolean_t
142match_pattern(const char *pattern, const char *value,
143              svn_boolean_t caseblind, apr_pool_t *scratch_pool)
144{
145  const char *p = apr_psprintf(scratch_pool, "*%s*", pattern);
146  int flags = (caseblind ? APR_FNM_CASE_BLIND : 0);
147
148  return (apr_fnmatch(p, value, flags) == APR_SUCCESS);
149}
150
151static svn_boolean_t
152match_certificate(svn_x509_certinfo_t **certinfo,
153                  const char *pattern,
154                  const svn_string_t *ascii_cert,
155                  apr_pool_t *result_pool,
156                  apr_pool_t *scratch_pool)
157{
158  const char *value;
159  const svn_checksum_t *checksum;
160  const apr_array_header_t *hostnames;
161  int i;
162
163  *certinfo = parse_certificate(ascii_cert, FALSE, result_pool, scratch_pool);
164  if (*certinfo == NULL)
165    return FALSE;
166
167  value = svn_x509_certinfo_get_subject(*certinfo, scratch_pool);
168  if (match_pattern(pattern, value, FALSE, scratch_pool))
169    return TRUE;
170
171  value = svn_x509_certinfo_get_issuer(*certinfo, scratch_pool);
172  if (match_pattern(pattern, value, FALSE, scratch_pool))
173    return TRUE;
174
175  checksum = svn_x509_certinfo_get_digest(*certinfo);
176  value = svn_checksum_to_cstring_display(checksum, scratch_pool);
177  if (match_pattern(pattern, value, TRUE, scratch_pool))
178    return TRUE;
179
180  hostnames = svn_x509_certinfo_get_hostnames(*certinfo);
181  if (hostnames)
182    {
183      for (i = 0; i < hostnames->nelts; i++)
184        {
185          const char *hostname = APR_ARRAY_IDX(hostnames, i, const char *);
186          if (match_pattern(pattern, hostname, TRUE, scratch_pool))
187            return TRUE;
188        }
189    }
190
191  return FALSE;
192}
193
194
195static svn_error_t *
196match_credential(svn_boolean_t *match,
197                 svn_x509_certinfo_t **certinfo,
198                 const char *cred_kind,
199                 const char *realmstring,
200                 apr_array_header_t *patterns,
201                 apr_array_header_t *cred_items,
202                 apr_pool_t *result_pool,
203                 apr_pool_t *scratch_pool)
204{
205  int i;
206  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
207
208  *match = FALSE;
209
210  for (i = 0; i < patterns->nelts; i++)
211    {
212      const char *pattern = APR_ARRAY_IDX(patterns, i, const char *);
213      int j;
214
215      *match = match_pattern(pattern, cred_kind, FALSE, iterpool);
216      if (!*match)
217        *match = match_pattern(pattern, realmstring, FALSE, iterpool);
218      if (!*match)
219        {
220          svn_pool_clear(iterpool);
221          for (j = 0; j < cred_items->nelts; j++)
222            {
223              svn_sort__item_t item;
224              const char *key;
225              svn_string_t *value;
226
227              item = APR_ARRAY_IDX(cred_items, j, svn_sort__item_t);
228              key = item.key;
229              value = item.value;
230              if (strcmp(key, SVN_CONFIG_AUTHN_PASSWORD_KEY) == 0 ||
231                  strcmp(key, SVN_CONFIG_AUTHN_PASSPHRASE_KEY) == 0)
232                continue; /* don't match secrets */
233              else if (strcmp(key, SVN_CONFIG_AUTHN_ASCII_CERT_KEY) == 0)
234                *match = match_certificate(certinfo, pattern, value,
235                                           result_pool, iterpool);
236              else
237                *match = match_pattern(pattern, value->data, FALSE, iterpool);
238
239              if (*match)
240                break;
241            }
242        }
243      if (!*match)
244        break;
245    }
246
247  return SVN_NO_ERROR;
248}
249
250static svn_error_t *
251show_cert(svn_x509_certinfo_t *certinfo, const svn_string_t *pem_cert,
252          apr_pool_t *scratch_pool)
253{
254  const apr_array_header_t *hostnames;
255
256  if (certinfo == NULL)
257    certinfo = parse_certificate(pem_cert, TRUE, scratch_pool, scratch_pool);
258  if (certinfo == NULL)
259    return SVN_NO_ERROR;
260
261  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Subject: %s\n"),
262                             svn_x509_certinfo_get_subject(certinfo, scratch_pool)));
263  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Valid from: %s\n"),
264                             svn_time_to_human_cstring(
265                                 svn_x509_certinfo_get_valid_from(certinfo),
266                                 scratch_pool)));
267  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Valid until: %s\n"),
268                             svn_time_to_human_cstring(
269                                 svn_x509_certinfo_get_valid_to(certinfo),
270                                 scratch_pool)));
271  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Issuer: %s\n"),
272                             svn_x509_certinfo_get_issuer(certinfo, scratch_pool)));
273  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Fingerprint: %s\n"),
274                             svn_checksum_to_cstring_display(
275                                 svn_x509_certinfo_get_digest(certinfo),
276                                 scratch_pool)));
277
278  hostnames = svn_x509_certinfo_get_hostnames(certinfo);
279  if (hostnames && !apr_is_empty_array(hostnames))
280    {
281      int i;
282      svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
283      for (i = 0; i < hostnames->nelts; ++i)
284        {
285          const char *hostname = APR_ARRAY_IDX(hostnames, i, const char*);
286          if (i > 0)
287            svn_stringbuf_appendbytes(buf, ", ", 2);
288          svn_stringbuf_appendbytes(buf, hostname, strlen(hostname));
289        }
290      SVN_ERR(svn_cmdline_printf(scratch_pool, _("Hostnames: %s\n"),
291                                 buf->data));
292    }
293
294  return SVN_NO_ERROR;
295}
296
297static svn_error_t *
298list_credential(const char *cred_kind,
299                const char *realmstring,
300                apr_array_header_t *cred_items,
301                svn_boolean_t show_passwords,
302                svn_x509_certinfo_t *certinfo,
303                apr_pool_t *scratch_pool)
304{
305  int i;
306  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
307
308  SVN_ERR(svn_cmdline_printf(scratch_pool, SEP_STRING));
309  SVN_ERR(svn_cmdline_printf(scratch_pool,
310                             _("Credential kind: %s\n"), cred_kind));
311  SVN_ERR(svn_cmdline_printf(scratch_pool,
312                             _("Authentication realm: %s\n"), realmstring));
313
314  for (i = 0; i < cred_items->nelts; i++)
315    {
316      svn_sort__item_t item;
317      const char *key;
318      svn_string_t *value;
319
320      svn_pool_clear(iterpool);
321      item = APR_ARRAY_IDX(cred_items, i, svn_sort__item_t);
322      key = item.key;
323      value = item.value;
324      if (strcmp(value->data, realmstring) == 0)
325        continue; /* realm string was already shown above */
326      else if (strcmp(key, SVN_CONFIG_AUTHN_PASSWORD_KEY) == 0)
327        {
328          if (show_passwords)
329            SVN_ERR(svn_cmdline_printf(iterpool,
330                                       _("Password: %s\n"), value->data));
331          else
332            SVN_ERR(svn_cmdline_printf(iterpool, _("Password: [not shown]\n")));
333        }
334      else if (strcmp(key, SVN_CONFIG_AUTHN_PASSPHRASE_KEY) == 0)
335        {
336          if (show_passwords)
337            SVN_ERR(svn_cmdline_printf(iterpool,
338                                       _("Passphrase: %s\n"), value->data));
339          else
340            SVN_ERR(svn_cmdline_printf(iterpool,
341                                       _("Passphrase: [not shown]\n")));
342        }
343      else if (strcmp(key, SVN_CONFIG_AUTHN_PASSTYPE_KEY) == 0)
344        SVN_ERR(svn_cmdline_printf(iterpool, _("Password cache: %s\n"),
345                                   value->data));
346      else if (strcmp(key, SVN_CONFIG_AUTHN_USERNAME_KEY) == 0)
347        SVN_ERR(svn_cmdline_printf(iterpool, _("Username: %s\n"), value->data));
348      else if (strcmp(key, SVN_CONFIG_AUTHN_ASCII_CERT_KEY) == 0)
349       SVN_ERR(show_cert(certinfo, value, iterpool));
350      else if (strcmp(key, SVN_CONFIG_AUTHN_FAILURES_KEY) == 0)
351        SVN_ERR(show_cert_failures(value->data, iterpool));
352      else
353        SVN_ERR(svn_cmdline_printf(iterpool, "%s: %s\n", key, value->data));
354    }
355  svn_pool_destroy(iterpool);
356
357  SVN_ERR(svn_cmdline_printf(scratch_pool, "\n"));
358  return SVN_NO_ERROR;
359}
360
361/* This implements `svn_config_auth_walk_func_t` */
362static svn_error_t *
363walk_credentials(svn_boolean_t *delete_cred,
364                 void *baton,
365                 const char *cred_kind,
366                 const char *realmstring,
367                 apr_hash_t *cred_hash,
368                 apr_pool_t *scratch_pool)
369{
370  struct walk_credentials_baton_t *b = baton;
371  apr_array_header_t *sorted_cred_items;
372  svn_x509_certinfo_t *certinfo = NULL;
373
374  *delete_cred = FALSE;
375
376  sorted_cred_items = svn_sort__hash(cred_hash,
377                                     svn_sort_compare_items_lexically,
378                                     scratch_pool);
379  if (b->patterns->nelts > 0)
380    {
381      svn_boolean_t match;
382
383      SVN_ERR(match_credential(&match, &certinfo, cred_kind, realmstring,
384                               b->patterns, sorted_cred_items,
385                               scratch_pool, scratch_pool));
386      if (!match)
387        return SVN_NO_ERROR;
388    }
389
390  b->matches++;
391
392  if (b->list)
393    SVN_ERR(list_credential(cred_kind, realmstring, sorted_cred_items,
394                            b->show_passwords, certinfo, scratch_pool));
395  if (b->delete)
396    {
397      *delete_cred = TRUE;
398      SVN_ERR(svn_cmdline_printf(scratch_pool,
399                                 _("Deleting %s credential for realm '%s'\n"),
400                                 cred_kind, realmstring));
401    }
402
403  return SVN_NO_ERROR;
404}
405
406
407/* This implements `svn_opt_subcommand_t'. */
408svn_error_t *
409svn_cl__auth(apr_getopt_t *os, void *baton, apr_pool_t *pool)
410{
411  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
412  const char *config_path;
413  struct walk_credentials_baton_t b;
414
415  b.matches = 0;
416  b.show_passwords = opt_state->show_passwords;
417  b.list = !opt_state->remove;
418  b.delete = opt_state->remove;
419  b.patterns = apr_array_make(pool, 1, sizeof(const char *));
420  for (; os->ind < os->argc; os->ind++)
421    {
422      /* The apr_getopt targets are still in native encoding. */
423      const char *raw_target = os->argv[os->ind];
424      const char *utf8_target;
425
426      SVN_ERR(svn_utf_cstring_to_utf8(&utf8_target,
427                                      raw_target, pool));
428      APR_ARRAY_PUSH(b.patterns, const char *) = utf8_target;
429    }
430
431  SVN_ERR(svn_config_get_user_config_path(&config_path,
432                                          opt_state->config_dir, NULL,
433                                          pool));
434
435  if (b.delete && b.patterns->nelts < 1)
436    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
437
438  SVN_ERR(svn_config_walk_auth_data(config_path, walk_credentials, &b, pool));
439
440  if (b.list)
441    {
442      if (b.matches == 0)
443        {
444          if (b.patterns->nelts == 0)
445            SVN_ERR(svn_cmdline_printf(pool,
446                      _("Credentials cache in '%s' is empty\n"),
447                      svn_dirent_local_style(config_path, pool)));
448          else
449            return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, 0,
450                                     _("Credentials cache in '%s' contains "
451                                       "no matching credentials"),
452                                     svn_dirent_local_style(config_path, pool));
453        }
454      else
455        {
456          if (b.patterns->nelts == 0)
457            SVN_ERR(svn_cmdline_printf(pool,
458                      Q_("Credentials cache in '%s' contains %d credential\n",
459                         "Credentials cache in '%s' contains %d credentials\n",
460                         b.matches),
461                      svn_dirent_local_style(config_path, pool), b.matches));
462          else
463            SVN_ERR(svn_cmdline_printf(pool,
464                      Q_("Credentials cache in '%s' contains %d matching credential\n",
465                         "Credentials cache in '%s' contains %d matching credentials\n",
466                         b.matches),
467                      svn_dirent_local_style(config_path, pool), b.matches));
468        }
469
470    }
471
472  if (b.delete)
473    {
474      if (b.matches == 0)
475        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, 0,
476                                 _("Credentials cache in '%s' contains "
477                                   "no matching credentials"),
478                                 svn_dirent_local_style(config_path, pool));
479      else
480        SVN_ERR(svn_cmdline_printf(pool,
481                  Q_("Deleted %d matching credential from '%s'\n",
482                     "Deleted %d matching credentials from '%s'\n",
483                     b.matches),
484                  b.matches, svn_dirent_local_style(config_path, pool)));
485    }
486
487  return SVN_NO_ERROR;
488}
489