| /* 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. | |
| */ | |
| #include <assert.h> | |
| #include <apr_optional.h> | |
| #include <apr_time.h> | |
| #include <apr_date.h> | |
| #include <apr_strings.h> | |
| #include <httpd.h> | |
| #include <http_core.h> | |
| #include <http_protocol.h> | |
| #include <http_request.h> | |
| #include <http_log.h> | |
| #include "mod_status.h" | |
| #include "md.h" | |
| #include "md_curl.h" | |
| #include "md_crypt.h" | |
| #include "md_http.h" | |
| #include "md_ocsp.h" | |
| #include "md_json.h" | |
| #include "md_status.h" | |
| #include "md_store.h" | |
| #include "md_store_fs.h" | |
| #include "md_log.h" | |
| #include "md_reg.h" | |
| #include "md_util.h" | |
| #include "md_acme.h" | |
| #include "md_acme_authz.h" | |
| #include "mod_md.h" | |
| #include "mod_md_private.h" | |
| #include "mod_md_config.h" | |
| #include "mod_md_drive.h" | |
| #include "mod_md_status.h" | |
| /**************************************************************************************************/ | |
| /* Certificate status */ | |
| #define APACHE_PREFIX "/.httpd/" | |
| #define MD_STATUS_RESOURCE APACHE_PREFIX"certificate-status" | |
| #define HTML_STATUS(X) (!((X)->flags & AP_STATUS_SHORT)) | |
| int md_http_cert_status(request_rec *r) | |
| { | |
| int i; | |
| md_json_t *resp, *mdj, *cj; | |
| const md_srv_conf_t *sc; | |
| const md_t *md; | |
| md_pkey_spec_t *spec; | |
| const char *keyname; | |
| apr_bucket_brigade *bb; | |
| apr_status_t rv; | |
| if (!r->parsed_uri.path || strcmp(MD_STATUS_RESOURCE, r->parsed_uri.path)) | |
| return DECLINED; | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, | |
| "requesting status for: %s", r->hostname); | |
| /* We are looking for information about a staged certificate */ | |
| sc = ap_get_module_config(r->server->module_config, &md_module); | |
| if (!sc || !sc->mc || !sc->mc->reg || !sc->mc->certificate_status_enabled) return DECLINED; | |
| md = md_get_by_domain(sc->mc->mds, r->hostname); | |
| if (!md) return DECLINED; | |
| if (r->method_number != M_GET) { | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, | |
| "md(%s): status supports only GET", md->name); | |
| return HTTP_NOT_IMPLEMENTED; | |
| } | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, | |
| "requesting status for MD: %s", md->name); | |
| rv = md_status_get_md_json(&mdj, md, sc->mc->reg, sc->mc->ocsp, r->pool); | |
| if (APR_SUCCESS != rv) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10204) | |
| "loading md status for %s", md->name); | |
| return HTTP_INTERNAL_SERVER_ERROR; | |
| } | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, 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); | |
| } | |
| for (i = 0; i < md_cert_count(md); ++i) { | |
| 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); | |
| } | |
| md_json_setj(cj, resp, keyname, 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 */ | |
| cj = md_json_getj(mdj, MD_KEY_RENEWAL, MD_KEY_CERT, NULL); | |
| cj = cj? cj : md_json_create(r->pool); | |
| md_json_setj(cj, resp, MD_KEY_RENEWAL, MD_KEY_CERT, 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); | |
| md_json_writeb(resp, MD_JSON_FMT_INDENT, bb); | |
| ap_pass_brigade(r->output_filters, bb); | |
| apr_brigade_cleanup(bb); | |
| return DONE; | |
| } | |
| /**************************************************************************************************/ | |
| /* Status hook */ | |
| typedef struct { | |
| apr_pool_t *p; | |
| const md_mod_conf_t *mc; | |
| apr_bucket_brigade *bb; | |
| int flags; | |
| const char *prefix; | |
| const char *separator; | |
| } status_ctx; | |
| typedef struct status_info status_info; | |
| static void add_json_val(status_ctx *ctx, md_json_t *j); | |
| typedef void add_status_fn(status_ctx *ctx, md_json_t *mdj, const status_info *info); | |
| struct status_info { | |
| const char *label; | |
| const char *key; | |
| add_status_fn *fn; | |
| }; | |
| static void si_val_status(status_ctx *ctx, md_json_t *mdj, const status_info *info) | |
| { | |
| const char *s = "unknown"; | |
| apr_time_t until; | |
| (void)info; | |
| switch (md_json_getl(mdj, info->key, NULL)) { | |
| case MD_S_INCOMPLETE: | |
| s = md_json_gets(mdj, MD_KEY_STATE_DESCR, NULL); | |
| s = s? apr_psprintf(ctx->p, "incomplete: %s", s) : "incomplete"; | |
| break; | |
| case MD_S_EXPIRED_DEPRECATED: | |
| case MD_S_COMPLETE: | |
| until = md_json_get_time(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_UNTIL, NULL); | |
| s = (!until || until > apr_time_now())? "good" : "expired"; | |
| break; | |
| case MD_S_ERROR: s = "error"; break; | |
| case MD_S_MISSING_INFORMATION: s = "missing information"; break; | |
| default: break; | |
| } | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, s); | |
| } | |
| else { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "%s%s: %s\n", | |
| ctx->prefix, info->label, s); | |
| } | |
| } | |
| static void si_val_url(status_ctx *ctx, md_json_t *mdj, const status_info *info) | |
| { | |
| const char *url, *s; | |
| s = url = md_json_gets(mdj, info->key, NULL); | |
| if (!url) return; | |
| s = md_get_ca_name_from_url(ctx->p, url); | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "<a href='%s'>%s</a>", | |
| ap_escape_html2(ctx->p, url, 1), | |
| ap_escape_html2(ctx->p, s, 1)); | |
| } | |
| else { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sName: %s\n", | |
| ctx->prefix, info->label, s); | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sURL: %s\n", | |
| ctx->prefix, info->label, url); | |
| } | |
| } | |
| static void print_date(status_ctx *ctx, apr_time_t timestamp, const char *title) | |
| { | |
| apr_bucket_brigade *bb = ctx->bb; | |
| if (timestamp > 0) { | |
| char ts[128]; | |
| char ts2[128]; | |
| apr_time_exp_t texp; | |
| apr_size_t len; | |
| apr_time_exp_gmt(&texp, timestamp); | |
| apr_strftime(ts, &len, sizeof(ts2)-1, "%Y-%m-%d", &texp); | |
| ts[len] = '\0'; | |
| if (!title) { | |
| apr_strftime(ts2, &len, sizeof(ts)-1, "%Y-%m-%dT%H:%M:%SZ", &texp); | |
| ts2[len] = '\0'; | |
| title = ts2; | |
| } | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_printf(bb, NULL, NULL, | |
| "<span title='%s' style='white-space: nowrap;'>%s</span>", | |
| ap_escape_html2(bb->p, title, 1), ts); | |
| } | |
| else { | |
| apr_brigade_printf(bb, NULL, NULL, "%s%s: %s\n", | |
| ctx->prefix, title, ts); | |
| } | |
| } | |
| } | |
| static void print_time(status_ctx *ctx, const char *label, apr_time_t t) | |
| { | |
| apr_bucket_brigade *bb = ctx->bb; | |
| apr_time_t now; | |
| const char *pre, *post, *sep; | |
| char ts[APR_RFC822_DATE_LEN]; | |
| char ts2[128]; | |
| apr_time_exp_t texp; | |
| apr_size_t len; | |
| apr_interval_time_t delta; | |
| if (t == 0) { | |
| /* timestamp is 0, we use that for "not set" */ | |
| return; | |
| } | |
| apr_time_exp_gmt(&texp, t); | |
| now = apr_time_now(); | |
| pre = post = ""; | |
| sep = (label && strlen(label))? " " : ""; | |
| delta = 0; | |
| if (HTML_STATUS(ctx)) { | |
| apr_rfc822_date(ts, t); | |
| if (t > now) { | |
| delta = t - now; | |
| pre = "in "; | |
| } | |
| else { | |
| delta = now - t; | |
| post = " ago"; | |
| } | |
| if (delta >= (4 * apr_time_from_sec(MD_SECS_PER_DAY))) { | |
| apr_strftime(ts2, &len, sizeof(ts2)-1, "%Y-%m-%d", &texp); | |
| ts2[len] = '\0'; | |
| apr_brigade_printf(bb, NULL, NULL, "%s%s<span title='%s' " | |
| "style='white-space: nowrap;'>%s</span>", | |
| label, sep, ts, ts2); | |
| } | |
| else { | |
| apr_brigade_printf(bb, NULL, NULL, "%s%s<span title='%s'>%s%s%s</span>", | |
| label, sep, ts, pre, md_duration_roughly(bb->p, delta), post); | |
| } | |
| } | |
| else { | |
| delta = t - now; | |
| apr_brigade_printf(bb, NULL, NULL, "%s%s: %" APR_TIME_T_FMT "\n", | |
| ctx->prefix, label, apr_time_sec(delta)); | |
| } | |
| } | |
| static void si_val_valid_time(status_ctx *ctx, md_json_t *mdj, const status_info *info) | |
| { | |
| const char *sfrom, *suntil, *sep, *title; | |
| apr_time_t from, until; | |
| sep = NULL; | |
| sfrom = md_json_gets(mdj, info->key, MD_KEY_FROM, NULL); | |
| from = sfrom? apr_date_parse_rfc(sfrom) : 0; | |
| suntil = md_json_gets(mdj, info->key, MD_KEY_UNTIL, NULL); | |
| until = suntil?apr_date_parse_rfc(suntil) : 0; | |
| if (HTML_STATUS(ctx)) { | |
| if (from > apr_time_now()) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "from "); | |
| print_date(ctx, from, sfrom); | |
| sep = " "; | |
| } | |
| if (until) { | |
| if (sep) apr_brigade_puts(ctx->bb, NULL, NULL, sep); | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "until "); | |
| title = sfrom? apr_psprintf(ctx->p, "%s - %s", sfrom, suntil) : suntil; | |
| print_date(ctx, until, title); | |
| } | |
| } | |
| else { | |
| if (from > apr_time_now()) { | |
| print_date(ctx, from, | |
| apr_pstrcat(ctx->p, info->label, "From", NULL)); | |
| } | |
| if (until) { | |
| print_date(ctx, until, | |
| apr_pstrcat(ctx->p, info->label, "Until", NULL)); | |
| } | |
| } | |
| } | |
| static void si_add_header(status_ctx *ctx, const status_info *info) | |
| { | |
| if (HTML_STATUS(ctx)) { | |
| const char *html = ap_escape_html2(ctx->p, info->label, 1); | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "<th class=\"%s\">%s</th>", html, html); | |
| } | |
| } | |
| static void si_val_cert_valid_time(status_ctx *ctx, md_json_t *mdj, const status_info *info) | |
| { | |
| md_json_t *jcert; | |
| status_info sub = *info; | |
| sub.key = MD_KEY_VALID; | |
| jcert = md_json_getj(mdj, info->key, NULL); | |
| if (jcert) si_val_valid_time(ctx, jcert, &sub); | |
| } | |
| static void val_url_print(status_ctx *ctx, const status_info *info, | |
| const char*url, const char *proto, int i) | |
| { | |
| const char *s; | |
| (void)proto; | |
| if (url) { | |
| s = md_get_ca_name_from_url(ctx->p, url); | |
| } | |
| else { | |
| return; | |
| } | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "%s<a href='%s'>%s</a>", | |
| i? " " : "", | |
| ap_escape_html2(ctx->p, url, 1), | |
| ap_escape_html2(ctx->p, s, 1)); | |
| } | |
| else if (i == 0) { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sName: %s\n", | |
| ctx->prefix, info->label, s); | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sURL: %s\n", | |
| ctx->prefix, info->label, url); | |
| } | |
| else { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sName%d: %s\n", | |
| ctx->prefix, info->label, i, s); | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "%s%sURL%d: %s\n", | |
| ctx->prefix, info->label, i, url); | |
| } | |
| } | |
| static void si_val_ca_urls(status_ctx *ctx, md_json_t *mdj, const status_info *info) | |
| { | |
| md_json_t *jcert; | |
| const char *proto, *url; | |
| apr_array_header_t *urls; | |
| int i; | |
| jcert = md_json_getj(mdj, info->key, NULL); | |
| if (!jcert) { | |
| return; | |
| } | |
| proto = md_json_gets(jcert, MD_KEY_PROTO, NULL); | |
| url = md_json_gets(jcert, MD_KEY_URL, NULL); | |
| if (url) { | |
| /* print the effective CA url used, if set */ | |
| val_url_print(ctx, info, url, proto, 0); | |
| } | |
| else { | |
| /* print the available CA urls configured */ | |
| urls = apr_array_make(ctx->p, 3, sizeof(const char*)); | |
| md_json_getsa(urls, jcert, MD_KEY_URLS, NULL); | |
| for (i = 0; i < urls->nelts; ++i) { | |
| url = APR_ARRAY_IDX(urls, i, const char*); | |
| val_url_print(ctx, info, url, proto, i); | |
| } | |
| } | |
| } | |
| static int count_certs(void *baton, const char *key, md_json_t *json) | |
| { | |
| int *pcount = baton; | |
| (void)json; | |
| if (strcmp(key, MD_KEY_VALID)) { | |
| *pcount += 1; | |
| } | |
| return 1; | |
| } | |
| static void print_job_summary(status_ctx *ctx, md_json_t *mdj, const char *key, | |
| const char *separator) | |
| { | |
| apr_bucket_brigade *bb = ctx->bb; | |
| char buffer[HUGE_STRING_LEN]; | |
| apr_status_t rv; | |
| int finished, errors, cert_count; | |
| apr_time_t t; | |
| const char *s, *line; | |
| if (!md_json_has_key(mdj, key, NULL)) { | |
| return; | |
| } | |
| finished = md_json_getb(mdj, key, MD_KEY_FINISHED, NULL); | |
| errors = (int)md_json_getl(mdj, key, MD_KEY_ERRORS, NULL); | |
| rv = (apr_status_t)md_json_getl(mdj, key, MD_KEY_LAST, MD_KEY_STATUS, NULL); | |
| line = separator? separator : ""; | |
| if (rv != APR_SUCCESS) { | |
| char *errstr = apr_strerror(rv, buffer, sizeof(buffer)); | |
| s = md_json_gets(mdj, key, MD_KEY_LAST, MD_KEY_PROBLEM, NULL); | |
| if (HTML_STATUS(ctx)) { | |
| line = apr_psprintf(bb->p, "%s Error[%s]: %s", line, | |
| errstr, s? s : ""); | |
| } | |
| else { | |
| apr_brigade_printf(bb, NULL, NULL, "%sLastStatus: %s\n", ctx->prefix, errstr); | |
| apr_brigade_printf(bb, NULL, NULL, "%sLastProblem: %s\n", ctx->prefix, s); | |
| } | |
| } | |
| if (!HTML_STATUS(ctx)) { | |
| apr_brigade_printf(bb, NULL, NULL, "%sFinished: %s\n", ctx->prefix, | |
| finished ? "yes" : "no"); | |
| } | |
| if (finished) { | |
| cert_count = 0; | |
| md_json_iterkey(count_certs, &cert_count, mdj, key, MD_KEY_CERT, NULL); | |
| if (HTML_STATUS(ctx)) { | |
| if (cert_count > 0) { | |
| line =apr_psprintf(bb->p, "%s finished, %d new certificate%s staged.", | |
| line, cert_count, cert_count > 1? "s" : ""); | |
| } | |
| else { | |
| line = apr_psprintf(bb->p, "%s finished successfully.", line); | |
| } | |
| } | |
| else { | |
| apr_brigade_printf(bb, NULL, NULL, "%sNewStaged: %d\n", ctx->prefix, cert_count); | |
| } | |
| } | |
| else { | |
| s = md_json_gets(mdj, key, MD_KEY_LAST, MD_KEY_DETAIL, NULL); | |
| if (s) { | |
| if (HTML_STATUS(ctx)) { | |
| line = apr_psprintf(bb->p, "%s %s", line, s); | |
| } | |
| else { | |
| apr_brigade_printf(bb, NULL, NULL, "%sLastDetail: %s\n", ctx->prefix, s); | |
| } | |
| } | |
| } | |
| errors = (int)md_json_getl(mdj, MD_KEY_ERRORS, NULL); | |
| if (errors > 0) { | |
| if (HTML_STATUS(ctx)) { | |
| line = apr_psprintf(bb->p, "%s (%d retr%s) ", line, | |
| errors, (errors > 1)? "y" : "ies"); | |
| } | |
| else { | |
| apr_brigade_printf(bb, NULL, NULL, "%sRetries: %d\n", ctx->prefix, errors); | |
| } | |
| } | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_puts(bb, NULL, NULL, line); | |
| } | |
| t = md_json_get_time(mdj, key, MD_KEY_NEXT_RUN, NULL); | |
| if (t > apr_time_now() && !finished) { | |
| print_time(ctx, | |
| HTML_STATUS(ctx) ? "\nNext run" : "NextRun", | |
| t); | |
| } | |
| else if (line[0] != '\0') { | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_puts(bb, NULL, NULL, "\nOngoing..."); | |
| } | |
| else { | |
| apr_brigade_printf(bb, NULL, NULL, "%s: Ongoing\n", ctx->prefix); | |
| } | |
| } | |
| } | |
| static void si_val_activity(status_ctx *ctx, md_json_t *mdj, const status_info *info) | |
| { | |
| apr_time_t t; | |
| const char *prefix = ctx->prefix; | |
| (void)info; | |
| if (!HTML_STATUS(ctx)) { | |
| ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL); | |
| } | |
| if (md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) { | |
| print_job_summary(ctx, mdj, MD_KEY_RENEWAL, NULL); | |
| return; | |
| } | |
| t = md_json_get_time(mdj, MD_KEY_RENEW_AT, NULL); | |
| if (t > apr_time_now()) { | |
| print_time(ctx, "Renew", t); | |
| } | |
| else if (t) { | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "Pending"); | |
| } | |
| else { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "%s: %s", ctx->prefix, "Pending\n"); | |
| } | |
| } | |
| else if (MD_RENEW_MANUAL == md_json_getl(mdj, MD_KEY_RENEW_MODE, NULL)) { | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "Manual renew"); | |
| } | |
| else { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "%s: %s", ctx->prefix, "Manual renew\n"); | |
| } | |
| } | |
| if (!HTML_STATUS(ctx)) { | |
| ctx->prefix = prefix; | |
| } | |
| } | |
| static int cert_check_iter(void *baton, const char *key, md_json_t *json) | |
| { | |
| status_ctx *ctx = baton; | |
| const char *fingerprint; | |
| fingerprint = md_json_gets(json, MD_KEY_SHA256_FINGERPRINT, NULL); | |
| if (fingerprint) { | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, | |
| "<a href=\"%s%s\">%s[%s]</a><br>", | |
| ctx->mc->cert_check_url, fingerprint, | |
| ctx->mc->cert_check_name, key); | |
| } | |
| else { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, | |
| "%sType: %s\n", | |
| ctx->prefix, | |
| key); | |
| apr_brigade_printf(ctx->bb, NULL, NULL, | |
| "%sName: %s\n", | |
| ctx->prefix, | |
| ctx->mc->cert_check_name); | |
| apr_brigade_printf(ctx->bb, NULL, NULL, | |
| "%sURL: %s%s\n", | |
| ctx->prefix, | |
| ctx->mc->cert_check_url, fingerprint); | |
| apr_brigade_printf(ctx->bb, NULL, NULL, | |
| "%sFingerprint: %s\n", | |
| ctx->prefix, | |
| fingerprint); | |
| } | |
| } | |
| return 1; | |
| } | |
| static void si_val_remote_check(status_ctx *ctx, md_json_t *mdj, const status_info *info) | |
| { | |
| (void)info; | |
| if (ctx->mc->cert_check_name && ctx->mc->cert_check_url) { | |
| const char *prefix = ctx->prefix; | |
| if (!HTML_STATUS(ctx)) { | |
| ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL); | |
| } | |
| md_json_iterkey(cert_check_iter, ctx, mdj, MD_KEY_CERT, NULL); | |
| if (!HTML_STATUS(ctx)) { | |
| ctx->prefix = prefix; | |
| } | |
| } | |
| } | |
| static void si_val_stapling(status_ctx *ctx, md_json_t *mdj, const status_info *info) | |
| { | |
| (void)info; | |
| if (!md_json_getb(mdj, MD_KEY_STAPLING, NULL)) return; | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "on"); | |
| } | |
| else { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "%sStapling: on\n", ctx->prefix); | |
| } | |
| } | |
| static int json_iter_val(void *data, size_t index, md_json_t *json) | |
| { | |
| status_ctx *ctx = data; | |
| const char *prefix = ctx->prefix; | |
| if (HTML_STATUS(ctx)) { | |
| if (index) apr_brigade_puts(ctx->bb, NULL, NULL, ctx->separator); | |
| } | |
| else { | |
| ctx->prefix = apr_pstrcat(ctx->p, prefix, apr_psprintf(ctx->p, "[%" APR_SIZE_T_FMT "]", index), NULL); | |
| } | |
| add_json_val(ctx, json); | |
| if (!HTML_STATUS(ctx)) { | |
| ctx->prefix = prefix; | |
| } | |
| return 1; | |
| } | |
| static void add_json_val(status_ctx *ctx, md_json_t *j) | |
| { | |
| if (!j) return; | |
| if (md_json_is(MD_JSON_TYPE_ARRAY, j, NULL)) { | |
| md_json_itera(json_iter_val, ctx, j, NULL); | |
| return; | |
| } | |
| if (!HTML_STATUS(ctx)) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, ctx->prefix); | |
| apr_brigade_puts(ctx->bb, NULL, NULL, ": "); | |
| } | |
| if (md_json_is(MD_JSON_TYPE_INT, j, NULL)) { | |
| md_json_writeb(j, MD_JSON_FMT_COMPACT, ctx->bb); | |
| } | |
| else if (md_json_is(MD_JSON_TYPE_STRING, j, NULL)) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, md_json_gets(j, NULL)); | |
| } | |
| else if (md_json_is(MD_JSON_TYPE_OBJECT, j, NULL)) { | |
| md_json_writeb(j, MD_JSON_FMT_COMPACT, ctx->bb); | |
| } | |
| else if (md_json_is(MD_JSON_TYPE_BOOL, j, NULL)) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, md_json_getb(j, NULL)? "on" : "off"); | |
| } | |
| if (!HTML_STATUS(ctx)) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "\n"); | |
| } | |
| } | |
| static void si_val_names(status_ctx *ctx, md_json_t *mdj, const status_info *info) | |
| { | |
| const char *prefix = ctx->prefix; | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "<div style=\"max-width:400px;\">"); | |
| } | |
| else { | |
| ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL); | |
| } | |
| add_json_val(ctx, md_json_getj(mdj, info->key, NULL)); | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "</div>"); | |
| } | |
| else { | |
| ctx->prefix = prefix; | |
| } | |
| } | |
| static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info *info) | |
| { | |
| if (info->fn) { | |
| info->fn(ctx, mdj, info); | |
| } | |
| else { | |
| const char *prefix = ctx->prefix; | |
| if (!HTML_STATUS(ctx)) { | |
| ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL); | |
| } | |
| add_json_val(ctx, md_json_getj(mdj, info->key, NULL)); | |
| if (!HTML_STATUS(ctx)) { | |
| ctx->prefix = prefix; | |
| } | |
| } | |
| } | |
| static const status_info status_infos[] = { | |
| { "Domain", MD_KEY_NAME, 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_urls }, | |
| { "Stapling", MD_KEY_STAPLING, si_val_stapling }, | |
| { "CheckAt", MD_KEY_SHA256_FINGERPRINT, si_val_remote_check }, | |
| { "Activity", MD_KEY_NOTIFIED, si_val_activity }, | |
| }; | |
| static int add_md_row(void *baton, apr_size_t index, md_json_t *mdj) | |
| { | |
| status_ctx *ctx = baton; | |
| const char *prefix = ctx->prefix; | |
| int i; | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "<tr class=\"%s\">", (index % 2)? "odd" : "even"); | |
| for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "<td>"); | |
| add_status_cell(ctx, mdj, &status_infos[i]); | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "</td>"); | |
| } | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "</tr>"); | |
| } else { | |
| for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) { | |
| ctx->prefix = apr_pstrcat(ctx->p, prefix, apr_psprintf(ctx->p, "[%" APR_SIZE_T_FMT "]", index), NULL); | |
| add_status_cell(ctx, mdj, &status_infos[i]); | |
| ctx->prefix = prefix; | |
| } | |
| } | |
| return 1; | |
| } | |
| static int md_name_cmp(const void *v1, const void *v2) | |
| { | |
| return strcmp((*(const md_t**)v1)->name, (*(const md_t**)v2)->name); | |
| } | |
| int md_domains_status_hook(request_rec *r, int flags) | |
| { | |
| const md_srv_conf_t *sc; | |
| const md_mod_conf_t *mc; | |
| int i; | |
| status_ctx ctx; | |
| apr_array_header_t *mds; | |
| md_json_t *jstatus, *jstock; | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for managed domains, start"); | |
| sc = ap_get_module_config(r->server->module_config, &md_module); | |
| if (!sc) return DECLINED; | |
| mc = sc->mc; | |
| if (!mc || !mc->server_status_enabled) return DECLINED; | |
| ctx.p = r->pool; | |
| ctx.mc = mc; | |
| ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); | |
| ctx.flags = flags; | |
| ctx.prefix = "ManagedCertificates"; | |
| ctx.separator = " "; | |
| mds = apr_array_copy(r->pool, mc->mds); | |
| qsort(mds->elts, (size_t)mds->nelts, sizeof(md_t *), md_name_cmp); | |
| if (!HTML_STATUS(&ctx)) { | |
| int total = 0, complete = 0, renewing = 0, errored = 0, ready = 0; | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "no-html managed domain status summary"); | |
| if (mc->mds->nelts > 0) { | |
| md_status_take_stock(&jstock, mds, mc->reg, r->pool); | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON managed domain status summary"); | |
| total = (int)md_json_getl(jstock, MD_KEY_TOTAL, NULL); | |
| complete = (int)md_json_getl(jstock, MD_KEY_COMPLETE, NULL); | |
| renewing = (int)md_json_getl(jstock, MD_KEY_RENEWING, NULL); | |
| errored = (int)md_json_getl(jstock, MD_KEY_ERRORED, NULL); | |
| ready = (int)md_json_getl(jstock, MD_KEY_READY, NULL); | |
| } | |
| apr_brigade_printf(ctx.bb, NULL, NULL, "%sTotal: %d\n", ctx.prefix, total); | |
| apr_brigade_printf(ctx.bb, NULL, NULL, "%sOK: %d\n", ctx.prefix, complete); | |
| apr_brigade_printf(ctx.bb, NULL, NULL, "%sRenew: %d\n", ctx.prefix, renewing); | |
| apr_brigade_printf(ctx.bb, NULL, NULL, "%sErrored: %d\n", ctx.prefix, errored); | |
| apr_brigade_printf(ctx.bb, NULL, NULL, "%sReady: %d\n", ctx.prefix, ready); | |
| } | |
| if (mc->mds->nelts > 0) { | |
| md_status_get_json(&jstatus, mds, mc->reg, mc->ocsp, r->pool); | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON managed domain status"); | |
| if (HTML_STATUS(&ctx)) { | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "html managed domain status table"); | |
| apr_brigade_puts(ctx.bb, NULL, NULL, | |
| "<hr>\n<h3>Managed Certificates</h3>\n<table class='md_status'><thead><tr>\n"); | |
| for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) { | |
| si_add_header(&ctx, &status_infos[i]); | |
| } | |
| apr_brigade_puts(ctx.bb, NULL, NULL, "</tr>\n</thead><tbody>"); | |
| } | |
| else { | |
| ctx.prefix = "ManagedDomain"; | |
| } | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "iterating JSON managed domain status"); | |
| md_json_itera(add_md_row, &ctx, jstatus, MD_KEY_MDS, NULL); | |
| if (HTML_STATUS(&ctx)) { | |
| apr_brigade_puts(ctx.bb, NULL, NULL, "</td></tr>\n</tbody>\n</table>\n"); | |
| } | |
| } | |
| ap_pass_brigade(r->output_filters, ctx.bb); | |
| apr_brigade_cleanup(ctx.bb); | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for managed domains, end"); | |
| return OK; | |
| } | |
| static void si_val_ocsp_activity(status_ctx *ctx, md_json_t *mdj, const status_info *info) | |
| { | |
| apr_time_t t; | |
| const char *prefix = ctx->prefix; | |
| (void)info; | |
| if (!HTML_STATUS(ctx)) { | |
| ctx->prefix = apr_pstrcat(ctx->p, prefix, info->label, NULL); | |
| } | |
| t = md_json_get_time(mdj, MD_KEY_RENEW_AT, NULL); | |
| print_time(ctx, "Refresh", t); | |
| print_job_summary(ctx, mdj, MD_KEY_RENEWAL, ": "); | |
| if (!HTML_STATUS(ctx)) { | |
| ctx->prefix = prefix; | |
| } | |
| } | |
| static const status_info ocsp_status_infos[] = { | |
| { "Domain", MD_KEY_DOMAIN, NULL }, | |
| { "CertificateID", MD_KEY_ID, NULL }, | |
| { "OCSPStatus", MD_KEY_STATUS, NULL }, | |
| { "StaplingValid", MD_KEY_VALID, si_val_valid_time }, | |
| { "Responder", MD_KEY_URL, si_val_url }, | |
| { "Activity", MD_KEY_NOTIFIED, si_val_ocsp_activity }, | |
| }; | |
| static int add_ocsp_row(void *baton, apr_size_t index, md_json_t *mdj) | |
| { | |
| status_ctx *ctx = baton; | |
| const char *prefix = ctx->prefix; | |
| int i; | |
| if (HTML_STATUS(ctx)) { | |
| apr_brigade_printf(ctx->bb, NULL, NULL, "<tr class=\"%s\">", (index % 2)? "odd" : "even"); | |
| for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) { | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "<td>"); | |
| add_status_cell(ctx, mdj, &ocsp_status_infos[i]); | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "</td>"); | |
| } | |
| apr_brigade_puts(ctx->bb, NULL, NULL, "</tr>"); | |
| } else { | |
| for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) { | |
| ctx->prefix = apr_pstrcat(ctx->p, prefix, apr_psprintf(ctx->p, "[%" APR_SIZE_T_FMT "]", index), NULL); | |
| add_status_cell(ctx, mdj, &ocsp_status_infos[i]); | |
| ctx->prefix = prefix; | |
| } | |
| } | |
| return 1; | |
| } | |
| int md_ocsp_status_hook(request_rec *r, int flags) | |
| { | |
| const md_srv_conf_t *sc; | |
| const md_mod_conf_t *mc; | |
| int i; | |
| status_ctx ctx; | |
| md_json_t *jstatus, *jstock; | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for ocsp stapling, start"); | |
| sc = ap_get_module_config(r->server->module_config, &md_module); | |
| if (!sc) return DECLINED; | |
| mc = sc->mc; | |
| if (!mc || !mc->server_status_enabled) return DECLINED; | |
| ctx.p = r->pool; | |
| ctx.mc = mc; | |
| ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); | |
| ctx.flags = flags; | |
| ctx.prefix = "ManagedStaplings"; | |
| ctx.separator = " "; | |
| if (!HTML_STATUS(&ctx)) { | |
| int total = 0, good = 0, revoked = 0, unknown = 0; | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "no-html ocsp stapling status summary"); | |
| if (md_ocsp_count(mc->ocsp) > 0) { | |
| md_ocsp_get_summary(&jstock, mc->ocsp, r->pool); | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON ocsp stapling status summary"); | |
| total = (int)md_json_getl(jstock, MD_KEY_TOTAL, NULL); | |
| good = (int)md_json_getl(jstock, MD_KEY_GOOD, NULL); | |
| revoked = (int)md_json_getl(jstock, MD_KEY_REVOKED, NULL); | |
| unknown = (int)md_json_getl(jstock, MD_KEY_UNKNOWN, NULL); | |
| } | |
| apr_brigade_printf(ctx.bb, NULL, NULL, "%sTotal: %d\n", ctx.prefix, total); | |
| apr_brigade_printf(ctx.bb, NULL, NULL, "%sOK: %d\n", ctx.prefix, good); | |
| apr_brigade_printf(ctx.bb, NULL, NULL, "%sRenew: %d\n", ctx.prefix, revoked); | |
| apr_brigade_printf(ctx.bb, NULL, NULL, "%sErrored: %d\n", ctx.prefix, unknown); | |
| } | |
| if (md_ocsp_count(mc->ocsp) > 0) { | |
| md_ocsp_get_status_all(&jstatus, mc->ocsp, r->pool); | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON ocsp stapling status"); | |
| if (HTML_STATUS(&ctx)) { | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "html ocsp stapling status table"); | |
| apr_brigade_puts(ctx.bb, NULL, NULL, | |
| "<hr>\n<h3>Managed Staplings</h3>\n<table class='md_ocsp_status'><thead><tr>\n"); | |
| for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) { | |
| si_add_header(&ctx, &ocsp_status_infos[i]); | |
| } | |
| apr_brigade_puts(ctx.bb, NULL, NULL, "</tr>\n</thead><tbody>"); | |
| } | |
| else { | |
| ctx.prefix = "ManagedStapling"; | |
| } | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "iterating JSON ocsp stapling status"); | |
| md_json_itera(add_ocsp_row, &ctx, jstatus, MD_KEY_OCSPS, NULL); | |
| if (HTML_STATUS(&ctx)) { | |
| apr_brigade_puts(ctx.bb, NULL, NULL, "</td></tr>\n</tbody>\n</table>\n"); | |
| } | |
| } | |
| ap_pass_brigade(r->output_filters, ctx.bb); | |
| apr_brigade_cleanup(ctx.bb); | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for ocsp stapling, end"); | |
| return OK; | |
| } | |
| /**************************************************************************************************/ | |
| /* Status handlers */ | |
| int md_status_handler(request_rec *r) | |
| { | |
| const md_srv_conf_t *sc; | |
| const md_mod_conf_t *mc; | |
| apr_array_header_t *mds; | |
| md_json_t *jstatus; | |
| apr_bucket_brigade *bb; | |
| const md_t *md; | |
| const char *name; | |
| if (strcmp(r->handler, "md-status")) { | |
| return DECLINED; | |
| } | |
| sc = ap_get_module_config(r->server->module_config, &md_module); | |
| if (!sc) return DECLINED; | |
| mc = sc->mc; | |
| if (!mc) return DECLINED; | |
| if (r->method_number != M_GET) { | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "md-status supports only GET"); | |
| return HTTP_NOT_IMPLEMENTED; | |
| } | |
| jstatus = NULL; | |
| md = NULL; | |
| if (r->path_info && r->path_info[0] == '/' && r->path_info[1] != '\0') { | |
| name = strrchr(r->path_info, '/') + 1; | |
| md = md_get_by_name(mc->mds, name); | |
| if (!md) md = md_get_by_domain(mc->mds, name); | |
| } | |
| if (md) { | |
| md_status_get_md_json(&jstatus, md, mc->reg, mc->ocsp, r->pool); | |
| } | |
| else { | |
| mds = apr_array_copy(r->pool, mc->mds); | |
| qsort(mds->elts, (size_t)mds->nelts, sizeof(md_t *), md_name_cmp); | |
| md_status_get_json(&jstatus, mds, mc->reg, mc->ocsp, r->pool); | |
| } | |
| if (jstatus) { | |
| apr_table_set(r->headers_out, "Content-Type", "application/json"); | |
| bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); | |
| md_json_writeb(jstatus, MD_JSON_FMT_INDENT, bb); | |
| ap_pass_brigade(r->output_filters, bb); | |
| apr_brigade_cleanup(bb); | |
| return DONE; | |
| } | |
| return DECLINED; | |
| } | |