1289177Speter/*
2289177Speter * auth-cmd.c:  Subversion auth creds cache administration
3289177Speter *
4289177Speter * ====================================================================
5289177Speter *    Licensed to the Apache Software Foundation (ASF) under one
6289177Speter *    or more contributor license agreements.  See the NOTICE file
7289177Speter *    distributed with this work for additional information
8289177Speter *    regarding copyright ownership.  The ASF licenses this file
9289177Speter *    to you under the Apache License, Version 2.0 (the
10289177Speter *    "License"); you may not use this file except in compliance
11289177Speter *    with the License.  You may obtain a copy of the License at
12289177Speter *
13289177Speter *      http://www.apache.org/licenses/LICENSE-2.0
14289177Speter *
15289177Speter *    Unless required by applicable law or agreed to in writing,
16289177Speter *    software distributed under the License is distributed on an
17289177Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18289177Speter *    KIND, either express or implied.  See the License for the
19289177Speter *    specific language governing permissions and limitations
20289177Speter *    under the License.
21289177Speter * ====================================================================
22289177Speter */
23289177Speter
24289177Speter/*** Includes. ***/
25289177Speter
26289177Speter#include <apr_general.h>
27289177Speter#include <apr_getopt.h>
28289177Speter#include <apr_fnmatch.h>
29289177Speter#include <apr_tables.h>
30289177Speter
31289177Speter#include "svn_private_config.h"
32289177Speter
33289177Speter#include "svn_private_config.h"
34289177Speter#include "svn_pools.h"
35289177Speter#include "svn_error.h"
36289177Speter#include "svn_opt.h"
37289177Speter#include "svn_dirent_uri.h"
38289177Speter#include "svn_hash.h"
39289177Speter#include "svn_utf.h"
40289177Speter#include "svn_cmdline.h"
41289177Speter#include "svn_config.h"
42289177Speter#include "svn_auth.h"
43289177Speter#include "svn_sorts.h"
44289177Speter#include "svn_base64.h"
45289177Speter#include "svn_x509.h"
46289177Speter#include "svn_time.h"
47289177Speter
48289177Speter#include "private/svn_cmdline_private.h"
49289177Speter#include "private/svn_token.h"
50289177Speter#include "private/svn_sorts_private.h"
51289177Speter
52289177Speter#include "cl.h"
53289177Speter
54289177Speter/* The separator between credentials . */
55289177Speter#define SEP_STRING \
56289177Speter  "------------------------------------------------------------------------\n"
57289177Speter
58289177Speterstatic svn_error_t *
59289177Spetershow_cert_failures(const char *failure_string,
60289177Speter                   apr_pool_t *scratch_pool)
61289177Speter{
62289177Speter  unsigned int failures;
63289177Speter
64289177Speter  SVN_ERR(svn_cstring_atoui(&failures, failure_string));
65289177Speter
66289177Speter  if (0 == (failures & (SVN_AUTH_SSL_NOTYETVALID | SVN_AUTH_SSL_EXPIRED |
67289177Speter                        SVN_AUTH_SSL_CNMISMATCH | SVN_AUTH_SSL_UNKNOWNCA |
68289177Speter                        SVN_AUTH_SSL_OTHER)))
69289177Speter    return SVN_NO_ERROR;
70289177Speter
71289177Speter  SVN_ERR(svn_cmdline_printf(
72289177Speter            scratch_pool, _("Automatic certificate validity check failed "
73289177Speter                            "because:\n")));
74289177Speter
75289177Speter  if (failures & SVN_AUTH_SSL_NOTYETVALID)
76289177Speter    SVN_ERR(svn_cmdline_printf(
77289177Speter              scratch_pool, _("  The certificate is not yet valid.\n")));
78289177Speter
79289177Speter  if (failures & SVN_AUTH_SSL_EXPIRED)
80289177Speter    SVN_ERR(svn_cmdline_printf(
81289177Speter              scratch_pool, _("  The certificate has expired.\n")));
82289177Speter
83289177Speter  if (failures & SVN_AUTH_SSL_CNMISMATCH)
84289177Speter    SVN_ERR(svn_cmdline_printf(
85289177Speter              scratch_pool, _("  The certificate's Common Name (hostname) "
86289177Speter                              "does not match the remote hostname.\n")));
87289177Speter
88289177Speter  if (failures & SVN_AUTH_SSL_UNKNOWNCA)
89289177Speter    SVN_ERR(svn_cmdline_printf(
90289177Speter              scratch_pool, _("  The certificate issuer is unknown.\n")));
91289177Speter
92289177Speter  if (failures & SVN_AUTH_SSL_OTHER)
93289177Speter    SVN_ERR(svn_cmdline_printf(
94289177Speter              scratch_pool, _("  Unknown verification failure.\n")));
95289177Speter
96289177Speter  return SVN_NO_ERROR;
97289177Speter}
98289177Speter
99289177Speter
100289177Speter/* decodes from format we store certs in for auth creds and
101289177Speter * turns parsing errors into warnings if PRINT_WARNING is TRUE
102289177Speter * and ignores them otherwise. returns NULL if it couldn't
103289177Speter * parse a cert for any reason. */
104289177Speterstatic svn_x509_certinfo_t *
105289177Speterparse_certificate(const svn_string_t *ascii_cert,
106289177Speter                  svn_boolean_t print_warning,
107289177Speter                  apr_pool_t *result_pool,
108289177Speter                  apr_pool_t *scratch_pool)
109289177Speter{
110289177Speter  svn_x509_certinfo_t *certinfo;
111289177Speter  const svn_string_t *der_cert;
112289177Speter  svn_error_t *err;
113289177Speter
114289177Speter  /* Convert header-less PEM to DER by undoing base64 encoding. */
115289177Speter  der_cert = svn_base64_decode_string(ascii_cert, scratch_pool);
116289177Speter
117289177Speter  err = svn_x509_parse_cert(&certinfo, der_cert->data, der_cert->len,
118289177Speter                            result_pool, scratch_pool);
119289177Speter  if (err)
120289177Speter    {
121289177Speter      /* Just display X.509 parsing errors as warnings and continue */
122289177Speter      if (print_warning)
123289177Speter        svn_handle_warning2(stderr, err, "svn: ");
124289177Speter      svn_error_clear(err);
125289177Speter      return NULL;
126289177Speter    }
127289177Speter
128289177Speter  return certinfo;
129289177Speter}
130289177Speter
131289177Speter
132289177Speterstruct walk_credentials_baton_t
133289177Speter{
134289177Speter  int matches;
135289177Speter  svn_boolean_t list;
136289177Speter  svn_boolean_t delete;
137289177Speter  svn_boolean_t show_passwords;
138289177Speter  apr_array_header_t *patterns;
139289177Speter};
140289177Speter
141289177Speterstatic svn_boolean_t
142289177Spetermatch_pattern(const char *pattern, const char *value,
143289177Speter              svn_boolean_t caseblind, apr_pool_t *scratch_pool)
144289177Speter{
145289177Speter  const char *p = apr_psprintf(scratch_pool, "*%s*", pattern);
146289177Speter  int flags = (caseblind ? APR_FNM_CASE_BLIND : 0);
147289177Speter
148289177Speter  return (apr_fnmatch(p, value, flags) == APR_SUCCESS);
149289177Speter}
150289177Speter
151289177Speterstatic svn_boolean_t
152289177Spetermatch_certificate(svn_x509_certinfo_t **certinfo,
153289177Speter                  const char *pattern,
154289177Speter                  const svn_string_t *ascii_cert,
155289177Speter                  apr_pool_t *result_pool,
156289177Speter                  apr_pool_t *scratch_pool)
157289177Speter{
158289177Speter  const char *value;
159289177Speter  const svn_checksum_t *checksum;
160289177Speter  const apr_array_header_t *hostnames;
161289177Speter  int i;
162289177Speter
163289177Speter  *certinfo = parse_certificate(ascii_cert, FALSE, result_pool, scratch_pool);
164289177Speter  if (*certinfo == NULL)
165289177Speter    return FALSE;
166289177Speter
167289177Speter  value = svn_x509_certinfo_get_subject(*certinfo, scratch_pool);
168289177Speter  if (match_pattern(pattern, value, FALSE, scratch_pool))
169289177Speter    return TRUE;
170289177Speter
171289177Speter  value = svn_x509_certinfo_get_issuer(*certinfo, scratch_pool);
172289177Speter  if (match_pattern(pattern, value, FALSE, scratch_pool))
173289177Speter    return TRUE;
174289177Speter
175289177Speter  checksum = svn_x509_certinfo_get_digest(*certinfo);
176289177Speter  value = svn_checksum_to_cstring_display(checksum, scratch_pool);
177289177Speter  if (match_pattern(pattern, value, TRUE, scratch_pool))
178289177Speter    return TRUE;
179289177Speter
180289177Speter  hostnames = svn_x509_certinfo_get_hostnames(*certinfo);
181289177Speter  if (hostnames)
182289177Speter    {
183289177Speter      for (i = 0; i < hostnames->nelts; i++)
184289177Speter        {
185289177Speter          const char *hostname = APR_ARRAY_IDX(hostnames, i, const char *);
186289177Speter          if (match_pattern(pattern, hostname, TRUE, scratch_pool))
187289177Speter            return TRUE;
188289177Speter        }
189289177Speter    }
190289177Speter
191289177Speter  return FALSE;
192289177Speter}
193289177Speter
194289177Speter
195289177Speterstatic svn_error_t *
196289177Spetermatch_credential(svn_boolean_t *match,
197289177Speter                 svn_x509_certinfo_t **certinfo,
198289177Speter                 const char *cred_kind,
199289177Speter                 const char *realmstring,
200289177Speter                 apr_array_header_t *patterns,
201289177Speter                 apr_array_header_t *cred_items,
202289177Speter                 apr_pool_t *result_pool,
203289177Speter                 apr_pool_t *scratch_pool)
204289177Speter{
205289177Speter  int i;
206289177Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
207289177Speter
208289177Speter  *match = FALSE;
209289177Speter
210289177Speter  for (i = 0; i < patterns->nelts; i++)
211289177Speter    {
212289177Speter      const char *pattern = APR_ARRAY_IDX(patterns, i, const char *);
213289177Speter      int j;
214289177Speter
215289177Speter      *match = match_pattern(pattern, cred_kind, FALSE, iterpool);
216289177Speter      if (!*match)
217289177Speter        *match = match_pattern(pattern, realmstring, FALSE, iterpool);
218289177Speter      if (!*match)
219289177Speter        {
220289177Speter          svn_pool_clear(iterpool);
221289177Speter          for (j = 0; j < cred_items->nelts; j++)
222289177Speter            {
223289177Speter              svn_sort__item_t item;
224289177Speter              const char *key;
225289177Speter              svn_string_t *value;
226289177Speter
227289177Speter              item = APR_ARRAY_IDX(cred_items, j, svn_sort__item_t);
228289177Speter              key = item.key;
229289177Speter              value = item.value;
230289177Speter              if (strcmp(key, SVN_CONFIG_AUTHN_PASSWORD_KEY) == 0 ||
231289177Speter                  strcmp(key, SVN_CONFIG_AUTHN_PASSPHRASE_KEY) == 0)
232289177Speter                continue; /* don't match secrets */
233289177Speter              else if (strcmp(key, SVN_CONFIG_AUTHN_ASCII_CERT_KEY) == 0)
234289177Speter                *match = match_certificate(certinfo, pattern, value,
235289177Speter                                           result_pool, iterpool);
236289177Speter              else
237289177Speter                *match = match_pattern(pattern, value->data, FALSE, iterpool);
238289177Speter
239289177Speter              if (*match)
240289177Speter                break;
241289177Speter            }
242289177Speter        }
243289177Speter      if (!*match)
244289177Speter        break;
245289177Speter    }
246289177Speter
247289177Speter  return SVN_NO_ERROR;
248289177Speter}
249289177Speter
250289177Speterstatic svn_error_t *
251289177Spetershow_cert(svn_x509_certinfo_t *certinfo, const svn_string_t *pem_cert,
252289177Speter          apr_pool_t *scratch_pool)
253289177Speter{
254289177Speter  const apr_array_header_t *hostnames;
255289177Speter
256289177Speter  if (certinfo == NULL)
257289177Speter    certinfo = parse_certificate(pem_cert, TRUE, scratch_pool, scratch_pool);
258289177Speter  if (certinfo == NULL)
259289177Speter    return SVN_NO_ERROR;
260289177Speter
261289177Speter  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Subject: %s\n"),
262289177Speter                             svn_x509_certinfo_get_subject(certinfo, scratch_pool)));
263289177Speter  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Valid from: %s\n"),
264289177Speter                             svn_time_to_human_cstring(
265289177Speter                                 svn_x509_certinfo_get_valid_from(certinfo),
266289177Speter                                 scratch_pool)));
267289177Speter  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Valid until: %s\n"),
268289177Speter                             svn_time_to_human_cstring(
269289177Speter                                 svn_x509_certinfo_get_valid_to(certinfo),
270289177Speter                                 scratch_pool)));
271289177Speter  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Issuer: %s\n"),
272289177Speter                             svn_x509_certinfo_get_issuer(certinfo, scratch_pool)));
273289177Speter  SVN_ERR(svn_cmdline_printf(scratch_pool, _("Fingerprint: %s\n"),
274289177Speter                             svn_checksum_to_cstring_display(
275289177Speter                                 svn_x509_certinfo_get_digest(certinfo),
276289177Speter                                 scratch_pool)));
277289177Speter
278289177Speter  hostnames = svn_x509_certinfo_get_hostnames(certinfo);
279289177Speter  if (hostnames && !apr_is_empty_array(hostnames))
280289177Speter    {
281289177Speter      int i;
282289177Speter      svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
283289177Speter      for (i = 0; i < hostnames->nelts; ++i)
284289177Speter        {
285289177Speter          const char *hostname = APR_ARRAY_IDX(hostnames, i, const char*);
286289177Speter          if (i > 0)
287289177Speter            svn_stringbuf_appendbytes(buf, ", ", 2);
288289177Speter          svn_stringbuf_appendbytes(buf, hostname, strlen(hostname));
289289177Speter        }
290289177Speter      SVN_ERR(svn_cmdline_printf(scratch_pool, _("Hostnames: %s\n"),
291289177Speter                                 buf->data));
292289177Speter    }
293289177Speter
294289177Speter  return SVN_NO_ERROR;
295289177Speter}
296289177Speter
297289177Speterstatic svn_error_t *
298289177Speterlist_credential(const char *cred_kind,
299289177Speter                const char *realmstring,
300289177Speter                apr_array_header_t *cred_items,
301289177Speter                svn_boolean_t show_passwords,
302289177Speter                svn_x509_certinfo_t *certinfo,
303289177Speter                apr_pool_t *scratch_pool)
304289177Speter{
305289177Speter  int i;
306289177Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
307289177Speter
308289177Speter  SVN_ERR(svn_cmdline_printf(scratch_pool, SEP_STRING));
309289177Speter  SVN_ERR(svn_cmdline_printf(scratch_pool,
310289177Speter                             _("Credential kind: %s\n"), cred_kind));
311289177Speter  SVN_ERR(svn_cmdline_printf(scratch_pool,
312289177Speter                             _("Authentication realm: %s\n"), realmstring));
313289177Speter
314289177Speter  for (i = 0; i < cred_items->nelts; i++)
315289177Speter    {
316289177Speter      svn_sort__item_t item;
317289177Speter      const char *key;
318289177Speter      svn_string_t *value;
319289177Speter
320289177Speter      svn_pool_clear(iterpool);
321289177Speter      item = APR_ARRAY_IDX(cred_items, i, svn_sort__item_t);
322289177Speter      key = item.key;
323289177Speter      value = item.value;
324289177Speter      if (strcmp(value->data, realmstring) == 0)
325289177Speter        continue; /* realm string was already shown above */
326289177Speter      else if (strcmp(key, SVN_CONFIG_AUTHN_PASSWORD_KEY) == 0)
327289177Speter        {
328289177Speter          if (show_passwords)
329289177Speter            SVN_ERR(svn_cmdline_printf(iterpool,
330289177Speter                                       _("Password: %s\n"), value->data));
331289177Speter          else
332289177Speter            SVN_ERR(svn_cmdline_printf(iterpool, _("Password: [not shown]\n")));
333289177Speter        }
334289177Speter      else if (strcmp(key, SVN_CONFIG_AUTHN_PASSPHRASE_KEY) == 0)
335289177Speter        {
336289177Speter          if (show_passwords)
337289177Speter            SVN_ERR(svn_cmdline_printf(iterpool,
338289177Speter                                       _("Passphrase: %s\n"), value->data));
339289177Speter          else
340289177Speter            SVN_ERR(svn_cmdline_printf(iterpool,
341289177Speter                                       _("Passphrase: [not shown]\n")));
342289177Speter        }
343289177Speter      else if (strcmp(key, SVN_CONFIG_AUTHN_PASSTYPE_KEY) == 0)
344289177Speter        SVN_ERR(svn_cmdline_printf(iterpool, _("Password cache: %s\n"),
345289177Speter                                   value->data));
346289177Speter      else if (strcmp(key, SVN_CONFIG_AUTHN_USERNAME_KEY) == 0)
347289177Speter        SVN_ERR(svn_cmdline_printf(iterpool, _("Username: %s\n"), value->data));
348289177Speter      else if (strcmp(key, SVN_CONFIG_AUTHN_ASCII_CERT_KEY) == 0)
349289177Speter       SVN_ERR(show_cert(certinfo, value, iterpool));
350289177Speter      else if (strcmp(key, SVN_CONFIG_AUTHN_FAILURES_KEY) == 0)
351289177Speter        SVN_ERR(show_cert_failures(value->data, iterpool));
352289177Speter      else
353289177Speter        SVN_ERR(svn_cmdline_printf(iterpool, "%s: %s\n", key, value->data));
354289177Speter    }
355289177Speter  svn_pool_destroy(iterpool);
356289177Speter
357289177Speter  SVN_ERR(svn_cmdline_printf(scratch_pool, "\n"));
358289177Speter  return SVN_NO_ERROR;
359289177Speter}
360289177Speter
361289177Speter/* This implements `svn_config_auth_walk_func_t` */
362289177Speterstatic svn_error_t *
363289177Speterwalk_credentials(svn_boolean_t *delete_cred,
364289177Speter                 void *baton,
365289177Speter                 const char *cred_kind,
366289177Speter                 const char *realmstring,
367289177Speter                 apr_hash_t *cred_hash,
368289177Speter                 apr_pool_t *scratch_pool)
369289177Speter{
370289177Speter  struct walk_credentials_baton_t *b = baton;
371289177Speter  apr_array_header_t *sorted_cred_items;
372289177Speter  svn_x509_certinfo_t *certinfo = NULL;
373289177Speter
374289177Speter  *delete_cred = FALSE;
375289177Speter
376289177Speter  sorted_cred_items = svn_sort__hash(cred_hash,
377289177Speter                                     svn_sort_compare_items_lexically,
378289177Speter                                     scratch_pool);
379289177Speter  if (b->patterns->nelts > 0)
380289177Speter    {
381289177Speter      svn_boolean_t match;
382289177Speter
383289177Speter      SVN_ERR(match_credential(&match, &certinfo, cred_kind, realmstring,
384289177Speter                               b->patterns, sorted_cred_items,
385289177Speter                               scratch_pool, scratch_pool));
386289177Speter      if (!match)
387289177Speter        return SVN_NO_ERROR;
388289177Speter    }
389289177Speter
390289177Speter  b->matches++;
391289177Speter
392289177Speter  if (b->list)
393289177Speter    SVN_ERR(list_credential(cred_kind, realmstring, sorted_cred_items,
394289177Speter                            b->show_passwords, certinfo, scratch_pool));
395289177Speter  if (b->delete)
396289177Speter    {
397289177Speter      *delete_cred = TRUE;
398289177Speter      SVN_ERR(svn_cmdline_printf(scratch_pool,
399289177Speter                                 _("Deleting %s credential for realm '%s'\n"),
400289177Speter                                 cred_kind, realmstring));
401289177Speter    }
402289177Speter
403289177Speter  return SVN_NO_ERROR;
404289177Speter}
405289177Speter
406289177Speter
407289177Speter/* This implements `svn_opt_subcommand_t'. */
408289177Spetersvn_error_t *
409289177Spetersvn_cl__auth(apr_getopt_t *os, void *baton, apr_pool_t *pool)
410289177Speter{
411289177Speter  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
412289177Speter  const char *config_path;
413289177Speter  struct walk_credentials_baton_t b;
414289177Speter
415289177Speter  b.matches = 0;
416289177Speter  b.show_passwords = opt_state->show_passwords;
417289177Speter  b.list = !opt_state->remove;
418289177Speter  b.delete = opt_state->remove;
419289177Speter  b.patterns = apr_array_make(pool, 1, sizeof(const char *));
420289177Speter  for (; os->ind < os->argc; os->ind++)
421289177Speter    {
422289177Speter      /* The apr_getopt targets are still in native encoding. */
423289177Speter      const char *raw_target = os->argv[os->ind];
424289177Speter      const char *utf8_target;
425289177Speter
426289177Speter      SVN_ERR(svn_utf_cstring_to_utf8(&utf8_target,
427289177Speter                                      raw_target, pool));
428289177Speter      APR_ARRAY_PUSH(b.patterns, const char *) = utf8_target;
429289177Speter    }
430289177Speter
431289177Speter  SVN_ERR(svn_config_get_user_config_path(&config_path,
432289177Speter                                          opt_state->config_dir, NULL,
433289177Speter                                          pool));
434289177Speter
435289177Speter  if (b.delete && b.patterns->nelts < 1)
436289177Speter    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
437289177Speter
438289177Speter  SVN_ERR(svn_config_walk_auth_data(config_path, walk_credentials, &b, pool));
439289177Speter
440289177Speter  if (b.list)
441289177Speter    {
442289177Speter      if (b.matches == 0)
443289177Speter        {
444289177Speter          if (b.patterns->nelts == 0)
445289177Speter            SVN_ERR(svn_cmdline_printf(pool,
446289177Speter                      _("Credentials cache in '%s' is empty\n"),
447289177Speter                      svn_dirent_local_style(config_path, pool)));
448289177Speter          else
449289177Speter            return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, 0,
450289177Speter                                     _("Credentials cache in '%s' contains "
451289177Speter                                       "no matching credentials"),
452289177Speter                                     svn_dirent_local_style(config_path, pool));
453289177Speter        }
454289177Speter      else
455289177Speter        {
456289177Speter          if (b.patterns->nelts == 0)
457289177Speter            SVN_ERR(svn_cmdline_printf(pool,
458289177Speter                      _("Credentials cache in '%s' contains %d credentials\n"),
459289177Speter                      svn_dirent_local_style(config_path, pool), b.matches));
460289177Speter          else
461289177Speter            SVN_ERR(svn_cmdline_printf(pool,
462289177Speter                      _("Credentials cache in '%s' contains %d matching "
463289177Speter                        "credentials\n"),
464289177Speter                      svn_dirent_local_style(config_path, pool), b.matches));
465289177Speter        }
466289177Speter
467289177Speter    }
468289177Speter
469289177Speter  if (b.delete)
470289177Speter    {
471289177Speter      if (b.matches == 0)
472289177Speter        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, 0,
473289177Speter                                 _("Credentials cache in '%s' contains "
474289177Speter                                   "no matching credentials"),
475289177Speter                                 svn_dirent_local_style(config_path, pool));
476289177Speter      else
477289177Speter        SVN_ERR(svn_cmdline_printf(pool, _("Deleted %d matching credentials "
478289177Speter                                   "from '%s'\n"), b.matches,
479289177Speter                                   svn_dirent_local_style(config_path, pool)));
480289177Speter    }
481289177Speter
482289177Speter  return SVN_NO_ERROR;
483289177Speter}
484