You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2019/10/16 12:31:44 UTC

svn commit: r1868506 [5/6] - in /httpd/httpd/trunk: ./ build/ docs/manual/mod/ modules/md/

Modified: httpd/httpd/trunk/modules/md/mod_md.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/mod_md.c?rev=1868506&r1=1868505&r2=1868506&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/mod_md.c (original)
+++ httpd/httpd/trunk/modules/md/mod_md.c Wed Oct 16 12:31:43 2019
@@ -37,8 +37,10 @@
 #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,6 +49,7 @@
 #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_md_status.h"
 #include "mod_ssl_openssl.h"
@@ -129,7 +132,92 @@ static void init_setups(apr_pool_t *p, s
 }
 
 /**************************************************************************************************/
-/* store & registry setup */
+/* 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[] = {
+    { "renewed", apr_time_from_sec(28 * MD_SECS_PER_DAY) }, /* once per month */
+    { "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 (md_timeperiod_length(&since_last) < min_interim) {
+            /* not enough time has passed since we sent the last notification
+             * for this 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;
+}
+
+/**************************************************************************************************/
+/* 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, 
@@ -150,6 +238,7 @@ static apr_status_t store_file_ev(void *
         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;
@@ -185,19 +274,21 @@ static apr_status_t setup_store(md_store
     
     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;
+        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;
     }
     
-out:
+leave:
     return rv;
 }
 
@@ -245,15 +336,20 @@ static void merge_srv_config(md_t *md, m
     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 {
@@ -266,40 +362,27 @@ static apr_status_t check_coverage(md_t
     }
 }
 
-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(server_rec *s, int port)
 {
     server_addr_rec *sa;
@@ -317,61 +400,94 @@ static int uses_port(server_rec *s, int
     return match;
 }
 
-static apr_status_t detect_supported_ports(md_mod_conf_t *mc, server_rec *s, 
-                                           apr_pool_t *p, int log_level)
+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;
 
-    mc->can_http = 0;
-    mc->can_https = 0;
+    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))) {
-                mc->can_http = 1;
+                can_http = 1;
             }
             else if (sa->port == mc->local_443
                      && (!lr->protocol || !strncmp("http", lr->protocol, 4))) {
-                mc->can_https = 1;
+                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: (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);
+                 "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_https_server(const char *domain, server_rec *base_server)
+static server_rec *get_public_https_server(md_t *md, const char *domain, server_rec *base_server)
 {
     md_srv_conf_t *sc;
     md_mod_conf_t *mc;
     server_rec *s;
     request_rec r;
+    int i;
 
     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;
+    if (!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 && 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)) {
+                    return s;
+                }
+            }
         }
     }
     return NULL;
 }
 
+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;
@@ -379,10 +495,16 @@ static void init_acme_tls_1_domains(md_t
     /* 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);
+    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*);
-        if (NULL == (s = get_https_server(domain, base_server))) {
+        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;
@@ -398,15 +520,13 @@ static void init_acme_tls_1_domains(md_t
 }
 
 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)
+                                       apr_pool_t *p)
 {
-    server_rec *s, *s_https;
+    server_rec *s;
     request_rec r;
     md_srv_conf_t *sc;
-    apr_status_t rv = APR_SUCCESS;
     int i;
-    const char *domain;
-    apr_array_header_t *servers;
+    const char *domain, *uri;
     
     sc = md_config_get(base_server);
 
@@ -414,8 +534,6 @@ static apr_status_t link_md_to_servers(m
      * 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 */
@@ -429,65 +547,15 @@ static apr_status_t link_md_to_servers(m
             if (ap_matches_request_vhost(&r, domain, s->port)) {
                 /* Create a unique md_srv_conf_t record for this server, if there is none yet */
                 sc = md_config_get_unique(s, p);
+                if (!sc->assigned) sc->assigned = apr_array_make(p, 2, sizeof(md_t*));
                 
+                APR_ARRAY_PUSH(sc->assigned, md_t*) = md;
                 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10041)
-                             "Server %s:%d matches md %s (config %s)", 
-                             s->server_hostname, s->port, md->name, sc->name);
+                             "Server %s:%d matches md %s (config %s) for domain %s, "
+                             "has now %d MDs", 
+                             s->server_hostname, s->port, md->name, sc->name,
+                             domain, (int)sc->assigned->nelts);
                 
-                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) {
@@ -496,50 +564,14 @@ static apr_status_t link_md_to_servers(m
                                      "%s: added contact %s", md->name, uri);
                     }
                 }
+                break;
             }
-            
-            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;
+    return APR_SUCCESS;
 }
 
-static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s, 
-                                            apr_pool_t *p, apr_pool_t *ptemp)
+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;
@@ -548,7 +580,7 @@ static apr_status_t link_mds_to_servers(
     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))) {
+        if (APR_SUCCESS != (rv = link_md_to_servers(mc, md, s, p))) {
             goto leave;
         }
     }
@@ -562,7 +594,7 @@ static apr_status_t merge_mds_with_conf(
     md_srv_conf_t *base_conf;
     md_t *md, *omd;
     const char *domain;
-    const md_timeslice_t *ts;
+    md_timeslice_t *ts;
     apr_status_t rv = APR_SUCCESS;
     int i, j;
 
@@ -612,8 +644,6 @@ static apr_status_t merge_mds_with_conf(
             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 "
@@ -635,7 +665,7 @@ static void load_staged_data(md_mod_conf
     
     for (i = 0; i < mc->mds->nelts; ++i) {
         md = APR_ARRAY_IDX(mc->mds, i, md_t *);
-        result = md_result_md_make(p, md);
+        result = md_result_md_make(p, md->name);
         if (APR_SUCCESS == (rv = md_reg_load_staging(mc->reg, md, mc->env, result, p))) {
             ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10068) 
                          "%s: staged set activated", md->name);
@@ -647,39 +677,84 @@ static void load_staged_data(md_mod_conf
     }
 }
 
-static apr_status_t reinit_mds(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+static apr_status_t check_invalid_duplicates(server_rec *base_server)
 {
-    md_t *md; 
-    apr_status_t rv = APR_SUCCESS;
-    int i;
+    server_rec *s;
+    md_srv_conf_t *sc;
     
-    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;
+    ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, base_server, 
+                 "cecking 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;
+        }
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t check_usage(md_mod_conf_t *mc, md_t *md, server_rec *base_server, 
+                                apr_pool_t *p, apr_pool_t *ptemp)
+{
+    server_rec *s;
+    md_srv_conf_t *sc;
+    apr_status_t rv = APR_SUCCESS;
+    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;
+            }
+        }
+    }
+
+    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);
+    }
+    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;
         }
     }
     return rv;
 }
 
-static void init_watched_names(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *ptemp, server_rec *s)
+static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *ptemp, server_rec *s)
 {
-    const md_t *md;
+    md_t *md;
     md_result_t *result;
-    int i;
+    int i, count;
     
     /* Calculate the list of MD names which we need to watch:
      * - all MDs that are used somewhere
      * - all MDs in drive mode 'AUTO' that are not in 'unused_names'
      */
+    count = 0;
     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 = 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 "
@@ -704,8 +779,10 @@ static void init_watched_names(md_mod_co
             }
         }
         
-        APR_ARRAY_PUSH(mc->watched_names, const char *) = md->name; 
+        md->watched = 1;
+        ++count;
     }
+    return count;
 }   
 
 static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
@@ -755,11 +832,21 @@ static apr_status_t md_post_config_befor
         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10072) "setup md registry");
         goto leave;
     }
+    md_reg_set_notify_cb(mc->reg, notify, mc);
 
+    /* renew on 30% remaining /*/
+    rv = md_ocsp_reg_make(&mc->ocsp, p, store, mc->ocsp_renew_window,
+                          AP_SERVER_BASEVERSION, mc->proxy_url);
+    if (APR_SUCCESS != rv) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10196) "setup ocsp registry");
+        goto leave;
+    }
+    md_ocsp_set_notify_cb(mc->ocsp, notify, mc);
+    
     init_ssl();
 
     /* How to bootstrap this module:
-     * 1. find out if we know where http: and https: requests will arrive
+     * 1. find out if we know if http: and/or 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.
@@ -778,25 +865,63 @@ static apr_status_t md_post_config_befor
      * 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;
+    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, ptemp))) goto leave;
+    if (APR_SUCCESS != (rv = link_mds_to_servers(mc, s, p))) goto leave;
     /*4*/
-    if (APR_SUCCESS != (rv = md_reg_sync(mc->reg, p, ptemp, mc->mds))) {
+    if (APR_SUCCESS != (rv = md_reg_sync_start(mc->reg, mc->mds, ptemp))) {
         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);
+leave:
+    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 (dry_run) goto leave;
+    if (!sc || !sc->mc || sc->mc->dry_run) goto leave;
+    mc = sc->mc;
+    
     /*7*/
-    if (APR_SUCCESS != (rv = reinit_mds(mc, s, p))) goto leave;
+    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, md_t *);
+
+        if (APR_SUCCESS != (rv = auto_add_domains(md, s, p))) {
+            goto leave;
+        }
+        init_acme_tls_1_domains(md, s);
+        if (APR_SUCCESS != (rv = check_usage(mc, md, s, p, ptemp))) {
+            goto leave;
+        }
+        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 synching to store", md->name);
+            goto leave;
+        }
+    }
     /*8*/
-    init_watched_names(mc, p, ptemp, s);
+    watched = init_cert_watch_status(mc, p, ptemp, s);
     /*9*/
     md_reg_cleanup_challenges(mc->reg, p, ptemp, mc->mds);
     
@@ -804,18 +929,23 @@ static apr_status_t md_post_config_befor
      * and only staging/challenges may be manipulated */
     md_reg_freeze_domains(mc->reg, mc->mds);
     
-    if (mc->watched_names->nelts > 0) {
+    if (watched) {
         /*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);
+                     "%d out of %d mds need watching", watched, mc->mds->nelts);
     
         md_http_use_implementation(md_curl_get_impl(p));
-        rv = md_start_watching(mc, s, p);
+        rv = md_renew_start_watching(mc, s, p);
     }
     else {
-        ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) "no mds to drive");
+        ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) "no mds to supervise");
     }
+
+    if (!mc->ocsp || md_ocsp_count(mc->ocsp) == 0) goto leave;
+    
+    md_http_use_implementation(md_curl_get_impl(p));
+    rv = md_ocsp_start_watching(mc, s, p);
+    
 leave:
     return rv;
 }
@@ -877,20 +1007,6 @@ static int md_protocol_switch(conn_rec *
 /**************************************************************************************************/
 /* 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)
 {
@@ -938,23 +1054,27 @@ static apr_status_t get_certificate(serv
         return APR_ENOENT;
     }
     
+    assert(sc->mc);
+    reg = sc->mc->reg;
+    assert(reg);
+
+    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;
     }
-    
-    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;
+    else if (sc->assigned->nelts != 1) {
+        if (!fallback) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10042)
+                         "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*);
     
     rv = md_reg_get_cred_files(pkeyfile, pcertfile, reg, MD_SG_DOMAINS, md, p);
     if (APR_STATUS_IS_ENOENT(rv)) {
@@ -990,12 +1110,6 @@ static apr_status_t get_certificate(serv
     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)
@@ -1176,51 +1290,62 @@ static int md_require_https_maybe(reques
 {
     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)) {
+    /* 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 
+        || !opt_ssl_is_https || !r->parsed_uri.path
+        || !strncmp(WELL_KNOWN_PREFIX, r->parsed_uri.path, sizeof(WELL_KNOWN_PREFIX)-1)) {
+        goto declined;
+    }
         
-        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);
-                }
+    host = ap_get_server_name_for_url(r);
+    md = md_get_for_domain(r->server, host);
+    if (!md) goto declined;
+    
+    if (opt_ssl_is_https(r->connection)) {
+        /* Using https:
+         * if 'permanent' and no one else set a HSTS header already, do it */
+        if (md->require_https == MD_REQUIRE_PERMANENT 
+            && 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;
 }
 
@@ -1248,6 +1373,7 @@ static void md_hooks(apr_pool_t *pool)
      * Run again after mod_ssl is done.
      */
     ap_hook_post_config(md_post_config_before_ssl, NULL, mod_ssl, APR_HOOK_MIDDLE);
+    ap_hook_post_config(md_post_config_after_ssl, mod_ssl, NULL, APR_HOOK_MIDDLE);
     
     /* Run once after a child process has been created.
      */
@@ -1263,22 +1389,18 @@ static void md_hooks(apr_pool_t *pool)
 
     /* 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);
+    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);
 
-#ifdef SSL_CERT_HOOKS
-    (void)md_is_managed;
-    (void)md_get_certificate;
+
+#ifndef SSL_CERT_HOOKS
+#error "This version of mod_md requires Apache httpd 2.4.41 or newer."
+#endif
     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
+    APR_OPTIONAL_HOOK(ssl, init_stapling_status, md_ocsp_init_stapling_status, NULL, NULL, APR_HOOK_MIDDLE);
+    APR_OPTIONAL_HOOK(ssl, get_stapling_status, md_ocsp_get_stapling_status, NULL, NULL, APR_HOOK_MIDDLE);
 }
 

Modified: httpd/httpd/trunk/modules/md/mod_md.dsp
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/mod_md.dsp?rev=1868506&r1=1868505&r2=1868506&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/mod_md.dsp (original)
+++ httpd/httpd/trunk/modules/md/mod_md.dsp Wed Oct 16 12:31:43 2019
@@ -113,6 +113,10 @@ SOURCE=./mod_md_drive.c
 # End Source File
 # Begin Source File
 
+SOURCE=./mod_md_ocsp.c
+# End Source File
+# Begin Source File
+
 SOURCE=./mod_md_os.c
 # End Source File
 # Begin Source File
@@ -177,6 +181,10 @@ SOURCE=./md_log.c
 # End Source File
 # Begin Source File
 
+SOURCE=./md_ocsp.c
+# End Source File
+# Begin Source File
+
 SOURCE=./md_reg.c
 # End Source File
 # Begin Source File

Modified: httpd/httpd/trunk/modules/md/mod_md_config.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/mod_md_config.c?rev=1868506&r1=1868505&r2=1868506&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/mod_md_config.c (original)
+++ httpd/httpd/trunk/modules/md/mod_md_config.c Wed Oct 16 12:31:43 2019
@@ -41,25 +41,35 @@
 #define MD_DEFAULT_BASE_DIR "md"
 #endif
 
+static md_timeslice_t def_ocsp_keep_window = {
+    0,
+    MD_TIME_OCSP_KEEP_NORM,
+};
+
+static md_timeslice_t def_ocsp_renew_window = {
+    MD_TIME_LIFE_NORM,
+    MD_TIME_RENEW_WINDOW_DEF,
+};
+
 /* Default settings for the global conf */
 static md_mod_conf_t defmc = {
     NULL,                      /* list of mds */
 #if AP_MODULE_MAGIC_AT_LEAST(20180906, 2)
-    NULL,                      /* base dir by default state-dir-relative */
+    NULL,                      /* base dirm by default state-dir-relative */
 #else
     MD_DEFAULT_BASE_DIR,
 #endif
     NULL,                      /* proxy url for outgoing http */
-    NULL,                      /* md_reg */
+    NULL,                      /* md_reg_t */
+    NULL,                      /* md_ocsp_reg_t */
     80,                        /* local http: port */
     443,                       /* local https: port */
-    0,                         /* can http: */
-    0,                         /* can https: */
+    -1,                        /* can http: */
+    -1,                        /* can https: */
     0,                         /* manage base server */
     MD_HSTS_MAX_AGE_DEFAULT,   /* hsts max-age */
     NULL,                      /* hsts headers */
     NULL,                      /* unused names */
-    NULL,                      /* watched names */
     NULL,                      /* init errors hash */
     NULL,                      /* notify cmd */
     NULL,                      /* message cmd */
@@ -67,6 +77,10 @@ static md_mod_conf_t defmc = {
     0,                         /* dry_run flag */
     1,                         /* server_status_enabled */
     1,                         /* certificate_status_enabled */
+    &def_ocsp_keep_window,     /* default time to keep ocsp responses */
+    &def_ocsp_renew_window,    /* default time to renew ocsp responses */
+    "crt.sh",                  /* default cert checker site name */
+    "https://crt.sh?q=",       /* default cert checker site url */
 };
 
 static md_timeslice_t def_renew_window = {
@@ -94,8 +108,11 @@ static md_srv_conf_t defconf = {
     "ACME",                    /* ca protocol */
     NULL,                      /* ca agreemnent */
     NULL,                      /* ca challenges array */
+    0,                         /* stapling */
+    1,                         /* staple others */
     NULL,                      /* currently defined md */
     NULL,                      /* assigned md, post config */
+    0,                         /* is_ssl, set during mod_ssl post_config */
 };
 
 static md_mod_conf_t *mod_md_config;
@@ -118,7 +135,6 @@ static md_mod_conf_t *md_mod_conf_get(ap
         memcpy(mod_md_config, &defmc, sizeof(*mod_md_config));
         mod_md_config->mds = apr_array_make(pool, 5, sizeof(const md_t *));
         mod_md_config->unused_names = apr_array_make(pool, 5, sizeof(const md_t *));
-        mod_md_config->watched_names = apr_array_make(pool, 5, sizeof(const md_t *));
         mod_md_config->env = apr_table_make(pool, 10);
         mod_md_config->init_errors = apr_hash_make(pool);
          
@@ -143,6 +159,8 @@ static void srv_conf_props_clear(md_srv_
     sc->ca_proto = NULL;
     sc->ca_agreement = NULL;
     sc->ca_challenges = NULL;
+    sc->stapling = DEF_VAL;
+    sc->staple_others = DEF_VAL;
 }
 
 static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
@@ -158,6 +176,8 @@ static void srv_conf_props_copy(md_srv_c
     to->ca_proto = from->ca_proto;
     to->ca_agreement = from->ca_agreement;
     to->ca_challenges = from->ca_challenges;
+    to->stapling = from->stapling;
+    to->staple_others = from->staple_others;
 }
 
 static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t *p)
@@ -173,6 +193,7 @@ static void srv_conf_props_apply(md_t *m
     if (from->ca_proto) md->ca_proto = from->ca_proto;
     if (from->ca_agreement) md->ca_agreement = from->ca_agreement;
     if (from->ca_challenges) md->ca_challenges = apr_array_copy(p, from->ca_challenges);
+    if (from->stapling != DEF_VAL) md->stapling = from->stapling;
 }
 
 void *md_config_create_svr(apr_pool_t *pool, server_rec *s)
@@ -198,7 +219,6 @@ static void *md_config_merge(apr_pool_t
     nsc = (md_srv_conf_t *)apr_pcalloc(pool, sizeof(md_srv_conf_t));
     nsc->name = name;
     nsc->mc = add->mc? add->mc : base->mc;
-    nsc->assigned = add->assigned? add->assigned : base->assigned;
 
     nsc->transitive = (add->transitive != DEF_VAL)? add->transitive : base->transitive;
     nsc->require_https = (add->require_https != MD_REQUIRE_UNSET)? add->require_https : base->require_https;
@@ -213,8 +233,9 @@ static void *md_config_merge(apr_pool_t
     nsc->ca_agreement = add->ca_agreement? add->ca_agreement : base->ca_agreement;
     nsc->ca_challenges = (add->ca_challenges? apr_array_copy(pool, add->ca_challenges) 
                     : (base->ca_challenges? apr_array_copy(pool, base->ca_challenges) : NULL));
+    nsc->stapling = (add->stapling != DEF_VAL)? add->stapling : base->stapling;
+    nsc->staple_others = (add->staple_others != DEF_VAL)? add->staple_others : base->staple_others;
     nsc->current = NULL;
-    nsc->assigned = NULL;
     
     return nsc;
 }
@@ -487,6 +508,30 @@ static const char *md_config_set_must_st
     return set_on_off(&config->must_staple, value, cmd->pool);
 }
 
+static const char *md_config_set_stapling(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_srv_conf_t *config = md_config_get(cmd->server);
+    const char *err;
+
+    (void)dc;
+    if (!inside_md_section(cmd) && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+        return err;
+    }
+    return set_on_off(&config->stapling, value, cmd->pool);
+}
+
+static const char *md_config_set_staple_others(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_srv_conf_t *config = md_config_get(cmd->server);
+    const char *err;
+
+    (void)dc;
+    if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+        return err;
+    }
+    return set_on_off(&config->staple_others, value, cmd->pool);
+}
+
 static const char *md_config_set_base_server(cmd_parms *cmd, void *dc, const char *value)
 {
     md_srv_conf_t *config = md_config_get(cmd->server);
@@ -820,6 +865,69 @@ static const char *md_config_set_certifi
     return set_on_off(&sc->mc->certificate_status_enabled, value, cmd->pool);
 }
 
+static const char *md_config_set_ocsp_keep_window(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_srv_conf_t *sc = md_config_get(cmd->server);
+    const char *err;
+
+    (void)dc;
+    if (!inside_md_section(cmd) && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+        return err;
+    }
+    err = md_timeslice_parse(&sc->mc->ocsp_keep_window, cmd->pool, value, MD_TIME_OCSP_KEEP_NORM);
+    if (err) return apr_psprintf(cmd->pool, "MDStaplingKeepResponse %s", err);
+    return NULL;
+}
+
+static const char *md_config_set_ocsp_renew_window(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_srv_conf_t *sc = md_config_get(cmd->server);
+    const char *err;
+
+    (void)dc;
+    if (!inside_md_section(cmd) && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+        return err;
+    }
+    err = md_timeslice_parse(&sc->mc->ocsp_renew_window, cmd->pool, value, MD_TIME_LIFE_NORM);
+    if (!err && sc->mc->ocsp_renew_window->norm 
+        && (sc->mc->ocsp_renew_window->len >= sc->mc->ocsp_renew_window->norm)) {
+        err = "with a length of 100% or more is not allowed.";
+    }
+    if (err) return apr_psprintf(cmd->pool, "MDStaplingRenewWindow %s", err);
+    return NULL;
+}
+
+static const char *md_config_set_cert_check(cmd_parms *cmd, void *dc, 
+                                            const char *name, const char *url)
+{
+    md_srv_conf_t *sc = md_config_get(cmd->server);
+    const char *err;
+
+    (void)dc;
+    if (!inside_md_section(cmd) && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+        return err;
+    }
+    sc->mc->cert_check_name = name;
+    sc->mc->cert_check_url = url;
+    return NULL;
+}
+
+static const char *md_config_set_activation_delay(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+    md_srv_conf_t *sc = md_config_get(cmd->server);
+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+    apr_interval_time_t delay;
+
+    (void)mconfig;
+    if (err) {
+        return err;
+    }
+    if (md_duration_parse(&delay, arg, "d") != APR_SUCCESS) {
+        return "unrecognized duration format";
+    }
+    apr_table_set(sc->mc->env, MD_KEY_ACTIVATION_DELAY, md_duration_format(cmd->pool, delay));
+    return NULL;
+}
 
 const command_rec md_cmds[] = {
     AP_INIT_TAKE1("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF, 
@@ -860,7 +968,7 @@ const command_rec md_cmds[] = {
     AP_INIT_TAKE1("MDStoreDir", md_config_set_store_dir, NULL, RSRC_CONF, 
                   "the directory for file system storage of managed domain data."),
     AP_INIT_TAKE1("MDRenewWindow", md_config_set_renew_window, NULL, RSRC_CONF, 
-                  "Time length for renewal before certificate expires (defaults to days)"),
+                  "Time length for renewal before certificate expires (defaults to days)."),
     AP_INIT_TAKE1("MDRequireHttps", md_config_set_require_https, NULL, RSRC_CONF, 
                   "Redirect non-secure requests to the https: equivalent."),
     AP_INIT_RAW_ARGS("MDNotifyCmd", md_config_set_notify_cmd, NULL, RSRC_CONF, 
@@ -881,6 +989,18 @@ const command_rec md_cmds[] = {
                   "When less time remains for a certificate, send our/log a warning (defaults to days)"),
     AP_INIT_RAW_ARGS("MDMessageCmd", md_config_set_msg_cmd, NULL, RSRC_CONF, 
                   "Set the command run when a message about a domain is issued."),
+    AP_INIT_TAKE1("MDStapling", md_config_set_stapling, NULL, RSRC_CONF, 
+                  "Enable/Disable OCSP Stapling for this/all Managed Domain(s)."),
+    AP_INIT_TAKE1("MDStapleOthers", md_config_set_staple_others, NULL, RSRC_CONF, 
+                  "Enable/Disable OCSP Stapling for certificates not in Managed Domains."),
+    AP_INIT_TAKE1("MDStaplingKeepResponse", md_config_set_ocsp_keep_window, NULL, RSRC_CONF, 
+                  "The amount of time to keep an OCSP response in the store."),
+    AP_INIT_TAKE1("MDStaplingRenewWindow", md_config_set_ocsp_renew_window, NULL, RSRC_CONF, 
+                  "Time length for renewal before OCSP responses expire (defaults to days)."),
+    AP_INIT_TAKE2("MDCertificateCheck", md_config_set_cert_check, NULL, RSRC_CONF, 
+                  "Set name and URL pattern for a certificate monitoring site."),
+    AP_INIT_TAKE1("MDActivationDelay", md_config_set_activation_delay, NULL, RSRC_CONF, 
+                  "How long to delay activation of new certificates"),
 
     AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
 };
@@ -897,7 +1017,7 @@ apr_status_t md_config_post_config(serve
     if (mc->hsts_max_age > 0) {
         mc->hsts_header = apr_psprintf(p, "max-age=%d", mc->hsts_max_age);
     }
-    
+
 #if AP_MODULE_MAGIC_AT_LEAST(20180906, 2)
     if (mc->base_dir == NULL) {
         mc->base_dir = ap_state_dir_relative(p, MD_DEFAULT_BASE_DIR);
@@ -913,6 +1033,7 @@ static md_srv_conf_t *config_get_int(ser
     ap_assert(sc);
     if (sc->s != s && p) {
         sc = md_config_merge(p, &defconf, sc);
+        sc->s = s;
         sc->name = apr_pstrcat(p, CONF_S_NAME(s), sc->name, NULL);
         sc->mc = md_mod_conf_get(p, 1);
         ap_set_module_config(s->module_config, &md_module, sc);
@@ -961,22 +1082,22 @@ int md_config_geti(const md_srv_conf_t *
     switch (var) {
         case MD_CONFIG_DRIVE_MODE:
             return (sc->renew_mode != DEF_VAL)? sc->renew_mode : defconf.renew_mode;
-        case MD_CONFIG_LOCAL_80:
-            return sc->mc->local_80;
-        case MD_CONFIG_LOCAL_443:
-            return sc->mc->local_443;
         case MD_CONFIG_TRANSITIVE:
             return (sc->transitive != DEF_VAL)? sc->transitive : defconf.transitive;
         case MD_CONFIG_REQUIRE_HTTPS:
             return (sc->require_https != MD_REQUIRE_UNSET)? sc->require_https : defconf.require_https;
         case MD_CONFIG_MUST_STAPLE:
             return (sc->must_staple != DEF_VAL)? sc->must_staple : defconf.must_staple;
+        case MD_CONFIG_STAPLING:
+            return (sc->stapling != DEF_VAL)? sc->stapling : defconf.stapling;
+        case MD_CONFIG_STAPLE_OTHERS:
+            return (sc->staple_others != DEF_VAL)? sc->staple_others : defconf.staple_others;
         default:
             return 0;
     }
 }
 
-void md_config_get_timespan(const md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var)
+void md_config_get_timespan(md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var)
 {
     switch (var) {
         case MD_CONFIG_RENEW_WINDOW:
@@ -990,3 +1111,19 @@ void md_config_get_timespan(const md_tim
     }
 }
 
+const md_t *md_get_for_domain(server_rec *s, const char *domain)
+{
+    md_srv_conf_t *sc;
+    const md_t *md;
+    int i;
+    
+    sc = md_config_get(s);
+    for (i = 0; sc && sc->assigned && i < sc->assigned->nelts; ++i) {
+        md = APR_ARRAY_IDX(sc->assigned, i, const md_t*);
+        if (md_contains(md, domain, 0)) goto leave;
+    }
+    md = NULL;
+leave:
+    return md;
+}
+

Modified: httpd/httpd/trunk/modules/md/mod_md_config.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/mod_md_config.h?rev=1868506&r1=1868505&r2=1868506&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/mod_md_config.h (original)
+++ httpd/httpd/trunk/modules/md/mod_md_config.h Wed Oct 16 12:31:43 2019
@@ -20,6 +20,7 @@
 struct apr_hash_t;
 struct md_store_t;
 struct md_reg_t;
+struct md_ocsp_reg_t;
 struct md_pkey_spec_t;
 
 typedef enum {
@@ -28,8 +29,6 @@ typedef enum {
     MD_CONFIG_BASE_DIR,
     MD_CONFIG_CA_AGREEMENT,
     MD_CONFIG_DRIVE_MODE,
-    MD_CONFIG_LOCAL_80,
-    MD_CONFIG_LOCAL_443,
     MD_CONFIG_RENEW_WINDOW,
     MD_CONFIG_WARN_WINDOW,
     MD_CONFIG_TRANSITIVE,
@@ -38,6 +37,8 @@ typedef enum {
     MD_CONFIG_MUST_STAPLE,
     MD_CONFIG_NOTIFY_CMD,
     MD_CONFIG_MESSGE_CMD,
+    MD_CONFIG_STAPLING,
+    MD_CONFIG_STAPLE_OTHERS,
 } md_config_var_t;
 
 typedef struct md_mod_conf_t md_mod_conf_t;
@@ -45,7 +46,8 @@ struct md_mod_conf_t {
     apr_array_header_t *mds;           /* all md_t* defined in the config, shared */
     const char *base_dir;              /* base dir for store */
     const char *proxy_url;             /* proxy url to use (or NULL) */
-    struct md_reg_t *reg;              /* md registry instance, singleton, shared */
+    struct md_reg_t *reg;              /* md registry instance */
+    struct md_ocsp_reg_t *ocsp;        /* ocsp status registry */
 
     int local_80;                      /* On which port http:80 arrives */
     int local_443;                     /* On which port https:443 arrives */
@@ -55,7 +57,6 @@ struct md_mod_conf_t {
     int hsts_max_age;                  /* max-age of HSTS (rfc6797) header */
     const char *hsts_header;           /* computed HTST header to use or NULL */
     apr_array_header_t *unused_names;  /* post config, names of all MDs not assigned to a vhost */
-    apr_array_header_t *watched_names; /* post config, names of all MDs that we need to watch */
     struct apr_hash_t *init_errors;    /* init errors reported with MD name as key */
 
     const char *notify_cmd;            /* notification command to execute on signup/renew */
@@ -64,6 +65,10 @@ struct md_mod_conf_t {
     int dry_run;                       /* != 0 iff config dry run */
     int server_status_enabled;         /* if module should add to server-status handler */
     int certificate_status_enabled;    /* if module should expose /.httpd/certificate-status */
+    md_timeslice_t *ocsp_keep_window;  /* time that we keep ocsp responses around */
+    md_timeslice_t *ocsp_renew_window; /* time before exp. that we start renewing ocsp resp. */
+    const char *cert_check_name;       /* name of the linked certificate check site */
+    const char *cert_check_url;        /* url "template for" checking a certificate */
 };
 
 typedef struct md_srv_conf_t {
@@ -76,16 +81,20 @@ typedef struct md_srv_conf_t {
     int renew_mode;                    /* mode of obtaining credentials */
     int must_staple;                   /* certificates should set the OCSP Must Staple extension */
     struct md_pkey_spec_t *pkey_spec;  /* specification for generating private keys */
-    const md_timeslice_t *renew_window; /* time before expiration that starts renewal */
-    const md_timeslice_t *warn_window;  /* time before expiration that warning are sent out */
+    md_timeslice_t *renew_window; /* time before expiration that starts renewal */
+    md_timeslice_t *warn_window;  /* time before expiration that warning are sent out */
     
     const char *ca_url;                /* url of CA certificate service */
     const char *ca_proto;              /* protocol used vs CA (e.g. ACME) */
     const char *ca_agreement;          /* accepted agreement uri between CA and user */ 
     struct apr_array_header_t *ca_challenges; /* challenge types configured */
+    
+    int stapling;                      /* OCSP stapling enabled */
+    int staple_others;                 /* Provide OCSP stapling for non-MD certificates */
 
     md_t *current;                     /* md currently defined in <MDomainSet xxx> section */
-    md_t *assigned;                    /* post_config: MD that applies to this server or NULL */
+    struct apr_array_header_t *assigned; /* post_config: MDs that apply to this server */
+    int is_ssl;                        /* SSLEngine is enabled here */
 } md_srv_conf_t;
 
 void *md_config_create_svr(apr_pool_t *pool, server_rec *s);
@@ -106,7 +115,8 @@ md_srv_conf_t *md_config_get_unique(serv
 const char *md_config_gets(const md_srv_conf_t *config, md_config_var_t var);
 int md_config_geti(const md_srv_conf_t *config, md_config_var_t var);
 
-void md_config_get_timespan(const md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var);
+void md_config_get_timespan(md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var);
 
+const md_t *md_get_for_domain(server_rec *s, const char *domain);
 
 #endif /* md_config_h */

Modified: httpd/httpd/trunk/modules/md/mod_md_drive.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/mod_md_drive.c?rev=1868506&r1=1868505&r2=1868506&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/mod_md_drive.c (original)
+++ httpd/httpd/trunk/modules/md/mod_md_drive.c Wed Oct 16 12:31:43 2019
@@ -53,13 +53,13 @@
 /**************************************************************************************************/
 /* watchdog based impl. */
 
-#define MD_WATCHDOG_NAME   "_md_"
+#define MD_RENEW_WATCHDOG_NAME   "_md_renew_"
 
 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;
 
-struct md_drive_ctx {
+struct md_renew_ctx_t {
     apr_pool_t *p;
     server_rec *s;
     md_mod_conf_t *mc;
@@ -68,253 +68,92 @@ struct md_drive_ctx {
     apr_array_header_t *jobs;
 };
 
-typedef struct {
-    apr_pool_t *p;
-    md_job_t *job;
-    md_reg_t *reg;
-    md_result_t *last;
-    apr_time_t last_save;
-} md_job_result_ctx;
-
-static void job_result_update(md_result_t *result, void *data)
-{
-    md_job_result_ctx *ctx = data;
-    apr_time_t now;
-    const char *msg, *sep;
-    
-    if (md_result_cmp(ctx->last, result)) {
-        now = apr_time_now();
-        md_result_assign(ctx->last, result);
-        if (result->activity || result->problem || result->detail) {
-            msg = sep = "";
-            if (result->activity) {
-                msg = apr_psprintf(result->p, "%s", result->activity);
-                sep = ": ";
-            }
-            if (result->detail) {
-                msg = apr_psprintf(result->p, "%s%s%s", msg, sep, result->detail);
-                sep = ", ";
-            }
-            if (result->problem) {
-                msg = apr_psprintf(result->p, "%s%sproblem: %s", msg, sep, result->problem);
-                sep = " ";
-            }
-            md_job_log_append(ctx->job, "progress", NULL, msg);
-
-            if (apr_time_msec(now - ctx->last_save) > 500) {
-                md_job_save(ctx->job, ctx->reg, MD_SG_STAGING, result, ctx->p);
-                ctx->last_save = now;
-            }
-        }
-    }
-}
-
-static void job_result_observation_start(md_job_t *job, md_result_t *result, 
-                                         md_reg_t *reg, apr_pool_t *p)
-{
-    md_job_result_ctx *ctx;
-
-    ctx = apr_pcalloc(p, sizeof(*ctx));
-    ctx->p = p;
-    ctx->job = job;
-    ctx->reg = reg;
-    ctx->last = md_result_md_make(p, APR_SUCCESS);
-    md_result_assign(ctx->last, result);
-    md_result_on_change(result, job_result_update, ctx);
-}
-
-static void job_result_observation_end(md_job_t *job, md_result_t *result)
-{
-    (void)job;
-    md_result_on_change(result, NULL, NULL);
-} 
-
-static apr_time_t calc_err_delay(int err_count)
-{
-    apr_time_t delay = 0;
-    
-    if (err_count > 0) {
-        /* back off duration, depending on the errors we encounter in a row */
-        delay = apr_time_from_sec(5 << (err_count - 1));
-        if (delay > apr_time_from_sec(60*60)) {
-            delay = apr_time_from_sec(60*60);
-        }
-    }
-    return delay;
-}
-
-static apr_status_t send_notification(md_drive_ctx *dctx, md_job_t *job, const md_t *md, 
-                                      const char *reason, md_result_t *result, apr_pool_t *ptemp)
-{
-    const char * const *argv;
-    const char *cmdline;
-    int exit_code;
-    apr_status_t rv = APR_SUCCESS;            
-    
-    if (!strcmp("renewed", reason)) {
-        if (dctx->mc->notify_cmd) {
-            cmdline = apr_psprintf(ptemp, "%s %s", dctx->mc->notify_cmd, md->name); 
-            apr_tokenize_to_argv(cmdline, (char***)&argv, ptemp);
-            rv = md_util_exec(ptemp, argv[0], argv, &exit_code);
-            
-            if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL;
-            if (APR_SUCCESS != rv) {
-                if (!result) result = md_result_make(ptemp, rv);
-                md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10108)), 
-                                         "MDNotifyCmd %s failed with exit code %d.", 
-                                         dctx->mc->notify_cmd, exit_code);
-                md_result_log(result, MD_LOG_ERR);
-                md_job_log_append(job, "notify-error", result->problem, result->detail);
-                goto leave;
-            }
-        }
-        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, dctx->s, APLOGNO(10059) 
-                     "The Managed Domain %s has been setup and changes "
-                     "will be activated on next (graceful) server restart.", md->name);
-    }
-    if (dctx->mc->message_cmd) {
-        cmdline = apr_psprintf(ptemp, "%s %s %s", dctx->mc->message_cmd, reason, md->name); 
-        ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, dctx->s, "Message command: %s", cmdline);
-        apr_tokenize_to_argv(cmdline, (char***)&argv, ptemp);
-        rv = md_util_exec(ptemp, argv[0], argv, &exit_code);
-        
-        if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL;
-        if (APR_SUCCESS != rv) {
-            if (!result) result = md_result_make(ptemp, rv);
-            md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10109)), 
-                                     "MDMessageCmd %s failed with exit code %d.", 
-                                     dctx->mc->notify_cmd, exit_code);
-            md_result_log(result, MD_LOG_ERR);
-            md_job_log_append(job, "message-error", reason, result->detail);
-            goto leave;
-        }
-    }
-leave:
-    return rv;
-}
-
-static void check_expiration(md_drive_ctx *dctx, md_job_t *job, const md_t *md, apr_pool_t *ptemp)
-{
-    md_timeperiod_t since_last;
-    
-    ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, dctx->s, "md(%s): check expiration", md->name);
-    if (!md_reg_should_warn(dctx->mc->reg, md, dctx->p)) return;
-    
-    /* Sends these out at most once per day */
-    since_last.start = md_job_log_get_time_of_latest(job, "message-expiring");
-    since_last.end = apr_time_now();
-
-    if (md_timeperiod_length(&since_last) >= apr_time_from_sec(MD_SECS_PER_DAY)) {
-        ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, dctx->s, 
-                     "md(%s): message expiration warning", md->name);
-        send_notification(dctx, job, md, "expiring", NULL, ptemp);
-    }
-}
-
-static void process_drive_job(md_drive_ctx *dctx, md_job_t *job, apr_pool_t *ptemp)
+static void process_drive_job(md_renew_ctx_t *dctx, md_job_t *job, apr_pool_t *ptemp)
 {
     const md_t *md;
     md_result_t *result;
-    int error_run = 0, fatal_run = 0, save = 0;
     apr_status_t rv;
     
-    md_job_load(job, dctx->mc->reg, MD_SG_STAGING, ptemp);
+    md_job_load(job);
     /* Evaluate again on loaded value. Values will change when watchdog switches child process */
     if (apr_time_now() < job->next_run) return;
     
-    md = md_get_by_name(dctx->mc->mds, job->name);
+    job->next_run = 0;
+    if (job->finished && job->notified) {
+        /* finished and notification handled, nothing to do. */
+        goto leave;
+    }
+    
+    md = md_get_by_name(dctx->mc->mds, job->mdomain);
     AP_DEBUG_ASSERT(md);
 
-    result = md_result_md_make(ptemp, md);
-    if (job->last_result) md_result_assign(result, job->last_result); 
+    result = md_result_md_make(ptemp, md->name);
+    if (job->last_result) md_result_assign(result, job->last_result);
     
     if (md->state == MD_S_MISSING_INFORMATION) {
         /* Missing information, this will not change until configuration
          * is changed and server reloaded. */
-        fatal_run = 1;
+        job->fatal_error = 1;
+        job->next_run = 0;
         goto leave;
     }
     
-    while (md_will_renew_cert(md)) {
-        if (job->finished) {
-            job->next_run = 0;
-            /* Finished jobs might take a while before the results become valid.
-             * If that is in the future, request to run then */
-            if (apr_time_now() < job->valid_from) {
-                job->next_run = job->valid_from;
-            }
-            else if (md_job_log_get_time_of_latest(job, "notified") == 0) {
-                rv = send_notification(dctx, job, md, "renewed", result, ptemp);
-                if (APR_SUCCESS == rv) {
-                    md_job_log_append(job, "notified", NULL, NULL);
-                    save = 1;
-                }
-                else { 
-                    /* we treat this as an error that triggers retries */
-                    error_run = 1;
-                }
-            }
-            goto leave;
-        }
-        
-        if (!md_reg_should_renew(dctx->mc->reg, md, dctx->p)) {
-            ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10053) 
-                         "md(%s): no need to renew yet", job->name);
-            job->next_run = 0;
-            goto leave;
-        }
-
+    if (md_will_renew_cert(md)) {
         /* Renew the MDs credentials in a STAGING area. Might be invoked repeatedly 
          * without discarding previous/intermediate results.
          * Only returns SUCCESS when the renewal is complete, e.g. STAGING as a
          * complete set of new credentials.
          */
         ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10052) 
-                     "md(%s): state=%d, driving", job->name, md->state);
-        md_job_log_append(job, "renewal-start", NULL, NULL);
-        /* observe result changes and persist them with limited frequency */
-        job_result_observation_start(job, result, dctx->mc->reg, ptemp);
-        
+                     "md(%s): state=%d, driving", job->mdomain, md->state);
+
+        if (!md_reg_should_renew(dctx->mc->reg, md, dctx->p)) {
+            ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10053) 
+                         "md(%s): no need to renew", job->mdomain);
+            goto expiry;
+        }
+    
+        md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg)); 
         md_reg_renew(dctx->mc->reg, md, dctx->mc->env, 0, result, ptemp);
+        md_job_end_run(job, result);
         
-        job_result_observation_end(job, result);
-        if (APR_SUCCESS != result->status) {
+        if (APR_SUCCESS == result->status) {
+            /* Finished jobs might take a while before the results become valid.
+             * If that is in the future, request to run then */
+            if (apr_time_now() < result->ready_at) {
+                md_job_retry_at(job, result->ready_at);
+                goto leave;
+            }
+            
+            if (!job->notified) md_job_notify(job, "renewed", result);
+        }
+        else {
             ap_log_error( APLOG_MARK, APLOG_ERR, result->status, dctx->s, APLOGNO(10056) 
-                         "processing %s: %s", job->name, result->detail);
-            error_run = 1;
+                         "processing %s: %s", job->mdomain, result->detail);
             md_job_log_append(job, "renewal-error", result->problem, result->detail);
-            send_notification(dctx, job, md, "errored", result, ptemp);
-            goto leave;
+            md_job_holler(job, "errored");
+            ap_log_error(APLOG_MARK, APLOG_INFO, 0, dctx->s, APLOGNO(10057) 
+                         "%s: encountered error for the %d. time, next run in %s",
+                         job->mdomain, job->error_runs, 
+                         md_duration_print(ptemp, job->next_run - apr_time_now()));
         }
-        
-        job->finished = 1;
-        job->valid_from = result->ready_at;
-        job->error_runs = 0;
-        md_job_log_append(job, "renewal-finish", NULL, NULL);
-        save = 1;
     }
-    
-leave:
-    if (!job->finished) {
-        check_expiration(dctx, job, md, ptemp);
-    }
-    
-    if (fatal_run) {
-        save = 1;
-        job->next_run = 0;
+
+expiry:
+    if (!job->finished && md_reg_should_warn(dctx->mc->reg, md, dctx->p)) {
+        ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, dctx->s,
+                     "md(%s): warn about expiration", md->name);
+        md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg));
+        if (APR_SUCCESS == md_job_notify(job, "expiring", result)) {
+            md_result_set(result, APR_SUCCESS, NULL);
+        }
+        md_job_end_run(job, result);
     }
-    if (error_run) {
-        ++job->error_runs;
-        save = 1;
-        job->next_run = apr_time_now() + calc_err_delay(job->error_runs);
-        ap_log_error(APLOG_MARK, APLOG_INFO, 0, dctx->s, APLOGNO(10057) 
-                     "%s: encountered error for the %d. time, next run in %s",
-                     job->name, job->error_runs, 
-                     md_duration_print(ptemp, job->next_run - apr_time_now()));
-    }
-    if (save) {
-        apr_status_t rv2 = md_job_save(job, dctx->mc->reg, MD_SG_STAGING, result, ptemp);
-        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv2, dctx->s, "%s: saving job props", job->name);
+
+leave:
+    if (job->dirty) {
+        rv = md_job_save(job, result, ptemp);
+        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, dctx->s, "%s: saving job props", job->mdomain);
     }
 }
 
@@ -337,7 +176,7 @@ static apr_time_t next_run_default(void)
 
 static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
 {
-    md_drive_ctx *dctx = baton;
+    md_renew_ctx_t *dctx = baton;
     md_job_t *job;
     apr_time_t next_run, wait_time;
     int i;
@@ -390,19 +229,18 @@ static apr_status_t run_watchdog(int sta
     return APR_SUCCESS;
 }
 
-apr_status_t md_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+apr_status_t md_renew_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
 {
     apr_allocator_t *allocator;
-    md_drive_ctx *dctx;
+    md_renew_ctx_t *dctx;
     apr_pool_t *dctxp;
     apr_status_t rv;
-    const char *name;
     md_t *md;
     md_job_t *job;
     int i;
     
     /* We use mod_watchdog to run a single thread in one of the child processes
-     * to monitor the MDs in mc->watched_names, using the const data in the list
+     * to monitor the MDs marked as watched, using the const data in the list
      * mc->mds of our MD structures.
      *
      * The data in mc cannot be changed, as we may spawn copies in new child processes
@@ -438,29 +276,28 @@ apr_status_t md_start_watching(md_mod_co
     apr_allocator_max_free_set(allocator, 1);
     rv = apr_pool_create_ex(&dctxp, p, NULL, allocator);
     if (rv != APR_SUCCESS) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10062) "md_drive_ctx: create pool");
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10062) "md_renew_watchdog: create pool");
         return rv;
     }
     apr_allocator_owner_set(allocator, dctxp);
-    apr_pool_tag(dctxp, "md_drive_ctx");
+    apr_pool_tag(dctxp, "md_renew_watchdog");
 
     dctx = apr_pcalloc(dctxp, sizeof(*dctx));
     dctx->p = dctxp;
     dctx->s = s;
     dctx->mc = mc;
     
-    dctx->jobs = apr_array_make(dctx->p, mc->watched_names->nelts, sizeof(md_job_t *));
-    for (i = 0; i < mc->watched_names->nelts; ++i) {
-        name = APR_ARRAY_IDX(mc->watched_names, i, const char *);
-        md = md_get_by_name(mc->mds, name);
-        if (!md) continue;
+    dctx->jobs = apr_array_make(dctx->p, mc->mds->nelts, sizeof(md_job_t *));
+    for (i = 0; i < mc->mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mc->mds, i, md_t*);
+        if (!md || !md->watched) continue;
         
-        job = md_job_make(p, md->name);
+        job = md_reg_job_make(mc->reg, md->name, p);
         APR_ARRAY_PUSH(dctx->jobs, md_job_t*) = job;
         ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, dctx->s,  
-                     "md(%s): state=%d, created drive job", name, md->state);
+                     "md(%s): state=%d, created drive job", md->name, md->state);
         
-        md_job_load(job, mc->reg, MD_SG_STAGING, dctx->p);
+        md_job_load(job);
         if (job->error_runs) {
             /* Server has just restarted. If we encounter an MD job with errors
              * on a previous driving, we purge its STAGING area.
@@ -470,7 +307,7 @@ apr_status_t md_start_watching(md_mod_co
              */
             ap_log_error( APLOG_MARK, APLOG_NOTICE, 0, dctx->s, APLOGNO(10064) 
                          "md(%s): previous drive job showed %d errors, purging STAGING "
-                         "area to reset.", name, job->error_runs);
+                         "area to reset.", md->name, job->error_runs);
             md_store_purge(md_reg_store_get(dctx->mc->reg), p, MD_SG_STAGING, md->name);
             md_store_purge(md_reg_store_get(dctx->mc->reg), p, MD_SG_CHALLENGES, md->name);
             job->error_runs = 0;
@@ -484,13 +321,13 @@ apr_status_t md_start_watching(md_mod_co
         return APR_SUCCESS;
     }
     
-    if (APR_SUCCESS != (rv = wd_get_instance(&dctx->watchdog, MD_WATCHDOG_NAME, 0, 1, dctx->p))) {
+    if (APR_SUCCESS != (rv = wd_get_instance(&dctx->watchdog, MD_RENEW_WATCHDOG_NAME, 0, 1, dctx->p))) {
         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10066) 
-                     "create md watchdog(%s)", MD_WATCHDOG_NAME);
+                     "create md renew watchdog(%s)", MD_RENEW_WATCHDOG_NAME);
         return rv;
     }
     rv = wd_register_callback(dctx->watchdog, 0, dctx, run_watchdog);
     ap_log_error(APLOG_MARK, rv? APLOG_CRIT : APLOG_DEBUG, rv, s, APLOGNO(10067) 
-                 "register md watchdog(%s)", MD_WATCHDOG_NAME);
+                 "register md renew watchdog(%s)", MD_RENEW_WATCHDOG_NAME);
     return rv;
 }

Modified: httpd/httpd/trunk/modules/md/mod_md_drive.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/mod_md_drive.h?rev=1868506&r1=1868505&r2=1868506&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/mod_md_drive.h (original)
+++ httpd/httpd/trunk/modules/md/mod_md_drive.h Wed Oct 16 12:31:43 2019
@@ -20,14 +20,14 @@
 struct md_mod_conf_t;
 struct md_reg_t;
 
-typedef struct md_drive_ctx md_drive_ctx;
+typedef struct md_renew_ctx_t md_renew_ctx_t;
 
 int md_will_renew_cert(const md_t *md);
 
 /**
- * Start driving the certificate procotol for the domains mentioned in mc->watched_names.
+ * Start driving the certificate renewal for MDs marked with watched.
  */
-apr_status_t md_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr_pool_t *p);
+apr_status_t md_renew_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr_pool_t *p);
 
 
 

Added: httpd/httpd/trunk/modules/md/mod_md_ocsp.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/mod_md_ocsp.c?rev=1868506&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/md/mod_md_ocsp.c (added)
+++ httpd/httpd/trunk/modules/md/mod_md_ocsp.c Wed Oct 16 12:31:43 2019
@@ -0,0 +1,250 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ 
+#include <assert.h>
+#include <apr_optional.h>
+#include <apr_time.h>
+#include <apr_date.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+
+#include "mod_watchdog.h"
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_http.h"
+#include "md_json.h"
+#include "md_ocsp.h"
+#include "md_store.h"
+#include "md_log.h"
+#include "md_reg.h"
+#include "md_time.h"
+#include "md_util.h"
+
+#include "mod_md.h"
+#include "mod_md_config.h"
+#include "mod_md_private.h"
+#include "mod_md_ocsp.h"
+
+static int staple_here(md_srv_conf_t *sc) 
+{
+    if (!sc || !sc->mc->ocsp) return 0;
+    if (sc->assigned 
+        && sc->assigned->nelts == 1
+        && APR_ARRAY_IDX(sc->assigned, 0, const md_t*)->stapling) return 1;
+    return (md_config_geti(sc, MD_CONFIG_STAPLING) 
+            && md_config_geti(sc, MD_CONFIG_STAPLE_OTHERS));
+}
+
+apr_status_t md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p, 
+                                          X509 *cert, X509 *issuer)
+{
+    md_srv_conf_t *sc;
+    const md_t *md;
+    apr_status_t rv;
+
+    sc = md_config_get(s);
+    if (!staple_here(sc)) goto declined;
+
+    md = ((sc->assigned || sc->assigned->nelts == 1)?
+          APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL);
+    rv = md_ocsp_prime(sc->mc->ocsp, md_cert_wrap(p, cert), 
+                       md_cert_wrap(p, issuer), md);
+    ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, s, "init stapling for: %s", 
+                 md? md->name : s->server_hostname);
+    if (APR_SUCCESS == rv) {
+        return OK;
+    }
+declined:
+    return DECLINED;
+}
+
+apr_status_t md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen, 
+                                         conn_rec *c, server_rec *s, X509 *cert)
+{
+    md_srv_conf_t *sc;
+    const md_t *md;
+    apr_status_t rv;
+    
+    sc = md_config_get(s);
+    if (!staple_here(sc)) goto declined;
+    
+    md = ((sc->assigned || sc->assigned->nelts == 1)?
+          APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "get stapling for: %s", 
+                  md? md->name : s->server_hostname);
+    rv = md_ocsp_get_status(pder, pderlen, sc->mc->ocsp, 
+                            md_cert_wrap(c->pool, cert), c->pool, md);
+    if (APR_STATUS_IS_ENOENT(rv)) goto declined;
+    return rv;
+    
+declined:
+    return DECLINED;
+}
+                          
+/**************************************************************************************************/
+/* watchdog based impl. */
+
+#define MD_OCSP_WATCHDOG_NAME   "_md_ocsp_"
+
+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_ocsp_ctx_t md_ocsp_ctx_t;
+
+struct md_ocsp_ctx_t {
+    apr_pool_t *p;
+    server_rec *s;
+    md_mod_conf_t *mc;
+    ap_watchdog_t *watchdog;
+};
+
+static apr_time_t next_run_default(void)
+{
+    /* we'd like to run at least hourly */
+    return apr_time_now() + apr_time_from_sec(MD_SECS_PER_HOUR);
+}
+
+static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
+{
+    md_ocsp_ctx_t *octx = baton;
+    apr_time_t next_run, wait_time;
+    
+    /* mod_watchdog invoked us as a single thread inside the whole server (on this machine).
+     * This might be a repeated run inside the same child (mod_watchdog keeps affinity as
+     * long as the child lives) or another/new child.
+     */
+    switch (state) {
+        case AP_WATCHDOG_STATE_STARTING:
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10197)
+                         "md ocsp watchdog start, ocsp stapling %d certificates", 
+                         (int)md_ocsp_count(octx->mc->ocsp));
+            break;
+            
+        case AP_WATCHDOG_STATE_RUNNING:
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10198)
+                         "md ocsp watchdog run, ocsp stapling %d certificates", 
+                         (int)md_ocsp_count(octx->mc->ocsp));
+                         
+            /* Process all drive jobs. They will update their next_run property
+             * and we schedule ourself at the earliest of all. A job may specify 0
+             * as next_run to indicate that it wants to participate in the normal
+             * regular runs. */
+            next_run = next_run_default();
+            
+            md_ocsp_renew(octx->mc->ocsp, octx->p, ptemp, &next_run);
+            
+            wait_time = next_run - apr_time_now();
+            if (APLOGdebug(octx->s)) {
+                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10199)
+                             "md ocsp watchdog next run in %s", 
+                             md_duration_print(ptemp, wait_time));
+            }
+            wd_set_interval(octx->watchdog, wait_time, octx, run_watchdog);
+            break;
+            
+        case AP_WATCHDOG_STATE_STOPPING:
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10200)
+                         "md ocsp watchdog stopping");
+            break;
+    }
+    
+    return APR_SUCCESS;
+}
+
+static apr_status_t ocsp_remove_old_responses(md_mod_conf_t *mc, apr_pool_t *p)
+{
+    md_timeperiod_t keep_norm, keep;
+    
+    keep_norm.end = apr_time_now();
+    keep_norm.start = keep_norm.end - MD_TIME_OCSP_KEEP_NORM;
+    keep = md_timeperiod_slice_before_end(&keep_norm, mc->ocsp_keep_window);
+    /* remove any ocsp response older than keep.start */
+    return md_ocsp_remove_responses_older_than(mc->ocsp, p, keep.start);
+}
+
+apr_status_t md_ocsp_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+{
+    apr_allocator_t *allocator;
+    md_ocsp_ctx_t *octx;
+    apr_pool_t *octxp;
+    apr_status_t rv;
+    
+    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(10201) 
+                     "mod_watchdog is required for OCSP stapling");
+        return APR_EGENERAL;
+    }
+    
+    /* We want our own pool with own allocator to keep data across watchdog invocations.
+     * Since we'll run in a single watchdog thread, using our own allocator will prevent 
+     * any confusion in the parent pool. */
+    apr_allocator_create(&allocator);
+    apr_allocator_max_free_set(allocator, 1);
+    rv = apr_pool_create_ex(&octxp, p, NULL, allocator);
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10205) "md_ocsp_watchdog: create pool");
+        return rv;
+    }
+    apr_allocator_owner_set(allocator, octxp);
+    apr_pool_tag(octxp, "md_ocsp_watchdog");
+
+    octx = apr_pcalloc(octxp, sizeof(*octx));
+    octx->p = octxp;
+    octx->s = s;
+    octx->mc = mc;
+    
+    /* Time for some house keeping, before the server goes live (again):
+     * - we store OCSP responses for each certificate individually by its SHA-1 id
+     * - this means, as long as certificate do not change, the number of response
+     *   files remains stable.
+     * - But when a certificate changes (is replaced), the response is obsolete
+     * - we do not get notified when a certificate is no longer used. An admin
+     *   might just reconfigure or change the content of a file (backup/restore etc.)
+     * - also, certificates might be added by some openssl config commands or other
+     *   modules that we do not immediately see right at startup. We cannot assume
+     *   that any OCSP response we cannot relate to a certificate RIGHT NOW, is no
+     *   longer needed.
+     * - since the response files are relatively small, we have no problem with
+     *   keeping them around for a while. We just do not want an ever growing store. 
+     * - The simplest and effective way seems to be to just remove files older
+     *   a certain amount of time. Take a 7 day default and let the admin configure
+     *   it for very special setups. 
+     */ 
+    ocsp_remove_old_responses(mc, octx->p);
+    
+    rv = wd_get_instance(&octx->watchdog, MD_OCSP_WATCHDOG_NAME, 0, 1, octx->p);
+    if (APR_SUCCESS != rv) {
+        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10202) 
+                     "create md ocsp watchdog(%s)", MD_OCSP_WATCHDOG_NAME);
+        return rv;
+    }
+    rv = wd_register_callback(octx->watchdog, 0, octx, run_watchdog);
+    ap_log_error(APLOG_MARK, rv? APLOG_CRIT : APLOG_DEBUG, rv, s, APLOGNO(10203) 
+                 "register md ocsp watchdog(%s)", MD_OCSP_WATCHDOG_NAME);
+    return rv;
+}
+
+
+