| /* 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_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_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_http.h" |
| #include "md_json.h" |
| #include "md_store.h" |
| #include "md_store_fs.h" |
| #include "md_log.h" |
| #include "md_result.h" |
| #include "md_reg.h" |
| #include "md_util.h" |
| #include "md_version.h" |
| #include "md_acme.h" |
| #include "md_acme_authz.h" |
| |
| #include "mod_md.h" |
| #include "mod_md_config.h" |
| #include "mod_md_drive.h" |
| #include "mod_md_os.h" |
| #include "mod_md_status.h" |
| #include "mod_ssl.h" |
| |
| static void md_hooks(apr_pool_t *pool); |
| |
| AP_DECLARE_MODULE(md) = { |
| STANDARD20_MODULE_STUFF, |
| NULL, /* func to create per dir config */ |
| NULL, /* func to merge per dir config */ |
| md_config_create_svr, /* func to create per server config */ |
| md_config_merge_svr, /* func to merge per server config */ |
| md_cmds, /* command handlers */ |
| md_hooks, |
| #if defined(AP_MODULE_FLAG_NONE) |
| AP_MODULE_FLAG_ALWAYS_MERGE |
| #endif |
| }; |
| |
| /**************************************************************************************************/ |
| /* 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 APR_OPTIONAL_FN_TYPE(ssl_is_https) *opt_ssl_is_https; |
| |
| static void init_ssl(void) |
| { |
| opt_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); |
| } |
| |
| /**************************************************************************************************/ |
| /* 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); |
| } |
| |
| /**************************************************************************************************/ |
| /* store & registry 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 and STAGING are written to under a different user. |
| * Give him 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; |
| } |
| } |
| 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 out; |
| } |
| |
| 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))) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047) |
| "setup challenges directory"); |
| } |
| |
| out: |
| return rv; |
| } |
| |
| /**************************************************************************************************/ |
| /* post config handling */ |
| |
| static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p) |
| { |
| 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_proto) { |
| md->ca_proto = md_config_gets(md->sc, MD_CONFIG_CA_PROTO); |
| } |
| 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)) { |
| 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_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->require_https < 0) { |
| md->require_https = md_config_geti(md->sc, MD_CONFIG_REQUIRE_HTTPS); |
| } |
| if (md->must_staple < 0) { |
| md->must_staple = md_config_geti(md->sc, MD_CONFIG_MUST_STAPLE); |
| } |
| } |
| |
| static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s, 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); |
| return APR_SUCCESS; |
| } |
| else { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 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; |
| } |
| } |
| |
| static apr_status_t md_covers_server(md_t *md, server_rec *s, 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) { |
| name = APR_ARRAY_IDX(s->names, i, const char*); |
| if (APR_SUCCESS != (rv = check_coverage(md, name, s, p))) { |
| break; |
| } |
| } |
| } |
| 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(server_rec *s, int port) |
| { |
| server_addr_rec *sa; |
| int match = 0; |
| 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 */ |
| match = 1; |
| } |
| else { |
| /* uses other port/wildcard */ |
| return 0; |
| } |
| } |
| return match; |
| } |
| |
| static apr_status_t detect_supported_ports(md_mod_conf_t *mc, server_rec *s, |
| apr_pool_t *p, int log_level) |
| { |
| ap_listen_rec *lr; |
| apr_sockaddr_t *sa; |
| |
| mc->can_http = 0; |
| mc->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))) { |
| 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, log_level, 0, s, 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); |
| return md_reg_set_props(mc->reg, p, mc->can_http, mc->can_https); |
| } |
| |
| static server_rec *get_https_server(const char *domain, server_rec *base_server) |
| { |
| md_srv_conf_t *sc; |
| md_mod_conf_t *mc; |
| server_rec *s; |
| request_rec r; |
| |
| sc = md_config_get(base_server); |
| mc = sc->mc; |
| memset(&r, 0, sizeof(r)); |
| |
| for (s = base_server; s && (mc->local_443 > 0); s = s->next) { |
| if (!mc->manage_base_server && s == base_server) { |
| /* we shall not assign ourselves to the base server */ |
| continue; |
| } |
| r.server = s; |
| if (ap_matches_request_vhost(&r, domain, s->port) && uses_port(s, mc->local_443)) { |
| return s; |
| } |
| } |
| return NULL; |
| } |
| |
| static void init_acme_tls_1_domains(md_t *md, server_rec *base_server) |
| { |
| server_rec *s; |
| int i; |
| const char *domain; |
| |
| /* Collect those domains that support the "acme-tls/1" protocol. This |
| * is part of the MD (and not tested dynamically), since challenge selection |
| * may be done outside the server, e.g. in the a2md command. */ |
| 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*); |
| if (NULL == (s = get_https_server(domain, base_server))) { |
| 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, apr_pool_t *ptemp) |
| { |
| server_rec *s, *s_https; |
| request_rec r; |
| md_srv_conf_t *sc; |
| apr_status_t rv = APR_SUCCESS; |
| int i; |
| const char *domain; |
| apr_array_header_t *servers; |
| |
| 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)) { |
| /* 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 do 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(s, mc->local_80)) { |
| if (APR_SUCCESS != (rv = md_covers_server(md, s, p))) { |
| return rv; |
| } |
| } |
| |
| 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->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; |
| } |
| } |
| 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_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(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; |
| } |
| } |
| |
| 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); |
| } |
| } |
| |
| } |
| |
| } |
| return rv; |
| } |
| |
| static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s, |
| apr_pool_t *p, apr_pool_t *ptemp) |
| { |
| 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, ptemp))) { |
| goto leave; |
| } |
| } |
| leave: |
| return rv; |
| } |
| |
| static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p, |
| server_rec *base_server, int log_level) |
| { |
| md_srv_conf_t *base_conf; |
| md_t *md, *omd; |
| const char *domain; |
| const md_timeslice_t *ts; |
| apr_status_t rv = APR_SUCCESS; |
| int i, j; |
| |
| /* 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); |
| |
| /* Complete the properties of the MDs, now that we have the complete, merged |
| * server configurations. |
| */ |
| for (i = 0; i < mc->mds->nelts; ++i) { |
| md = APR_ARRAY_IDX(mc->mds, i, md_t*); |
| merge_srv_config(md, base_conf, 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); |
| return APR_EINVAL; |
| } |
| } |
| |
| if (md->cert_file && !md->pkey_file) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10170) |
| "The Managed Domain '%s', defined in %s(line %d), " |
| "has a MDCertificateFile but no MDCertificateKeyFile.", |
| md->name, md->defn_name, md->defn_line_number); |
| return APR_EINVAL; |
| } |
| if (!md->cert_file && md->pkey_file) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10171) |
| "The Managed Domain '%s', defined in %s(line %d), " |
| "has a MDCertificateKeyFile but no MDCertificateFile.", |
| md->name, md->defn_name, md->defn_line_number); |
| return APR_EINVAL; |
| } |
| |
| init_acme_tls_1_domains(md, base_server); |
| |
| 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_url, 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 rv; |
| } |
| |
| static void load_staged_data(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p) |
| { |
| apr_status_t rv; |
| md_t *md; |
| md_result_t *result; |
| int i; |
| |
| for (i = 0; i < mc->mds->nelts; ++i) { |
| md = APR_ARRAY_IDX(mc->mds, i, md_t *); |
| result = md_result_md_make(p, md); |
| if (APR_SUCCESS == (rv = md_reg_load_staging(mc->reg, md, mc->env, result, p))) { |
| ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10068) |
| "%s: staged set activated", md->name); |
| } |
| else if (!APR_STATUS_IS_ENOENT(rv)) { |
| ap_log_error( APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10069) |
| "%s: error loading staged set", md->name); |
| } |
| } |
| } |
| |
| static apr_status_t reinit_mds(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p) |
| { |
| md_t *md; |
| apr_status_t rv = APR_SUCCESS; |
| int i; |
| |
| for (i = 0; i < mc->mds->nelts; ++i) { |
| md = APR_ARRAY_IDX(mc->mds, i, md_t *); |
| if (APR_SUCCESS != (rv = md_reg_reinit_state(mc->reg, (md_t*)md, p))) { |
| ap_log_error( APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10172) |
| "%s: error reinitiazing from store", md->name); |
| break; |
| } |
| } |
| return rv; |
| } |
| |
| static void init_watched_names(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *ptemp, server_rec *s) |
| { |
| const md_t *md; |
| md_result_t *result; |
| int i; |
| |
| /* 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' |
| */ |
| result = md_result_make(ptemp, APR_SUCCESS); |
| apr_array_clear(mc->watched_names); |
| for (i = 0; i < mc->mds->nelts; ++i) { |
| md = APR_ARRAY_IDX(mc->mds, i, const md_t *); |
| md_result_set(result, APR_SUCCESS, NULL); |
| |
| 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 (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; |
| } |
| |
| 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); |
| } |
| } |
| |
| APR_ARRAY_PUSH(mc->watched_names, const char *) = md->name; |
| } |
| } |
| |
| static apr_status_t md_post_config(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; |
| apr_status_t rv = APR_SUCCESS; |
| 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. |
| * |
| * On a dry run, we therefore do all the cheap config things we |
| * need to do to find out if the settings are ok. More expensive |
| * things we delay to the real run. |
| */ |
| 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); |
| } |
| else { |
| ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(10071) |
| "mod_md (v%s), initializing...", MOD_MD_VERSION); |
| } |
| |
| (void)plog; |
| init_setups(p, s); |
| md_log_set(log_is_level, log_print, NULL); |
| |
| md_config_post_config(s, p); |
| sc = md_config_get(s); |
| mc = sc->mc; |
| mc->dry_run = dry_run; |
| |
| if (APR_SUCCESS != (rv = setup_store(&store, mc, p, s)) |
| || APR_SUCCESS != (rv = md_reg_create(&mc->reg, p, store, mc->proxy_url))) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10072) |
| "setup md registry"); |
| goto leave; |
| } |
| |
| init_ssl(); |
| |
| /* How to bootstrap this module: |
| * 1. find out if we know where http: and https: requests will arrive |
| * 2. apply the now complete configuration setttings 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 existance 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. |
| */ |
| /*1*/ |
| if (APR_SUCCESS != (rv = detect_supported_ports(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, ptemp))) goto leave; |
| /*4*/ |
| if (APR_SUCCESS != (rv = md_reg_sync(mc->reg, p, ptemp, mc->mds))) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10073) |
| "synching %d mds to registry", mc->mds->nelts); |
| goto leave; |
| } |
| /*5*/ |
| load_staged_data(mc, s, p); |
| /*6*/ |
| if (dry_run) goto leave; |
| /*7*/ |
| if (APR_SUCCESS != (rv = reinit_mds(mc, s, p))) goto leave; |
| /*8*/ |
| init_watched_names(mc, p, ptemp, s); |
| /*9*/ |
| md_reg_cleanup_challenges(mc->reg, p, ptemp, mc->mds); |
| |
| /* From here on, the domains in the registry are readonly |
| * and only staging/challenges may be manipulated */ |
| md_reg_freeze_domains(mc->reg, mc->mds); |
| |
| if (mc->watched_names->nelts > 0) { |
| /*10*/ |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10074) |
| "%d out of %d mds need watching", |
| mc->watched_names->nelts, mc->mds->nelts); |
| |
| md_http_use_implementation(md_curl_get_impl(p)); |
| rv = md_start_watching(mc, s, p); |
| } |
| else { |
| ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) "no mds to drive"); |
| } |
| leave: |
| return rv; |
| } |
| |
| /**************************************************************************************************/ |
| /* 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 && opt_ssl_is_https && opt_ssl_is_https(c) |
| && ap_array_str_contains(offers, PROTO_ACME_TLS_1)) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "proposing protocol '%s'", PROTO_ACME_TLS_1); |
| APR_ARRAY_PUSH(proposals, const char*) = PROTO_ACME_TLS_1; |
| return OK; |
| } |
| return DECLINED; |
| } |
| |
| static int md_protocol_switch(conn_rec *c, request_rec *r, server_rec *s, |
| const char *protocol) |
| { |
| md_conn_ctx *ctx; |
| |
| (void)s; |
| if (!r && opt_ssl_is_https && opt_ssl_is_https(c) && !strcmp(PROTO_ACME_TLS_1, protocol)) { |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, |
| "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; |
| } |
| return DECLINED; |
| } |
| |
| |
| /**************************************************************************************************/ |
| /* Access API to other httpd components */ |
| |
| static int md_is_managed(server_rec *s) |
| { |
| md_srv_conf_t *conf = md_config_get(s); |
| |
| 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; |
| } |
| ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, |
| "server %s is not managed", s->server_hostname); |
| return 0; |
| } |
| |
| static apr_status_t setup_fallback_cert(md_store_t *store, const md_t *md, |
| server_rec *s, apr_pool_t *p) |
| { |
| md_pkey_t *pkey; |
| md_cert_t *cert; |
| md_pkey_spec_t spec; |
| apr_status_t rv; |
| |
| spec.type = MD_PKEY_TYPE_RSA; |
| spec.params.rsa.bits = MD_PKEY_RSA_BITS_DEF; |
| |
| if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, p, &spec)) |
| || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, |
| MD_FN_FALLBACK_PKEY, MD_SV_PKEY, (void*)pkey, 0)) |
| || APR_SUCCESS != (rv = md_cert_self_sign(&cert, "Apache Managed Domain Fallback", |
| md->domains, pkey, apr_time_from_sec(14 * MD_SECS_PER_DAY), p)) |
| || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, |
| MD_FN_FALLBACK_CERT, MD_SV_CERT, (void*)cert, 0))) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10174) |
| "%s: setup fallback certificate", md->name); |
| } |
| return rv; |
| } |
| |
| static apr_status_t get_certificate(server_rec *s, apr_pool_t *p, int fallback, |
| const char **pcertfile, const char **pkeyfile) |
| { |
| apr_status_t rv = APR_ENOENT; |
| md_srv_conf_t *sc; |
| md_reg_t *reg; |
| md_store_t *store; |
| const md_t *md; |
| |
| *pkeyfile = NULL; |
| *pcertfile = NULL; |
| |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10113) |
| "get_certificate called for vhost %s.", s->server_hostname); |
| |
| sc = md_config_get(s); |
| if (!sc) { |
| ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, s, |
| "asked for certificate of server %s which has no md config", |
| 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 = sc->assigned; |
| 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); |
| return APR_ENOENT; |
| } |
| |
| rv = md_reg_get_cred_files(pkeyfile, pcertfile, reg, MD_SG_DOMAINS, md, p); |
| if (APR_STATUS_IS_ENOENT(rv)) { |
| 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. */ |
| store = md_reg_store_get(reg); |
| assert(store); |
| |
| 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 (!md_file_exists(*pkeyfile, p) || !md_file_exists(*pcertfile, p)) { |
| if (APR_SUCCESS != (rv = setup_fallback_cert(store, md, s, p))) { |
| 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; |
| } |
| } |
| else if (APR_SUCCESS != rv) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10110) |
| "retrieving credentials for MD %s", md->name); |
| return rv; |
| } |
| |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10077) |
| "%s[state=%d]: providing certificate for server %s", |
| md->name, md->state, s->server_hostname); |
| return rv; |
| } |
| |
| static apr_status_t md_get_certificate(server_rec *s, apr_pool_t *p, |
| const char **pkeyfile, const char **pcertfile) |
| { |
| return get_certificate(s, p, 1, pcertfile, pkeyfile); |
| } |
| |
| 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) |
| { |
| const char *certfile, *keyfile; |
| 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_certificate(s, p, 0, &certfile, &keyfile); |
| if (APR_SUCCESS == rv) { |
| if (!apr_is_empty_array(cert_files)) { |
| ap_log_error(APLOG_MARK, APLOG_WARNING, 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); |
| } |
| APR_ARRAY_PUSH(cert_files, const char*) = certfile; |
| APR_ARRAY_PUSH(key_files, const char*) = keyfile; |
| return DONE; |
| } |
| return DECLINED; |
| } |
| |
| 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) |
| { |
| const char *certfile, *keyfile; |
| apr_status_t rv; |
| |
| ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, "hook ssl_add_fallback_cert_files for %s", |
| s->server_hostname); |
| rv = get_certificate(s, p, 1, &certfile, &keyfile); |
| if (APR_EAGAIN == rv) { |
| APR_ARRAY_PUSH(cert_files, const char*) = certfile; |
| APR_ARRAY_PUSH(key_files, const char*) = keyfile; |
| return DONE; |
| } |
| return DECLINED; |
| } |
| |
| static int md_is_challenge(conn_rec *c, const char *servername, |
| X509 **pcert, EVP_PKEY **pkey) |
| { |
| md_srv_conf_t *sc; |
| const char *protocol, *challenge, *cert_name, *pkey_name; |
| apr_status_t rv; |
| |
| if (!servername) goto out; |
| |
| challenge = NULL; |
| if ((protocol = md_protocol_get(c)) && !strcmp(PROTO_ACME_TLS_1, protocol)) { |
| challenge = "tls-alpn-01"; |
| cert_name = MD_FN_TLSALPN01_CERT; |
| pkey_name = MD_FN_TLSALPN01_PKEY; |
| |
| 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; |
| |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "%s: load certs/keys %s/%s", |
| servername, cert_name, pkey_name); |
| rv = md_store_load(store, MD_SG_CHALLENGES, servername, cert_name, |
| MD_SV_CERT, (void**)&mdcert, c->pool); |
| if (APR_SUCCESS == rv && (*pcert = md_cert_get_X509(mdcert))) { |
| rv = md_store_load(store, MD_SG_CHALLENGES, servername, pkey_name, |
| 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 %s challenge host", servername, challenge); |
| 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 %s challenge host", servername, challenge); |
| } |
| } |
| } |
| out: |
| *pcert = NULL; |
| *pkey = NULL; |
| return 0; |
| } |
| |
| static int md_answer_challenge(conn_rec *c, const char *servername, |
| void **pX509, void **pEVP_PKEY) |
| { |
| if (md_is_challenge(c, servername, (X509**)pX509, (EVP_PKEY**)pEVP_PKEY)) { |
| return APR_SUCCESS; |
| } |
| return DECLINED; |
| } |
| |
| /**************************************************************************************************/ |
| /* ACME 'http-01' challenge responses */ |
| |
| #define WELL_KNOWN_PREFIX "/.well-known/" |
| #define ACME_CHALLENGE_PREFIX WELL_KNOWN_PREFIX"acme-challenge/" |
| |
| static int md_http_challenge_pr(request_rec *r) |
| { |
| apr_bucket_brigade *bb; |
| const md_srv_conf_t *sc; |
| const char *name, *data; |
| md_reg_t *reg; |
| const md_t *md; |
| apr_status_t rv; |
| |
| 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", |
| r->hostname, r->parsed_uri.path); |
| md = md_get_by_domain(sc->mc->mds, r->hostname); |
| name = r->parsed_uri.path + sizeof(ACME_CHALLENGE_PREFIX)-1; |
| reg = sc && sc->mc? sc->mc->reg : NULL; |
| |
| if (strlen(name) && !ap_strchr_c(name, '/') && reg) { |
| md_store_t *store = md_reg_store_get(reg); |
| |
| rv = md_store_load(store, MD_SG_CHALLENGES, r->hostname, |
| MD_FN_HTTP01, MD_SV_TEXT, (void**)&data, r->pool); |
| 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; |
| } |
| /* A GET on a challenge resource for a hostname we are |
| * configured for. Let's send the content back */ |
| r->status = HTTP_OK; |
| apr_table_setn(r->headers_out, "Content-Length", apr_ltoa(r->pool, (long)len)); |
| |
| bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); |
| apr_brigade_write(bb, NULL, NULL, data, len); |
| ap_pass_brigade(r->output_filters, bb); |
| apr_brigade_cleanup(bb); |
| |
| return DONE; |
| } |
| else if (!md || md->renew_mode == MD_RENEW_MANUAL |
| || (md->cert_file && 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 give others a chance to provide |
| * the answer. |
| */ |
| return DECLINED; |
| } |
| else if (APR_STATUS_IS_ENOENT(rv)) { |
| return HTTP_NOT_FOUND; |
| } |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10081) |
| "loading challenge %s from store", name); |
| return HTTP_INTERNAL_SERVER_ERROR; |
| } |
| } |
| } |
| return DECLINED; |
| } |
| |
| /**************************************************************************************************/ |
| /* Require Https hook */ |
| |
| static int md_require_https_maybe(request_rec *r) |
| { |
| const md_srv_conf_t *sc; |
| apr_uri_t uri; |
| const char *s; |
| 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); |
| } |
| } |
| 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; |
| } |
| } |
| } |
| } |
| } |
| return DECLINED; |
| } |
| |
| /* Runs once per created child process. Perform any process |
| * related initialization here. |
| */ |
| static void md_child_init(apr_pool_t *pool, server_rec *s) |
| { |
| (void)pool; |
| (void)s; |
| } |
| |
| /* Install this module into the apache2 infrastructure. |
| */ |
| static void md_hooks(apr_pool_t *pool) |
| { |
| static const char *const mod_ssl[] = { "mod_ssl.c", NULL}; |
| |
| /* Leave the ssl initialization to mod_ssl or friends. */ |
| md_acme_init(pool, AP_SERVER_BASEVERSION, 0); |
| |
| ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks"); |
| |
| /* Run once after configuration is set, before mod_ssl. |
| */ |
| ap_hook_post_config(md_post_config, NULL, mod_ssl, APR_HOOK_MIDDLE); |
| |
| /* Run once after a child process has been created. |
| */ |
| ap_hook_child_init(md_child_init, NULL, mod_ssl, APR_HOOK_MIDDLE); |
| |
| /* answer challenges *very* early, before any configured authentication may strike */ |
| 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); |
| |
| 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_status_hook, NULL, NULL, APR_HOOK_MIDDLE); |
| ap_hook_handler(md_status_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| |
| #ifdef SSL_CERT_HOOKS |
| (void)md_is_managed; |
| (void)md_get_certificate; |
| APR_OPTIONAL_HOOK(ssl, add_cert_files, md_add_cert_files, NULL, NULL, APR_HOOK_MIDDLE); |
| APR_OPTIONAL_HOOK(ssl, add_fallback_cert_files, md_add_fallback_cert_files, NULL, NULL, APR_HOOK_MIDDLE); |
| APR_OPTIONAL_HOOK(ssl, answer_challenge, md_answer_challenge, NULL, NULL, APR_HOOK_MIDDLE); |
| #else |
| (void)md_add_cert_files; |
| (void)md_add_fallback_cert_files; |
| (void)md_answer_challenge; |
| APR_REGISTER_OPTIONAL_FN(md_is_challenge); |
| APR_REGISTER_OPTIONAL_FN(md_is_managed); |
| APR_REGISTER_OPTIONAL_FN(md_get_certificate); |
| #endif |
| } |
| |