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