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
58 static svn_error_t *
show_cert_failures(const char * failure_string,apr_pool_t * scratch_pool)59 show_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. */
104 static svn_x509_certinfo_t *
parse_certificate(const svn_string_t * ascii_cert,svn_boolean_t print_warning,apr_pool_t * result_pool,apr_pool_t * scratch_pool)105 parse_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
132 struct 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
141 static svn_boolean_t
match_pattern(const char * pattern,const char * value,svn_boolean_t caseblind,apr_pool_t * scratch_pool)142 match_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
151 static svn_boolean_t
match_certificate(svn_x509_certinfo_t ** certinfo,const char * pattern,const svn_string_t * ascii_cert,apr_pool_t * result_pool,apr_pool_t * scratch_pool)152 match_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
195 static svn_error_t *
match_credential(svn_boolean_t * match,svn_x509_certinfo_t ** certinfo,const char * cred_kind,const char * realmstring,apr_array_header_t * patterns,apr_array_header_t * cred_items,apr_pool_t * result_pool,apr_pool_t * scratch_pool)196 match_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
250 static svn_error_t *
show_cert(svn_x509_certinfo_t * certinfo,const svn_string_t * pem_cert,apr_pool_t * scratch_pool)251 show_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
297 static svn_error_t *
list_credential(const char * cred_kind,const char * realmstring,apr_array_header_t * cred_items,svn_boolean_t show_passwords,svn_x509_certinfo_t * certinfo,apr_pool_t * scratch_pool)298 list_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` */
362 static svn_error_t *
walk_credentials(svn_boolean_t * delete_cred,void * baton,const char * cred_kind,const char * realmstring,apr_hash_t * cred_hash,apr_pool_t * scratch_pool)363 walk_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'. */
408 svn_error_t *
svn_cl__auth(apr_getopt_t * os,void * baton,apr_pool_t * pool)409 svn_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