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 [3/6] - in /httpd/httpd/trunk: ./ build/ docs/manual/mod/ modules/md/

Added: httpd/httpd/trunk/modules/md/md_ocsp.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_ocsp.c?rev=1868506&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/md/md_ocsp.c (added)
+++ httpd/httpd/trunk/modules/md/md_ocsp.c Wed Oct 16 12:31:43 2019
@@ -0,0 +1,1037 @@
+/* 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 <stdio.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_hash.h>
+#include <apr_time.h>
+#include <apr_date.h>
+#include <apr_strings.h>
+#include <apr_thread_mutex.h>
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/ocsp.h>
+#include <openssl/pem.h>
+#include <openssl/x509v3.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_log.h"
+#include "md_http.h"
+#include "md_json.h"
+#include "md_result.h"
+#include "md_status.h"
+#include "md_store.h"
+#include "md_util.h"
+#include "md_ocsp.h"
+
+#define MD_OCSP_ID_LENGTH   SHA_DIGEST_LENGTH
+   
+struct md_ocsp_reg_t {
+    apr_pool_t *p;
+    md_store_t *store;
+    const char *user_agent;
+    const char *proxy_url;
+    apr_hash_t *hash;
+    apr_thread_mutex_t *mutex;
+    md_timeslice_t renew_window;
+    md_job_notify_cb *notify;
+    void *notify_ctx;
+};
+
+typedef struct md_ocsp_status_t md_ocsp_status_t; 
+struct md_ocsp_status_t {
+    md_data_t id;
+    const char *hexid;
+    const char *hex_sha256;
+    OCSP_CERTID *certid;
+    const char *responder_url;
+    
+    apr_time_t next_run;      /* when the responder shall be asked again */
+    int errors;               /* consecutive failed attempts */
+
+    md_ocsp_cert_stat_t resp_stat;
+    md_data_t resp_der;
+    md_timeperiod_t resp_valid;
+    
+    md_data_t req_der;
+    OCSP_REQUEST *ocsp_req;
+    md_ocsp_reg_t *reg;
+
+    const char *md_name;
+    const char *file_name;
+    
+    apr_time_t resp_mtime;
+    apr_time_t resp_last_check;
+};
+
+const char *md_ocsp_cert_stat_name(md_ocsp_cert_stat_t stat)
+{
+    switch (stat) {
+        case MD_OCSP_CERT_ST_GOOD: return "good";
+        case MD_OCSP_CERT_ST_REVOKED: return "revoked";
+        default: return "unknown";
+    }
+}
+
+md_ocsp_cert_stat_t md_ocsp_cert_stat_value(const char *name)
+{
+    if (name && !strcmp("good", name)) return MD_OCSP_CERT_ST_GOOD;
+    if (name && !strcmp("revoked", name)) return MD_OCSP_CERT_ST_REVOKED;
+    return MD_OCSP_CERT_ST_UNKNOWN;
+}
+
+static apr_status_t init_cert_id(md_data_t *data, const md_cert_t *cert)
+{
+    X509 *x = md_cert_get_X509(cert);
+    unsigned int ulen = 0;
+    
+    assert(data->len == SHA_DIGEST_LENGTH);
+    if (X509_digest(x, EVP_sha1(), (unsigned char*)data->data, &ulen) != 1) {
+        return APR_EGENERAL;
+    }
+    data->len = ulen;
+    return APR_SUCCESS;
+}
+
+static void ostat_req_cleanup(md_ocsp_status_t *ostat)
+{
+    if (ostat->ocsp_req) {
+        OCSP_REQUEST_free(ostat->ocsp_req);
+        ostat->ocsp_req = NULL;
+    }
+    if (ostat->req_der.data) {
+        OPENSSL_free((void*)ostat->req_der.data);
+        ostat->req_der.data = NULL;
+        ostat->req_der.len = 0;
+    }
+}
+
+static int ostat_cleanup(void *ctx, const void *key, apr_ssize_t klen, const void *val)
+{
+    md_ocsp_reg_t *reg = ctx;
+    md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+    
+    (void)reg;
+    (void)key;
+    (void)klen;
+    ostat_req_cleanup(ostat);
+    if (ostat->certid) {
+        OCSP_CERTID_free(ostat->certid);
+        ostat->certid = NULL;
+    }
+    if (ostat->resp_der.data) {
+        OPENSSL_free((void*)ostat->resp_der.data);
+        ostat->resp_der.data = NULL;
+        ostat->resp_der.len = 0;
+    }
+    return 1;
+}
+
+static int ostat_should_renew(md_ocsp_status_t *ostat) 
+{
+    md_timeperiod_t renewal;
+    
+    renewal = md_timeperiod_slice_before_end(&ostat->resp_valid, &ostat->reg->renew_window);
+    return md_timeperiod_has_started(&renewal, apr_time_now());
+}  
+
+static apr_status_t ostat_set(md_ocsp_status_t *ostat, md_ocsp_cert_stat_t stat,
+                              md_data_t *der, md_timeperiod_t *valid, apr_time_t mtime)
+{
+    apr_status_t rv = APR_SUCCESS;
+    char *s = (char*)der->data;
+    
+    if (der->len) {
+        s = OPENSSL_malloc(der->len);
+        if (!s) {
+            rv = APR_ENOMEM;
+            goto leave;
+        }
+        memcpy((char*)s, der->data, der->len);
+    }
+ 
+    if (ostat->resp_der.data) {
+        OPENSSL_free((void*)ostat->resp_der.data);
+        ostat->resp_der.data = NULL;
+        ostat->resp_der.len = 0;
+    }
+    
+    ostat->resp_stat = stat;
+    ostat->resp_der.data = s;
+    ostat->resp_der.len = der->len;
+    ostat->resp_valid = *valid;
+    ostat->resp_mtime = mtime;
+    
+    ostat->errors = 0;
+    ostat->next_run = md_timeperiod_slice_before_end(
+        &ostat->resp_valid, &ostat->reg->renew_window).start;
+    
+leave:
+    return rv;
+}
+
+static apr_status_t ostat_from_json(md_ocsp_cert_stat_t *pstat, 
+                                    md_data_t *resp_der, md_timeperiod_t *resp_valid, 
+                                    md_json_t *json, apr_pool_t *p)
+{
+    const char *s;
+    md_timeperiod_t valid;
+    apr_status_t rv = APR_ENOENT;
+    
+    memset(resp_der, 0, sizeof(*resp_der));
+    memset(resp_valid, 0, sizeof(*resp_valid));
+    s = md_json_dups(p, json, MD_KEY_VALID, MD_KEY_FROM, NULL);
+    if (s && *s) valid.start = apr_date_parse_rfc(s);
+    s = md_json_dups(p, json, MD_KEY_VALID, MD_KEY_UNTIL, NULL);
+    if (s && *s) valid.end = apr_date_parse_rfc(s);
+    s = md_json_dups(p, json, MD_KEY_RESPONSE, NULL);
+    if (!s || !*s) goto leave;
+    md_util_base64url_decode(resp_der, s, p);
+    *pstat = md_ocsp_cert_stat_value(md_json_gets(json, MD_KEY_STATUS, NULL));
+    *resp_valid = valid;
+    rv = APR_SUCCESS;
+leave:
+    return rv;
+}
+
+static void ostat_to_json(md_json_t *json, md_ocsp_cert_stat_t stat,
+                          const md_data_t *resp_der, const md_timeperiod_t *resp_valid, 
+                          apr_pool_t *p)
+{
+    const char *s = NULL;
+
+    if (resp_der->len > 0) {
+        md_json_sets(md_util_base64url_encode(resp_der, p), json, MD_KEY_RESPONSE, NULL);
+        s = md_ocsp_cert_stat_name(stat);
+        if (s) md_json_sets(s, json, MD_KEY_STATUS, NULL);
+        md_json_set_timeperiod(resp_valid, json, MD_KEY_VALID, NULL);
+    }
+}
+
+static apr_status_t ocsp_status_refresh(md_ocsp_status_t *ostat, apr_pool_t *ptemp)
+{
+    md_store_t *store = ostat->reg->store;
+    md_json_t *jprops;
+    apr_time_t mtime;
+    apr_status_t rv = APR_EAGAIN;
+    md_data_t resp_der;
+    md_timeperiod_t resp_valid;
+    md_ocsp_cert_stat_t resp_stat;
+    /* Check if the store holds a newer response than the one we have */
+    mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp);
+    if (mtime <= ostat->resp_mtime) goto leave;
+    rv = md_store_load_json(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, &jprops, ptemp);
+    if (APR_SUCCESS != rv) goto leave;
+    rv = ostat_from_json(&resp_stat, &resp_der, &resp_valid, jprops, ptemp);
+    if (APR_SUCCESS != rv) goto leave;
+    rv = ostat_set(ostat, resp_stat, &resp_der, &resp_valid, mtime);
+    if (APR_SUCCESS != rv) goto leave;
+leave:
+    return rv;
+}
+
+
+static apr_status_t ocsp_status_save(md_ocsp_cert_stat_t stat, const md_data_t *resp_der, 
+                                     const md_timeperiod_t *resp_valid,
+                                     md_ocsp_status_t *ostat, apr_pool_t *ptemp)
+{
+    md_store_t *store = ostat->reg->store;
+    md_json_t *jprops;
+    apr_time_t mtime;
+    apr_status_t rv;
+    
+    jprops = md_json_create(ptemp);
+    ostat_to_json(jprops, stat, resp_der, resp_valid, ptemp);
+    rv = md_store_save_json(store, ptemp, MD_SG_OCSP, ostat->md_name, ostat->file_name, jprops, 0);
+    if (APR_SUCCESS != rv) goto leave;
+    mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp);
+    if (mtime) ostat->resp_mtime = mtime;
+leave:
+    return rv;
+}
+
+static apr_status_t ocsp_reg_cleanup(void *data)
+{
+    md_ocsp_reg_t *reg = data;
+    
+    /* free all OpenSSL structures that we hold */
+    apr_hash_do(ostat_cleanup, reg, reg->hash);
+    return APR_SUCCESS;
+}
+
+apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p, md_store_t *store, 
+                              const md_timeslice_t *renew_window,
+                              const char *user_agent, const char *proxy_url)
+{
+    md_ocsp_reg_t *reg;
+    apr_status_t rv = APR_SUCCESS;
+    
+    reg = apr_palloc(p, sizeof(*reg));
+    if (!reg) {
+        rv = APR_ENOMEM;
+        goto leave;
+    }
+    reg->p = p;
+    reg->store = store;
+    reg->user_agent = user_agent;
+    reg->proxy_url = proxy_url;
+    reg->hash = apr_hash_make(p);
+    reg->renew_window = *renew_window;
+    
+    rv = apr_thread_mutex_create(&reg->mutex, APR_THREAD_MUTEX_NESTED, p);
+    if (APR_SUCCESS != rv) goto leave;
+
+    apr_pool_cleanup_register(p, reg, ocsp_reg_cleanup, apr_pool_cleanup_null);
+leave:
+    *preg = (APR_SUCCESS == rv)? reg : NULL;
+    return rv;
+}
+
+apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issuer, const md_t *md)
+{
+    char iddata[MD_OCSP_ID_LENGTH];
+    md_ocsp_status_t *ostat;
+    STACK_OF(OPENSSL_STRING) *ssk = NULL;
+    const char *name, *s;
+    md_data_t id;
+    apr_status_t rv;
+    
+    /* Called during post_config. no mutex protection needed */
+    name = md? md->name : MD_OTHER;
+    id.data = iddata; id.len = sizeof(iddata);
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, reg->p, 
+                  "md[%s]: priming OCSP status", name);
+    rv = init_cert_id(&id, cert);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len);
+    if (ostat) goto leave; /* already seen it, cert is used in >1 server_rec */
+    
+    ostat = apr_pcalloc(reg->p, sizeof(*ostat));
+    md_data_assign_pcopy(&ostat->id, &id, reg->p);
+    ostat->reg = reg;
+    ostat->md_name = name;
+    md_data_to_hex(&ostat->hexid, 0, reg->p, &ostat->id);
+    ostat->file_name = apr_psprintf(reg->p, "ocsp-%s.json", ostat->hexid);
+    rv = md_cert_to_sha256_fingerprint(&ostat->hex_sha256, cert, reg->p); 
+    if (APR_SUCCESS != rv) goto leave;
+
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                  "md[%s]: getting ocsp responder from cert", name);
+    ssk = X509_get1_ocsp(md_cert_get_X509(cert));
+    if (!ssk) {
+        rv = APR_ENOENT;
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p, 
+                      "md[%s]: certificate with serial %s has not OCSP responder URL", 
+                      name, md_cert_get_serial_number(cert, reg->p));
+        goto leave;
+    }
+    s = sk_OPENSSL_STRING_value(ssk, 0);
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                  "md[%s]: ocsp responder found '%s'", name, s);
+    ostat->responder_url = apr_pstrdup(reg->p, s);
+    X509_email_free(ssk);
+
+    ostat->certid = OCSP_cert_to_id(NULL, md_cert_get_X509(cert), md_cert_get_X509(issuer));
+    if (!ostat->certid) {
+        rv = APR_EGENERAL;
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p, 
+                      "md[%s]: unable to create OCSP certid for certificate with serial %s", 
+                      name, md_cert_get_serial_number(cert, reg->p));
+        goto leave;
+    }
+    
+    /* See, if we have something in store */
+    ocsp_status_refresh(ostat, reg->p);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, reg->p, 
+                  "md[%s]: adding ocsp info (responder=%s)", 
+                  name, ostat->responder_url);
+    apr_hash_set(reg->hash, ostat->id.data, (apr_ssize_t)ostat->id.len, ostat);
+    rv = APR_SUCCESS;
+leave:
+    return rv;
+}
+
+apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen,
+                                md_ocsp_reg_t *reg, const md_cert_t *cert,
+                                apr_pool_t *p, const md_t *md)
+{
+    char iddata[MD_OCSP_ID_LENGTH];
+    md_ocsp_status_t *ostat;
+    const char *name;
+    apr_status_t rv;
+    int locked = 0;
+    md_data_t id;
+    
+    (void)p;
+    (void)md;
+    id.data = iddata; id.len = sizeof(iddata);
+    *pder = NULL;
+    *pderlen = 0;
+    name = md? md->name : MD_OTHER;
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                  "md[%s]: OCSP, get_status", name);
+    rv = init_cert_id(&id, cert);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len);
+    if (!ostat) {
+        rv = APR_ENOENT;
+        goto leave;
+    }
+    
+    /* While the ostat instance itself always exists, the response data it holds
+     * may vary over time and we need locked access to make a copy. */
+    apr_thread_mutex_lock(reg->mutex);
+    locked = 1;
+    
+    if (ostat->resp_der.len <= 0) {
+        /* No response known, check store for new response. */
+        ocsp_status_refresh(ostat, p);
+        if (ostat->resp_der.len <= 0) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                          "md[%s]: OCSP, no response available", name);
+            goto leave;
+        }
+    }
+    /* We have a response */
+    if (ostat_should_renew(ostat)) {
+        /* But it is up for renewal. A watchdog should be busy with
+         * retrieving a new one. In case of outages, this might take
+         * a while, however. Pace the frequency of checks with the
+         * urgency of a new response based on the remaining time. */
+        long secs = (long)apr_time_sec(md_timeperiod_remaining(&ostat->resp_valid, apr_time_now()));
+        apr_time_t waiting_time; 
+        
+        /* every hour, every minute, every second */
+        waiting_time = ((secs >= MD_SECS_PER_DAY)?
+                        apr_time_from_sec(60 * 60) : ((secs >= 60)? 
+                        apr_time_from_sec(60) : apr_time_from_sec(1)));
+        if ((apr_time_now() - ostat->resp_last_check) >= waiting_time) {
+            ostat->resp_last_check = apr_time_now();
+            ocsp_status_refresh(ostat, p);
+        }
+    }
+    
+    *pder = OPENSSL_malloc(ostat->resp_der.len);
+    if (*pder == NULL) {
+        rv = APR_ENOMEM;
+        goto leave;
+    }
+    memcpy(*pder, ostat->resp_der.data, ostat->resp_der.len);
+    *pderlen = (int)ostat->resp_der.len;
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                  "md[%s]: OCSP, returning %ld bytes of response", 
+                  name, (long)ostat->resp_der.len);
+leave:
+    if (locked) apr_thread_mutex_unlock(reg->mutex);
+    return rv;
+}
+
+static void ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid, 
+                          md_ocsp_reg_t *reg, md_ocsp_status_t *ostat, apr_pool_t *p)
+{
+    apr_thread_mutex_lock(reg->mutex);
+    if (ostat->resp_der.len <= 0) {
+        /* No resonse known, check the store if out watchdog retrieved one 
+         * in the meantime. */
+        ocsp_status_refresh(ostat, p);
+    }
+    *pvalid = ostat->resp_valid;
+    *pstat = ostat->resp_stat;
+    apr_thread_mutex_unlock(reg->mutex);
+}
+
+apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid,
+                              md_ocsp_reg_t *reg, const md_cert_t *cert,
+                              apr_pool_t *p, const md_t *md)
+{
+    char iddata[MD_OCSP_ID_LENGTH];
+    md_ocsp_status_t *ostat;
+    const char *name;
+    apr_status_t rv;
+    md_timeperiod_t valid;
+    md_ocsp_cert_stat_t stat;
+    md_data_t id;
+    
+    (void)p;
+    (void)md;
+    id.data = iddata; id.len = sizeof(iddata);
+    name = md? md->name : MD_OTHER;
+    memset(&valid, 0, sizeof(valid));
+    stat = MD_OCSP_CERT_ST_UNKNOWN;
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                  "md[%s]: OCSP, get_status", name);
+    
+    rv = init_cert_id(&id, cert);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len);
+    if (!ostat) {
+        rv = APR_ENOENT;
+        goto leave;
+    }
+    ocsp_get_meta(&stat, &valid, reg, ostat, p);
+leave:
+    *pstat = stat;
+    *pvalid = valid;  
+    return rv;
+}
+
+apr_size_t md_ocsp_count(md_ocsp_reg_t *reg)
+{
+    return apr_hash_count(reg->hash);
+}
+
+static const char *certid_as_hex(const OCSP_CERTID *certid, apr_pool_t *p)
+{
+    md_data_t der;
+    const char *hex;
+    
+    memset(&der, 0, sizeof(der));
+    der.len = (apr_size_t)i2d_OCSP_CERTID((OCSP_CERTID*)certid, (unsigned char**)&der.data);
+    md_data_to_hex(&hex, 0, p, &der);
+    OPENSSL_free((void*)der.data);
+    return hex;
+}
+
+static const char *certid_summary(const OCSP_CERTID *certid, apr_pool_t *p)
+{
+    const char *serial, *issuer, *key, *s;
+    ASN1_INTEGER *aserial;
+    ASN1_OCTET_STRING *aname_hash, *akey_hash;
+    ASN1_OBJECT *amd_nid;
+    BIGNUM *bn; 
+    md_data_t data;
+    
+    serial = issuer = key = "???";
+    OCSP_id_get0_info(&aname_hash, &amd_nid, &akey_hash, &aserial, (OCSP_CERTID*)certid);
+    if (aname_hash) {
+        data.len = (apr_size_t)aname_hash->length;
+        data.data = (const char*)aname_hash->data;
+        md_data_to_hex(&issuer, 0, p, &data);
+    }
+    if (akey_hash) {
+        data.len = (apr_size_t)akey_hash->length;
+        data.data = (const char*)akey_hash->data;
+        md_data_to_hex(&key, 0, p, &data);
+    }
+    if (aserial) {
+        bn = ASN1_INTEGER_to_BN(aserial, NULL);
+        s = BN_bn2hex(bn);
+        serial = apr_pstrdup(p, s);
+        OPENSSL_free((void*)bn);
+        OPENSSL_free((void*)s);
+    }
+    return apr_psprintf(p, "certid[der=%s, issuer=%s, key=%s, serial=%s]",
+                        certid_as_hex(certid, p), issuer, key, serial);
+}
+
+static const char *certstatus_string(int status)
+{
+    switch (status) {
+        case V_OCSP_CERTSTATUS_GOOD: return "good";
+        case V_OCSP_CERTSTATUS_REVOKED: return "revoked";
+        case V_OCSP_CERTSTATUS_UNKNOWN: return "unknown";
+        default: return "???";
+    }
+
+}
+
+static const char *single_resp_summary(OCSP_SINGLERESP* resp, apr_pool_t *p)
+{
+    const OCSP_CERTID *certid;
+    int status, reason = 0;
+    ASN1_GENERALIZEDTIME *bup = NULL, *bnextup = NULL;
+    md_timeperiod_t valid;
+    
+    certid = OCSP_SINGLERESP_get0_id(resp);
+    status = OCSP_single_get0_status(resp, &reason, NULL, &bup, &bnextup);
+    valid.start = bup? md_asn1_generalized_time_get(bup) : apr_time_now();
+    valid.end = md_asn1_generalized_time_get(bnextup);
+
+    return apr_psprintf(p, "ocsp-single-resp[%s, status=%s, reason=%d, valid=%s]",
+                        certid_summary(certid, p),
+                        certstatus_string(status), reason,
+                        md_timeperiod_print(p, &valid));
+}
+
+typedef struct {
+    apr_pool_t *p;
+    md_ocsp_status_t *ostat;
+    md_result_t *result;
+    md_job_t *job;
+} md_ocsp_update_t;
+
+static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton)
+{
+    md_ocsp_update_t *update = baton;
+    md_ocsp_status_t *ostat = update->ostat;
+    md_http_request_t *req = resp->req;
+    OCSP_RESPONSE *ocsp_resp = NULL;
+    OCSP_BASICRESP *basic_resp = NULL;
+    OCSP_SINGLERESP *single_resp;
+    apr_status_t rv = APR_SUCCESS;
+    int n, breason = 0, bstatus;
+    ASN1_GENERALIZEDTIME *bup = NULL, *bnextup = NULL;
+    md_data_t der, new_der;
+    md_timeperiod_t valid;
+    md_ocsp_cert_stat_t nstat;
+    
+    der.data = new_der.data = NULL;
+    der.len  = new_der.len = 0;
+
+    md_result_activity_printf(update->result, "status of certid %s, reading response", 
+                              ostat->hexid);
+    if (APR_SUCCESS != (rv = apr_brigade_pflatten(resp->body, (char**)&der.data, 
+                                                  &der.len, req->pool))) {
+        goto leave;
+    }
+    if (NULL == (ocsp_resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char**)&der.data, 
+                                               (long)der.len))) {
+        rv = APR_EINVAL;
+        md_result_set(update->result, rv, "response body does not parse as OCSP response");
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    /* got a response! but what does it say? */
+    n = OCSP_response_status(ocsp_resp);
+    if (OCSP_RESPONSE_STATUS_SUCCESSFUL != n) {
+        rv = APR_EINVAL;
+        md_result_printf(update->result, rv, "OCSP response status is, unsuccessfully, %d", n);
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    basic_resp = OCSP_response_get1_basic(ocsp_resp);
+    if (!basic_resp) {
+        rv = APR_EINVAL;
+        md_result_set(update->result, rv, "OCSP response has no basicresponse");
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    /* The notion of nonce enabled freshness in OCSP responses, e.g. that the response
+     * contains the signed nonce we sent to the responder, does not scale well. Responders
+     * like to return cached response bytes and therefore do not add a nonce to it.
+     * So, in reality, we can only detect a mismatch when present and otherwise have
+     * to accept it. */
+    switch ((n = OCSP_check_nonce(ostat->ocsp_req, basic_resp))) {
+        case 1:
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->pool, 
+                          "req[%d]: OCSP respoonse nonce does match", req->id);
+            break;
+        case 0:
+            rv = APR_EINVAL;
+            md_result_printf(update->result, rv, "OCSP nonce mismatch in response", n);
+            md_result_log(update->result, MD_LOG_WARNING);
+            goto leave;
+            
+        case -1:
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->pool, 
+                          "req[%d]: OCSP respoonse did not return the nonce", req->id);
+            break;
+        default:
+            break;
+    }
+    
+    if (!OCSP_resp_find_status(basic_resp, ostat->certid, &bstatus,
+                               &breason, NULL, &bup, &bnextup)) {
+        const char *prefix, *slist = "", *sep = "";
+        int i;
+        
+        rv = APR_EINVAL;
+        prefix = apr_psprintf(req->pool, "OCSP response, no matching status reported for  %s",
+                              certid_summary(ostat->certid, req->pool));
+        for (i = 0; i < OCSP_resp_count(basic_resp); ++i) {
+            single_resp = OCSP_resp_get0(basic_resp, i);
+            slist = apr_psprintf(req->pool, "%s%s%s", slist, sep, 
+                                 single_resp_summary(single_resp, req->pool));
+            sep = ", ";
+        }
+        md_result_printf(update->result, rv, "%s, status list [%s]", prefix, slist);
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    if (V_OCSP_CERTSTATUS_UNKNOWN == bstatus) {
+        rv = APR_ENOENT;
+        md_result_set(update->result, rv, "OCSP basicresponse says cert is unknown");
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    if (!bnextup) {
+        rv = APR_EINVAL;
+        md_result_set(update->result, rv, "OCSP basicresponse reports not valid dates");
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    
+    /* Coming here, we have a response for our certid and it is either GOOD
+     * or REVOKED. Both cases we want to remember and use in stapling. */
+    n = i2d_OCSP_RESPONSE(ocsp_resp, (unsigned char**)&new_der.data);
+    if (n <= 0) {
+        rv = APR_EGENERAL;
+        md_result_set(update->result, rv, "error DER encoding OCSP response");
+        md_result_log(update->result, MD_LOG_WARNING);
+        goto leave;
+    }
+    nstat = (bstatus == V_OCSP_CERTSTATUS_GOOD)? MD_OCSP_CERT_ST_GOOD : MD_OCSP_CERT_ST_REVOKED;
+    new_der.len = (apr_size_t)n;
+    valid.start = bup? md_asn1_generalized_time_get(bup) : apr_time_now();
+    valid.end = md_asn1_generalized_time_get(bnextup);
+    
+    /* First, update the instance with a copy */
+    apr_thread_mutex_lock(ostat->reg->mutex);
+    ostat_set(ostat, nstat, &new_der, &valid, apr_time_now());
+    apr_thread_mutex_unlock(ostat->reg->mutex);
+    
+    /* Next, save the original response */
+    rv = ocsp_status_save(nstat, &new_der, &valid, ostat, req->pool); 
+    if (APR_SUCCESS != rv) {
+        md_result_set(update->result, rv, "error saving OCSP status");
+        md_result_log(update->result, MD_LOG_ERR);
+        goto leave;
+    }
+    
+    md_result_printf(update->result, rv, "certificate status is %s, status valid %s", 
+                     (nstat == MD_OCSP_CERT_ST_GOOD)? "GOOD" : "REVOKED",
+                     md_timeperiod_print(req->pool, &ostat->resp_valid));
+    md_result_log(update->result, MD_LOG_DEBUG);
+
+leave:
+    if (new_der.data) OPENSSL_free((void*)new_der.data);
+    if (basic_resp) OCSP_BASICRESP_free(basic_resp);
+    if (ocsp_resp) OCSP_RESPONSE_free(ocsp_resp);
+    return rv;
+}
+
+static apr_status_t ostat_on_req_status(const md_http_request_t *req, apr_status_t status, 
+                                        void *baton)
+{
+    md_ocsp_update_t *update = baton;
+    md_ocsp_status_t *ostat = update->ostat;
+
+    (void)req;
+    md_job_end_run(update->job, update->result);
+    if (APR_SUCCESS != status) {
+        ++ostat->errors;
+        ostat->next_run = apr_time_now() + md_job_delay_on_errors(ostat->errors); 
+        md_result_printf(update->result, status, "OCSP status update failed (%d. time)",  
+                         ostat->errors);
+        md_result_log(update->result, MD_LOG_DEBUG);
+        md_job_log_append(update->job, "ocsp-error", 
+                          update->result->problem, update->result->detail);
+        md_job_holler(update->job, "ocsp-errored");
+        goto leave;
+    }
+    md_job_notify(update->job, "ocsp-renewed", update->result);
+
+leave:
+    md_job_save(update->job, update->result, update->p);
+    ostat_req_cleanup(ostat);
+    return APR_SUCCESS;
+}
+
+typedef struct {
+    md_ocsp_reg_t *reg;
+    apr_array_header_t *todos;
+    apr_pool_t *ptemp;
+    apr_time_t time;
+    int max_parallel;
+} md_ocsp_todo_ctx_t;
+
+static apr_status_t next_todo(md_http_request_t **preq, void *baton, 
+                              md_http_t *http, int in_flight)
+{
+    md_ocsp_todo_ctx_t *ctx = baton;
+    md_ocsp_update_t *update, **pupdate;    
+    md_ocsp_status_t *ostat;
+    OCSP_CERTID *certid = NULL;
+    md_http_request_t *req = NULL;
+    apr_status_t rv = APR_ENOENT;
+    apr_table_t *headers;
+    int len;
+    
+    if (in_flight < ctx->max_parallel) {
+        pupdate = apr_array_pop(ctx->todos);
+        if (pupdate) {
+            update = *pupdate;
+            ostat = update->ostat;
+            
+            update->job = md_ocsp_job_make(ctx->reg, ostat->md_name, update->p);
+            md_job_load(update->job);
+            md_job_start_run(update->job, update->result, ctx->reg->store);
+             
+            if (!ostat->ocsp_req) {
+                ostat->ocsp_req = OCSP_REQUEST_new();
+                if (!ostat->ocsp_req) goto leave;
+                certid = OCSP_CERTID_dup(ostat->certid);
+                if (!certid) goto leave;
+                if (!OCSP_request_add0_id(ostat->ocsp_req, certid)) goto leave;
+                OCSP_request_add1_nonce(ostat->ocsp_req, 0, -1);
+                certid = NULL;
+            }
+            if (0 == ostat->req_der.len) {
+                len = i2d_OCSP_REQUEST(ostat->ocsp_req, (unsigned char**)&ostat->req_der.data);
+                if (len < 0) goto leave;
+                ostat->req_der.len = (apr_size_t)len;
+            }
+            md_result_activity_printf(update->result, "status of certid %s, "
+                                      "contacting %s", ostat->hexid, ostat->responder_url);
+            headers = apr_table_make(ctx->ptemp, 5);
+            apr_table_set(headers, "Expect", "");
+            rv = md_http_POSTd_create(&req, http, ostat->responder_url, headers, 
+                                      "application/ocsp-request", &ostat->req_der);
+            if (APR_SUCCESS != rv) goto leave;
+            md_http_set_on_status_cb(req, ostat_on_req_status, update);
+            md_http_set_on_response_cb(req, ostat_on_resp, update);
+            rv = APR_SUCCESS;
+        }
+    }
+leave:
+    *preq = (APR_SUCCESS == rv)? req : NULL;
+    if (certid) OCSP_CERTID_free(certid);
+    return rv;
+}
+
+static int select_updates(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+    md_ocsp_todo_ctx_t *ctx = baton;
+    md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+    md_ocsp_update_t *update;
+    
+    (void)key;
+    (void)klen;
+    if (ostat->next_run <= ctx->time) {
+        update = apr_pcalloc(ctx->ptemp, sizeof(*update));
+        update->p = ctx->ptemp;
+        update->ostat = ostat;
+        update->result = md_result_md_make(update->p, ostat->md_name);
+        update->job = NULL;
+        APR_ARRAY_PUSH(ctx->todos, md_ocsp_update_t*) = update;
+    }
+    return 1;
+}
+
+static int select_next_run(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+    md_ocsp_todo_ctx_t *ctx = baton;
+    md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+    
+    (void)key;
+    (void)klen;
+    if (ostat->next_run < ctx->time && ostat->next_run > apr_time_now()) {
+        ctx->time = ostat->next_run;
+    }
+    return 1;
+}
+
+void md_ocsp_renew(md_ocsp_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, apr_time_t *pnext_run)
+{
+    md_ocsp_todo_ctx_t ctx;
+    md_http_t *http;
+    apr_status_t rv = APR_SUCCESS;
+    
+    (void)p;
+    (void)pnext_run;
+    
+    ctx.reg = reg;
+    ctx.ptemp = ptemp;
+    ctx.todos = apr_array_make(ptemp, (int)md_ocsp_count(reg), sizeof(md_ocsp_status_t*));
+    ctx.max_parallel = 6; /* the magic number in HTTP */
+    
+    /* Create a list of update tasks that are needed now or in the next minute */
+    ctx.time = apr_time_now() + apr_time_from_sec(60);;
+    apr_hash_do(select_updates, &ctx, reg->hash);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                  "OCSP status updates due: %d",  ctx.todos->nelts);
+    if (!ctx.todos->nelts) goto leave;
+    
+    rv = md_http_create(&http, ptemp, reg->user_agent, reg->proxy_url);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    rv = md_http_multi_perform(http, next_todo, &ctx);
+
+leave:
+    /* When do we need to run next? *pnext_run contains the planned schedule from
+     * the watchdog. We can make that earlier if we need it. */
+    ctx.time = *pnext_run;
+    apr_hash_do(select_next_run, &ctx, reg->hash);
+
+    /* sanity check and return */
+    if (ctx.time < apr_time_now()) ctx.time = apr_time_now() + apr_time_from_sec(1);
+    *pnext_run = ctx.time;
+
+    if (APR_SUCCESS != rv) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "ocsp_renew done");
+    }
+    return;
+}
+
+apr_status_t md_ocsp_remove_responses_older_than(md_ocsp_reg_t *reg, apr_pool_t *p, 
+                                                 apr_time_t timestamp)
+{
+    return md_store_remove_not_modified_since(reg->store, p, timestamp, 
+                                              MD_SG_OCSP, "*", "ocsp*.json");
+}
+
+typedef struct {
+    apr_pool_t *p;
+    md_ocsp_reg_t *reg;
+    int good;
+    int revoked;
+    int unknown;
+} ocsp_summary_ctx_t;
+
+static int add_to_summary(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+    ocsp_summary_ctx_t *ctx = baton;
+    md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+    md_ocsp_cert_stat_t stat;
+    md_timeperiod_t valid;
+    
+    (void)key;
+    (void)klen;
+    ocsp_get_meta(&stat, &valid, ctx->reg, ostat, ctx->p);
+    switch (stat) {
+        case MD_OCSP_CERT_ST_GOOD: ++ctx->good; break;
+        case MD_OCSP_CERT_ST_REVOKED: ++ctx->revoked; break;
+        case MD_OCSP_CERT_ST_UNKNOWN: ++ctx->unknown; break;
+    }
+    return 1;
+}
+
+void  md_ocsp_get_summary(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+    md_json_t *json;
+    ocsp_summary_ctx_t ctx;
+    
+    memset(&ctx, 0, sizeof(ctx));
+    ctx.p = p;
+    ctx.reg = reg;
+    apr_hash_do(add_to_summary, &ctx, reg->hash);
+
+    json = md_json_create(p);
+    md_json_setl(ctx.good+ctx.revoked+ctx.unknown, json, MD_KEY_TOTAL, NULL);
+    md_json_setl(ctx.good, json, MD_KEY_GOOD, NULL);
+    md_json_setl(ctx.revoked, json, MD_KEY_REVOKED, NULL);
+    md_json_setl(ctx.unknown, json, MD_KEY_UNKNOWN, NULL);
+    *pjson = json;
+}
+
+static apr_status_t job_loadj(md_json_t **pjson, const char *name, 
+                              md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+    return md_store_load_json(reg->store, MD_SG_OCSP, name, MD_FN_JOB, pjson, p);
+}
+
+typedef struct {
+    apr_pool_t *p;
+    md_ocsp_reg_t *reg;
+    apr_array_header_t *ostats;
+} ocsp_status_ctx_t;
+
+static md_json_t *mk_jstat(md_ocsp_status_t *ostat, md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+    md_ocsp_cert_stat_t stat;
+    md_timeperiod_t valid, renewal;
+    md_json_t *json, *jobj;
+    apr_status_t rv;
+    
+    json = md_json_create(p);
+    md_json_sets(ostat->md_name, json, MD_KEY_DOMAIN, NULL);
+    md_json_sets(ostat->hexid, json, MD_KEY_ID, NULL);
+    ocsp_get_meta(&stat, &valid, reg, ostat, p);
+    md_json_sets(md_ocsp_cert_stat_name(stat), json, MD_KEY_STATUS, NULL);
+    md_json_sets(ostat->hex_sha256, json, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL);
+    md_json_sets(ostat->responder_url, json, MD_KEY_URL, NULL);
+    md_json_set_timeperiod(&valid, json, MD_KEY_VALID, NULL);
+    renewal = md_timeperiod_slice_before_end(&valid, &reg->renew_window);
+    md_json_set_time(renewal.start, json, MD_KEY_RENEW_AT, NULL);
+    if ((MD_OCSP_CERT_ST_UNKNOWN == stat) || renewal.start < apr_time_now()) {
+        /* We have no answer yet, or it should be in renew now. Add job information */
+        rv = job_loadj(&jobj, ostat->md_name, reg, p);
+        if (APR_SUCCESS == rv) {
+            md_json_setj(jobj, json, MD_KEY_RENEWAL, NULL);
+        }
+    }
+    return json;
+}
+
+static int add_ostat(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+    ocsp_status_ctx_t *ctx = baton;
+    const md_ocsp_status_t *ostat = val;
+    
+    (void)key;
+    (void)klen;
+    APR_ARRAY_PUSH(ctx->ostats, const md_ocsp_status_t*) = ostat;
+    return 1;
+}
+
+static int md_ostat_cmp(const void *v1, const void *v2)
+{
+    int n;
+    n = strcmp((*(md_ocsp_status_t**)v1)->md_name, (*(md_ocsp_status_t**)v2)->md_name);
+    if (!n) {
+        n = strcmp((*(md_ocsp_status_t**)v1)->hexid, (*(md_ocsp_status_t**)v2)->hexid);
+    }
+    return n;
+}
+
+void md_ocsp_get_status_all(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+    md_json_t *json;
+    ocsp_status_ctx_t ctx;
+    md_ocsp_status_t *ostat;
+    int i;
+    
+    memset(&ctx, 0, sizeof(ctx));
+    ctx.p = p;
+    ctx.reg = reg;
+    ctx.ostats = apr_array_make(p, (int)apr_hash_count(reg->hash), sizeof(md_ocsp_status_t*));
+    json = md_json_create(p);
+    
+    apr_hash_do(add_ostat, &ctx, reg->hash);
+    qsort(ctx.ostats->elts, (size_t)ctx.ostats->nelts, sizeof(md_json_t*), md_ostat_cmp);
+    
+    for (i = 0; i < ctx.ostats->nelts; ++i) {
+        ostat = APR_ARRAY_IDX(ctx.ostats, i, md_ocsp_status_t*);
+        md_json_addj(mk_jstat(ostat, reg, p), json, MD_KEY_OCSPS, NULL);
+    }
+    *pjson = json;
+}
+
+void md_ocsp_set_notify_cb(md_ocsp_reg_t *ocsp, md_job_notify_cb *cb, void *baton)
+{
+    ocsp->notify = cb;
+    ocsp->notify_ctx = baton;
+}
+
+md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p)
+{
+    md_job_t *job;
+    
+    job = md_job_make(p, ocsp->store, MD_SG_OCSP, mdomain);
+    md_job_set_notify_cb(job, ocsp->notify, ocsp->notify_ctx);
+    return job;
+}

Added: httpd/httpd/trunk/modules/md/md_ocsp.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_ocsp.h?rev=1868506&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/md/md_ocsp.h (added)
+++ httpd/httpd/trunk/modules/md/md_ocsp.h Wed Oct 16 12:31:43 2019
@@ -0,0 +1,66 @@
+/* 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.
+ */
+
+#ifndef md_ocsp_h
+#define md_ocsp_h
+
+struct md_job_t;
+struct md_json_t;
+struct md_result_t;
+struct md_store_t;
+struct md_timeslice_t;
+
+typedef enum {
+    MD_OCSP_CERT_ST_UNKNOWN,
+    MD_OCSP_CERT_ST_GOOD,
+    MD_OCSP_CERT_ST_REVOKED,
+} md_ocsp_cert_stat_t;
+
+const char *md_ocsp_cert_stat_name(md_ocsp_cert_stat_t stat);
+md_ocsp_cert_stat_t md_ocsp_cert_stat_value(const char *name);
+
+typedef struct md_ocsp_reg_t md_ocsp_reg_t;
+
+apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p,
+                              struct md_store_t *store, 
+                              const md_timeslice_t *renew_window,
+                              const char *user_agent, const char *proxy_url);
+
+apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *x, 
+                           md_cert_t *issuer, const md_t *md);
+
+apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen,
+                                md_ocsp_reg_t *reg, const md_cert_t *cert,
+                                apr_pool_t *p, const md_t *md);
+
+apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid,
+                              md_ocsp_reg_t *reg, const md_cert_t *cert,
+                              apr_pool_t *p, const md_t *md);
+
+apr_size_t md_ocsp_count(md_ocsp_reg_t *reg);
+
+void md_ocsp_renew(md_ocsp_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, apr_time_t *pnext_run);
+
+apr_status_t md_ocsp_remove_responses_older_than(md_ocsp_reg_t *reg, apr_pool_t *p, 
+                                                 apr_time_t timestamp);
+
+void md_ocsp_get_summary(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p);
+void md_ocsp_get_status_all(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p);
+
+void md_ocsp_set_notify_cb(md_ocsp_reg_t *reg, md_job_notify_cb *cb, void *baton);
+struct md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p);
+
+#endif /* md_ocsp_h */

Modified: httpd/httpd/trunk/modules/md/md_reg.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_reg.c?rev=1868506&r1=1868505&r2=1868506&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_reg.c (original)
+++ httpd/httpd/trunk/modules/md/md_reg.c Wed Oct 16 12:31:43 2019
@@ -46,8 +46,10 @@ struct md_reg_t {
     int can_https;
     const char *proxy_url;
     int domains_frozen;
-    const md_timeslice_t *renew_window;
-    const md_timeslice_t *warn_window;
+    md_timeslice_t *renew_window;
+    md_timeslice_t *warn_window;
+    md_job_notify_cb *notify;
+    void *notify_ctx;
 };
 
 /**************************************************************************************************/
@@ -292,11 +294,6 @@ md_t *md_reg_get(md_reg_t *reg, const ch
     return NULL;
 }
 
-apr_status_t md_reg_reinit_state(md_reg_t *reg, md_t *md, apr_pool_t *p)
-{
-    return state_init(reg, p, md);
-}
-
 typedef struct {
     const char *domain;
     md_t *md;
@@ -417,7 +414,7 @@ static apr_status_t p_md_update(void *ba
         return APR_ENOENT;
     }
     
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "update md %s", name);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "md[%s]: update store", name);
     
     if (do_checks && APR_SUCCESS != (rv = check_values(reg, ptemp, updates, fields))) {
         return rv;
@@ -455,11 +452,11 @@ static apr_status_t p_md_update(void *ba
     }
     if (MD_UPD_RENEW_WINDOW & fields) {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update renew-window: %s", name);
-        nmd->renew_window = updates->renew_window;
+        *nmd->renew_window = *updates->renew_window;
     }
     if (MD_UPD_WARN_WINDOW & fields) {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update warn-window: %s", name);
-        nmd->warn_window = updates->warn_window;
+        *nmd->warn_window = *updates->warn_window;
     }
     if (MD_UPD_CA_CHALLENGES & fields) {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update ca challenges: %s", name);
@@ -489,6 +486,10 @@ static apr_status_t p_md_update(void *ba
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update proto: %s", name);
         nmd->acme_tls_1_domains = updates->acme_tls_1_domains;
     }
+    if (MD_UPD_STAPLING & fields) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update stapling: %s", name);
+        nmd->stapling = updates->stapling;
+    }
     
     if (fields && APR_SUCCESS == (rv = md_save(reg->store, p, MD_SG_DOMAINS, nmd, 0))) {
         rv = state_init(reg, ptemp, nmd);
@@ -496,17 +497,11 @@ static apr_status_t p_md_update(void *ba
     return rv;
 }
 
-static apr_status_t update_md(md_reg_t *reg, apr_pool_t *p, 
-                              const char *name, const md_t *md, 
-                              int fields, int do_checks)
-{
-    return md_util_pool_vdo(p_md_update, reg, p, name, md, fields, do_checks, NULL);
-}
-
 apr_status_t md_reg_update(md_reg_t *reg, apr_pool_t *p, 
-                           const char *name, const md_t *md, int fields)
+                           const char *name, const md_t *md, int fields, 
+                           int do_checks)
 {
-    return update_md(reg, p, name, md, fields, 1);
+    return md_util_pool_vdo(p_md_update, reg, p, name, md, fields, do_checks, NULL);
 }
 
 apr_status_t md_reg_delete_acct(md_reg_t *reg, apr_pool_t *p, const char *acct_id) 
@@ -612,16 +607,16 @@ apr_status_t md_reg_get_cred_files(const
     return APR_SUCCESS;
 }
 
-int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p) 
+apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p)
 {
     const md_pubcert_t *pub;
     const md_cert_t *cert;
     md_timeperiod_t certlife, renewal;
     apr_status_t rv;
     
-    if (md->state == MD_S_INCOMPLETE) return 1;
+    if (md->state == MD_S_INCOMPLETE) return apr_time_now();
     rv = md_reg_get_pubcert(&pub, reg, md, p);
-    if (APR_STATUS_IS_ENOENT(rv)) return 1;
+    if (APR_STATUS_IS_ENOENT(rv)) return apr_time_now();
     if (APR_SUCCESS == rv) {
         cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
         certlife.start = md_cert_get_not_before(cert);
@@ -629,16 +624,24 @@ int md_reg_should_renew(md_reg_t *reg, c
 
         renewal = md_timeperiod_slice_before_end(&certlife, md->renew_window);
         if (md_log_is_level(p, MD_LOG_TRACE1)) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, 
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, 
                           "md[%s]: cert-life[%s] renewal[%s]", md->name, 
                           md_timeperiod_print(p, &certlife),
                           md_timeperiod_print(p, &renewal));
         }
-        return md_timeperiod_has_started(&renewal, apr_time_now());
+        return renewal.start;
     }
     return 0;
 }
 
+int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p) 
+{
+    apr_time_t renew_at;
+    
+    renew_at = md_reg_renew_at(reg, md, p);
+    return renew_at && (renew_at <= apr_time_now());
+}
+
 int md_reg_should_warn(md_reg_t *reg, const md_t *md, apr_pool_t *p)
 {
     const md_pubcert_t *pub;
@@ -656,7 +659,7 @@ int md_reg_should_warn(md_reg_t *reg, co
 
         warn = md_timeperiod_slice_before_end(&certlife, md->warn_window);
         if (md_log_is_level(p, MD_LOG_TRACE1)) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, 
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, 
                           "md[%s]: cert-life[%s] warn[%s]", md->name, 
                           md_timeperiod_print(p, &certlife),
                           md_timeperiod_print(p, &warn));
@@ -669,33 +672,6 @@ int md_reg_should_warn(md_reg_t *reg, co
 /**************************************************************************************************/
 /* synching */
 
-typedef struct {
-    apr_pool_t *p;
-    apr_array_header_t *store_mds;
-} sync_ctx;
-
-static int do_add_md(void *baton, md_store_t *store, md_t *md, apr_pool_t *ptemp)
-{
-    sync_ctx *ctx = baton;
-
-    (void)store;
-    (void)ptemp;
-    APR_ARRAY_PUSH(ctx->store_mds, const md_t*) = md_clone(ctx->p, md);
-    return 1;
-}
-
-static apr_status_t read_store_mds(md_reg_t *reg, sync_ctx *ctx)
-{
-    int rv;
-    
-    apr_array_clear(ctx->store_mds);
-    rv = md_store_md_iter(do_add_md, ctx, reg->store, ctx->p, MD_SG_DOMAINS, "*");
-    if (APR_STATUS_IS_ENOENT(rv) || APR_STATUS_IS_EINVAL(rv)) {
-        rv = APR_SUCCESS;
-    }
-    return rv;
-}
-
 apr_status_t md_reg_set_props(md_reg_t *reg, apr_pool_t *p, int can_http, int can_https)
 {
     if (reg->can_http != can_http || reg->can_https != can_https) {
@@ -714,192 +690,211 @@ apr_status_t md_reg_set_props(md_reg_t *
     return APR_SUCCESS;
 }
 
-static apr_status_t update_md(md_reg_t *reg, apr_pool_t *p, 
-                              const char *name, const md_t *md, 
-                              int fields, int do_checks);
- 
-/**
- * Procedure:
- * 1. Collect all defined "managed domains" (MD). It does not matter where a MD is defined. 
- *    All MDs need to be unique and have no overlaps in their domain names. 
- *    Fail the config otherwise. Also, if a vhost matches an MD, it
- *    needs to *only* have ServerAliases from that MD. There can be no more than one
- *    matching MD for a vhost. But an MD can apply to several vhosts.
- * 2. Synchronize with the persistent store. Iterate over all configured MDs and 
- *   a. create them in the store if they do not already exist, neither under the
- *      name or with a common domain.
- *   b. compare domain lists from store and config, if
- *      - store has dns name in other MD than from config, remove dns name from store def,
- *        issue WARNING.
- *      - store misses dns name from config, add dns name and update store
- *   c. compare MD acme url/protocol, update if changed
+static md_t *find_closest_match(apr_array_header_t *mds, const md_t *md)
+{
+    md_t *candidate, *m;
+    apr_size_t cand_n, n;
+    int i;
+    
+    candidate = md_get_by_name(mds, md->name);
+    if (!candidate) {
+        /* try to find an instance that contains all domain names from md */ 
+        for (i = 0; i < mds->nelts; ++i) {
+            m = APR_ARRAY_IDX(mds, i, md_t *);
+            if (md_contains_domains(m, md)) {
+                return m;
+            }
+        }
+        /* no matching name and no md in the list has all domains.
+         * We consider that managed domain as closest match that contains at least one
+         * domain name from md, ONLY if there is no other one that also has.
+         */
+        cand_n = 0;
+        for (i = 0; i < mds->nelts; ++i) {
+            m = APR_ARRAY_IDX(mds, i, md_t *);
+            n = md_common_name_count(md, m);
+            if (n > cand_n) {
+                candidate = m;
+                cand_n = n;
+            }
+        }
+    }
+    return candidate;
+}
+
+typedef struct {
+    apr_pool_t *p;
+    apr_array_header_t *master_mds;
+    apr_array_header_t *store_names;
+    apr_array_header_t *maybe_new_mds;
+    apr_array_header_t *new_mds;
+    apr_array_header_t *unassigned_mds;
+} sync_ctx_v2;
+
+static int iter_add_name(void *baton, const char *dir, const char *name, 
+                         md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
+{
+    sync_ctx_v2 *ctx = baton;
+    
+    (void)dir;
+    (void)value;
+    (void)ptemp;
+    (void)vtype;
+    APR_ARRAY_PUSH(ctx->store_names, const char*) = apr_pstrdup(ctx->p, name);
+    return APR_SUCCESS;
+}
+
+/* A better scaling version:
+ *  1. The consistency of the MDs in 'master_mds' has already been verified. E.g.
+ *     that no domain lists overlap etc.
+ *  2. All MD storage that exists will be overwritten by the settings we have.
+ *     And "exists" meaning that "store/MD_SG_DOMAINS/name" exists.
+ *  3. For MDs that have no directory in "store/MD_SG_DOMAINS", we load all MDs
+ *     outside the list of known names from MD_SG_DOMAINS. In this list, we
+ *     look for the MD with the most domain overlap. 
+ *      - if we find it, we assume this is a rename and move the old MD to the new name.
+ *      - if not, MD is completely new.
+ *  4. Any MD in store that does not match the "master_mds" will just be left as is. 
  */
-apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, 
-                         apr_array_header_t *master_mds) 
+apr_status_t md_reg_sync_start(md_reg_t *reg, apr_array_header_t *master_mds, apr_pool_t *p) 
 {
-    sync_ctx ctx;
+    sync_ctx_v2 ctx;
     apr_status_t rv;
-
-    ctx.p = ptemp;
-    ctx.store_mds = apr_array_make(ptemp, 100, sizeof(md_t *));
-    rv = read_store_mds(reg, &ctx);
+    md_t *md, *oldmd;
+    const char *name;
+    int i, idx;
     
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                  "sync: found %d mds in store", ctx.store_mds->nelts);
-    if (reg->domains_frozen) return APR_EACCES; 
-    if (APR_SUCCESS == rv) {
-        int i, fields;
-        md_t *md, *config_md, *smd, *omd;
-        const char *common;
-        
-        for (i = 0; i < master_mds->nelts; ++i) {
-            md = APR_ARRAY_IDX(master_mds, i, md_t *);
-            
-            /* find the store md that is closest match for the configured md */
-            smd = md_find_closest_match(ctx.store_mds, md);
-            if (smd) {
-                fields = 0;
-                
-                /* Did the name change? This happens when the order of names in configuration
-                 * changes or when the first name is removed. Use the name from the store, but
-                 * remember the original one. We try to align this later on. */
-                if (strcmp(md->name, smd->name)) {
-                    md->configured_name = md->name;
-                    md->name = apr_pstrdup(p, smd->name);
-                }
-                
-                /* Make the stored domain list *exactly* the same, even if
-                 * someone only changed upper/lowercase, we'd like to persist that. */
-                if (!md_equal_domains(md, smd, 1)) {
-                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                                 "%s: domains changed", smd->name);
-                    smd->domains = md_array_str_clone(ptemp, md->domains);
-                    fields |= MD_UPD_DOMAINS;
-                }
-                
-                /* Look for other store mds which have domains now being part of smd */
-                while (APR_SUCCESS == rv && (omd = md_get_by_dns_overlap(ctx.store_mds, md))) {
-                    /* find the name now duplicate */
-                    common = md_common_name(md, omd);
-                    assert(common);
-                    
-                    /* Is this md still configured or has it been abandoned in the config? */
-                    config_md = md_get_by_name(master_mds, omd->name);
-                    if (config_md && md_contains(config_md, common, 0)) {
-                        /* domain used in two configured mds, not allowed */
-                        rv = APR_EINVAL;
-                        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, 
-                                      "domain %s used in md %s and %s", 
-                                      common, md->name, omd->name);
-                    }
-                    else {
-                        /* remove it from the other md and update store, or, if it
-                         * is now empty, move it into the archive */
-                        omd->domains = md_array_str_remove(ptemp, omd->domains, common, 0);
-                        if (apr_is_empty_array(omd->domains)) {
-                            md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, 
-                                          "All domains of the MD %s have moved elsewhere, "
-                                          " moving it to the archive. ", omd->name);
-                            md_reg_remove(reg, ptemp, omd->name, 1); /* best effort */
-                        }
-                        else {
-                            rv = update_md(reg, ptemp, omd->name, omd, MD_UPD_DOMAINS, 0);
-                        }
-                    }
-                }
-
-                /* If no CA url/proto is configured for the MD, take the default */
-                if (!md->ca_url) {
-                    md->ca_url = MD_ACME_DEF_URL;
-                    md->ca_proto = MD_PROTO_ACME; 
-                }
-                
-                if (MD_SVAL_UPDATE(md, smd, ca_url)) {
-                    smd->ca_url = md->ca_url;
-                    fields |= MD_UPD_CA_URL;
-                }
-                if (MD_SVAL_UPDATE(md, smd, ca_proto)) {
-                    smd->ca_proto = md->ca_proto;
-                    fields |= MD_UPD_CA_PROTO;
-                }
-                if (MD_SVAL_UPDATE(md, smd, ca_agreement)) {
-                    smd->ca_agreement = md->ca_agreement;
-                    fields |= MD_UPD_AGREEMENT;
-                }
-                if (MD_VAL_UPDATE(md, smd, transitive)) {
-                    smd->transitive = md->transitive;
-                    fields |= MD_UPD_TRANSITIVE;
-                }
-                if (MD_VAL_UPDATE(md, smd, renew_mode)) {
-                    smd->renew_mode = md->renew_mode;
-                    fields |= MD_UPD_DRIVE_MODE;
-                }
-                if (!apr_is_empty_array(md->contacts) 
-                    && !md_array_str_eq(md->contacts, smd->contacts, 0)) {
-                    smd->contacts = md->contacts;
-                    fields |= MD_UPD_CONTACTS;
-                }
-                if (!md_timeslice_eq(md->renew_window, smd->renew_window)) {
-                    smd->renew_window = md->renew_window;
-                    fields |= MD_UPD_RENEW_WINDOW;
-                }
-                if (!md_timeslice_eq(md->warn_window, smd->warn_window)) {
-                    smd->warn_window = md->warn_window;
-                    fields |= MD_UPD_WARN_WINDOW;
-                }
-                if (md->ca_challenges) {
-                    md->ca_challenges = md_array_str_compact(p, md->ca_challenges, 0);
-                    if (!smd->ca_challenges 
-                        || !md_array_str_eq(md->ca_challenges, smd->ca_challenges, 0)) {
-                        smd->ca_challenges = apr_array_copy(ptemp, md->ca_challenges);
-                        fields |= MD_UPD_CA_CHALLENGES;
-                    }
-                }
-                else if (smd->ca_challenges) {
-                    smd->ca_challenges = NULL;
-                    fields |= MD_UPD_CA_CHALLENGES;
-                }
-                if (!md_pkey_spec_eq(md->pkey_spec, smd->pkey_spec)) {
-                    fields |= MD_UPD_PKEY_SPEC;
-                    smd->pkey_spec = NULL;
-                    if (md->pkey_spec) {
-                        smd->pkey_spec = apr_pmemdup(p, md->pkey_spec, sizeof(md_pkey_spec_t));
-                    }
-                }
-                if (MD_VAL_UPDATE(md, smd, require_https)) {
-                    smd->require_https = md->require_https;
-                    fields |= MD_UPD_REQUIRE_HTTPS;
-                }
-                if (MD_VAL_UPDATE(md, smd, must_staple)) {
-                    smd->must_staple = md->must_staple;
-                    fields |= MD_UPD_MUST_STAPLE;
-                }
-                if (!md_array_str_eq(md->acme_tls_1_domains, smd->acme_tls_1_domains, 0)) {
-                    smd->acme_tls_1_domains = md->acme_tls_1_domains;
-                    fields |= MD_UPD_PROTO;
-                }
-                
-                if (fields) {
-                    rv = update_md(reg, ptemp, smd->name, smd, fields, 0);
-                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md %s updated", smd->name);
-                }
-            }
-            else {
-                /* new managed domain */
-                /* If no CA url/proto is configured for the MD, take the default */
-                if (!md->ca_url) {
-                    md->ca_url = MD_ACME_DEF_URL;
-                    md->ca_proto = MD_PROTO_ACME; 
-                }
-                rv = add_md(reg, md, ptemp, 0);
-                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "new md %s added", md->name);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "sync MDs, start");
+     
+    ctx.p = p;
+    ctx.master_mds = master_mds;
+    ctx.store_names = apr_array_make(p, master_mds->nelts + 100, sizeof(const char*));
+    ctx.maybe_new_mds = apr_array_make(p, master_mds->nelts, sizeof(md_t*));
+    ctx.new_mds = apr_array_make(p, master_mds->nelts, sizeof(md_t*));
+    ctx.unassigned_mds = apr_array_make(p, master_mds->nelts, sizeof(md_t*));
+    
+    rv = md_store_iter_names(iter_add_name, &ctx, reg->store, p, MD_SG_DOMAINS, "*");
+    if (APR_SUCCESS != rv) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "listing existing store MD names"); 
+        goto leave;
+    }
+    
+    /* Get all MDs that are not already present in store */
+    for (i = 0; i < ctx.master_mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(ctx.master_mds, i, md_t*);
+        idx = md_array_str_index(ctx.store_names, md->name, 0, 1);
+        if (idx < 0) {
+            APR_ARRAY_PUSH(ctx.maybe_new_mds, md_t*) = md;
+            md_array_remove_at(ctx.store_names, idx);
+        }
+    }
+    
+    if (ctx.maybe_new_mds->nelts == 0) goto leave; /* none new */
+    if (ctx.store_names->nelts == 0) goto leave;   /* all new */
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                  "sync MDs, %d potentially new MDs detected, looking for renames among "
+                  "the %d unassigned store domains", (int)ctx.maybe_new_mds->nelts,
+                  (int)ctx.store_names->nelts);
+    for (i = 0; i < ctx.store_names->nelts; ++i) {
+        name = APR_ARRAY_IDX(ctx.store_names, i, const char*);
+        if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, name, &md, p)) {
+            APR_ARRAY_PUSH(ctx.unassigned_mds, md_t*) = md;
+        } 
+    }
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                  "sync MDs, %d MDs maybe new, checking store", (int)ctx.maybe_new_mds->nelts);
+    for (i = 0; i < ctx.maybe_new_mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(ctx.maybe_new_mds, i, md_t*);
+        oldmd = find_closest_match(ctx.unassigned_mds, md);
+        if (oldmd) {
+            /* found the rename, move the domains and possible staging directory */
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                          "sync MDs, found MD %s under previous name %s", md->name, oldmd->name);
+            rv = md_store_rename(reg->store, p, MD_SG_DOMAINS, oldmd->name, md->name);
+            if (APR_SUCCESS != rv) {
+                md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, 
+                              "sync MDs, renaming MD %s to %s failed", oldmd->name, md->name);
+                /* ignore it? */
             }
+            md_store_rename(reg->store, p, MD_SG_STAGING, oldmd->name, md->name);
+            md_array_remove(ctx.unassigned_mds, oldmd);
+        }
+        else {
+            APR_ARRAY_PUSH(ctx.new_mds, md_t*) = md;
         }
     }
-    else {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "loading mds");
+
+leave:
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                  "sync MDs, %d existing, %d moved, %d new.", 
+                  (int)ctx.master_mds->nelts - ctx.maybe_new_mds->nelts,
+                  (int)ctx.maybe_new_mds->nelts - ctx.new_mds->nelts,
+                  (int)ctx.new_mds->nelts);
+    return rv;
+}
+
+/** 
+ * Finish synching an MD with the store. 
+ * 1. if there are changed properties (or if the MD is new), save it.
+ * 2. read any existing certificate and init the state of the memory MD
+ */
+apr_status_t md_reg_sync_finish(md_reg_t *reg, md_t *md, apr_pool_t *p, apr_pool_t *ptemp)
+{
+    md_t *old;
+    apr_status_t rv;
+    int changed = 1;
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "sync MDs, finish start");
+    
+    if (!md->ca_url) {
+        md->ca_url = MD_ACME_DEF_URL;
+        md->ca_proto = MD_PROTO_ACME; 
     }
     
+    rv = state_init(reg, ptemp, md);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, md->name, &old, ptemp)) {
+        /* Some parts are kept from old, lacking new values */
+        if ((!md->contacts || apr_is_empty_array(md->contacts)) && old->contacts) {
+            md->contacts = md_array_str_clone(p, old->contacts);
+        } 
+        if (md->ca_challenges && old->ca_challenges) {
+            if (!md_array_str_eq(md->ca_challenges, old->ca_challenges, 0)) {
+                md->ca_challenges = md_array_str_compact(p, md->ca_challenges, 0);
+            }
+        }
+        if (!md->ca_account && old->ca_account) {
+            md->ca_account = apr_pstrdup(p, old->ca_account);
+        }
+        
+        /* if everything remains the same, spare the write back */
+        if (!MD_VAL_UPDATE(md, old, state)
+            && !MD_SVAL_UPDATE(md, old, ca_url)
+            && !MD_SVAL_UPDATE(md, old, ca_proto)
+            && !MD_SVAL_UPDATE(md, old, ca_agreement)
+            && !MD_VAL_UPDATE(md, old, transitive)
+            && md_equal_domains(md, old, 1)
+            && !MD_VAL_UPDATE(md, old, renew_mode)
+            && md_timeslice_eq(md->renew_window, old->renew_window)
+            && md_timeslice_eq(md->warn_window, old->warn_window)
+            && md_pkey_spec_eq(md->pkey_spec, old->pkey_spec)
+            && !MD_VAL_UPDATE(md, old, require_https)
+            && !MD_VAL_UPDATE(md, old, must_staple)
+            && md_array_str_eq(md->acme_tls_1_domains, old->acme_tls_1_domains, 0)
+            && !MD_VAL_UPDATE(md, old, stapling)
+            && md_array_str_eq(md->contacts, old->contacts, 0)
+            && md_array_str_eq(md->ca_challenges, old->ca_challenges, 0)) {
+            changed = 0;
+        }
+    }
+    if (changed) {
+        rv = md_save(reg->store, ptemp, MD_SG_DOMAINS, md, 0);
+    }
+leave:
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "sync MDs, finish done");
     return rv;
 }
 
@@ -970,11 +965,14 @@ static apr_status_t run_init(void *baton
     md_proto_driver_t *driver, **pdriver;
     md_result_t *result;
     apr_table_t *env;
+    const char *s;
+    int preload;
     
     (void)p;
     va_start(ap, p);
     pdriver = va_arg(ap, md_proto_driver_t **);
     md = va_arg(ap, const md_t *);
+    preload = va_arg(ap, int);
     env = va_arg(ap, apr_table_t *);
     result = va_arg(ap, md_result_t *); 
     va_end(ap);
@@ -989,6 +987,11 @@ static apr_status_t run_init(void *baton
     driver->md = md;
     driver->can_http = reg->can_http;
     driver->can_https = reg->can_https;
+    
+    s = apr_table_get(driver->env, MD_KEY_ACTIVATION_DELAY);
+    if (!s || APR_SUCCESS != md_duration_parse(&driver->activation_delay, s, "d")) {
+        driver->activation_delay = apr_time_from_sec(MD_SECS_PER_DAY);
+    }
 
     if (!md->ca_proto) {
         md_result_printf(result, APR_EGENERAL, "CA protocol is not defined"); 
@@ -1002,7 +1005,12 @@ static apr_status_t run_init(void *baton
         goto leave;
     }
     
-    result->status = driver->proto->init(driver, result);
+    if (preload) {
+        result->status = driver->proto->init_preload(driver, result);
+    }
+    else {
+        result->status = driver->proto->init(driver, result);
+    }
 
 leave:
     if (APR_SUCCESS != result->status) {
@@ -1027,7 +1035,7 @@ static apr_status_t run_test_init(void *
     env = va_arg(ap, apr_table_t *);
     result = va_arg(ap, md_result_t *); 
 
-    return run_init(baton, ptemp, &driver, md, env, result, NULL);
+    return run_init(baton, ptemp, &driver, md, 0, env, result, NULL);
 }
 
 apr_status_t md_reg_test_init(md_reg_t *reg, const md_t *md, struct apr_table_t *env, 
@@ -1051,7 +1059,7 @@ static apr_status_t run_renew(void *bato
     reset = va_arg(ap, int); 
     result = va_arg(ap, md_result_t *); 
 
-    rv = run_init(baton, ptemp, &driver, md, env, result, NULL);
+    rv = run_init(baton, ptemp, &driver, md, 0, env, result, NULL);
     if (APR_SUCCESS == rv) { 
         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run staging", md->name);
         driver->reset = reset;
@@ -1089,11 +1097,11 @@ static apr_status_t run_load_staging(voi
     result =  va_arg(ap, md_result_t*);
     
     if (APR_STATUS_IS_ENOENT(rv = md_load(reg->store, MD_SG_STAGING, md->name, NULL, ptemp))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, ptemp, "%s: nothing staged", md->name);
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, ptemp, "%s: nothing staged", md->name);
         goto out;
     }
     
-    rv = run_init(baton, ptemp, &driver, md, env, result, NULL);
+    rv = run_init(baton, ptemp, &driver, md, 1, env, result, NULL);
     if (APR_SUCCESS != rv) goto out;
     
     apr_hash_set(reg->certs, md->name, (apr_ssize_t)strlen(md->name), NULL);
@@ -1102,9 +1110,10 @@ static apr_status_t run_load_staging(voi
     if (APR_SUCCESS != rv) goto out;
 
     /* If we had a job saved in STAGING, copy it over too */
-    job = md_job_make(ptemp, md->name);
-    if (APR_SUCCESS == md_job_load(job, reg, MD_SG_STAGING, ptemp)) {
-        md_job_save(job, reg, MD_SG_TMP, NULL, ptemp);
+    job = md_reg_job_make(reg, md->name, ptemp);
+    if (APR_SUCCESS == md_job_load(job)) {
+        md_job_set_group(job, MD_SG_TMP);
+        md_job_save(job, NULL, ptemp);
     }
     
     /* swap */
@@ -1118,9 +1127,13 @@ static apr_status_t run_load_staging(voi
     md_store_purge(reg->store, p, MD_SG_STAGING, md->name);
     md_store_purge(reg->store, p, MD_SG_CHALLENGES, md->name);
     md_result_set(result, APR_SUCCESS, "new certificate successfully saved in domains");
-
+    md_job_notify(job, "installed", result);
+    if (job->dirty) md_job_save(job, result, ptemp);
+    
 out:
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: load done", md->name);
+    if (!APR_STATUS_IS_ENOENT(rv)) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ptemp, "%s: load done", md->name);
+    }
     return rv;
 }
 
@@ -1150,12 +1163,27 @@ leave:
     return rv;
 }
 
-void md_reg_set_renew_window_default(md_reg_t *reg, const md_timeslice_t *renew_window)
+void md_reg_set_renew_window_default(md_reg_t *reg, md_timeslice_t *renew_window)
 {
-    reg->renew_window = renew_window;
+    *reg->renew_window = *renew_window;
 }
 
-void md_reg_set_warn_window_default(md_reg_t *reg, const md_timeslice_t *warn_window)
+void md_reg_set_warn_window_default(md_reg_t *reg, md_timeslice_t *warn_window)
 {
-    reg->warn_window = warn_window;
+    *reg->warn_window = *warn_window;
+}
+
+void md_reg_set_notify_cb(md_reg_t *reg, md_job_notify_cb *cb, void *baton)
+{
+    reg->notify = cb;
+    reg->notify_ctx = baton;
+}
+
+md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p)
+{
+    md_job_t *job;
+    
+    job = md_job_make(p, reg->store, MD_SG_STAGING, mdomain);
+    md_job_set_notify_cb(job, reg->notify, reg->notify_ctx);
+    return job;
 }

Modified: httpd/httpd/trunk/modules/md/md_reg.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_reg.h?rev=1868506&r1=1868505&r2=1868506&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_reg.h (original)
+++ httpd/httpd/trunk/modules/md/md_reg.h Wed Oct 16 12:31:43 2019
@@ -19,11 +19,12 @@
 
 struct apr_hash_t;
 struct apr_array_header_t;
-struct md_store_t;
 struct md_pkey_t;
 struct md_cert_t;
 struct md_result_t;
 
+#include "md_store.h"
+
 /**
  * A registry for managed domains with a md_store_t as persistence.
  *
@@ -33,10 +34,10 @@ typedef struct md_reg_t md_reg_t;
 /**
  * Create the MD registry, using the pool and store.
  */
-apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *pm, struct md_store_t *store,
+apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *pm, md_store_t *store,
                            const char *proxy_url);
 
-struct md_store_t *md_reg_store_get(md_reg_t *reg);
+md_store_t *md_reg_store_get(md_reg_t *reg);
 
 apr_status_t md_reg_set_props(md_reg_t *reg, apr_pool_t *p, int can_http, int can_https);
 
@@ -66,11 +67,6 @@ md_t *md_reg_find_overlap(md_reg_t *reg,
 md_t *md_reg_get(md_reg_t *reg, const char *name, apr_pool_t *p);
 
 /**
- * Re-compute the state of the MD, given current store contents.
- */
-apr_status_t md_reg_reinit_state(md_reg_t *reg, md_t *md, apr_pool_t *p);
-
-/**
  * Callback invoked for every md in the registry. If 0 is returned, iteration stops.
  */
 typedef int md_reg_do_cb(void *baton, md_reg_t *reg, md_t *md);
@@ -85,21 +81,22 @@ int md_reg_do(md_reg_do_cb *cb, void *ba
 /**
  * Bitmask for fields that are updated.
  */
-#define MD_UPD_DOMAINS       0x0001
-#define MD_UPD_CA_URL        0x0002
-#define MD_UPD_CA_PROTO      0x0004
-#define MD_UPD_CA_ACCOUNT    0x0008
-#define MD_UPD_CONTACTS      0x0010
-#define MD_UPD_AGREEMENT     0x0020
-#define MD_UPD_DRIVE_MODE    0x0080
-#define MD_UPD_RENEW_WINDOW  0x0100
-#define MD_UPD_CA_CHALLENGES 0x0200
-#define MD_UPD_PKEY_SPEC     0x0400
-#define MD_UPD_REQUIRE_HTTPS 0x0800
-#define MD_UPD_TRANSITIVE    0x1000
-#define MD_UPD_MUST_STAPLE   0x2000
-#define MD_UPD_PROTO         0x4000
-#define MD_UPD_WARN_WINDOW   0x8000
+#define MD_UPD_DOMAINS       0x00001
+#define MD_UPD_CA_URL        0x00002
+#define MD_UPD_CA_PROTO      0x00004
+#define MD_UPD_CA_ACCOUNT    0x00008
+#define MD_UPD_CONTACTS      0x00010
+#define MD_UPD_AGREEMENT     0x00020
+#define MD_UPD_DRIVE_MODE    0x00080
+#define MD_UPD_RENEW_WINDOW  0x00100
+#define MD_UPD_CA_CHALLENGES 0x00200
+#define MD_UPD_PKEY_SPEC     0x00400
+#define MD_UPD_REQUIRE_HTTPS 0x00800
+#define MD_UPD_TRANSITIVE    0x01000
+#define MD_UPD_MUST_STAPLE   0x02000
+#define MD_UPD_PROTO         0x04000
+#define MD_UPD_WARN_WINDOW   0x08000
+#define MD_UPD_STAPLING      0x10000
 #define MD_UPD_ALL           0x7FFFFFFF
 
 /**
@@ -107,7 +104,8 @@ int md_reg_do(md_reg_do_cb *cb, void *ba
  * values from the given md, all other values remain unchanged.
  */
 apr_status_t md_reg_update(md_reg_t *reg, apr_pool_t *p, 
-                           const char *name, const md_t *md, int fields);
+                           const char *name, const md_t *md, 
+                           int fields, int check_consistency);
 
 /**
  * Get the chain of public certificates of the managed domain md, starting with the cert
@@ -127,8 +125,13 @@ apr_status_t md_reg_get_cred_files(const
 /**
  * Synchronise the give master mds with the store.
  */
-apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, 
-                         apr_array_header_t *master_mds);
+apr_status_t md_reg_sync_start(md_reg_t *reg, apr_array_header_t *master_mds, apr_pool_t *p);
+
+/**
+ * Re-compute the state of the MD, given current store contents.
+ */
+apr_status_t md_reg_sync_finish(md_reg_t *reg, md_t *md, apr_pool_t *p, apr_pool_t *ptemp);
+
 
 apr_status_t md_reg_remove(md_reg_t *reg, apr_pool_t *p, const char *name, int archive);
 
@@ -164,6 +167,12 @@ apr_status_t md_reg_freeze_domains(md_re
 int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p);
 
 /**
+ * Return the timestamp when the certificate should be renewed. A value of 0
+ * indicates that that renewal is not configured (see renew_mode).
+ */
+apr_time_t md_reg_renew_at(md_reg_t *reg, const md_t *md, apr_pool_t *p);
+
+/**
  * Return if a warning should be issued about the certificate expiration. 
  * This applies the configured warn window to the remaining lifetime of the 
  * current certiciate. If no certificate is present, this returns 0.
@@ -188,17 +197,19 @@ struct md_proto_driver_t {
     struct apr_table_t *env;
 
     md_reg_t *reg;
-    struct md_store_t *store;
+    md_store_t *store;
     const char *proxy_url;
     const md_t *md;
 
     int can_http;
     int can_https;
     int reset;
+    apr_interval_time_t activation_delay;
 };
 
 typedef apr_status_t md_proto_init_cb(md_proto_driver_t *driver, struct md_result_t *result);
 typedef apr_status_t md_proto_renew_cb(md_proto_driver_t *driver, struct md_result_t *result);
+typedef apr_status_t md_proto_init_preload_cb(md_proto_driver_t *driver, struct md_result_t *result);
 typedef apr_status_t md_proto_preload_cb(md_proto_driver_t *driver, 
                                          md_store_group_t group, struct md_result_t *result);
 
@@ -206,6 +217,7 @@ struct md_proto_t {
     const char *protocol;
     md_proto_init_cb *init;
     md_proto_renew_cb *renew;
+    md_proto_init_preload_cb *init_preload;
     md_proto_preload_cb *preload;
 };
 
@@ -239,7 +251,10 @@ apr_status_t md_reg_renew(md_reg_t *reg,
 apr_status_t md_reg_load_staging(md_reg_t *reg, const md_t *md, struct apr_table_t *env, 
                                  struct md_result_t *result, apr_pool_t *p);
 
-void md_reg_set_renew_window_default(md_reg_t *reg, const md_timeslice_t *renew_window);
-void md_reg_set_warn_window_default(md_reg_t *reg, const md_timeslice_t *warn_window);
+void md_reg_set_renew_window_default(md_reg_t *reg, md_timeslice_t *renew_window);
+void md_reg_set_warn_window_default(md_reg_t *reg, md_timeslice_t *warn_window);
+
+void md_reg_set_notify_cb(md_reg_t *reg, md_job_notify_cb *cb, void *baton);
+struct md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p);
 
 #endif /* mod_md_md_reg_h */

Modified: httpd/httpd/trunk/modules/md/md_result.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_result.c?rev=1868506&r1=1868505&r2=1868506&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_result.c (original)
+++ httpd/httpd/trunk/modules/md/md_result.c Wed Oct 16 12:31:43 2019
@@ -42,14 +42,15 @@ md_result_t *md_result_make(apr_pool_t *
     
     result = apr_pcalloc(p, sizeof(*result));
     result->p = p;
+    result->md_name = MD_OTHER;
     result->status = status;
     return result;
 }
 
-md_result_t *md_result_md_make(apr_pool_t *p, const struct md_t *md)
+md_result_t *md_result_md_make(apr_pool_t *p, const char *md_name)
 {
     md_result_t *result = md_result_make(p, APR_SUCCESS);
-    result->md = md;
+    result->md_name = md_name;
     return result;
 }
 
@@ -74,6 +75,7 @@ void md_result_activity_setn(md_result_t
 {
     result->activity = activity;
     result->problem = result->detail = NULL;
+    result->subproblems = NULL;
     on_change(result);
 }
 
@@ -91,15 +93,18 @@ void md_result_set(md_result_t *result,
     result->status = status;
     result->problem = NULL;
     result->detail = detail? apr_pstrdup(result->p, detail) : NULL;
+    result->subproblems = NULL;
     on_change(result);
 }
 
 void md_result_problem_set(md_result_t *result, apr_status_t status,
-                           const char *problem, const char *detail)
+                           const char *problem, const char *detail, 
+                           const md_json_t *subproblems)
 {
     result->status = status;
     result->problem = dup_trim(result->p, problem);
     result->detail = apr_pstrdup(result->p, detail);
+    result->subproblems = subproblems? md_json_clone(result->p, subproblems) : NULL;
     on_change(result);
 }
 
@@ -114,6 +119,7 @@ void md_result_problem_printf(md_result_
     va_start(ap, fmt);
     result->detail = apr_pvsprintf(result->p, fmt, ap);
     va_end(ap);
+    result->subproblems = NULL;
     on_change(result);
 }
 
@@ -125,6 +131,7 @@ void md_result_printf(md_result_t *resul
     va_start(ap, fmt);
     result->detail = apr_pvsprintf(result->p, fmt, ap);
     va_end(ap);
+    result->subproblems = NULL;
     on_change(result);
 }
 
@@ -146,7 +153,7 @@ md_result_t*md_result_from_json(const st
     result->activity = md_json_dups(p, json, MD_KEY_ACTIVITY, NULL);
     s = md_json_dups(p, json, MD_KEY_VALID_FROM, NULL);
     if (s && *s) result->ready_at = apr_date_parse_rfc(s);
-
+    result->subproblems = md_json_dupj(p, json, MD_KEY_SUBPROBLEMS, NULL);
     return result;
 }
 
@@ -169,6 +176,9 @@ struct md_json_t *md_result_to_json(cons
         apr_rfc822_date(ts, result->ready_at);
         md_json_sets(ts, json, MD_KEY_VALID_FROM, NULL);
     }
+    if (result->subproblems) {
+        md_json_setj(result->subproblems, json, MD_KEY_SUBPROBLEMS, NULL);
+    }
     return json;
 }
 
@@ -200,6 +210,7 @@ void md_result_assign(md_result_t *dest,
    dest->detail = src->detail;
    dest->activity = src->activity;
    dest->ready_at = src->ready_at;
+   dest->subproblems = src->subproblems;
 }
 
 void md_result_dup(md_result_t *dest, const md_result_t *src)
@@ -209,17 +220,18 @@ void md_result_dup(md_result_t *dest, co
    dest->detail = src->detail? apr_pstrdup(dest->p, src->detail) : NULL; 
    dest->activity = src->activity? apr_pstrdup(dest->p, src->activity) : NULL; 
    dest->ready_at = src->ready_at;
+   dest->subproblems = src->subproblems? md_json_clone(dest->p, src->subproblems) : NULL;
    on_change(dest);
 }
 
-void md_result_log(md_result_t *result, int level)
+void md_result_log(md_result_t *result, unsigned int level)
 {
     if (md_log_is_level(result->p, (md_log_level_t)level)) {
         const char *sep = "";
         const char *msg = "";
         
-        if (result->md) {
-            msg = apr_psprintf(result->p, "md[%s]", result->md->name);
+        if (result->md_name) {
+            msg = apr_psprintf(result->p, "md[%s]", result->md_name);
             sep = " ";
         }
         if (result->activity) {
@@ -234,6 +246,11 @@ void md_result_log(md_result_t *result,
             msg = apr_psprintf(result->p, "%s%sdetail[%s]", msg, sep, result->detail);
             sep = " ";
         }
+        if (result->subproblems) {
+            msg = apr_psprintf(result->p, "%s%ssubproblems[%s]", msg, sep, 
+                md_json_writep(result->subproblems, result->p, MD_JSON_FMT_COMPACT));
+            sep = " ";
+        }
         md_log_perror(MD_LOG_MARK, (md_log_level_t)level, result->status, result->p, "%s", msg);
     }
 }

Modified: httpd/httpd/trunk/modules/md/md_result.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_result.h?rev=1868506&r1=1868505&r2=1868506&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_result.h (original)
+++ httpd/httpd/trunk/modules/md/md_result.h Wed Oct 16 12:31:43 2019
@@ -26,10 +26,11 @@ typedef void md_result_change_cb(md_resu
 
 struct md_result_t {
     apr_pool_t *p;
-    const struct md_t *md;
+    const char *md_name;
     apr_status_t status;
     const char *problem;
     const char *detail;
+    const struct md_json_t *subproblems;
     const char *activity;
     apr_time_t ready_at;
     md_result_change_cb *on_change;
@@ -37,7 +38,7 @@ struct md_result_t {
 };
 
 md_result_t *md_result_make(apr_pool_t *p, apr_status_t status);
-md_result_t *md_result_md_make(apr_pool_t *p, const struct md_t *md);
+md_result_t *md_result_md_make(apr_pool_t *p, const char *md_name);
 void md_result_reset(md_result_t *result);
 
 void md_result_activity_set(md_result_t *result, const char *activity);
@@ -46,7 +47,8 @@ void md_result_activity_printf(md_result
 
 void md_result_set(md_result_t *result, apr_status_t status, const char *detail);
 void md_result_problem_set(md_result_t *result, apr_status_t status, 
-                           const char *problem, const char *detail);
+                           const char *problem, const char *detail, 
+                           const struct md_json_t *subproblems);
 void md_result_problem_printf(md_result_t *result, apr_status_t status,
                               const char *problem, const char *fmt, ...);
 
@@ -64,7 +66,7 @@ int md_result_cmp(const md_result_t *r1,
 void md_result_assign(md_result_t *dest, const md_result_t *src);
 void md_result_dup(md_result_t *dest, const md_result_t *src);
 
-void md_result_log(md_result_t *result, int level);
+void md_result_log(md_result_t *result, unsigned int level);
 
 void md_result_on_change(md_result_t *result, md_result_change_cb *cb, void *data);