| /* |
| * auth-cmd.c: Subversion auth creds cache administration |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| /*** Includes. ***/ |
| |
| #include <apr_general.h> |
| #include <apr_getopt.h> |
| #include <apr_fnmatch.h> |
| #include <apr_tables.h> |
| |
| #include "svn_private_config.h" |
| |
| #include "svn_private_config.h" |
| #include "svn_pools.h" |
| #include "svn_error.h" |
| #include "svn_opt.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_hash.h" |
| #include "svn_utf.h" |
| #include "svn_cmdline.h" |
| #include "svn_config.h" |
| #include "svn_auth.h" |
| #include "svn_sorts.h" |
| #include "svn_base64.h" |
| #include "svn_x509.h" |
| #include "svn_time.h" |
| |
| #include "private/svn_cmdline_private.h" |
| #include "private/svn_token.h" |
| #include "private/svn_sorts_private.h" |
| |
| #include "cl.h" |
| |
| /* The separator between credentials . */ |
| #define SEP_STRING \ |
| "------------------------------------------------------------------------\n" |
| |
| static svn_error_t * |
| show_cert_failures(const char *failure_string, |
| apr_pool_t *scratch_pool) |
| { |
| unsigned int failures; |
| |
| SVN_ERR(svn_cstring_atoui(&failures, failure_string)); |
| |
| if (0 == (failures & (SVN_AUTH_SSL_NOTYETVALID | SVN_AUTH_SSL_EXPIRED | |
| SVN_AUTH_SSL_CNMISMATCH | SVN_AUTH_SSL_UNKNOWNCA | |
| SVN_AUTH_SSL_OTHER))) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR(svn_cmdline_printf( |
| scratch_pool, _("Automatic certificate validity check failed " |
| "because:\n"))); |
| |
| if (failures & SVN_AUTH_SSL_NOTYETVALID) |
| SVN_ERR(svn_cmdline_printf( |
| scratch_pool, _(" The certificate is not yet valid.\n"))); |
| |
| if (failures & SVN_AUTH_SSL_EXPIRED) |
| SVN_ERR(svn_cmdline_printf( |
| scratch_pool, _(" The certificate has expired.\n"))); |
| |
| if (failures & SVN_AUTH_SSL_CNMISMATCH) |
| SVN_ERR(svn_cmdline_printf( |
| scratch_pool, _(" The certificate's Common Name (hostname) " |
| "does not match the remote hostname.\n"))); |
| |
| if (failures & SVN_AUTH_SSL_UNKNOWNCA) |
| SVN_ERR(svn_cmdline_printf( |
| scratch_pool, _(" The certificate issuer is unknown.\n"))); |
| |
| if (failures & SVN_AUTH_SSL_OTHER) |
| SVN_ERR(svn_cmdline_printf( |
| scratch_pool, _(" Unknown verification failure.\n"))); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* decodes from format we store certs in for auth creds and |
| * turns parsing errors into warnings if PRINT_WARNING is TRUE |
| * and ignores them otherwise. returns NULL if it couldn't |
| * parse a cert for any reason. */ |
| 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) |
| { |
| svn_x509_certinfo_t *certinfo; |
| const svn_string_t *der_cert; |
| svn_error_t *err; |
| |
| /* Convert header-less PEM to DER by undoing base64 encoding. */ |
| der_cert = svn_base64_decode_string(ascii_cert, scratch_pool); |
| |
| err = svn_x509_parse_cert(&certinfo, der_cert->data, der_cert->len, |
| result_pool, scratch_pool); |
| if (err) |
| { |
| /* Just display X.509 parsing errors as warnings and continue */ |
| if (print_warning) |
| svn_handle_warning2(stderr, err, "svn: "); |
| svn_error_clear(err); |
| return NULL; |
| } |
| |
| return certinfo; |
| } |
| |
| |
| struct walk_credentials_baton_t |
| { |
| int matches; |
| svn_boolean_t list; |
| svn_boolean_t delete; |
| svn_boolean_t show_passwords; |
| apr_array_header_t *patterns; |
| }; |
| |
| static svn_boolean_t |
| match_pattern(const char *pattern, const char *value, |
| svn_boolean_t caseblind, apr_pool_t *scratch_pool) |
| { |
| const char *p = apr_psprintf(scratch_pool, "*%s*", pattern); |
| int flags = (caseblind ? APR_FNM_CASE_BLIND : 0); |
| |
| return (apr_fnmatch(p, value, flags) == APR_SUCCESS); |
| } |
| |
| 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) |
| { |
| const char *value; |
| const svn_checksum_t *checksum; |
| const apr_array_header_t *hostnames; |
| int i; |
| |
| *certinfo = parse_certificate(ascii_cert, FALSE, result_pool, scratch_pool); |
| if (*certinfo == NULL) |
| return FALSE; |
| |
| value = svn_x509_certinfo_get_subject(*certinfo, scratch_pool); |
| if (match_pattern(pattern, value, FALSE, scratch_pool)) |
| return TRUE; |
| |
| value = svn_x509_certinfo_get_issuer(*certinfo, scratch_pool); |
| if (match_pattern(pattern, value, FALSE, scratch_pool)) |
| return TRUE; |
| |
| checksum = svn_x509_certinfo_get_digest(*certinfo); |
| value = svn_checksum_to_cstring_display(checksum, scratch_pool); |
| if (match_pattern(pattern, value, TRUE, scratch_pool)) |
| return TRUE; |
| |
| hostnames = svn_x509_certinfo_get_hostnames(*certinfo); |
| if (hostnames) |
| { |
| for (i = 0; i < hostnames->nelts; i++) |
| { |
| const char *hostname = APR_ARRAY_IDX(hostnames, i, const char *); |
| if (match_pattern(pattern, hostname, TRUE, scratch_pool)) |
| return TRUE; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| |
| 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) |
| { |
| int i; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| |
| *match = FALSE; |
| |
| for (i = 0; i < patterns->nelts; i++) |
| { |
| const char *pattern = APR_ARRAY_IDX(patterns, i, const char *); |
| int j; |
| |
| *match = match_pattern(pattern, cred_kind, FALSE, iterpool); |
| if (!*match) |
| *match = match_pattern(pattern, realmstring, FALSE, iterpool); |
| if (!*match) |
| { |
| svn_pool_clear(iterpool); |
| for (j = 0; j < cred_items->nelts; j++) |
| { |
| svn_sort__item_t item; |
| const char *key; |
| svn_string_t *value; |
| |
| item = APR_ARRAY_IDX(cred_items, j, svn_sort__item_t); |
| key = item.key; |
| value = item.value; |
| if (strcmp(key, SVN_CONFIG_AUTHN_PASSWORD_KEY) == 0 || |
| strcmp(key, SVN_CONFIG_AUTHN_PASSPHRASE_KEY) == 0) |
| continue; /* don't match secrets */ |
| else if (strcmp(key, SVN_CONFIG_AUTHN_ASCII_CERT_KEY) == 0) |
| *match = match_certificate(certinfo, pattern, value, |
| result_pool, iterpool); |
| else |
| *match = match_pattern(pattern, value->data, FALSE, iterpool); |
| |
| if (*match) |
| break; |
| } |
| } |
| if (!*match) |
| break; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| show_cert(svn_x509_certinfo_t *certinfo, const svn_string_t *pem_cert, |
| apr_pool_t *scratch_pool) |
| { |
| const apr_array_header_t *hostnames; |
| |
| if (certinfo == NULL) |
| certinfo = parse_certificate(pem_cert, TRUE, scratch_pool, scratch_pool); |
| if (certinfo == NULL) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR(svn_cmdline_printf(scratch_pool, _("Subject: %s\n"), |
| svn_x509_certinfo_get_subject(certinfo, scratch_pool))); |
| SVN_ERR(svn_cmdline_printf(scratch_pool, _("Valid from: %s\n"), |
| svn_time_to_human_cstring( |
| svn_x509_certinfo_get_valid_from(certinfo), |
| scratch_pool))); |
| SVN_ERR(svn_cmdline_printf(scratch_pool, _("Valid until: %s\n"), |
| svn_time_to_human_cstring( |
| svn_x509_certinfo_get_valid_to(certinfo), |
| scratch_pool))); |
| SVN_ERR(svn_cmdline_printf(scratch_pool, _("Issuer: %s\n"), |
| svn_x509_certinfo_get_issuer(certinfo, scratch_pool))); |
| SVN_ERR(svn_cmdline_printf(scratch_pool, _("Fingerprint: %s\n"), |
| svn_checksum_to_cstring_display( |
| svn_x509_certinfo_get_digest(certinfo), |
| scratch_pool))); |
| |
| hostnames = svn_x509_certinfo_get_hostnames(certinfo); |
| if (hostnames && !apr_is_empty_array(hostnames)) |
| { |
| int i; |
| svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool); |
| for (i = 0; i < hostnames->nelts; ++i) |
| { |
| const char *hostname = APR_ARRAY_IDX(hostnames, i, const char*); |
| if (i > 0) |
| svn_stringbuf_appendbytes(buf, ", ", 2); |
| svn_stringbuf_appendbytes(buf, hostname, strlen(hostname)); |
| } |
| SVN_ERR(svn_cmdline_printf(scratch_pool, _("Hostnames: %s\n"), |
| buf->data)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| 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) |
| { |
| int i; |
| apr_pool_t *iterpool = svn_pool_create(scratch_pool); |
| |
| SVN_ERR(svn_cmdline_printf(scratch_pool, SEP_STRING)); |
| SVN_ERR(svn_cmdline_printf(scratch_pool, |
| _("Credential kind: %s\n"), cred_kind)); |
| SVN_ERR(svn_cmdline_printf(scratch_pool, |
| _("Authentication realm: %s\n"), realmstring)); |
| |
| for (i = 0; i < cred_items->nelts; i++) |
| { |
| svn_sort__item_t item; |
| const char *key; |
| svn_string_t *value; |
| |
| svn_pool_clear(iterpool); |
| item = APR_ARRAY_IDX(cred_items, i, svn_sort__item_t); |
| key = item.key; |
| value = item.value; |
| if (strcmp(value->data, realmstring) == 0) |
| continue; /* realm string was already shown above */ |
| else if (strcmp(key, SVN_CONFIG_AUTHN_PASSWORD_KEY) == 0) |
| { |
| if (show_passwords) |
| SVN_ERR(svn_cmdline_printf(iterpool, |
| _("Password: %s\n"), value->data)); |
| else |
| SVN_ERR(svn_cmdline_printf(iterpool, _("Password: [not shown]\n"))); |
| } |
| else if (strcmp(key, SVN_CONFIG_AUTHN_PASSPHRASE_KEY) == 0) |
| { |
| if (show_passwords) |
| SVN_ERR(svn_cmdline_printf(iterpool, |
| _("Passphrase: %s\n"), value->data)); |
| else |
| SVN_ERR(svn_cmdline_printf(iterpool, |
| _("Passphrase: [not shown]\n"))); |
| } |
| else if (strcmp(key, SVN_CONFIG_AUTHN_PASSTYPE_KEY) == 0) |
| SVN_ERR(svn_cmdline_printf(iterpool, _("Password cache: %s\n"), |
| value->data)); |
| else if (strcmp(key, SVN_CONFIG_AUTHN_USERNAME_KEY) == 0) |
| SVN_ERR(svn_cmdline_printf(iterpool, _("Username: %s\n"), value->data)); |
| else if (strcmp(key, SVN_CONFIG_AUTHN_ASCII_CERT_KEY) == 0) |
| SVN_ERR(show_cert(certinfo, value, iterpool)); |
| else if (strcmp(key, SVN_CONFIG_AUTHN_FAILURES_KEY) == 0) |
| SVN_ERR(show_cert_failures(value->data, iterpool)); |
| else |
| SVN_ERR(svn_cmdline_printf(iterpool, "%s: %s\n", key, value->data)); |
| } |
| svn_pool_destroy(iterpool); |
| |
| SVN_ERR(svn_cmdline_printf(scratch_pool, "\n")); |
| return SVN_NO_ERROR; |
| } |
| |
| /* This implements `svn_config_auth_walk_func_t` */ |
| 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) |
| { |
| struct walk_credentials_baton_t *b = baton; |
| apr_array_header_t *sorted_cred_items; |
| svn_x509_certinfo_t *certinfo = NULL; |
| |
| *delete_cred = FALSE; |
| |
| sorted_cred_items = svn_sort__hash(cred_hash, |
| svn_sort_compare_items_lexically, |
| scratch_pool); |
| if (b->patterns->nelts > 0) |
| { |
| svn_boolean_t match; |
| |
| SVN_ERR(match_credential(&match, &certinfo, cred_kind, realmstring, |
| b->patterns, sorted_cred_items, |
| scratch_pool, scratch_pool)); |
| if (!match) |
| return SVN_NO_ERROR; |
| } |
| |
| b->matches++; |
| |
| if (b->list) |
| SVN_ERR(list_credential(cred_kind, realmstring, sorted_cred_items, |
| b->show_passwords, certinfo, scratch_pool)); |
| if (b->delete) |
| { |
| *delete_cred = TRUE; |
| SVN_ERR(svn_cmdline_printf(scratch_pool, |
| _("Deleting %s credential for realm '%s'\n"), |
| cred_kind, realmstring)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* This implements `svn_opt_subcommand_t'. */ |
| svn_error_t * |
| svn_cl__auth(apr_getopt_t *os, void *baton, apr_pool_t *pool) |
| { |
| svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; |
| const char *config_path; |
| struct walk_credentials_baton_t b; |
| |
| b.matches = 0; |
| b.show_passwords = opt_state->show_passwords; |
| b.list = !opt_state->remove; |
| b.delete = opt_state->remove; |
| b.patterns = apr_array_make(pool, 1, sizeof(const char *)); |
| for (; os->ind < os->argc; os->ind++) |
| { |
| /* The apr_getopt targets are still in native encoding. */ |
| const char *raw_target = os->argv[os->ind]; |
| const char *utf8_target; |
| |
| SVN_ERR(svn_utf_cstring_to_utf8(&utf8_target, |
| raw_target, pool)); |
| APR_ARRAY_PUSH(b.patterns, const char *) = utf8_target; |
| } |
| |
| SVN_ERR(svn_config_get_user_config_path(&config_path, |
| opt_state->config_dir, NULL, |
| pool)); |
| |
| if (b.delete && b.patterns->nelts < 1) |
| return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); |
| |
| SVN_ERR(svn_config_walk_auth_data(config_path, walk_credentials, &b, pool)); |
| |
| if (b.list) |
| { |
| if (b.matches == 0) |
| { |
| if (b.patterns->nelts == 0) |
| SVN_ERR(svn_cmdline_printf(pool, |
| _("Credentials cache in '%s' is empty\n"), |
| svn_dirent_local_style(config_path, pool))); |
| else |
| return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, 0, |
| _("Credentials cache in '%s' contains " |
| "no matching credentials"), |
| svn_dirent_local_style(config_path, pool)); |
| } |
| else |
| { |
| if (b.patterns->nelts == 0) |
| SVN_ERR(svn_cmdline_printf(pool, |
| Q_("Credentials cache in '%s' contains %d credential\n", |
| "Credentials cache in '%s' contains %d credentials\n", |
| b.matches), |
| svn_dirent_local_style(config_path, pool), b.matches)); |
| else |
| SVN_ERR(svn_cmdline_printf(pool, |
| Q_("Credentials cache in '%s' contains %d matching credential\n", |
| "Credentials cache in '%s' contains %d matching credentials\n", |
| b.matches), |
| svn_dirent_local_style(config_path, pool), b.matches)); |
| } |
| |
| } |
| |
| if (b.delete) |
| { |
| if (b.matches == 0) |
| return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, 0, |
| _("Credentials cache in '%s' contains " |
| "no matching credentials"), |
| svn_dirent_local_style(config_path, pool)); |
| else |
| SVN_ERR(svn_cmdline_printf(pool, |
| Q_("Deleted %d matching credential from '%s'\n", |
| "Deleted %d matching credentials from '%s'\n", |
| b.matches), |
| b.matches, svn_dirent_local_style(config_path, pool))); |
| } |
| |
| return SVN_NO_ERROR; |
| } |