diff --git a/.gitignore b/.gitignore index 488bb739..bbb65335 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ mod_md-*.tar.gz m4 .cache src/a2md +a2md.1 # Automake test-suite artifacts /test-driver diff --git a/ChangeLog b/ChangeLog index fd3620d9..70d98c25 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +V2.3.next +---------------------------------------------------------------------------------------------------- + * Fix lowercasing of filenames + * Improved format of Managed Certificate Status + * Provide fallback certificates for all key types requested in MDPrivateKeys + * Update /.httpd/certificate-status to correctly handle multiple keys + v2.3.0 (BETA) ---------------------------------------------------------------------------------------------------- * MDPrivateKeys checks for duplicate key specifications. There can only be one RSA key diff --git a/README.md b/README.md index d8ec4391..403039cb 100644 --- a/README.md +++ b/README.md @@ -934,10 +934,17 @@ upcoming certificates on a domain. You invoke it like this: ``` > curl https://eissing.org/.httpd/certificate-status { - "valid-from": "Mon, 01 Apr 2019 06:47:43 GMT", - "valid-until": "Sun, 30 Jun 2019 06:47:43 GMT", + "rsa": { + "valid": { + "from": "Mon, 01 Apr 2019 06:47:43 GMT", + "until": "Sun, 30 Jun 2019 06:47:43 GMT" + }, "serial": "03D02EDA041CB95BF23B030C308FDE0B35B7", "sha256-fingerprint" : "xx:yy:zz:..." + }, + "P-256": { + ... + } } ``` @@ -949,16 +956,29 @@ When a new certificate has been obtained, but is not activated yet, this will sh ``` { - "valid-from": "Mon, 01 Apr 2019 06:47:43 GMT", - "valid-until": "Sun, 30 Jun 2019 06:47:43 GMT", - "serial": "03D02EDA041CB95BF23B030C308FDE0B35B7" + "rsa": { + "valid": { + "from": "Mon, 01 Apr 2019 06:47:43 GMT", + "until": "Sun, 30 Jun 2019 06:47:43 GMT" + }, + "serial": "03D02EDA041CB95BF23B030C308FDE0B35B7", "sha256-fingerprint" : "xx:yy:zz:..." "renewal": { - "valid-from": "Tue, 21 May 2019 11:53:59 GMT", - "valid-until": "Mon, 19 Aug 2019 11:53:59 GMT", - "serial": "FFC16E5FEFBE90805AC153D70EF9E8D3873A", - "sha256-fingerprint" : "aa:bb:cc:..." + "name": "example.net", + "finished": true, + "notified": false, + "last-run": "Thu, 02 May 2019 21:54:22 GMT", + "errors": 0, + "last": { + "status": 0, + "detail": "certificate status is GOOD, status valid Mon, 01 Apr 2019 06:47:43 GMT - Sun, 30 Jun 2019 06:47:43 GMT", + "activity": "status of certid xxyyzzqq, reading response" + } + }, + "P-256": { + ... } +} ``` with `renewal` giving the properties of the new certificate, once it has been obtained. This can be exposed publicly as well, since - once the server is reloaded, it is part of every TLS connection. diff --git a/src/md_store.c b/src/md_store.c index b2c2c3b5..2a00c244 100644 --- a/src/md_store.c +++ b/src/md_store.c @@ -256,14 +256,15 @@ typedef struct { static const char *pk_filename(const char *keyname, const char *base, apr_pool_t *p) { - char *s; + char *s, *t; /* We also run on various filesystems with difference upper/lower preserve matching * rules. Normalize the names we use, since private key specifications are basically * user input. */ s = (keyname && apr_strnatcasecmp("rsa", keyname))? apr_pstrcat(p, base, ".", keyname, ".pem", NULL) : apr_pstrcat(p, base, ".pem", NULL); - apr_tolower(s); + for (t = s; *t; t++ ) + *t = apr_tolower(*t); return s; } diff --git a/src/md_store.h b/src/md_store.h index 4fac548d..969d3f56 100644 --- a/src/md_store.h +++ b/src/md_store.h @@ -78,8 +78,8 @@ typedef enum { #define MD_FN_CERT "cert.pem" #define MD_FN_HTTPD_JSON "httpd.json" -#define MD_FN_FALLBACK_PKEY "fallback-privkey.pem" -#define MD_FN_FALLBACK_CERT "fallback-cert.pem" +#define MD_FN_FALLBACK_PKEY "fallback-%s" +#define MD_FN_FALLBACK_CERT "fallback-%s" /** * Load the JSON value at key "group/name/aspect", allocated from pool p. diff --git a/src/mod_md.c b/src/mod_md.c index 867488c7..ccd45a7d 100644 --- a/src/mod_md.c +++ b/src/mod_md.c @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + #include #include #include @@ -86,12 +86,12 @@ static int log_is_level(void *baton, apr_pool_t *p, md_log_level_t level) #define LOG_BUF_LEN 16*1024 -static void log_print(const char *file, int line, md_log_level_t level, +static void log_print(const char *file, int line, md_log_level_t level, apr_status_t rv, void *baton, apr_pool_t *p, const char *fmt, va_list ap) { if (log_is_level(baton, p, level)) { char buffer[LOG_BUF_LEN]; - + memset(buffer, 0, sizeof(buffer)); apr_vsnprintf(buffer, LOG_BUF_LEN-1, fmt, ap); buffer[LOG_BUF_LEN-1] = '\0'; @@ -125,7 +125,7 @@ static apr_status_t cleanup_setups(void *dummy) return APR_SUCCESS; } -static void init_setups(apr_pool_t *p, server_rec *base_server) +static void init_setups(apr_pool_t *p, server_rec *base_server) { log_server = base_server; apr_pool_cleanup_register(p, NULL, cleanup_setups, apr_pool_cleanup_null); @@ -148,7 +148,7 @@ static notify_rate notify_rates[] = { { "ocsp-errored", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */ }; -static apr_status_t notify(md_job_t *job, const char *reason, +static apr_status_t notify(md_job_t *job, const char *reason, md_result_t *result, apr_pool_t *p, void *baton) { md_mod_conf_t *mc = baton; @@ -160,7 +160,7 @@ static apr_status_t notify(md_job_t *job, const char *reason, md_timeperiod_t since_last; const char *log_msg_reason; int i; - + log_msg_reason = apr_psprintf(p, "message-%s", reason); for (i = 0; i < (int)(sizeof(notify_rates)/sizeof(notify_rates[0])); ++i) { if (!strcmp(reason, notify_rates[i].reason)) { @@ -176,36 +176,36 @@ static apr_status_t notify(md_job_t *job, const char *reason, return APR_SUCCESS; } } - + if (!strcmp("renewed", reason)) { if (mc->notify_cmd) { - cmdline = apr_psprintf(p, "%s %s", mc->notify_cmd, job->mdomain); + cmdline = apr_psprintf(p, "%s %s", mc->notify_cmd, job->mdomain); apr_tokenize_to_argv(cmdline, (char***)&argv, p); rv = md_util_exec(p, argv[0], argv, &exit_code); - + if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL; if (APR_SUCCESS != rv) { - md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10108)), - "MDNotifyCmd %s failed with exit code %d.", + md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10108)), + "MDNotifyCmd %s failed with exit code %d.", mc->notify_cmd, exit_code); md_result_log(result, MD_LOG_ERR); md_job_log_append(job, "notify-error", result->problem, result->detail); return rv; } } - md_log_perror(MD_LOG_MARK, MD_LOG_NOTICE, 0, p, APLOGNO(10059) + md_log_perror(MD_LOG_MARK, MD_LOG_NOTICE, 0, p, APLOGNO(10059) "The Managed Domain %s has been setup and changes " "will be activated on next (graceful) server restart.", job->mdomain); } if (mc->message_cmd) { - cmdline = apr_psprintf(p, "%s %s %s", mc->message_cmd, reason, job->mdomain); + cmdline = apr_psprintf(p, "%s %s %s", mc->message_cmd, reason, job->mdomain); apr_tokenize_to_argv(cmdline, (char***)&argv, p); rv = md_util_exec(p, argv[0], argv, &exit_code); - + if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL; if (APR_SUCCESS != rv) { - md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10109)), - "MDMessageCmd %s failed with exit code %d.", + md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10109)), + "MDMessageCmd %s failed with exit code %d.", mc->message_cmd, exit_code); md_result_log(result, MD_LOG_ERR); md_job_log_append(job, "message-error", reason, result->detail); @@ -220,19 +220,19 @@ static apr_status_t notify(md_job_t *job, const char *reason, /* store setup */ static apr_status_t store_file_ev(void *baton, struct md_store_t *store, - md_store_fs_ev_t ev, unsigned int group, - const char *fname, apr_filetype_e ftype, + md_store_fs_ev_t ev, unsigned int group, + const char *fname, apr_filetype_e ftype, apr_pool_t *p) { server_rec *s = baton; apr_status_t rv; - + (void)store; - ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "store event=%d on %s %s (group %d)", + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "store event=%d on %s %s (group %d)", ev, (ftype == APR_DIR)? "dir" : "file", fname, group); - - /* Directories in group CHALLENGES, STAGING and OCSP are written to - * under a different user. Give her ownership. + + /* Directories in group CHALLENGES, STAGING and OCSP are written to + * under a different user. Give her ownership. */ if (ftype == APR_DIR) { switch (group) { @@ -244,19 +244,19 @@ static apr_status_t store_file_ev(void *baton, struct md_store_t *store, return rv; } break; - default: + default: break; } } return APR_SUCCESS; } -static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group, +static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group, apr_pool_t *p, server_rec *s) { const char *dir; apr_status_t rv; - + if (APR_SUCCESS == (rv = md_store_get_fname(&dir, store, group, NULL, NULL, p)) && APR_SUCCESS == (rv = apr_dir_make_recursive(dir, MD_FPROT_D_UALL_GREAD, p))) { rv = store_file_ev(s, store, MD_S_FS_EV_CREATED, group, dir, APR_DIR, p); @@ -264,14 +264,14 @@ static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group, return rv; } -static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc, +static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc, apr_pool_t *p, server_rec *s) { const char *base_dir; apr_status_t rv; - + base_dir = ap_server_root_relative(p, mc->base_dir); - + if (APR_SUCCESS != (rv = md_store_fs_init(pstore, p, base_dir))) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10046)"setup store for %s", base_dir); goto leave; @@ -283,11 +283,11 @@ static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc, || APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_ACCOUNTS, p, s)) || APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_OCSP, p, s)) ) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047) + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047) "setup challenges directory"); goto leave; } - + leave: return rv; } @@ -332,7 +332,7 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p) } if (!md->ca_challenges && md->sc->ca_challenges) { md->ca_challenges = apr_array_copy(p, md->sc->ca_challenges); - } + } if (md_pkeys_spec_is_empty(md->pks)) { md->pks = md->sc->pks; } @@ -347,7 +347,7 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p) } } -static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s, +static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s, int *pupdates, apr_pool_t *p) { if (md_contains(md, domain, 0)) { @@ -373,16 +373,16 @@ static apr_status_t md_cover_server(md_t *md, server_rec *s, int *pupdates, apr_ apr_status_t rv; const char *name; int i; - + if (APR_SUCCESS == (rv = check_coverage(md, s->server_hostname, s, pupdates, p))) { - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "md[%s]: auto add, covers name %s", md->name, s->server_hostname); for (i = 0; s->names && i < s->names->nelts; ++i) { name = APR_ARRAY_IDX(s->names, i, const char*); if (APR_SUCCESS != (rv = check_coverage(md, name, s, pupdates, p))) { break; } - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "md[%s]: auto add, covers alias %s", md->name, name); } } @@ -406,7 +406,7 @@ static int uses_port(server_rec *s, int port) return match; } -static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s, +static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p, int log_level) { ap_listen_rec *lr; @@ -414,11 +414,11 @@ static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s, int can_http, can_https; if (mc->can_http >= 0 && mc->can_https >= 0) goto set_and_leave; - + can_http = can_https = 0; for (lr = ap_listeners; lr; lr = lr->next) { for (sa = lr->bind_addr; sa; sa = sa->next) { - if (sa->port == mc->local_80 + if (sa->port == mc->local_80 && (!lr->protocol || !strncmp("http", lr->protocol, 4))) { can_http = 1; } @@ -428,13 +428,13 @@ static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s, } } } - if (mc->can_http < 0) mc->can_http = can_http; + if (mc->can_http < 0) mc->can_http = can_http; if (mc->can_https < 0) mc->can_https = can_https; ap_log_error(APLOG_MARK, log_level, 0, s, APLOGNO(10037) "server seems%s reachable via http: and%s reachable via https:", mc->can_http? "" : " not", mc->can_https? "" : " not"); set_and_leave: - return md_reg_set_props(mc->reg, p, mc->can_http, mc->can_https); + return md_reg_set_props(mc->reg, p, mc->can_http, mc->can_https); } static server_rec *get_public_https_server(md_t *md, const char *domain, server_rec *base_server) @@ -492,9 +492,9 @@ static apr_status_t auto_add_domains(md_t *md, server_rec *base_server, apr_pool server_rec *s; apr_status_t rv = APR_SUCCESS; int updates; - + /* Ad all domain names used in SSL VirtualHosts, if not already there */ - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, base_server, + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, base_server, "md[%s]: auto add domains", md->name); updates = 0; for (s = base_server; s; s = s->next) { @@ -515,12 +515,12 @@ static void init_acme_tls_1_domains(md_t *md, server_rec *base_server) server_rec *s; int i; const char *domain; - + /* Collect those domains that support the "acme-tls/1" protocol. This * is part of the MD (and not tested dynamically), since challenge selection * may be done outside the server, e.g. in the a2md command. */ sc = md_config_get(base_server); - mc = sc->mc; + mc = sc->mc; apr_array_clear(md->acme_tls_1_domains); for (i = 0; i < md->domains->nelts; ++i) { domain = APR_ARRAY_IDX(md->domains, i, const char*); @@ -535,7 +535,7 @@ static void init_acme_tls_1_domains(md_t *md, server_rec *base_server) } if (!ap_is_allowed_protocol(NULL, NULL, s, PROTO_ACME_TLS_1)) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10169) - "%s: https server_rec for %s does not have protocol %s enabled", + "%s: https server_rec for %s does not have protocol %s enabled", md->name, domain, PROTO_ACME_TLS_1); continue; } @@ -543,7 +543,7 @@ static void init_acme_tls_1_domains(md_t *md, server_rec *base_server) } } -static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *base_server, +static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *base_server, apr_pool_t *p) { server_rec *s; @@ -551,7 +551,7 @@ static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec * md_srv_conf_t *sc; int i; const char *domain, *uri; - + sc = md_config_get(base_server); /* Assign the MD to all server_rec configs that it matches. If there already @@ -563,23 +563,23 @@ static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec * /* we shall not assign ourselves to the base server */ continue; } - + r.server = s; for (i = 0; i < md->domains->nelts; ++i) { domain = APR_ARRAY_IDX(md->domains, i, const char*); - + if (ap_matches_request_vhost(&r, domain, s->port)) { /* Create a unique md_srv_conf_t record for this server, if there is none yet */ sc = md_config_get_unique(s, p); if (!sc->assigned) sc->assigned = apr_array_make(p, 2, sizeof(md_t*)); - + APR_ARRAY_PUSH(sc->assigned, md_t*) = md; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10041) "Server %s:%d matches md %s (config %s) for domain %s, " - "has now %d MDs", + "has now %d MDs", s->server_hostname, s->port, md->name, sc->name, domain, (int)sc->assigned->nelts); - + if (sc->ca_contact && sc->ca_contact[0]) { uri = md_util_schemify(p, sc->ca_contact, "mailto"); if (md_array_str_index(md->contacts, uri, 0, 0) < 0) { @@ -607,7 +607,7 @@ static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s, apr_po int i; md_t *md; apr_status_t rv = APR_SUCCESS; - + apr_array_clear(mc->unused_names); for (i = 0; i < mc->mds->nelts; ++i) { md = APR_ARRAY_IDX(mc->mds, i, md_t*); @@ -619,7 +619,7 @@ static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s, apr_po return rv; } -static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p, +static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p, server_rec *base_server, int log_level) { md_srv_conf_t *base_conf; @@ -633,13 +633,13 @@ static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p, * in the server. This list is collected during configuration processing and, * in the post config phase, get updated from all merged server configurations * before the server starts processing. - */ + */ base_conf = md_config_get(base_server); md_config_get_timespan(&ts, base_conf, MD_CONFIG_RENEW_WINDOW); if (ts) md_reg_set_renew_window_default(mc->reg, ts); md_config_get_timespan(&ts, base_conf, MD_CONFIG_WARN_WINDOW); if (ts) md_reg_set_warn_window_default(mc->reg, ts); - + /* Complete the properties of the MDs, now that we have the complete, merged * server configurations. */ @@ -659,7 +659,7 @@ static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p, return APR_EINVAL; } } - + if (md->cert_file && !md->pkey_file) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10170) "The Managed Domain '%s', defined in %s(line %d), " @@ -693,12 +693,12 @@ static void load_staged_data(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p) md_t *md; md_result_t *result; int i; - + for (i = 0; i < mc->mds->nelts; ++i) { md = APR_ARRAY_IDX(mc->mds, i, md_t *); result = md_result_md_make(p, md->name); if (APR_SUCCESS == (rv = md_reg_load_staging(mc->reg, md, mc->env, result, p))) { - ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10068) + ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10068) "%s: staged set activated", md->name); } else if (!APR_STATUS_IS_ENOENT(rv)) { @@ -712,13 +712,13 @@ static apr_status_t check_invalid_duplicates(server_rec *base_server) { server_rec *s; md_srv_conf_t *sc; - - ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, base_server, + + ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, base_server, "checking duplicate ssl assignments"); for (s = base_server; s; s = s->next) { sc = md_config_get(s); if (!sc || !sc->assigned) continue; - + if (sc->assigned->nelts > 1 && sc->is_ssl) { /* duplicate assignment to SSL VirtualHost, not allowed */ ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10042) @@ -730,7 +730,7 @@ static apr_status_t check_invalid_duplicates(server_rec *base_server) return APR_SUCCESS; } -static apr_status_t check_usage(md_mod_conf_t *mc, md_t *md, server_rec *base_server, +static apr_status_t check_usage(md_mod_conf_t *mc, md_t *md, server_rec *base_server, apr_pool_t *p, apr_pool_t *ptemp) { server_rec *s; @@ -775,7 +775,7 @@ static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t * md_t *md; md_result_t *result; int i, count; - + /* Calculate the list of MD names which we need to watch: * - all MDs that are used somewhere * - all MDs in drive mode 'AUTO' that are not in 'unused_names' @@ -787,7 +787,7 @@ static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t * md_result_set(result, APR_SUCCESS, NULL); md->watched = 0; if (md->state == MD_S_ERROR) { - md_result_set(result, APR_EGENERAL, + md_result_set(result, APR_EGENERAL, "in error state, unable to drive forward. This " "indicates an incomplete or inconsistent configuration. " "Please check the log for warnings in this regard."); @@ -799,22 +799,22 @@ static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t * /* This MD is not used in any virtualhost, do not watch */ continue; } - + if (md_will_renew_cert(md)) { /* make a test init to detect early errors. */ md_reg_test_init(mc->reg, md, mc->env, result, p); if (APR_SUCCESS != result->status && result->detail) { apr_hash_set(mc->init_errors, md->name, APR_HASH_KEY_STRING, apr_pstrdup(p, result->detail)); - ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10173) + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10173) "md[%s]: %s", md->name, result->detail); } } - + md->watched = 1; ++count; } return count; -} +} static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) @@ -832,7 +832,7 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, /* At the first start, httpd makes a config check dry run. It * runs all config hooks to check if it can. If so, it does * this all again and starts serving requests. - * + * * On a dry run, we therefore do all the cheap config things we * need to do to find out if the settings are ok. More expensive * things we delay to the real run. @@ -873,7 +873,7 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, goto leave; } md_ocsp_set_notify_cb(mc->ocsp, notify, mc); - + init_ssl(); /* How to bootstrap this module: @@ -883,17 +883,17 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, * 4. Update the store with the MDs. Change domain names, create new MDs, etc. * Basically all MD properties that are configured directly. * WARNING: this may change the name of an MD. If an MD loses the first - * of its domain names, it first gets the new first one as name. The + * of its domain names, it first gets the new first one as name. The * store will find the old settings and "recover" the previous name. * 5. Load any staged data from previous driving. * 6. on a dry run, this is all we do * 7. Read back the MD properties that reflect the existence and aspect of - * credentials that are in the store (or missing there). + * credentials that are in the store (or missing there). * Expiry times, MD state, etc. * 8. Determine the list of MDs that need driving/supervision. * 9. Cleanup any left-overs in registry/store that are no longer needed for * the list of MDs as we know it now. - * 10. If this list is non-empty, setup a watchdog to run. + * 10. If this list is non-empty, setup a watchdog to run. */ /*1*/ if (APR_SUCCESS != (rv = detect_supported_protocols(mc, s, p, log_level))) goto leave; @@ -929,7 +929,7 @@ static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog, /*6*/ if (!sc || !sc->mc || sc->mc->dry_run) goto leave; mc = sc->mc; - + /*7*/ if (APR_SUCCESS != (rv = check_invalid_duplicates(s))) { goto leave; @@ -955,16 +955,16 @@ static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog, watched = init_cert_watch_status(mc, p, ptemp, s); /*9*/ md_reg_cleanup_challenges(mc->reg, p, ptemp, mc->mds); - - /* From here on, the domains in the registry are readonly + + /* From here on, the domains in the registry are readonly * and only staging/challenges may be manipulated */ md_reg_freeze_domains(mc->reg, mc->mds); - + if (watched) { /*10*/ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10074) "%d out of %d mds need watching", watched, mc->mds->nelts); - + md_http_use_implementation(md_curl_get_impl(p)); rv = md_renew_start_watching(mc, s, p); } @@ -973,10 +973,10 @@ static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog, } if (!mc->ocsp || md_ocsp_count(mc->ocsp) == 0) goto leave; - + md_http_use_implementation(md_curl_get_impl(p)); rv = md_ocsp_start_watching(mc, s, p); - + leave: return rv; } @@ -1005,7 +1005,7 @@ static int md_protocol_propose(conn_rec *c, request_rec *r, apr_array_header_t *proposals) { (void)s; - if (!r && offers && opt_ssl_is_https && opt_ssl_is_https(c) + if (!r && offers && opt_ssl_is_https && opt_ssl_is_https(c) && ap_array_str_contains(offers, PROTO_ACME_TLS_1)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "proposing protocol '%s'", PROTO_ACME_TLS_1); @@ -1019,7 +1019,7 @@ static int md_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, const char *protocol) { md_conn_ctx *ctx; - + (void)s; if (!r && opt_ssl_is_https && opt_ssl_is_https(c) && !strcmp(PROTO_ACME_TLS_1, protocol)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, @@ -1034,39 +1034,41 @@ static int md_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, return DECLINED; } - + /**************************************************************************************************/ /* Access API to other httpd components */ -static apr_status_t setup_fallback_cert(md_store_t *store, const md_t *md, - server_rec *s, apr_pool_t *p) +static void fallback_fnames(apr_pool_t *p, md_pkey_spec_t *kspec, char **keyfn, char **certfn ) +{ + *keyfn = apr_psprintf(p, MD_FN_FALLBACK_PKEY, md_pkey_filename(kspec, p)); + *certfn = apr_psprintf(p, MD_FN_FALLBACK_CERT, md_chain_filename(kspec, p)); +} + +static apr_status_t make_fallback_cert(md_store_t *store, const md_t *md, md_pkey_spec_t *kspec, + server_rec *s, apr_pool_t *p, char *keyfn, char *crtfn) { md_pkey_t *pkey; md_cert_t *cert; - md_pkey_spec_t spec; apr_status_t rv; - - spec.type = MD_PKEY_TYPE_RSA; - spec.params.rsa.bits = MD_PKEY_RSA_BITS_DEF; - - if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, p, &spec)) - || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, - MD_FN_FALLBACK_PKEY, MD_SV_PKEY, (void*)pkey, 0)) - || APR_SUCCESS != (rv = md_cert_self_sign(&cert, "Apache Managed Domain Fallback", + + if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, p, kspec)) + || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, + keyfn, MD_SV_PKEY, (void*)pkey, 0)) + || APR_SUCCESS != (rv = md_cert_self_sign(&cert, "Apache Managed Domain Fallback", md->domains, pkey, apr_time_from_sec(14 * MD_SECS_PER_DAY), p)) - || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, - MD_FN_FALLBACK_CERT, MD_SV_CERT, (void*)cert, 0))) { + || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, + crtfn, MD_SV_CERT, (void*)cert, 0))) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10174) - "%s: setup fallback certificate", md->name); + "%s: make fallback %s certificate", md->name, md_pkey_spec_name(kspec)); } return rv; } static apr_status_t get_certificates(server_rec *s, apr_pool_t *p, int fallback, - apr_array_header_t **pcert_files, + apr_array_header_t **pcert_files, apr_array_header_t **pkey_files) { - apr_status_t rv = APR_ENOENT; + apr_status_t rv = APR_ENOENT; md_srv_conf_t *sc; md_reg_t *reg; md_store_t *store; @@ -1085,12 +1087,12 @@ static apr_status_t get_certificates(server_rec *s, apr_pool_t *p, int fallback, sc = md_config_get(s); if (!sc) { - ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s, - "asked for certificate of server %s which has no md config", + ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s, + "asked for certificate of server %s which has no md config", s->server_hostname); return APR_ENOENT; } - + assert(sc->mc); reg = sc->mc->reg; assert(reg); @@ -1112,7 +1114,7 @@ static apr_status_t get_certificates(server_rec *s, apr_pool_t *p, int fallback, return APR_EINVAL; } md = APR_ARRAY_IDX(sc->assigned, 0, const md_t*); - + for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) { spec = md_pkeys_spec_get(md->pks, i); rv = md_reg_get_cred_files(&keyfile, &chainfile, reg, MD_SG_DOMAINS, md, spec, p); @@ -1121,40 +1123,73 @@ static apr_status_t get_certificates(server_rec *s, apr_pool_t *p, int fallback, APR_ARRAY_PUSH(chain_files, const char*) = chainfile; } else if (!APR_STATUS_IS_ENOENT(rv)) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10110) - "retrieving credentials for MD %s (%s)", + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10110) + "retrieving credentials for MD %s (%s)", md->name, md_pkey_spec_name(spec)); return rv; } } - + if (md_array_is_empty(key_files)) { if (fallback) { /* Provide temporary, self-signed certificate as fallback, so that * clients do not get obscure TLS handshake errors or will see a fallback * virtual host that is not intended to be served here. */ + int n; + char *kfn, *cfn; + store = md_reg_store_get(reg); - assert(store); + assert(store); - md_store_get_fname(&keyfile, store, MD_SG_DOMAINS, md->name, MD_FN_FALLBACK_PKEY, p); - md_store_get_fname(&chainfile, store, MD_SG_DOMAINS, md->name, MD_FN_FALLBACK_CERT, p); - if (!md_file_exists(keyfile, p) || !md_file_exists(chainfile, p)) { - if (APR_SUCCESS != (rv = setup_fallback_cert(store, md, s, p))) { - return rv; + if( (n = md_pkeys_spec_count(md->pks)) <= 0 ) { + md_pkey_spec_t dspec; + + dspec.type = MD_PKEY_TYPE_RSA; + dspec.params.rsa.bits = MD_PKEY_RSA_BITS_DEF; + fallback_fnames(p, &dspec, &kfn, &cfn); + + md_store_get_fname(&keyfile, store, MD_SG_DOMAINS, md->name, kfn, p); + md_store_get_fname(&chainfile, store, MD_SG_DOMAINS, md->name, cfn, p); + if (!md_file_exists(keyfile, p) || !md_file_exists(chainfile, p)) { + if (APR_SUCCESS != (rv = make_fallback_cert(store, md, &dspec, s, p, kfn, cfn))) { + return rv; + } + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10116) + "%s: providing %s fallback certificate for server %s", + md->name, md_pkey_spec_name(&dspec), s->server_hostname); + APR_ARRAY_PUSH(key_files, const char*) = keyfile; + APR_ARRAY_PUSH(chain_files, const char*) = chainfile; + } else { + int i; + + for (i = 0; i < n; ++i) { + md_pkey_spec_t *spec; + + spec = md_pkeys_spec_get(md->pks, i); + fallback_fnames(p, spec, &kfn, &cfn); + + md_store_get_fname(&keyfile, store, MD_SG_DOMAINS, md->name, kfn, p); + md_store_get_fname(&chainfile, store, MD_SG_DOMAINS, md->name, cfn, p); + if (!md_file_exists(keyfile, p) || !md_file_exists(chainfile, p)) { + if (APR_SUCCESS != (rv = make_fallback_cert(store, md, spec, s, p, kfn, cfn))) { + return rv; + } + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10116) + "%s: providing %s fallback certificate for server %s", + md->name, md_pkey_spec_name(spec), s->server_hostname); + APR_ARRAY_PUSH(key_files, const char*) = keyfile; + APR_ARRAY_PUSH(chain_files, const char*) = chainfile; } } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10116) - "%s: providing fallback certificate for server %s", - md->name, s->server_hostname); - APR_ARRAY_PUSH(key_files, const char*) = keyfile; - APR_ARRAY_PUSH(chain_files, const char*) = chainfile; rv = APR_EAGAIN; goto leave; } } - - ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10077) - "%s[state=%d]: providing certificates for server %s", + + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10077) + "%s[state=%d]: providing certificates for server %s", md->name, md->state, s->server_hostname); leave: if (!md_array_is_empty(key_files) && !md_array_is_empty(chain_files)) { @@ -1165,13 +1200,13 @@ static apr_status_t get_certificates(server_rec *s, apr_pool_t *p, int fallback, } static int md_add_cert_files(server_rec *s, apr_pool_t *p, - apr_array_header_t *cert_files, + apr_array_header_t *cert_files, apr_array_header_t *key_files) { - apr_array_header_t *md_cert_files; + apr_array_header_t *md_cert_files; apr_array_header_t *md_key_files; apr_status_t rv; - + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "hook ssl_add_cert_files for %s", s->server_hostname); rv = get_certificates(s, p, 0, &md_cert_files, &md_key_files); @@ -1182,10 +1217,10 @@ static int md_add_cert_files(server_rec *s, apr_pool_t *p, ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10084) "host '%s' is covered by a Managed Domain, but " "certificate/key files are already configured " - "for it (most likely via SSLCertificateFile).", + "for it (most likely via SSLCertificateFile).", s->server_hostname); } - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "host '%s' is covered by a Managed Domaina and " "is being provided with %d key/certificate files.", s->server_hostname, md_cert_files->nelts); @@ -1197,13 +1232,13 @@ static int md_add_cert_files(server_rec *s, apr_pool_t *p, } static int md_add_fallback_cert_files(server_rec *s, apr_pool_t *p, - apr_array_header_t *cert_files, + apr_array_header_t *cert_files, apr_array_header_t *key_files) { - apr_array_header_t *md_cert_files; + apr_array_header_t *md_cert_files; apr_array_header_t *md_key_files; apr_status_t rv; - + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "hook ssl_add_fallback_cert_files for %s", s->server_hostname); rv = get_certificates(s, p, 1, &md_cert_files, &md_key_files); @@ -1223,7 +1258,7 @@ static int md_is_challenge(conn_rec *c, const char *servername, apr_status_t rv; if (!servername) goto out; - + challenge = NULL; if ((protocol = md_protocol_get(c)) && !strcmp(PROTO_ACME_TLS_1, protocol)) { challenge = "tls-alpn-01"; @@ -1235,13 +1270,13 @@ static int md_is_challenge(conn_rec *c, const char *servername, md_store_t *store = md_reg_store_get(sc->mc->reg); md_cert_t *mdcert; md_pkey_t *mdpkey; - + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "%s: load certs/keys %s/%s", servername, cert_name, pkey_name); - rv = md_store_load(store, MD_SG_CHALLENGES, servername, cert_name, + rv = md_store_load(store, MD_SG_CHALLENGES, servername, cert_name, MD_SV_CERT, (void**)&mdcert, c->pool); if (APR_SUCCESS == rv && (*pcert = md_cert_get_X509(mdcert))) { - rv = md_store_load(store, MD_SG_CHALLENGES, servername, pkey_name, + rv = md_store_load(store, MD_SG_CHALLENGES, servername, pkey_name, MD_SV_PKEY, (void**)&mdpkey, c->pool); if (APR_SUCCESS == rv && (*pkey = md_pkey_get_EVP_PKEY(mdpkey))) { ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10078) @@ -1286,28 +1321,28 @@ static int md_http_challenge_pr(request_rec *r) md_reg_t *reg; const md_t *md; apr_status_t rv; - - if (r->parsed_uri.path + + if (r->parsed_uri.path && !strncmp(ACME_CHALLENGE_PREFIX, r->parsed_uri.path, sizeof(ACME_CHALLENGE_PREFIX)-1)) { sc = ap_get_module_config(r->server->module_config, &md_module); if (sc && sc->mc) { - ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, - "access inside /.well-known/acme-challenge for %s%s", + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "access inside /.well-known/acme-challenge for %s%s", r->hostname, r->parsed_uri.path); md = md_get_by_domain(sc->mc->mds, r->hostname); name = r->parsed_uri.path + sizeof(ACME_CHALLENGE_PREFIX)-1; reg = sc && sc->mc? sc->mc->reg : NULL; - + if (strlen(name) && !ap_strchr_c(name, '/') && reg) { md_store_t *store = md_reg_store_get(reg); - - rv = md_store_load(store, MD_SG_CHALLENGES, r->hostname, + + rv = md_store_load(store, MD_SG_CHALLENGES, r->hostname, MD_FN_HTTP01, MD_SV_TEXT, (void**)&data, r->pool); - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, "loading challenge for %s (%s)", r->hostname, r->uri); if (APR_SUCCESS == rv) { apr_size_t len = strlen(data); - + if (r->method_number != M_GET) { return HTTP_NOT_IMPLEMENTED; } @@ -1315,12 +1350,12 @@ static int md_http_challenge_pr(request_rec *r) * configured for. Let's send the content back */ r->status = HTTP_OK; apr_table_setn(r->headers_out, "Content-Length", apr_ltoa(r->pool, (long)len)); - + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_brigade_write(bb, NULL, NULL, data, len); ap_pass_brigade(r->output_filters, bb); apr_brigade_cleanup(bb); - + return DONE; } else if (!md || md->renew_mode == MD_RENEW_MANUAL @@ -1355,25 +1390,25 @@ static int md_require_https_maybe(request_rec *r) const char *s, *host; const md_t *md; int status; - + /* Requests outside the /.well-known path are subject to possible * https: redirects or HSTS header additions. */ sc = ap_get_module_config(r->server->module_config, &md_module); - if (!sc || !sc->assigned || !sc->assigned->nelts + if (!sc || !sc->assigned || !sc->assigned->nelts || !opt_ssl_is_https || !r->parsed_uri.path || !strncmp(WELL_KNOWN_PREFIX, r->parsed_uri.path, sizeof(WELL_KNOWN_PREFIX)-1)) { goto declined; } - + host = ap_get_server_name_for_url(r); md = md_get_for_domain(r->server, host); if (!md) goto declined; - + if (opt_ssl_is_https(r->connection)) { /* Using https: * if 'permanent' and no one else set a HSTS header already, do it */ - if (md->require_https == MD_REQUIRE_PERMANENT + if (md->require_https == MD_REQUIRE_PERMANENT && sc->mc->hsts_header && !apr_table_get(r->headers_out, MD_HSTS_HEADER)) { apr_table_setn(r->headers_out, MD_HSTS_HEADER, sc->mc->hsts_header); } @@ -1383,15 +1418,15 @@ static int md_require_https_maybe(request_rec *r) /* Not using https:, but require it. Redirect. */ if (r->method_number == M_GET) { /* safe to use the old-fashioned codes */ - status = ((MD_REQUIRE_PERMANENT == md->require_https)? + status = ((MD_REQUIRE_PERMANENT == md->require_https)? HTTP_MOVED_PERMANENTLY : HTTP_MOVED_TEMPORARILY); } else { /* these should keep the method unchanged on retry */ - status = ((MD_REQUIRE_PERMANENT == md->require_https)? + status = ((MD_REQUIRE_PERMANENT == md->require_https)? HTTP_PERMANENT_REDIRECT : HTTP_TEMPORARY_REDIRECT); } - + s = ap_construct_url(r->pool, r->uri, r); if (APR_SUCCESS == apr_uri_parse(r->pool, s, &uri)) { uri.scheme = (char*)"https"; @@ -1411,7 +1446,7 @@ static int md_require_https_maybe(request_rec *r) return DECLINED; } -/* Runs once per created child process. Perform any process +/* Runs once per created child process. Perform any process * related initialization here. */ static void md_child_init(apr_pool_t *pool, server_rec *s) @@ -1428,15 +1463,15 @@ static void md_hooks(apr_pool_t *pool) /* Leave the ssl initialization to mod_ssl or friends. */ md_acme_init(pool, AP_SERVER_BASEVERSION, 0); - + ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks"); - + /* Run once after configuration is set, before mod_ssl. * Run again after mod_ssl is done. */ ap_hook_post_config(md_post_config_before_ssl, NULL, mod_ssl, APR_HOOK_MIDDLE); ap_hook_post_config(md_post_config_after_ssl, mod_ssl, NULL, APR_HOOK_MIDDLE); - + /* Run once after a child process has been created. */ ap_hook_child_init(md_child_init, NULL, mod_ssl, APR_HOOK_MIDDLE); diff --git a/src/mod_md_status.c b/src/mod_md_status.c index 648ce40b..241efcd6 100644 --- a/src/mod_md_status.c +++ b/src/mod_md_status.c @@ -58,7 +58,8 @@ int md_http_cert_status(request_rec *r) { - md_json_t *resp, *j, *mdj, *certj; + int i; + md_json_t *resp, *mdj; const md_srv_conf_t *sc; const md_t *md; md_pkey_spec_t *spec; @@ -98,30 +99,40 @@ int md_http_cert_status(request_rec *r) "status for MD: %s is %s", md->name, md_json_writep(mdj, r->pool, MD_JSON_FMT_INDENT)); resp = md_json_create(r->pool); - if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_VALID, NULL)) { - md_json_setj(md_json_getj(mdj, MD_KEY_CERT, MD_KEY_VALID, NULL), resp, MD_KEY_VALID, NULL); - } - spec = md_pkeys_spec_get(md->pks, 0); - keyname = md_pkey_spec_name(spec); - if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_SERIAL, NULL)) { - md_json_sets(md_json_gets(mdj, MD_KEY_CERT, keyname, MD_KEY_SERIAL, NULL), - resp, MD_KEY_SERIAL, NULL); - } - if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_SHA256_FINGERPRINT, NULL)) { - md_json_sets(md_json_gets(mdj, MD_KEY_CERT, keyname, MD_KEY_SHA256_FINGERPRINT, NULL), - resp, MD_KEY_SHA256_FINGERPRINT, NULL); - } + for (i = 0; i < md_pkeys_spec_count(md->pks); ++i) { + md_json_t *cj; + + spec = md_pkeys_spec_get(md->pks, i); + keyname = md_pkey_spec_name(spec); + cj = md_json_create(r->pool); + + if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_VALID, NULL)) { + md_json_setj(md_json_getj(mdj, MD_KEY_CERT, keyname, MD_KEY_VALID, NULL), + cj, MD_KEY_VALID, NULL); + } + + if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_SERIAL, NULL)) { + md_json_sets(md_json_gets(mdj, MD_KEY_CERT, keyname, MD_KEY_SERIAL, NULL), + cj, MD_KEY_SERIAL, NULL); + } + if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_SHA256_FINGERPRINT, NULL)) { + md_json_sets(md_json_gets(mdj, MD_KEY_CERT, keyname, MD_KEY_SHA256_FINGERPRINT, NULL), + cj, MD_KEY_SHA256_FINGERPRINT, NULL); + } - if (md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) { - /* copy over the information we want to make public about this: - * - when not finished, add an empty object to indicate something is going on - * - when a certificate is staged, add the information from that */ - certj = md_json_getj(mdj, MD_KEY_RENEWAL, MD_KEY_CERT, NULL); - j = certj? certj : md_json_create(r->pool);; - md_json_setj(j, resp, MD_KEY_RENEWAL, MD_KEY_CERT, NULL); + if (md_json_has_key(mdj, MD_KEY_CERT, keyname, MD_KEY_RENEWAL, NULL)) { + md_json_t *certj, *j; + /* copy over the information we want to make public about this: + * - when not finished, add an empty object to indicate something is going on + * - when a certificate is staged, add the information from that */ + certj = md_json_getj(mdj, MD_KEY_CERT, keyname, MD_KEY_RENEWAL, NULL); + j = certj? certj : md_json_create(r->pool); + md_json_setj(j, cj, MD_KEY_RENEWAL, NULL); + } + md_json_setj(cj, resp, keyname, NULL ); } - + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "md[%s]: sending status", md->name); apr_table_set(r->headers_out, "Content-Type", "application/json"); bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); @@ -395,7 +406,7 @@ static int cert_check_iter(void *baton, const char *key, md_json_t *json) fingerprint = md_json_gets(json, MD_KEY_SHA256_FINGERPRINT, NULL); if (fingerprint) { apr_brigade_printf(ctx->bb, NULL, NULL, - "%s[%s] ", + "%s[%s]
", ctx->mc->cert_check_url, fingerprint, ctx->mc->cert_check_name, key); } @@ -445,6 +456,13 @@ static void add_json_val(status_ctx *ctx, md_json_t *j) } } +static void si_val_names(status_ctx *ctx, md_json_t *mdj, const status_info *info) +{ + apr_brigade_puts(ctx->bb, NULL, NULL, "
"); + add_json_val(ctx, md_json_getj(mdj, info->key, NULL)); + apr_brigade_puts(ctx->bb, NULL, NULL, "
"); +} + static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info *info) { if (info->fn) { @@ -457,7 +475,7 @@ static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info * static const status_info status_infos[] = { { "Domain", MD_KEY_NAME, NULL }, - { "Names", MD_KEY_DOMAINS, NULL }, + { "Names", MD_KEY_DOMAINS, si_val_names }, { "Status", MD_KEY_STATE, si_val_status }, { "Valid", MD_KEY_CERT, si_val_cert_valid_time }, { "CA", MD_KEY_CA, si_val_ca_url },