diff options
Diffstat (limited to '')
-rw-r--r-- | modules/md/mod_md.c | 2179 |
1 files changed, 1134 insertions, 1045 deletions
diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c index 249a0f0..6d3f5b7 100644 --- a/modules/md/mod_md.c +++ b/modules/md/mod_md.c @@ -13,33 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + #include <assert.h> #include <apr_optional.h> #include <apr_strings.h> -#include <ap_release.h> -#ifndef AP_ENABLE_EXCEPTION_HOOK -#define AP_ENABLE_EXCEPTION_HOOK 0 -#endif #include <mpm_common.h> #include <httpd.h> #include <http_core.h> #include <http_protocol.h> #include <http_request.h> +#include <http_ssl.h> #include <http_log.h> #include <http_vhost.h> #include <ap_listen.h> +#include "mod_status.h" + #include "md.h" #include "md_curl.h" #include "md_crypt.h" +#include "md_event.h" #include "md_http.h" #include "md_json.h" #include "md_store.h" #include "md_store_fs.h" #include "md_log.h" +#include "md_ocsp.h" +#include "md_result.h" #include "md_reg.h" +#include "md_status.h" #include "md_util.h" #include "md_version.h" #include "md_acme.h" @@ -47,9 +50,10 @@ #include "mod_md.h" #include "mod_md_config.h" +#include "mod_md_drive.h" +#include "mod_md_ocsp.h" #include "mod_md_os.h" -#include "mod_ssl.h" -#include "mod_watchdog.h" +#include "mod_md_status.h" static void md_hooks(apr_pool_t *pool); @@ -66,14 +70,251 @@ AP_DECLARE_MODULE(md) = { #endif }; -static void md_merge_srv(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p) +/**************************************************************************************************/ +/* logging setup */ + +static server_rec *log_server; + +static int log_is_level(void *baton, apr_pool_t *p, md_log_level_t level) +{ + (void)baton; + (void)p; + if (log_server) { + return APLOG_IS_LEVEL(log_server, (int)level); + } + return level <= MD_LOG_INFO; +} + +#define LOG_BUF_LEN 16*1024 + +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'; + + if (log_server) { + ap_log_error(file, line, APLOG_MODULE_INDEX, (int)level, rv, log_server, "%s", buffer); + } + else { + ap_log_perror(file, line, APLOG_MODULE_INDEX, (int)level, rv, p, "%s", buffer); + } + } +} + +/**************************************************************************************************/ +/* mod_ssl interface */ + +static void init_ssl(void) +{ + /* nop */ +} + +/**************************************************************************************************/ +/* lifecycle */ + +static apr_status_t cleanup_setups(void *dummy) +{ + (void)dummy; + log_server = NULL; + return APR_SUCCESS; +} + +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); +} + +/**************************************************************************************************/ +/* notification handling */ + +typedef struct { + const char *reason; /* what the notification is about */ + apr_time_t min_interim; /* minimum time between notifying for this reason */ +} notify_rate; + +static notify_rate notify_rates[] = { + { "renewing", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */ + { "renewed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */ + { "installed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */ + { "expiring", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */ + { "errored", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */ + { "ocsp-renewed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */ + { "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, + md_result_t *result, apr_pool_t *p, void *baton) +{ + md_mod_conf_t *mc = baton; + const char * const *argv; + const char *cmdline; + int exit_code; + apr_status_t rv = APR_SUCCESS; + apr_time_t min_interim = 0; + 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)) { + min_interim = notify_rates[i].min_interim; + } + } + if (min_interim > 0) { + since_last.start = md_job_log_get_time_of_latest(job, log_msg_reason); + since_last.end = apr_time_now(); + if (since_last.start > 0 && md_timeperiod_length(&since_last) < min_interim) { + /* not enough time has passed since we sent the last notification + * for this reason. */ + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, APLOGNO(10267) + "%s: rate limiting notification about '%s'", job->mdomain, reason); + return APR_SUCCESS; + } + } + + if (!strcmp("renewed", reason)) { + if (mc->notify_cmd) { + 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.", + 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) + "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); + 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.", + mc->message_cmd, exit_code); + md_result_log(result, MD_LOG_ERR); + md_job_log_append(job, "message-error", reason, result->detail); + return rv; + } + } + + md_job_log_append(job, log_msg_reason, NULL, NULL); + return APR_SUCCESS; +} + +static apr_status_t on_event(const char *event, const char *mdomain, void *baton, + md_job_t *job, md_result_t *result, apr_pool_t *p) +{ + (void)mdomain; + return notify(job, event, result, p, baton); +} + +/**************************************************************************************************/ +/* 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, + 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)", + 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. + */ + if (ftype == APR_DIR) { + switch (group) { + case MD_SG_CHALLENGES: + case MD_SG_STAGING: + case MD_SG_OCSP: + rv = md_make_worker_accessible(fname, p); + if (APR_ENOTIMPL != rv) { + return rv; + } + break; + default: + break; + } + } + return APR_SUCCESS; +} + +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); + } + return rv; +} + +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; + } + + md_store_fs_set_event_cb(*pstore, store_file_ev, s); + if (APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_CHALLENGES, p, s)) + || APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_STAGING, p, s)) + || 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) + "setup challenges directory"); + goto leave; + } + +leave: + return rv; +} + +/**************************************************************************************************/ +/* post config handling */ + +static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p) +{ + const char *contact; + if (!md->sc) { md->sc = base_sc; } - if (!md->ca_url) { - md->ca_url = md_config_gets(md->sc, MD_CONFIG_CA_URL); + if (!md->ca_urls && md->sc->ca_urls) { + md->ca_urls = apr_array_copy(p, md->sc->ca_urls); } if (!md->ca_proto) { md->ca_proto = md_config_gets(md->sc, MD_CONFIG_CA_PROTO); @@ -81,90 +322,92 @@ static void md_merge_srv(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p) if (!md->ca_agreement) { md->ca_agreement = md_config_gets(md->sc, MD_CONFIG_CA_AGREEMENT); } - if (md->sc->s->server_admin && strcmp(DEFAULT_ADMIN, md->sc->s->server_admin)) { + contact = md_config_gets(md->sc, MD_CONFIG_CA_CONTACT); + if (md->contacts && md->contacts->nelts > 0) { + /* set explicitly */ + } + else if (contact && contact[0]) { apr_array_clear(md->contacts); - APR_ARRAY_PUSH(md->contacts, const char *) = - md_util_schemify(p, md->sc->s->server_admin, "mailto"); + APR_ARRAY_PUSH(md->contacts, const char *) = + md_util_schemify(p, contact, "mailto"); } - if (md->drive_mode == MD_DRIVE_DEFAULT) { - md->drive_mode = md_config_geti(md->sc, MD_CONFIG_DRIVE_MODE); + else if( md->sc->s->server_admin && strcmp(DEFAULT_ADMIN, md->sc->s->server_admin)) { + apr_array_clear(md->contacts); + APR_ARRAY_PUSH(md->contacts, const char *) = + md_util_schemify(p, md->sc->s->server_admin, "mailto"); } - if (md->renew_norm <= 0 && md->renew_window <= 0) { - md->renew_norm = md_config_get_interval(md->sc, MD_CONFIG_RENEW_NORM); - md->renew_window = md_config_get_interval(md->sc, MD_CONFIG_RENEW_WINDOW); + if (md->renew_mode == MD_RENEW_DEFAULT) { + md->renew_mode = md_config_geti(md->sc, MD_CONFIG_DRIVE_MODE); } + if (!md->renew_window) md_config_get_timespan(&md->renew_window, md->sc, MD_CONFIG_RENEW_WINDOW); + if (!md->warn_window) md_config_get_timespan(&md->warn_window, md->sc, MD_CONFIG_WARN_WINDOW); if (md->transitive < 0) { md->transitive = md_config_geti(md->sc, MD_CONFIG_TRANSITIVE); } if (!md->ca_challenges && md->sc->ca_challenges) { md->ca_challenges = apr_array_copy(p, md->sc->ca_challenges); - } - if (!md->pkey_spec) { - md->pkey_spec = md->sc->pkey_spec; - + } + if (md_pkeys_spec_is_empty(md->pks)) { + md->pks = md->sc->pks; } if (md->require_https < 0) { md->require_https = md_config_geti(md->sc, MD_CONFIG_REQUIRE_HTTPS); } + if (!md->ca_eab_kid) { + md->ca_eab_kid = md->sc->ca_eab_kid; + md->ca_eab_hmac = md->sc->ca_eab_hmac; + } if (md->must_staple < 0) { md->must_staple = md_config_geti(md->sc, MD_CONFIG_MUST_STAPLE); } + if (md->stapling < 0) { + md->stapling = md_config_geti(md->sc, MD_CONFIG_STAPLING); + } } -static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s, apr_pool_t *p) +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)) { return APR_SUCCESS; } else if (md->transitive) { APR_ARRAY_PUSH(md->domains, const char*) = apr_pstrdup(p, domain); + *pupdates |= MD_UPD_DOMAINS; return APR_SUCCESS; } else { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10040) + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10040) "Virtual Host %s:%d matches Managed Domain '%s', but the " "name/alias %s itself is not managed. A requested MD certificate " "will not match ServerName.", s->server_hostname, s->port, md->name, domain); - return APR_EINVAL; + return APR_SUCCESS; } } -static apr_status_t md_covers_server(md_t *md, server_rec *s, apr_pool_t *p) +static apr_status_t md_cover_server(md_t *md, server_rec *s, int *pupdates, apr_pool_t *p) { apr_status_t rv; const char *name; int i; - - if (APR_SUCCESS == (rv = check_coverage(md, s->server_hostname, s, p)) && s->names) { - for (i = 0; i < s->names->nelts; ++i) { + + if (APR_SUCCESS == (rv = check_coverage(md, s->server_hostname, s, pupdates, p))) { + 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, p))) { + if (APR_SUCCESS != (rv = check_coverage(md, name, s, pupdates, p))) { break; } + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, + "md[%s]: auto add, covers alias %s", md->name, name); } } return rv; } -static int matches_port_somewhere(server_rec *s, int port) -{ - server_addr_rec *sa; - - for (sa = s->addrs; sa; sa = sa->next) { - if (sa->host_port == port) { - /* host_addr might be general (0.0.0.0) or specific, we count this as match */ - return 1; - } - if (sa->host_port == 0) { - /* wildcard port, answers to all ports. Rare, but may work. */ - return 1; - } - } - return 0; -} - -static int uses_port_only(server_rec *s, int port) +static int uses_port(server_rec *s, int port) { server_addr_rec *sa; int match = 0; @@ -181,839 +424,444 @@ static int uses_port_only(server_rec *s, int port) return match; } -static apr_status_t assign_to_servers(md_t *md, server_rec *base_server, - apr_pool_t *p, apr_pool_t *ptemp) +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; + apr_sockaddr_t *sa; + 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 + && (!lr->protocol || !strncmp("http", lr->protocol, 4))) { + can_http = 1; + } + else if (sa->port == mc->local_443 + && (!lr->protocol || !strncmp("http", lr->protocol, 4))) { + can_https = 1; + } + } + } + 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); +} + +static server_rec *get_public_https_server(md_t *md, const char *domain, server_rec *base_server) { - server_rec *s, *s_https; - request_rec r; md_srv_conf_t *sc; md_mod_conf_t *mc; + server_rec *s; + server_rec *res = NULL; + request_rec r; + int i; + int check_port = 1; + + sc = md_config_get(base_server); + mc = sc->mc; + memset(&r, 0, sizeof(r)); + + if (md->ca_challenges && md->ca_challenges->nelts > 0) { + /* skip the port check if "tls-alpn-01" is pre-configured */ + check_port = !(md_array_str_index(md->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0, 0) >= 0); + } + + if (check_port && !mc->can_https) return NULL; + + /* find an ssl server matching domain from MD */ + for (s = base_server; s; s = s->next) { + sc = md_config_get(s); + if (!sc || !sc->is_ssl || !sc->assigned) continue; + if (base_server == s && !mc->manage_base_server) continue; + if (base_server != s && check_port && mc->local_443 > 0 && !uses_port(s, mc->local_443)) continue; + for (i = 0; i < sc->assigned->nelts; ++i) { + if (md == APR_ARRAY_IDX(sc->assigned, i, md_t*)) { + r.server = s; + if (ap_matches_request_vhost(&r, domain, s->port)) { + if (check_port) { + return s; + } + else { + /* there may be multiple matching servers because we ignore the port. + if possible, choose a server that supports the acme-tls/1 protocol */ + if (ap_is_allowed_protocol(NULL, NULL, s, PROTO_ACME_TLS_1)) { + return s; + } + res = s; + } + } + } + } + } + return res; +} + +static apr_status_t auto_add_domains(md_t *md, server_rec *base_server, apr_pool_t *p) +{ + md_srv_conf_t *sc; + 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, + "md[%s]: auto add domains", md->name); + updates = 0; + for (s = base_server; s; s = s->next) { + sc = md_config_get(s); + if (!sc || !sc->is_ssl || !sc->assigned || sc->assigned->nelts != 1) continue; + if (md != APR_ARRAY_IDX(sc->assigned, 0, md_t*)) continue; + if (APR_SUCCESS != (rv = md_cover_server(md, s, &updates, p))) { + return rv; + } + } + return rv; +} + +static void init_acme_tls_1_domains(md_t *md, server_rec *base_server) +{ + md_srv_conf_t *sc; + md_mod_conf_t *mc; + server_rec *s; int i; const char *domain; - apr_array_header_t *servers; - + + /* 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; + 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*); + s = get_public_https_server(md, domain, base_server); + /* If we did not find a specific virtualhost for md and manage + * the base_server, that one is inspected */ + if (NULL == s && mc->manage_base_server) s = base_server; + if (NULL == s) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10168) + "%s: no https server_rec found for %s", md->name, domain); + continue; + } + 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", + md->name, domain, PROTO_ACME_TLS_1); + continue; + } + APR_ARRAY_PUSH(md->acme_tls_1_domains, const char*) = domain; + } +} + +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; + request_rec r; + 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 * is an assigned MD not equal this one, the configuration is in error. */ memset(&r, 0, sizeof(r)); - servers = apr_array_make(ptemp, 5, sizeof(server_rec*)); - for (s = base_server; s; s = s->next) { if (!mc->manage_base_server && s == base_server) { /* 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)) { + + if ((mc->match_mode == MD_MATCH_ALL && + ap_matches_request_vhost(&r, domain, s->port)) + || (((mc->match_mode == MD_MATCH_SERVERNAMES) || md_dns_is_wildcard(p, domain)) && + md_dns_matches(domain, s->server_hostname))) { /* Create a unique md_srv_conf_t record for this server, if there is none yet */ sc = md_config_get_unique(s, p); - - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10041) - "Server %s:%d matches md %s (config %s)", - s->server_hostname, s->port, md->name, sc->name); - - if (sc->assigned == md) { - /* already matched via another domain name */ - goto next_server; - } - else if (sc->assigned) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10042) - "conflict: MD %s matches server %s, but MD %s also matches.", - md->name, s->server_hostname, sc->assigned->name); - return APR_EINVAL; - } - - /* If this server_rec is only for http: requests. Defined - * alias names to not matter for this MD. - * (see gh issue https://github.com/icing/mod_md/issues/57) - * Otherwise, if server has name or an alias not covered, - * it is by default auto-added (config transitive). - * If mode is "manual", a generated certificate will not match - * all necessary names. */ - if ((!mc->local_80 || !uses_port_only(s, mc->local_80)) - && APR_SUCCESS != (rv = md_covers_server(md, s, p))) { - return rv; + if (!sc->assigned) sc->assigned = apr_array_make(p, 2, sizeof(md_t*)); + if (sc->assigned->nelts == 1 && mc->match_mode == MD_MATCH_SERVERNAMES) { + /* there is already an MD assigned for this server. But in + * this match mode, wildcard matches are pre-empted by non-wildcards */ + int existing_wild = md_is_wild_match( + APR_ARRAY_IDX(sc->assigned, 0, const md_t*)->domains, + s->server_hostname); + if (!existing_wild && md_dns_is_wildcard(p, domain)) + continue; /* do not add */ + if (existing_wild && !md_dns_is_wildcard(p, domain)) + sc->assigned->nelts = 0; /* overwrite existing */ } + 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, match-mode=%d) " + "for domain %s, has now %d MDs", + s->server_hostname, s->port, md->name, sc->name, + mc->match_mode, domain, (int)sc->assigned->nelts); - sc->assigned = md; - APR_ARRAY_PUSH(servers, server_rec*) = s; - - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10043) - "Managed Domain %s applies to vhost %s:%d", md->name, - s->server_hostname, s->port); - - goto next_server; - } - } - next_server: - continue; - } - - if (APR_SUCCESS == rv) { - if (apr_is_empty_array(servers)) { - if (md->drive_mode != MD_DRIVE_ALWAYS) { - /* Not an error, but looks suspicious */ - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(10045) - "No VirtualHost matches Managed Domain %s", md->name); - APR_ARRAY_PUSH(mc->unused_names, const char*) = md->name; - } - } - else { - const char *uri; - - /* Found matching server_rec's. Collect all 'ServerAdmin's into MD's contact list */ - apr_array_clear(md->contacts); - for (i = 0; i < servers->nelts; ++i) { - s = APR_ARRAY_IDX(servers, i, server_rec*); - if (s->server_admin && strcmp(DEFAULT_ADMIN, s->server_admin)) { - uri = md_util_schemify(p, s->server_admin, "mailto"); + if (md->contacts && md->contacts->nelts > 0) { + /* set explicitly */ + } + else 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) { - APR_ARRAY_PUSH(md->contacts, const char *) = uri; + APR_ARRAY_PUSH(md->contacts, const char *) = uri; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10044) "%s: added contact %s", md->name, uri); } } - } - - if (md->require_https > MD_REQUIRE_OFF) { - /* We require https for this MD, but do we have port 443 (or a mapped one) - * available? */ - if (mc->local_443 <= 0) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10105) - "MDPortMap says there is no port for https (443), " - "but MD %s is configured to require https. This " - "only works when a 443 port is available.", md->name); - return APR_EINVAL; - - } - - /* Ok, we know which local port represents 443, do we have a server_rec - * for MD that has addresses with port 443? */ - s_https = NULL; - for (i = 0; i < servers->nelts; ++i) { - s = APR_ARRAY_IDX(servers, i, server_rec*); - if (matches_port_somewhere(s, mc->local_443)) { - s_https = s; - break; + else if (s->server_admin && strcmp(DEFAULT_ADMIN, s->server_admin)) { + uri = md_util_schemify(p, s->server_admin, "mailto"); + if (md_array_str_index(md->contacts, uri, 0, 0) < 0) { + APR_ARRAY_PUSH(md->contacts, const char *) = uri; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10237) + "%s: added contact %s", md->name, uri); } } - - if (!s_https) { - /* Did not find any server_rec that matches this MD *and* has an - * s->addrs match for the https port. Suspicious. */ - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(10106) - "MD %s is configured to require https, but there seems to be " - "no VirtualHost for it that has port %d in its address list. " - "This looks as if it will not work.", - md->name, mc->local_443); - } + break; } } - } + return APR_SUCCESS; +} + +static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p) +{ + 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*); + if (APR_SUCCESS != (rv = link_md_to_servers(mc, md, s, p))) { + goto leave; + } + } +leave: return rv; } -static apr_status_t md_calc_md_list(apr_pool_t *p, apr_pool_t *plog, - apr_pool_t *ptemp, server_rec *base_server) +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 *sc; - md_mod_conf_t *mc; + md_srv_conf_t *base_conf; md_t *md, *omd; const char *domain; + md_timeslice_t *ts; apr_status_t rv = APR_SUCCESS; - ap_listen_rec *lr; - apr_sockaddr_t *sa; int i, j; - (void)plog; - sc = md_config_get(base_server); - mc = sc->mc; - - mc->can_http = 0; - mc->can_https = 0; + /* The global module configuration 'mc' keeps a list of all configured MDomains + * 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); - for (lr = ap_listeners; lr; lr = lr->next) { - for (sa = lr->bind_addr; sa; sa = sa->next) { - if (sa->port == mc->local_80 - && (!lr->protocol || !strncmp("http", lr->protocol, 4))) { - mc->can_http = 1; - } - else if (sa->port == mc->local_443 - && (!lr->protocol || !strncmp("http", lr->protocol, 4))) { - mc->can_https = 1; - } - } - } - - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10037) - "server seems%s reachable via http: (port 80->%d) " - "and%s reachable via https: (port 443->%d) ", - mc->can_http? "" : " not", mc->local_80, - mc->can_https? "" : " not", mc->local_443); - /* Complete the properties of the MDs, now that we have the complete, merged - * server configurations. + * server configurations. */ for (i = 0; i < mc->mds->nelts; ++i) { md = APR_ARRAY_IDX(mc->mds, i, md_t*); - md_merge_srv(md, sc, p); - - /* Check that we have no overlap with the MDs already completed */ - for (j = 0; j < i; ++j) { - omd = APR_ARRAY_IDX(mc->mds, j, md_t*); - if ((domain = md_common_name(md, omd)) != NULL) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10038) - "two Managed Domains have an overlap in domain '%s'" - ", first definition in %s(line %d), second in %s(line %d)", - domain, md->defn_name, md->defn_line_number, - omd->defn_name, omd->defn_line_number); + merge_srv_config(md, base_conf, p); + + if (mc->match_mode == MD_MATCH_ALL) { + /* Check that we have no overlap with the MDs already completed */ + for (j = 0; j < i; ++j) { + omd = APR_ARRAY_IDX(mc->mds, j, md_t*); + if ((domain = md_common_name(md, omd)) != NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10038) + "two Managed Domains have an overlap in domain '%s'" + ", first definition in %s(line %d), second in %s(line %d)", + domain, md->defn_name, md->defn_line_number, + omd->defn_name, omd->defn_line_number); + return APR_EINVAL; + } + } + } + + if (md->cert_files && md->cert_files->nelts) { + if (!md->pkey_files || (md->cert_files->nelts != md->pkey_files->nelts)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10170) + "The Managed Domain '%s' " + "needs one MDCertificateKeyFile for each MDCertificateFile.", + md->name); return APR_EINVAL; } } - - /* Assign MD to the server_rec configs that it matches. Perform some - * last finishing touches on the MD. */ - if (APR_SUCCESS != (rv = assign_to_servers(md, base_server, p, ptemp))) { - return rv; + else if (md->pkey_files && md->pkey_files->nelts + && (!md->cert_files || !md->cert_files->nelts)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10171) + "The Managed Domain '%s' " + "has MDCertificateKeyFile(s) but no MDCertificateFile.", + md->name); + return APR_EINVAL; } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10039) - "Completed MD[%s, CA=%s, Proto=%s, Agreement=%s, Drive=%d, renew=%ld]", - md->name, md->ca_url, md->ca_proto, md->ca_agreement, - md->drive_mode, (long)md->renew_window); - } - - return rv; -} - -/**************************************************************************************************/ -/* store & registry setup */ - -static apr_status_t store_file_ev(void *baton, struct md_store_t *store, - md_store_fs_ev_t ev, 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)", - ev, (ftype == APR_DIR)? "dir" : "file", fname, group); - - /* Directories in group CHALLENGES and STAGING are written to by our watchdog, - * running on certain mpms in a child process under a different user. Give them - * ownership. - */ - if (ftype == APR_DIR) { - switch (group) { - case MD_SG_CHALLENGES: - case MD_SG_STAGING: - rv = md_make_worker_accessible(fname, p); - if (APR_ENOTIMPL != rv) { - return rv; - } - break; - default: - break; + if (APLOG_IS_LEVEL(base_server, log_level)) { + ap_log_error(APLOG_MARK, log_level, 0, base_server, APLOGNO(10039) + "Completed MD[%s, CA=%s, Proto=%s, Agreement=%s, renew-mode=%d " + "renew_window=%s, warn_window=%s", + md->name, md->ca_effective, md->ca_proto, md->ca_agreement, md->renew_mode, + md->renew_window? md_timeslice_format(md->renew_window, p) : "unset", + md->warn_window? md_timeslice_format(md->warn_window, p) : "unset"); } } - return APR_SUCCESS; -} - -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); - } return rv; } -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; - MD_CHK_VARS; - - base_dir = ap_server_root_relative(p, mc->base_dir); - - if (!MD_OK(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 out; - } - - md_store_fs_set_event_cb(*pstore, store_file_ev, s); - if ( !MD_OK(check_group_dir(*pstore, MD_SG_CHALLENGES, p, s)) - || !MD_OK(check_group_dir(*pstore, MD_SG_STAGING, p, s)) - || !MD_OK(check_group_dir(*pstore, MD_SG_ACCOUNTS, p, s))) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047) - "setup challenges directory, call %s", MD_LAST_CHK); - } - -out: - return rv; -} - -static apr_status_t setup_reg(md_reg_t **preg, apr_pool_t *p, server_rec *s, - int can_http, int can_https) +static apr_status_t check_invalid_duplicates(server_rec *base_server) { + server_rec *s; md_srv_conf_t *sc; - md_mod_conf_t *mc; - md_store_t *store; - apr_status_t rv; - MD_CHK_VARS; - - sc = md_config_get(s); - mc = sc->mc; - - if ( MD_OK(setup_store(&store, mc, p, s)) - && MD_OK(md_reg_init(preg, p, store, mc->proxy_url))) { - mc->reg = *preg; - return md_reg_set_props(*preg, p, can_http, can_https); - } - return rv; -} - -/**************************************************************************************************/ -/* logging setup */ - -static server_rec *log_server; - -static int log_is_level(void *baton, apr_pool_t *p, md_log_level_t level) -{ - (void)baton; - (void)p; - if (log_server) { - return APLOG_IS_LEVEL(log_server, (int)level); - } - return level <= MD_LOG_INFO; -} - -#define LOG_BUF_LEN 16*1024 -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'; - - if (log_server) { - ap_log_error(file, line, APLOG_MODULE_INDEX, level, rv, log_server, "%s",buffer); - } - else { - ap_log_perror(file, line, APLOG_MODULE_INDEX, level, rv, p, "%s", buffer); + 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) + "conflict: %d MDs match to SSL VirtualHost %s, there can at most be one.", + (int)sc->assigned->nelts, s->server_hostname); + return APR_EINVAL; } } -} - -/**************************************************************************************************/ -/* lifecycle */ - -static apr_status_t cleanup_setups(void *dummy) -{ - (void)dummy; - log_server = NULL; return APR_SUCCESS; } -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); -} - -/**************************************************************************************************/ -/* mod_ssl interface */ - -static APR_OPTIONAL_FN_TYPE(ssl_is_https) *opt_ssl_is_https; - -static void init_ssl(void) +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) { - opt_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); -} - -/**************************************************************************************************/ -/* watchdog based impl. */ - -#define MD_WATCHDOG_NAME "_md_" - -static APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *wd_get_instance; -static APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *wd_register_callback; -static APR_OPTIONAL_FN_TYPE(ap_watchdog_set_callback_interval) *wd_set_interval; - -typedef struct { - md_t *md; - - int stalled; - int renewed; - int renewal_notified; - apr_time_t restart_at; - int need_restart; - int restart_processed; - - apr_status_t last_rv; - apr_time_t next_check; - int error_runs; -} md_job_t; - -typedef struct { - apr_pool_t *p; server_rec *s; - md_mod_conf_t *mc; - ap_watchdog_t *watchdog; - - apr_time_t next_change; - - apr_array_header_t *jobs; - md_reg_t *reg; -} md_watchdog; - -static void assess_renewal(md_watchdog *wd, md_job_t *job, apr_pool_t *ptemp) -{ - apr_time_t now = apr_time_now(); - if (now >= job->restart_at) { - job->need_restart = 1; - ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, wd->s, - "md(%s): has been renewed, needs restart now", job->md->name); - } - else { - job->next_check = job->restart_at; - - if (job->renewal_notified) { - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, - "%s: renewed cert valid in %s", - job->md->name, md_print_duration(ptemp, job->restart_at - now)); - } - else { - char ts[APR_RFC822_DATE_LEN]; - - apr_rfc822_date(ts, job->restart_at); - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, wd->s, APLOGNO(10051) - "%s: has been renewed successfully and should be activated at %s" - " (this requires a server restart latest in %s)", - job->md->name, ts, md_print_duration(ptemp, job->restart_at - now)); - job->renewal_notified = 1; - } - } -} - -static apr_status_t load_job_props(md_reg_t *reg, md_job_t *job, apr_pool_t *p) -{ - md_store_t *store = md_reg_store_get(reg); - md_json_t *jprops; - apr_status_t rv; - - rv = md_store_load_json(store, MD_SG_STAGING, job->md->name, - MD_FN_JOB, &jprops, p); - if (APR_SUCCESS == rv) { - job->restart_processed = md_json_getb(jprops, MD_KEY_PROCESSED, NULL); - job->error_runs = (int)md_json_getl(jprops, MD_KEY_ERRORS, NULL); - } - return rv; -} - -static apr_status_t save_job_props(md_reg_t *reg, md_job_t *job, apr_pool_t *p) -{ - md_store_t *store = md_reg_store_get(reg); - md_json_t *jprops; - apr_status_t rv; - - rv = md_store_load_json(store, MD_SG_STAGING, job->md->name, MD_FN_JOB, &jprops, p); - if (APR_STATUS_IS_ENOENT(rv)) { - jprops = md_json_create(p); - rv = APR_SUCCESS; - } - if (APR_SUCCESS == rv) { - md_json_setb(job->restart_processed, jprops, MD_KEY_PROCESSED, NULL); - md_json_setl(job->error_runs, jprops, MD_KEY_ERRORS, NULL); - rv = md_store_save_json(store, p, MD_SG_STAGING, job->md->name, - MD_FN_JOB, jprops, 0); - } - return rv; -} - -static apr_status_t check_job(md_watchdog *wd, md_job_t *job, apr_pool_t *ptemp) -{ + md_srv_conf_t *sc; apr_status_t rv = APR_SUCCESS; - apr_time_t valid_from, delay; - int errored, renew, error_runs; - char ts[APR_RFC822_DATE_LEN]; - - if (apr_time_now() < job->next_check) { - /* Job needs to wait */ - return APR_EAGAIN; - } - - job->next_check = 0; - error_runs = job->error_runs; - - if (job->md->state == MD_S_MISSING) { - job->stalled = 1; - } - - if (job->stalled) { - /* Missing information, this will not change until configuration - * is changed and server restarted */ - rv = APR_INCOMPLETE; - ++job->error_runs; - goto out; - } - else if (job->renewed) { - assess_renewal(wd, job, ptemp); - } - else if (APR_SUCCESS == (rv = md_reg_assess(wd->reg, job->md, &errored, &renew, wd->p))) { - if (errored) { - ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10050) - "md(%s): in error state", job->md->name); - } - else if (renew) { - ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10052) - "md(%s): state=%d, driving", job->md->name, job->md->state); - - rv = md_reg_stage(wd->reg, job->md, NULL, 0, &valid_from, ptemp); - - if (APR_SUCCESS == rv) { - job->renewed = 1; - job->restart_at = valid_from; - assess_renewal(wd, job, ptemp); + int i, has_ssl; + apr_array_header_t *servers; + + (void)p; + servers = apr_array_make(ptemp, 5, sizeof(server_rec*)); + has_ssl = 0; + for (s = base_server; s; s = s->next) { + sc = md_config_get(s); + if (!sc || !sc->assigned) continue; + for (i = 0; i < sc->assigned->nelts; ++i) { + if (md == APR_ARRAY_IDX(sc->assigned, i, md_t*)) { + APR_ARRAY_PUSH(servers, server_rec*) = s; + if (sc->is_ssl) has_ssl = 1; } } - else { - /* Renew is not necessary yet, leave job->next_check as 0 since - * that keeps the default schedule of running twice a day. */ - apr_rfc822_date(ts, job->md->expires); - ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10053) - "md(%s): no need to renew yet, cert expires %s", job->md->name, ts); - } } - - if (APR_SUCCESS == rv) { - job->error_runs = 0; + + if (!has_ssl && md->require_https > MD_REQUIRE_OFF) { + /* We require https for this MD, but do we have a SSL vhost? */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(10105) + "MD %s does not match any VirtualHost with 'SSLEngine on', " + "but is configured to require https. This cannot work.", md->name); } - else { - ap_log_error( APLOG_MARK, APLOG_ERR, rv, wd->s, APLOGNO(10056) - "processing %s", job->md->name); - ++job->error_runs; - /* back off duration, depending on the errors we encounter in a row */ - delay = apr_time_from_sec(5 << (job->error_runs - 1)); - if (delay > apr_time_from_sec(60*60)) { - delay = apr_time_from_sec(60*60); + if (apr_is_empty_array(servers)) { + if (md->renew_mode != MD_RENEW_ALWAYS) { + /* Not an error, but looks suspicious */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(10045) + "No VirtualHost matches Managed Domain %s", md->name); + APR_ARRAY_PUSH(mc->unused_names, const char*) = md->name; } - job->next_check = apr_time_now() + delay; - ap_log_error(APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO(10057) - "%s: encountered error for the %d. time, next run in %s", - job->md->name, job->error_runs, md_print_duration(ptemp, delay)); - } - -out: - if (error_runs != job->error_runs) { - apr_status_t rv2 = save_job_props(wd->reg, job, ptemp); - ap_log_error(APLOG_MARK, APLOG_TRACE1, rv2, wd->s, "%s: saving job props", job->md->name); } - - job->last_rv = rv; return rv; } -static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) +static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *ptemp, server_rec *s) { - md_watchdog *wd = baton; - apr_status_t rv = APR_SUCCESS; - md_job_t *job; - apr_time_t next_run, now; - int restart = 0; - int i; - - switch (state) { - case AP_WATCHDOG_STATE_STARTING: - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10054) - "md watchdog start, auto drive %d mds", wd->jobs->nelts); - assert(wd->reg); - - for (i = 0; i < wd->jobs->nelts; ++i) { - job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *); - load_job_props(wd->reg, job, ptemp); - } - break; - case AP_WATCHDOG_STATE_RUNNING: - - wd->next_change = 0; - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10055) - "md watchdog run, auto drive %d mds", wd->jobs->nelts); - - /* normally, we'd like to run at least twice a day */ - next_run = apr_time_now() + apr_time_from_sec(MD_SECS_PER_DAY / 2); - - /* Check on all the jobs we have */ - for (i = 0; i < wd->jobs->nelts; ++i) { - job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *); - - rv = check_job(wd, job, ptemp); - - if (job->need_restart && !job->restart_processed) { - restart = 1; - } - if (job->next_check && job->next_check < next_run) { - next_run = job->next_check; - } - } + md_t *md; + md_result_t *result; + int i, count; - now = apr_time_now(); - if (APLOGdebug(wd->s)) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10107) - "next run in %s", md_print_duration(ptemp, next_run - now)); - } - wd_set_interval(wd->watchdog, next_run - now, wd, run_watchdog); - break; - - case AP_WATCHDOG_STATE_STOPPING: - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10058) - "md watchdog stopping"); - break; - } - - if (restart) { - const char *action, *names = ""; - int n; - - for (i = 0, n = 0; i < wd->jobs->nelts; ++i) { - job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *); - if (job->need_restart && !job->restart_processed) { - names = apr_psprintf(ptemp, "%s%s%s", names, n? " " : "", job->md->name); - ++n; - } + /* 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' + */ + count = 0; + result = md_result_make(ptemp, APR_SUCCESS); + for (i = 0; i < mc->mds->nelts; ++i) { + md = APR_ARRAY_IDX(mc->mds, i, md_t*); + md_result_set(result, APR_SUCCESS, NULL); + md->watched = 0; + if (md->state == MD_S_ERROR) { + 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."); + continue; } - if (n > 0) { - int notified = 1; - - /* Run notify command for ready MDs (if configured) and persist that - * we have done so. This process might be reaped after n requests or die - * of another cause. The one taking over the watchdog need to notify again. - */ - if (wd->mc->notify_cmd) { - const char * const *argv; - const char *cmdline; - int exit_code; - - cmdline = apr_psprintf(ptemp, "%s %s", wd->mc->notify_cmd, names); - apr_tokenize_to_argv(cmdline, (char***)&argv, ptemp); - if (APR_SUCCESS == (rv = md_util_exec(ptemp, argv[0], argv, &exit_code))) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, wd->s, APLOGNO(10108) - "notify command '%s' returned %d", - wd->mc->notify_cmd, exit_code); - } - else { - if (APR_EINCOMPLETE == rv && exit_code) { - rv = 0; - } - ap_log_error(APLOG_MARK, APLOG_ERR, rv, wd->s, APLOGNO(10109) - "executing MDNotifyCmd %s returned %d", - wd->mc->notify_cmd, exit_code); - notified = 0; - } - } - - if (notified) { - /* persist the jobs that were notified */ - for (i = 0, n = 0; i < wd->jobs->nelts; ++i) { - job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *); - if (job->need_restart && !job->restart_processed) { - job->restart_processed = 1; - save_job_props(wd->reg, job, ptemp); - } - } - } - - /* FIXME: the server needs to start gracefully to take the new certificate in. - * This poses a variety of problems to solve satisfactory for everyone: - * - I myself, have no implementation for Windows - * - on *NIX, child processes run with less privileges, preventing - * the signal based restart trigger to work - * - admins want better control of timing windows for restarts, e.g. - * during less busy hours/days. - */ - rv = md_server_graceful(ptemp, wd->s); - if (APR_ENOTIMPL == rv) { - /* self-graceful restart not supported in this setup */ - action = " and changes will be activated on next (graceful) server restart."; - } - else { - action = " and server has been asked to restart now."; - } - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, wd->s, APLOGNO(10059) - "The Managed Domain%s %s %s been setup%s", - (n > 1)? "s" : "", names, (n > 1)? "have" : "has", action); + if (md->renew_mode == MD_RENEW_AUTO + && md_array_str_index(mc->unused_names, md->name, 0, 0) >= 0) { + /* This MD is not used in any virtualhost, do not watch */ + continue; } - } - - return APR_SUCCESS; -} -static apr_status_t start_watchdog(apr_array_header_t *names, apr_pool_t *p, - md_reg_t *reg, server_rec *s, md_mod_conf_t *mc) -{ - apr_allocator_t *allocator; - md_watchdog *wd; - apr_pool_t *wdp; - apr_status_t rv; - const char *name; - md_t *md; - md_job_t *job; - int i, errored, renew; - - wd_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance); - wd_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback); - wd_set_interval = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_set_callback_interval); - - if (!wd_get_instance || !wd_register_callback || !wd_set_interval) { - ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(10061) "mod_watchdog is required"); - return !OK; - } - - /* We want our own pool with own allocator to keep data across watchdog invocations */ - apr_allocator_create(&allocator); - apr_allocator_max_free_set(allocator, ap_max_mem_free); - rv = apr_pool_create_ex(&wdp, p, NULL, allocator); - if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10062) "md_watchdog: create pool"); - return rv; - } - apr_allocator_owner_set(allocator, wdp); - apr_pool_tag(wdp, "md_watchdog"); - - wd = apr_pcalloc(wdp, sizeof(*wd)); - wd->p = wdp; - wd->reg = reg; - wd->s = s; - wd->mc = mc; - - wd->jobs = apr_array_make(wd->p, 10, sizeof(md_job_t *)); - for (i = 0; i < names->nelts; ++i) { - name = APR_ARRAY_IDX(names, i, const char *); - md = md_reg_get(wd->reg, name, wd->p); - if (md) { - md_reg_assess(wd->reg, md, &errored, &renew, wd->p); - if (errored) { - ap_log_error( APLOG_MARK, APLOG_WARNING, 0, wd->s, APLOGNO(10063) - "md(%s): seems errored. Will not process this any further.", name); - } - else { - job = apr_pcalloc(wd->p, sizeof(*job)); - - job->md = md; - APR_ARRAY_PUSH(wd->jobs, md_job_t*) = job; - - ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10064) - "md(%s): state=%d, driving", name, md->state); - - load_job_props(reg, job, wd->p); - if (job->error_runs) { - /* We are just restarting. If we encounter jobs that had errors - * running the protocol on previous staging runs, we reset - * the staging area for it, in case we persisted something that - * causes a loop. */ - md_store_t *store = md_reg_store_get(wd->reg); - - md_store_purge(store, p, MD_SG_STAGING, job->md->name); - md_store_purge(store, p, MD_SG_CHALLENGES, job->md->name); - } + 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) + "md[%s]: %s", md->name, result->detail); } } - } - if (!wd->jobs->nelts) { - ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10065) - "no managed domain in state to drive, no watchdog needed, " - "will check again on next server (graceful) restart"); - apr_pool_destroy(wd->p); - return APR_SUCCESS; - } - - if (APR_SUCCESS != (rv = wd_get_instance(&wd->watchdog, MD_WATCHDOG_NAME, 0, 1, wd->p))) { - ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10066) - "create md watchdog(%s)", MD_WATCHDOG_NAME); - return rv; + md->watched = 1; + ++count; } - rv = wd_register_callback(wd->watchdog, 0, wd, run_watchdog); - ap_log_error(APLOG_MARK, rv? APLOG_CRIT : APLOG_DEBUG, rv, s, APLOGNO(10067) - "register md watchdog(%s)", MD_WATCHDOG_NAME); - return rv; -} - -static void load_stage_sets(apr_array_header_t *names, apr_pool_t *p, - md_reg_t *reg, server_rec *s) -{ - const char *name; - apr_status_t rv; - int i; - - for (i = 0; i < names->nelts; ++i) { - name = APR_ARRAY_IDX(names, i, const char*); - if (APR_SUCCESS == (rv = md_reg_load(reg, name, p))) { - ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10068) - "%s: staged set activated", name); - } - else if (!APR_STATUS_IS_ENOENT(rv)) { - ap_log_error( APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10069) - "%s: error loading staged set", name); - } - } - return; + return count; } -static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog, - apr_pool_t *ptemp, server_rec *s) +static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) { void *data = NULL; const char *mod_md_init_key = "mod_md_init_counter"; md_srv_conf_t *sc; md_mod_conf_t *mc; - md_reg_t *reg; - const md_t *md; - apr_array_header_t *drive_names; apr_status_t rv = APR_SUCCESS; - int i, dry_run = 0; + int dry_run = 0, log_level = APLOG_DEBUG; + md_store_t *store; apr_pool_userdata_get(&data, mod_md_init_key, s->process->pool); if (data == NULL) { /* 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. - * - * This is known. * * On a dry run, we therefore do all the cheap config things we - * need to do. Because otherwise mod_ssl fails because it calls - * us unprepared. - * But synching our configuration with the md store - * and determining which domains to drive and start a watchdog - * and all that, we do not. + * need to do to find out if the settings are ok. More expensive + * things we delay to the real run. */ - ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10070) + dry_run = 1; + log_level = APLOG_TRACE1; + ap_log_error( APLOG_MARK, log_level, 0, s, APLOGNO(10070) "initializing post config dry run"); apr_pool_userdata_set((const void *)1, mod_md_init_key, apr_pool_cleanup_null, s->process->pool); - dry_run = 1; } else { ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(10071) @@ -1024,278 +872,478 @@ static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog, init_setups(p, s); md_log_set(log_is_level, log_print, NULL); - /* Check uniqueness of MDs, calculate global, configured MD list. - * If successful, we have a list of MD definitions that do not overlap. */ - /* We also need to find out if we can be reached on 80/443 from the outside (e.g. the CA) */ - if (APR_SUCCESS != (rv = md_calc_md_list(p, plog, ptemp, s))) { - return rv; - } - md_config_post_config(s, p); sc = md_config_get(s); mc = sc->mc; + mc->dry_run = dry_run; + + md_event_init(p); + md_event_subscribe(on_event, mc); - /* Synchronize the definitions we now have with the store via a registry (reg). */ - if (APR_SUCCESS != (rv = setup_reg(®, p, s, mc->can_http, mc->can_https))) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10072) - "setup md registry"); - goto out; + rv = setup_store(&store, mc, p, s); + if (APR_SUCCESS != rv) goto leave; + + rv = md_reg_create(&mc->reg, p, store, mc->proxy_url, mc->ca_certs, + mc->min_delay, mc->retry_failover, + mc->use_store_locks, mc->lock_wait_timeout); + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10072) "setup md registry"); + goto leave; } - - if (APR_SUCCESS != (rv = md_reg_sync(reg, p, ptemp, mc->mds))) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10073) - "synching %d mds to registry", mc->mds->nelts); - } - - /* Determine the managed domains that are in auto drive_mode. For those, - * determine in which state they are: - * - UNKNOWN: should not happen, report, don't drive - * - ERROR: something we do not know how to fix, report, don't drive - * - INCOMPLETE/EXPIRED: need to drive them right away - * - COMPLETE: determine when cert expires, drive when the time comes - * - * Start the watchdog if we have anything, now or in the future. + + /* renew on 30% remaining /*/ + rv = md_ocsp_reg_make(&mc->ocsp, p, store, mc->ocsp_renew_window, + AP_SERVER_BASEVERSION, mc->proxy_url, + mc->min_delay); + if (APR_SUCCESS != rv) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10196) "setup ocsp registry"); + goto leave; + } + + init_ssl(); + + /* How to bootstrap this module: + * 1. find out if we know if http: and/or https: requests will arrive + * 2. apply the now complete configuration settings to the MDs + * 3. Link MDs to the server_recs they are used in. Detect unused MDs. + * 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 + * 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). + * 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. */ - drive_names = apr_array_make(ptemp, mc->mds->nelts+1, sizeof(const char *)); + /*1*/ + if (APR_SUCCESS != (rv = detect_supported_protocols(mc, s, p, log_level))) goto leave; + /*2*/ + if (APR_SUCCESS != (rv = merge_mds_with_conf(mc, p, s, log_level))) goto leave; + /*3*/ + if (APR_SUCCESS != (rv = link_mds_to_servers(mc, s, p))) goto leave; + /*4*/ + if (APR_SUCCESS != (rv = md_reg_lock_global(mc->reg, ptemp))) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10398) + "unable to obtain global registry lock, " + "renewed certificates may remain inactive on " + "this httpd instance!"); + /* FIXME: or should we fail the server start/reload here? */ + rv = APR_SUCCESS; + goto leave; + } + if (APR_SUCCESS != (rv = md_reg_sync_start(mc->reg, mc->mds, ptemp))) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10073) + "syncing %d mds to registry", mc->mds->nelts); + goto leave; + } + /*5*/ + md_reg_load_stagings(mc->reg, mc->mds, mc->env, p); +leave: + md_reg_unlock_global(mc->reg, ptemp); + return rv; +} + +static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog, + apr_pool_t *ptemp, server_rec *s) +{ + md_srv_conf_t *sc; + apr_status_t rv = APR_SUCCESS; + md_mod_conf_t *mc; + int watched, i; + md_t *md; + + (void)ptemp; + (void)plog; + sc = md_config_get(s); + + /*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; + } + apr_array_clear(mc->unused_names); for (i = 0; i < mc->mds->nelts; ++i) { - md = APR_ARRAY_IDX(mc->mds, i, const md_t *); - switch (md->drive_mode) { - case MD_DRIVE_AUTO: - if (md_array_str_index(mc->unused_names, md->name, 0, 0) >= 0) { - break; - } - /* fall through */ - case MD_DRIVE_ALWAYS: - APR_ARRAY_PUSH(drive_names, const char *) = md->name; - break; - default: - /* leave out */ - break; + md = APR_ARRAY_IDX(mc->mds, i, md_t *); + + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "md{%s}: auto_add", md->name); + if (APR_SUCCESS != (rv = auto_add_domains(md, s, p))) { + goto leave; + } + init_acme_tls_1_domains(md, s); + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "md{%s}: check_usage", md->name); + if (APR_SUCCESS != (rv = check_usage(mc, md, s, p, ptemp))) { + goto leave; + } + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "md{%s}: sync_finish", md->name); + if (APR_SUCCESS != (rv = md_reg_sync_finish(mc->reg, md, p, ptemp))) { + ap_log_error( APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10172) + "md[%s]: error syncing to store", md->name); + goto leave; } } - - init_ssl(); - - if (dry_run) { - goto out; - } - - /* If there are MDs to drive, start a watchdog to check on them regularly */ - if (drive_names->nelts > 0) { + /*8*/ + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "init_cert_watch"); + watched = init_cert_watch_status(mc, p, ptemp, s); + /*9*/ + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "cleanup challenges"); + md_reg_cleanup_challenges(mc->reg, p, ptemp, mc->mds); + + /* 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 are configured for auto-drive", - drive_names->nelts, mc->mds->nelts); - - load_stage_sets(drive_names, p, reg, s); + "%d out of %d mds need watching", watched, mc->mds->nelts); + md_http_use_implementation(md_curl_get_impl(p)); - rv = start_watchdog(drive_names, p, reg, s, mc); + rv = md_renew_start_watching(mc, s, p); } else { - ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) - "no mds to auto drive, no watchdog needed"); + ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) "no mds to supervise"); } -out: + + if (!mc->ocsp || md_ocsp_count(mc->ocsp) == 0) { + ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, s, "no ocsp to manage"); + goto leave; + } + + md_http_use_implementation(md_curl_get_impl(p)); + rv = md_ocsp_start_watching(mc, s, p); + +leave: + ap_log_error( APLOG_MARK, APLOG_TRACE2, rv, s, "post_config done"); return rv; } /**************************************************************************************************/ -/* Access API to other httpd components */ +/* connection context */ + +typedef struct { + const char *protocol; +} md_conn_ctx; + +static const char *md_protocol_get(const conn_rec *c) +{ + md_conn_ctx *ctx; + + ctx = (md_conn_ctx*)ap_get_module_config(c->conn_config, &md_module); + return ctx? ctx->protocol : NULL; +} + +/**************************************************************************************************/ +/* ALPN handling */ + +static int md_protocol_propose(conn_rec *c, request_rec *r, + server_rec *s, + const apr_array_header_t *offers, + apr_array_header_t *proposals) +{ + (void)s; + if (!r && offers && ap_ssl_conn_is_ssl(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); + APR_ARRAY_PUSH(proposals, const char*) = PROTO_ACME_TLS_1; + return OK; + } + return DECLINED; +} -static int md_is_managed(server_rec *s) +static int md_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, + const char *protocol) { - md_srv_conf_t *conf = md_config_get(s); + md_conn_ctx *ctx; - if (conf && conf->assigned) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10076) - "%s: manages server %s", conf->assigned->name, s->server_hostname); - return 1; + (void)s; + if (!r && ap_ssl_conn_is_ssl(c) && !strcmp(PROTO_ACME_TLS_1, protocol)) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, + "switching protocol '%s'", PROTO_ACME_TLS_1); + ctx = apr_pcalloc(c->pool, sizeof(*ctx)); + ctx->protocol = PROTO_ACME_TLS_1; + ap_set_module_config(c->conn_config, &md_module, ctx); + + c->keepalive = AP_CONN_CLOSE; + return OK; } - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, - "server %s is not managed", s->server_hostname); - return 0; + return DECLINED; } -static apr_status_t setup_fallback_cert(md_store_t *store, const md_t *md, - server_rec *s, apr_pool_t *p) + +/**************************************************************************************************/ +/* Access API to other httpd components */ + +static void fallback_fnames(apr_pool_t *p, md_pkey_spec_t *kspec, char **keyfn, char **certfn ) +{ + *keyfn = apr_pstrcat(p, "fallback-", md_pkey_filename(kspec, p), NULL); + *certfn = apr_pstrcat(p, "fallback-", md_chain_filename(kspec, p), NULL); +} + +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; - MD_CHK_VARS; - - spec.type = MD_PKEY_TYPE_RSA; - spec.params.rsa.bits = MD_PKEY_RSA_BITS_DEF; - - if ( !MD_OK(md_pkey_gen(&pkey, p, &spec)) - || !MD_OK(md_store_save(store, p, MD_SG_DOMAINS, md->name, - MD_FN_FALLBACK_PKEY, MD_SV_PKEY, (void*)pkey, 0)) - || !MD_OK(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)) - || !MD_OK(md_store_save(store, p, MD_SG_DOMAINS, md->name, - MD_FN_FALLBACK_CERT, MD_SV_CERT, (void*)cert, 0))) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, - "%s: setup fallback certificate, call %s", md->name, MD_LAST_CHK); + || 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: make fallback %s certificate", md->name, md_pkey_spec_name(kspec)); } return rv; } -static int fexists(const char *fname, apr_pool_t *p) -{ - return (*fname && APR_SUCCESS == md_util_is_file(fname, p)); -} - -static apr_status_t md_get_certificate(server_rec *s, apr_pool_t *p, - const char **pkeyfile, const char **pcertfile) +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 **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; const md_t *md; - MD_CHK_VARS; - - *pkeyfile = NULL; - *pcertfile = NULL; + apr_array_header_t *key_files, *chain_files; + const char *keyfile, *chainfile; + int i; + + *pkey_files = *pcert_files = NULL; + key_files = apr_array_make(p, 5, sizeof(const char*)); + chain_files = apr_array_make(p, 5, sizeof(const char*)); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10113) - "md_get_certificate called for vhost %s.", s->server_hostname); + "get_certificates called for vhost %s.", s->server_hostname); sc = md_config_get(s); if (!sc) { - ap_log_error(APLOG_MARK, APLOG_TRACE1, 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; } - - if (!sc->assigned) { - /* Hmm, mod_ssl (or someone like it) asks for certificates for a server - * where we did not assign a MD to. Either the user forgot to configure - * that server with SSL certs, has misspelled a server name or we have - * a bug that prevented us from taking responsibility for this server. - * Either way, make some polite noise */ - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, APLOGNO(10114) - "asked for certificate of server %s which has no MD assigned. This " - "could be ok, but most likely it is either a misconfiguration or " - "a bug. Please check server names and MD names carefully and if " - "everything checks open, please open an issue.", - s->server_hostname); - return APR_ENOENT; - } - + assert(sc->mc); reg = sc->mc->reg; assert(reg); - - md = md_reg_get(reg, sc->assigned->name, p); - if (!md) { - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10115) - "unable to hand out certificates, as registry can no longer " - "find MD '%s'.", sc->assigned->name); + + sc->is_ssl = 1; + + if (!sc->assigned) { + /* With the new hooks in mod_ssl, we are invoked for all server_rec. It is + * therefore normal, when we have nothing to add here. */ return APR_ENOENT; } - - if (!MD_OK(md_reg_get_cred_files(reg, md, p, pkeyfile, pcertfile))) { - ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10110) - "retrieving credentials for MD %s", md->name); - return rv; - } - - if (!fexists(*pkeyfile, p) || !fexists(*pcertfile, p)) { - /* 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. */ - store = md_reg_store_get(reg); - assert(store); + else if (sc->assigned->nelts != 1) { + if (!fallback) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10238) + "conflict: %d MDs match Virtualhost %s which uses SSL, however " + "there can be at most 1.", + (int)sc->assigned->nelts, s->server_hostname); + } + return APR_EINVAL; + } + md = APR_ARRAY_IDX(sc->assigned, 0, const md_t*); + + if (md->cert_files && md->cert_files->nelts) { + apr_array_cat(chain_files, md->cert_files); + apr_array_cat(key_files, md->pkey_files); + rv = APR_SUCCESS; + } + else { + md_pkey_spec_t *spec; - md_store_get_fname(pkeyfile, store, MD_SG_DOMAINS, - md->name, MD_FN_FALLBACK_PKEY, p); - md_store_get_fname(pcertfile, store, MD_SG_DOMAINS, - md->name, MD_FN_FALLBACK_CERT, p); - if (!fexists(*pkeyfile, p) || !fexists(*pcertfile, p)) { - if (!MD_OK(setup_fallback_cert(store, md, s, p))) { + for (i = 0; i < md_cert_count(md); ++i) { + spec = md_pkeys_spec_get(md->pks, i); + rv = md_reg_get_cred_files(&keyfile, &chainfile, reg, MD_SG_DOMAINS, md, spec, p); + if (APR_SUCCESS == rv) { + APR_ARRAY_PUSH(key_files, const char*) = keyfile; + APR_ARRAY_PUSH(chain_files, const char*) = chainfile; + } + else if (APR_STATUS_IS_ENOENT(rv)) { + /* certificate for this pkey is not available, others might + * if pkeys have been added for a running mdomain. + * see issue #260 */ + rv = APR_SUCCESS; + } + else if (!APR_STATUS_IS_ENOENT(rv)) { + 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; } } - - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10116) - "%s: providing fallback certificate for server %s", - md->name, s->server_hostname); - return APR_EAGAIN; - } - - /* We have key and cert files, but they might no longer be valid or not - * match all domain names. Still use these files for now, but indicate that - * resources should no longer be served until we have a new certificate again. */ - if (md->state != MD_S_COMPLETE) { - rv = APR_EAGAIN; - } - ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10077) - "%s: providing certificate for server %s", md->name, s->server_hostname); + + 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. */ + char *kfn, *cfn; + + store = md_reg_store_get(reg); + assert(store); + + for (i = 0; i < md_cert_count(md); ++i) { + 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; + } + rv = APR_EAGAIN; + goto leave; + } + } + } + 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)) { + *pkey_files = key_files; + *pcert_files = chain_files; + } + else if (APR_SUCCESS == rv) { + rv = APR_ENOENT; + } return rv; } -static int compat_warned; -static apr_status_t md_get_credentials(server_rec *s, apr_pool_t *p, - const char **pkeyfile, - const char **pcertfile, - const char **pchainfile) +static int md_add_cert_files(server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files) { - *pchainfile = NULL; - if (!compat_warned) { - compat_warned = 1; - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, /* no APLOGNO */ - "You are using mod_md with an old patch to mod_ssl. This will " - " work for now, but support will be dropped in a future release."); - } - return md_get_certificate(s, p, pkeyfile, pcertfile); + 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); + if (APR_SUCCESS == rv) { + if (!apr_is_empty_array(cert_files)) { + /* downgraded fromm WARNING to DEBUG, since installing separate certificates + * may be a valid use case. */ + 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).", + s->server_hostname); + } + 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); + apr_array_cat(cert_files, md_cert_files); + apr_array_cat(key_files, md_key_files); + return DONE; + } + return DECLINED; } -static int md_is_challenge(conn_rec *c, const char *servername, - X509 **pcert, EVP_PKEY **pkey) +static int md_add_fallback_cert_files(server_rec *s, apr_pool_t *p, + apr_array_header_t *cert_files, + apr_array_header_t *key_files) { - md_srv_conf_t *sc; - apr_size_t slen, sufflen = sizeof(MD_TLSSNI01_DNS_SUFFIX) - 1; + apr_array_header_t *md_cert_files; + apr_array_header_t *md_key_files; apr_status_t rv; - slen = strlen(servername); - if (slen <= sufflen - || apr_strnatcasecmp(MD_TLSSNI01_DNS_SUFFIX, servername + slen - sufflen)) { - return 0; + 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); + if (APR_EAGAIN == rv) { + apr_array_cat(cert_files, md_cert_files); + apr_array_cat(key_files, md_key_files); + return DONE; + } + return DECLINED; +} + +static int md_answer_challenge(conn_rec *c, const char *servername, + const char **pcert_pem, const char **pkey_pem) +{ + const char *protocol; + int hook_rv = DECLINED; + apr_status_t rv = APR_ENOENT; + md_srv_conf_t *sc; + md_store_t *store; + char *cert_name, *pkey_name; + const char *cert_pem, *key_pem; + int i; + + if (!servername + || !(protocol = md_protocol_get(c)) + || strcmp(PROTO_ACME_TLS_1, protocol)) { + goto cleanup; } - sc = md_config_get(c->base_server); - if (sc && sc->mc->reg) { - md_store_t *store = md_reg_store_get(sc->mc->reg); - md_cert_t *mdcert; - md_pkey_t *mdpkey; - - rv = md_store_load(store, MD_SG_CHALLENGES, servername, - MD_FN_TLSSNI01_CERT, 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, - MD_FN_TLSSNI01_PKEY, 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) - "%s: is a tls-sni-01 challenge host", servername); - return 1; - } - ap_log_cerror(APLOG_MARK, APLOG_WARNING, rv, c, APLOGNO(10079) - "%s: challenge data not complete, key unavailable", servername); - } - else { - ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, APLOGNO(10080) - "%s: unknown TLS SNI challenge host", servername); - } + if (!sc || !sc->mc->reg) goto cleanup; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, + "Answer challenge[tls-alpn-01] for %s", servername); + store = md_reg_store_get(sc->mc->reg); + + for (i = 0; i < md_pkeys_spec_count( sc->pks ); i++) { + tls_alpn01_fnames(c->pool, md_pkeys_spec_get(sc->pks,i), + &pkey_name, &cert_name); + + rv = md_store_load(store, MD_SG_CHALLENGES, servername, cert_name, MD_SV_TEXT, + (void**)&cert_pem, c->pool); + if (APR_STATUS_IS_ENOENT(rv)) continue; + if (APR_SUCCESS != rv) goto cleanup; + + rv = md_store_load(store, MD_SG_CHALLENGES, servername, pkey_name, MD_SV_TEXT, + (void**)&key_pem, c->pool); + if (APR_STATUS_IS_ENOENT(rv)) continue; + if (APR_SUCCESS != rv) goto cleanup; + + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, + "Found challenge cert %s, key %s for %s", + cert_name, pkey_name, servername); + *pcert_pem = cert_pem; + *pkey_pem = key_pem; + hook_rv = OK; + break; + } + + if (DECLINED == hook_rv) { + ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, APLOGNO(10080) + "%s: unknown tls-alpn-01 challenge host", servername); } - *pcert = NULL; - *pkey = NULL; - return 0; + +cleanup: + return hook_rv; } + /**************************************************************************************************/ -/* ACME challenge responses */ +/* ACME 'http-01' challenge responses */ #define WELL_KNOWN_PREFIX "/.well-known/" #define ACME_CHALLENGE_PREFIX WELL_KNOWN_PREFIX"acme-challenge/" @@ -1306,30 +1354,39 @@ static int md_http_challenge_pr(request_rec *r) const md_srv_conf_t *sc; const char *name, *data; md_reg_t *reg; - int configured; + 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); - configured = (NULL != md_get_by_domain(sc->mc->mds, r->hostname)); + 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 (md && md->ca_challenges + && md_array_str_index(md->ca_challenges, MD_AUTHZ_CHA_HTTP_01, 0, 1) < 0) { + /* The MD this challenge is for does not allow http-01 challanges, + * we have to decline. See #279 for a setup example where this + * is necessary. + */ + return DECLINED; + } + 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; } @@ -1337,29 +1394,31 @@ 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 (!configured) { - /* The request hostname is not for a configured domain. We are not + else if (!md || md->renew_mode == MD_RENEW_MANUAL + || (md->cert_files && md->cert_files->nelts + && md->renew_mode == MD_RENEW_AUTO)) { + /* The request hostname is not for a domain - or at least not for + * a domain that we renew ourselves. We are not * the sole authority here for /.well-known/acme-challenge (see PR62189). - * So, we decline to handle this and let others step in. + * So, we decline to handle this and give others a chance to provide + * the answer. */ return DECLINED; } else if (APR_STATUS_IS_ENOENT(rv)) { return HTTP_NOT_FOUND; } - else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10081) - "loading challenge %s from store", name); - return HTTP_INTERNAL_SERVER_ERROR; - } + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10081) + "loading challenge %s from store", name); + return HTTP_INTERNAL_SERVER_ERROR; } } } @@ -1373,55 +1432,65 @@ static int md_require_https_maybe(request_rec *r) { const md_srv_conf_t *sc; apr_uri_t uri; - const char *s; + const char *s, *host; + const md_t *md; int status; - - if (opt_ssl_is_https && r->parsed_uri.path - && strncmp(WELL_KNOWN_PREFIX, r->parsed_uri.path, sizeof(WELL_KNOWN_PREFIX)-1)) { - - sc = ap_get_module_config(r->server->module_config, &md_module); - if (sc && sc->assigned && sc->assigned->require_https > MD_REQUIRE_OFF) { - if (opt_ssl_is_https(r->connection)) { - /* Using https: - * if 'permanent' and no one else set a HSTS header already, do it */ - if (sc->assigned->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); - } + + /* 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 || !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 (ap_ssl_conn_is_ssl(r->connection)) { + /* Using https: + * if 'permanent' and no one else set a HSTS header already, do it */ + 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); + } + } + else { + if (md->require_https > MD_REQUIRE_OFF) { + /* 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)? + HTTP_MOVED_PERMANENTLY : HTTP_MOVED_TEMPORARILY); } else { - /* Not using https:, but require it. Redirect. */ - if (r->method_number == M_GET) { - /* safe to use the old-fashioned codes */ - status = ((MD_REQUIRE_PERMANENT == sc->assigned->require_https)? - HTTP_MOVED_PERMANENTLY : HTTP_MOVED_TEMPORARILY); - } - else { - /* these should keep the method unchanged on retry */ - status = ((MD_REQUIRE_PERMANENT == sc->assigned->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"; - uri.port = 443; - uri.port_str = (char*)"443"; - uri.query = r->parsed_uri.query; - uri.fragment = r->parsed_uri.fragment; - s = apr_uri_unparse(r->pool, &uri, APR_URI_UNP_OMITUSERINFO); - if (s && *s) { - apr_table_setn(r->headers_out, "Location", s); - return status; - } + /* these should keep the method unchanged on retry */ + 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"; + uri.port = 443; + uri.port_str = (char*)"443"; + uri.query = r->parsed_uri.query; + uri.fragment = r->parsed_uri.fragment; + s = apr_uri_unparse(r->pool, &uri, APR_URI_UNP_OMITUSERINFO); + if (s && *s) { + apr_table_setn(r->headers_out, "Location", s); + return status; } } } } +declined: 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) @@ -1434,27 +1503,47 @@ static void md_child_init(apr_pool_t *pool, server_rec *s) */ static void md_hooks(apr_pool_t *pool) { - static const char *const mod_ssl[] = { "mod_ssl.c", NULL}; + static const char *const mod_ssl[] = { "mod_ssl.c", "mod_tls.c", NULL}; + static const char *const mod_wd[] = { "mod_watchdog.c", NULL}; + + /* Leave the ssl initialization to mod_ssl or friends. */ + md_acme_init(pool, AP_SERVER_BASEVERSION, 0); - md_acme_init(pool, AP_SERVER_BASEVERSION); - 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, NULL, mod_ssl, APR_HOOK_MIDDLE); - + 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, mod_wd, APR_HOOK_LAST); + /* Run once after a child process has been created. */ ap_hook_child_init(md_child_init, NULL, mod_ssl, APR_HOOK_MIDDLE); /* answer challenges *very* early, before any configured authentication may strike */ - ap_hook_post_read_request(md_require_https_maybe, NULL, NULL, APR_HOOK_FIRST); + ap_hook_post_read_request(md_require_https_maybe, mod_ssl, NULL, APR_HOOK_MIDDLE); ap_hook_post_read_request(md_http_challenge_pr, NULL, NULL, APR_HOOK_MIDDLE); - APR_REGISTER_OPTIONAL_FN(md_is_managed); - APR_REGISTER_OPTIONAL_FN(md_get_certificate); - APR_REGISTER_OPTIONAL_FN(md_is_challenge); - APR_REGISTER_OPTIONAL_FN(md_get_credentials); + ap_hook_protocol_propose(md_protocol_propose, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_protocol_switch(md_protocol_switch, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_protocol_get(md_protocol_get, NULL, NULL, APR_HOOK_MIDDLE); + + /* Status request handlers and contributors */ + ap_hook_post_read_request(md_http_cert_status, NULL, mod_ssl, APR_HOOK_MIDDLE); + APR_OPTIONAL_HOOK(ap, status_hook, md_domains_status_hook, NULL, NULL, APR_HOOK_MIDDLE); + APR_OPTIONAL_HOOK(ap, status_hook, md_ocsp_status_hook, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_handler(md_status_handler, NULL, NULL, APR_HOOK_MIDDLE); + + ap_hook_ssl_answer_challenge(md_answer_challenge, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_ssl_add_cert_files(md_add_cert_files, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_ssl_add_fallback_cert_files(md_add_fallback_cert_files, NULL, NULL, APR_HOOK_MIDDLE); + +#if AP_MODULE_MAGIC_AT_LEAST(20120211, 105) + ap_hook_ssl_ocsp_prime_hook(md_ocsp_prime_status, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_ssl_ocsp_get_resp_hook(md_ocsp_provide_status, NULL, NULL, APR_HOOK_MIDDLE); +#else +#error "This version of mod_md requires Apache httpd 2.4.48 or newer." +#endif /* AP_MODULE_MAGIC_AT_LEAST() */ } |