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/06/24 16:04:33 UTC

svn commit: r1862013 [5/8] - in /httpd/httpd/trunk: ./ modules/md/

Modified: httpd/httpd/trunk/modules/md/md_reg.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_reg.c?rev=1862013&r1=1862012&r2=1862013&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_reg.c (original)
+++ httpd/httpd/trunk/modules/md/md_reg.c Mon Jun 24 16:04:32 2019
@@ -28,19 +28,26 @@
 #include "md_crypt.h"
 #include "md_log.h"
 #include "md_json.h"
+#include "md_result.h"
 #include "md_reg.h"
 #include "md_store.h"
+#include "md_status.h"
 #include "md_util.h"
 
 #include "md_acme.h"
 #include "md_acme_acct.h"
 
 struct md_reg_t {
+    apr_pool_t *p;
     struct md_store_t *store;
     struct apr_hash_t *protos;
+    struct apr_hash_t *certs;
     int can_http;
     int can_https;
     const char *proxy_url;
+    int domains_frozen;
+    const md_timeslice_t *renew_window;
+    const md_timeslice_t *warn_window;
 };
 
 /**************************************************************************************************/
@@ -67,19 +74,24 @@ static apr_status_t load_props(md_reg_t
     return rv;
 }
 
-apr_status_t md_reg_init(md_reg_t **preg, apr_pool_t *p, struct md_store_t *store,
-                         const char *proxy_url)
+apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *store,
+                           const char *proxy_url)
 {
     md_reg_t *reg;
     apr_status_t rv;
     
     reg = apr_pcalloc(p, sizeof(*reg));
+    reg->p = p;
     reg->store = store;
     reg->protos = apr_hash_make(p);
+    reg->certs = apr_hash_make(p);
     reg->can_http = 1;
     reg->can_https = 1;
     reg->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
     
+    md_timeslice_create(&reg->renew_window, p, MD_TIME_LIFE_NORM, MD_TIME_RENEW_WINDOW_DEF); 
+    md_timeslice_create(&reg->warn_window, p, MD_TIME_LIFE_NORM, MD_TIME_WARN_WINDOW_DEF); 
+    
     if (APR_SUCCESS == (rv = md_acme_protos_add(reg->protos, p))) {
         rv = load_props(reg, p);
     }
@@ -114,7 +126,7 @@ static apr_status_t check_values(md_reg_
         
         for (i = 0; i < md->domains->nelts; ++i) {
             domain = APR_ARRAY_IDX(md->domains, i, const char *);
-            if (!md_util_is_dns_name(p, domain, 1)) {
+            if (!md_dns_is_name(p, domain, 1) && !md_dns_is_wildcard(p, domain)) {
                 md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p, 
                               "md %s with invalid domain name: %s", md->name, domain);
                 return APR_EINVAL;
@@ -162,7 +174,8 @@ static apr_status_t check_values(md_reg_
         /* hmm, in case we know the protocol, some checks could be done */
     }
 
-    if ((MD_UPD_AGREEMENT & fields) && md->ca_agreement) { /* setting to empty is ok */
+    if ((MD_UPD_AGREEMENT & fields) && md->ca_agreement
+        && strcmp("accepted", md->ca_agreement)) { /* setting to empty is ok */
         rv = md_util_abs_uri_check(p, md->ca_agreement, &err);
         if (err) {
             md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p, 
@@ -177,73 +190,43 @@ static apr_status_t check_values(md_reg_
 /**************************************************************************************************/
 /* state assessment */
 
-static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md, int save_changes)
+static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md)
 {
     md_state_t state = MD_S_UNKNOWN;
-    const md_creds_t *creds;
+    const md_pubcert_t *pub;
     const md_cert_t *cert;
-    apr_time_t expires = 0, valid_from = 0;
     apr_status_t rv;
-    int i;
 
-    if (APR_SUCCESS == (rv = md_reg_creds_get(&creds, reg, MD_SG_DOMAINS, md, p))) {
-        state = MD_S_INCOMPLETE;
-        if (!creds->privkey) {
+    if (md->renew_window == NULL) md->renew_window = reg->renew_window;
+    if (md->warn_window == NULL) md->warn_window = reg->warn_window;
+
+    if (APR_SUCCESS == (rv = md_reg_get_pubcert(&pub, reg, md, p))) {
+        cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+        if (!md_is_covered_by_alt_names(md, pub->alt_names)) {
+            state = MD_S_INCOMPLETE;
             md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                          "md{%s}: incomplete, without private key", md->name);
+                          "md{%s}: incomplete, cert no longer covers all domains, "
+                          "needs sign up for a new certificate", md->name);
+            goto out;
         }
-        else if (!creds->cert) {
+        if (!md->must_staple != !md_cert_must_staple(cert)) {
+            state = MD_S_INCOMPLETE;
             md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                          "md{%s}: incomplete, has key but no certificate", md->name);
-        }
-        else {
-            valid_from = md_cert_get_not_before(creds->cert);
-            expires = md_cert_get_not_after(creds->cert);
-            if (md_cert_has_expired(creds->cert)) {
-                state = MD_S_EXPIRED;
-                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                              "md{%s}: expired, certificate has expired", md->name);
-                goto out;
-            }
-            if (!md_cert_is_valid_now(creds->cert)) {
-                state = MD_S_ERROR;
-                md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, 
-                              "md{%s}: error, certificate valid in future (clock wrong?)", 
-                              md->name);
-                goto out;
-            }
-            if (!md_cert_covers_md(creds->cert, md)) {
-                state = MD_S_INCOMPLETE;
-                md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, 
-                              "md{%s}: incomplete, cert no longer covers all domains, "
-                              "needs sign up for a new certificate", md->name);
-                goto out;
-            }
-            if (!md->must_staple != !md_cert_must_staple(creds->cert)) {
-                state = MD_S_INCOMPLETE;
-                md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, 
-                              "md{%s}: OCSP Stapling is%s requested, but certificate "
-                              "has it%s enabled. Need to get a new certificate.", md->name,
-                              md->must_staple? "" : " not", 
-                              !md->must_staple? "" : " not");
-                goto out;
-            }
-
-            for (i = 1; i < creds->pubcert->nelts; ++i) {
-                cert = APR_ARRAY_IDX(creds->pubcert, i, const md_cert_t *);
-                if (!md_cert_is_valid_now(cert)) {
-                    state = MD_S_ERROR;
-                    md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, 
-                                  "md{%s}: error, the certificate itself is valid, however the %d. "
-                                  "certificate in the chain is not valid now (clock wrong?).", 
-                                  md->name, i);
-                    goto out;
-                }
-            } 
-
-            state = MD_S_COMPLETE;
-            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: is complete", md->name);
+                          "md{%s}: OCSP Stapling is%s requested, but certificate "
+                          "has it%s enabled. Need to get a new certificate.", md->name,
+                          md->must_staple? "" : " not", 
+                          !md->must_staple? "" : " not");
+            goto out;
         }
+        
+        state = MD_S_COMPLETE;
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: is complete", md->name);
+    }
+    else if (APR_STATUS_IS_ENOENT(rv)) {
+        state = MD_S_INCOMPLETE;
+        rv = APR_SUCCESS;
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
+                      "md{%s}: incomplete, credentials not all there", md->name);
     }
 
 out:    
@@ -251,64 +234,10 @@ out:
         state = MD_S_ERROR;
         md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "md{%s}: error", md->name);
     }
-    
-    if (save_changes && md->state == state
-        && md->valid_from == valid_from && md->expires == expires) {
-        save_changes = 0;
-    }
     md->state = state;
-    md->valid_from = valid_from;
-    md->expires = expires;
-    if (save_changes && APR_SUCCESS == rv) {
-        return md_save(reg->store, p, MD_SG_DOMAINS, md, 0);
-    }
     return rv;
 }
 
-apr_status_t md_reg_assess(md_reg_t *reg, md_t *md, int *perrored, int *prenew, apr_pool_t *p)
-{
-    int renew = 0;
-    int errored = 0;
-    
-    (void)reg;
-    switch (md->state) {
-        case MD_S_UNKNOWN:
-            md_log_perror( MD_LOG_MARK, MD_LOG_ERR, 0, p, "md(%s): in unknown state.", md->name);
-            break;
-        case MD_S_ERROR:
-            md_log_perror( MD_LOG_MARK, MD_LOG_ERR, 0, p,  
-                         "md(%s): in error state, unable to drive forward. If unable to "
-                         " detect the cause, you may remove the staging or even domain "
-                         " sub-directory for this MD and start all over.", md->name);
-            errored = 1;
-            break;
-        case MD_S_COMPLETE:
-            if (!md->expires) {
-                md_log_perror( MD_LOG_MARK, MD_LOG_WARNING, 0, p,  
-                             "md(%s): looks complete, but has unknown expiration date.", md->name);
-                errored = 1;
-            }
-            else if (md->expires <= apr_time_now()) {
-                /* Maybe we hibernated in the meantime? */
-                md->state = MD_S_EXPIRED;
-                renew = 1;
-            }
-            else {
-                renew = md_should_renew(md);
-            }
-            break;
-        case MD_S_INCOMPLETE:
-        case MD_S_EXPIRED:
-            renew = 1;
-            break;
-        case MD_S_MISSING:
-            break;
-    }
-    *prenew = renew;
-    *perrored = errored;
-    return APR_SUCCESS;
-}
-
 /**************************************************************************************************/
 /* iteration */
 
@@ -326,7 +255,7 @@ static int reg_md_iter(void *baton, md_s
     
     (void)store;
     if (!ctx->exclude || strcmp(ctx->exclude, md->name)) {
-        state_init(ctx->reg, ptemp, (md_t*)md, 1);
+        state_init(ctx->reg, ptemp, (md_t*)md);
         return ctx->cb(ctx->baton, ctx->reg, md);
     }
     return 1;
@@ -357,12 +286,17 @@ md_t *md_reg_get(md_reg_t *reg, const ch
     md_t *md;
     
     if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, name, &md, p)) {
-        state_init(reg, p, md, 1);
+        state_init(reg, p, md);
         return md;
     }
     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;
@@ -389,7 +323,7 @@ md_t *md_reg_find(md_reg_t *reg, const c
     
     md_reg_do(find_domain, &ctx, reg, p);
     if (ctx.md) {
-        state_init(reg, p, ctx.md, 1);
+        state_init(reg, p, ctx.md);
     }
     return ctx.md;
 }
@@ -427,23 +361,11 @@ md_t *md_reg_find_overlap(md_reg_t *reg,
         *pdomain = ctx.s;
     }
     if (ctx.md) {
-        state_init(reg, p, ctx.md, 1);
+        state_init(reg, p, ctx.md);
     }
     return ctx.md;
 }
 
-apr_status_t md_reg_get_cred_files(md_reg_t *reg, const md_t *md, apr_pool_t *p,
-                                   const char **pkeyfile, const char **pcertfile)
-{
-    apr_status_t rv;
-    
-    rv = md_store_get_fname(pkeyfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PRIVKEY, p);
-    if (APR_SUCCESS == rv) {
-        rv = md_store_get_fname(pcertfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PUBCERT, p);
-    }
-    return rv;
-}
-
 /**************************************************************************************************/
 /* manipulation */
 
@@ -452,19 +374,28 @@ static apr_status_t p_md_add(void *baton
     md_reg_t *reg = baton;
     apr_status_t rv = APR_SUCCESS;
     md_t *md, *mine;
+    int do_check;
     
     md = va_arg(ap, md_t *);
+    do_check = va_arg(ap, int);
+
+    if (reg->domains_frozen) return APR_EACCES; 
     mine = md_clone(ptemp, md);
-    if (APR_SUCCESS == (rv = check_values(reg, ptemp, md, MD_UPD_ALL))
-        && APR_SUCCESS == (rv = state_init(reg, ptemp, mine, 0))
-        && APR_SUCCESS == (rv = md_save(reg->store, p, MD_SG_DOMAINS, mine, 1))) {
-    }
+    if (do_check && APR_SUCCESS != (rv = check_values(reg, ptemp, md, MD_UPD_ALL))) goto leave;
+    if (APR_SUCCESS != (rv = state_init(reg, ptemp, mine))) goto leave;
+    rv = md_save(reg->store, p, MD_SG_DOMAINS, mine, 1);
+leave:
     return rv;
 }
 
+static apr_status_t add_md(md_reg_t *reg, md_t *md, apr_pool_t *p, int do_checks)
+{
+    return md_util_pool_vdo(p_md_add, reg, p, md, do_checks, NULL);
+}
+
 apr_status_t md_reg_add(md_reg_t *reg, md_t *md, apr_pool_t *p)
 {
-    return md_util_pool_vdo(p_md_add, reg, p, md, NULL);
+    return add_md(reg, md, p, 1);
 }
 
 static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
@@ -473,12 +404,13 @@ static apr_status_t p_md_update(void *ba
     apr_status_t rv = APR_SUCCESS;
     const char *name;
     const md_t *md, *updates;
-    int fields;
+    int fields, do_checks;
     md_t *nmd;
     
     name = va_arg(ap, const char *);
     updates = va_arg(ap, const md_t *);
     fields = va_arg(ap, int);
+    do_checks = va_arg(ap, int);
     
     if (NULL == (md = md_reg_get(reg, name, ptemp))) {
         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, APR_ENOENT, ptemp, "md %s", name);
@@ -487,10 +419,11 @@ static apr_status_t p_md_update(void *ba
     
     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "update md %s", name);
     
-    if (APR_SUCCESS != (rv = check_values(reg, ptemp, updates, fields))) {
+    if (do_checks && APR_SUCCESS != (rv = check_values(reg, ptemp, updates, fields))) {
         return rv;
     }
     
+    if (reg->domains_frozen) return APR_EACCES; 
     nmd = md_copy(ptemp, md);
     if (MD_UPD_DOMAINS & fields) {
         nmd->domains = updates->domains;
@@ -516,19 +449,18 @@ static apr_status_t p_md_update(void *ba
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update agreement: %s", name);
         nmd->ca_agreement = updates->ca_agreement;
     }
-    if (MD_UPD_CERT_URL & fields) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update cert url: %s", name);
-        nmd->cert_url = updates->cert_url;
-    }
     if (MD_UPD_DRIVE_MODE & fields) {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update drive-mode: %s", name);
-        nmd->drive_mode = updates->drive_mode;
+        nmd->renew_mode = updates->renew_mode;
     }
     if (MD_UPD_RENEW_WINDOW & fields) {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update renew-window: %s", name);
-        nmd->renew_norm = updates->renew_norm;
         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;
+    }
     if (MD_UPD_CA_CHALLENGES & fields) {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update ca challenges: %s", name);
         nmd->ca_challenges = (updates->ca_challenges? 
@@ -553,83 +485,187 @@ static apr_status_t p_md_update(void *ba
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update must-staple: %s", name);
         nmd->must_staple = updates->must_staple;
     }
+    if (MD_UPD_PROTO & fields) {
+        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 (fields && APR_SUCCESS == (rv = md_save(reg->store, p, MD_SG_DOMAINS, nmd, 0))) {
-        rv = state_init(reg, ptemp, nmd, 0);
+        rv = state_init(reg, ptemp, nmd);
     }
     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)
 {
-    return md_util_pool_vdo(p_md_update, reg, p, name, md, fields, NULL);
+    return update_md(reg, p, name, md, fields, 1);
 }
 
-/**************************************************************************************************/
-/* certificate related */
-
-static int ok_or_noent(apr_status_t rv) 
+apr_status_t md_reg_delete_acct(md_reg_t *reg, apr_pool_t *p, const char *acct_id) 
 {
-    return (APR_SUCCESS == rv || APR_ENOENT == rv);
+    apr_status_t rv = APR_SUCCESS;
+    
+    rv = md_store_remove(reg->store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCOUNT, p, 1);
+    if (APR_SUCCESS == rv) {
+        md_store_remove(reg->store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCT_KEY, p, 1);
+    }
+    return rv;
 }
 
-static apr_status_t creds_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+/**************************************************************************************************/
+/* certificate related */
+
+static apr_status_t pubcert_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
 {
     md_reg_t *reg = baton;
-    md_pkey_t *privkey;
-    apr_array_header_t *pubcert;
-    md_creds_t *creds, **pcreds;
+    apr_array_header_t *certs;
+    md_pubcert_t *pubcert, **ppubcert;
     const md_t *md;
+    const md_cert_t *cert;
     md_cert_state_t cert_state;
     md_store_group_t group;
     apr_status_t rv;
     
-    pcreds = va_arg(ap, md_creds_t **);
+    ppubcert = va_arg(ap, md_pubcert_t **);
     group = (md_store_group_t)va_arg(ap, int);
     md = va_arg(ap, const md_t *);
     
-    if (ok_or_noent(rv = md_pkey_load(reg->store, group, md->name, &privkey, p))
-        && ok_or_noent(rv = md_pubcert_load(reg->store, group, md->name, &pubcert, p))) {
-        rv = APR_SUCCESS;
+    if (md->cert_file) {
+        rv = md_chain_fload(&certs, p, md->cert_file);
+    }
+    else {
+        rv = md_pubcert_load(reg->store, group, md->name, &certs, p);
+    }
+    if (APR_SUCCESS != rv) goto leave;
             
-        creds = apr_pcalloc(p, sizeof(*creds));
-        creds->privkey = privkey;
-        if (pubcert && pubcert->nelts > 0) {
-            creds->pubcert = pubcert;
-            creds->cert = APR_ARRAY_IDX(pubcert, 0, md_cert_t *);
-        }
-        if (creds->cert) {
-            switch ((cert_state = md_cert_state_get(creds->cert))) {
-                case MD_CERT_VALID:
-                    creds->expired = 0;
-                    break;
-                case MD_CERT_EXPIRED:
-                    creds->expired = 1;
-                    break;
-                default:
-                    md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, ptemp, 
-                                  "md %s has unexpected cert state: %d", md->name, cert_state);
-                    rv = APR_ENOTIMPL;
-                    break;
-            }
-        }
+    pubcert = apr_pcalloc(p, sizeof(*pubcert));
+    pubcert->certs = certs;
+    cert = APR_ARRAY_IDX(certs, 0, const md_cert_t *);
+    if (APR_SUCCESS != (rv = md_cert_get_alt_names(&pubcert->alt_names, cert, p))) goto leave;
+    switch ((cert_state = md_cert_state_get(cert))) {
+        case MD_CERT_VALID:
+        case MD_CERT_EXPIRED:
+            break;
+        default:
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, ptemp, 
+                          "md %s has unexpected cert state: %d", md->name, cert_state);
+            rv = APR_ENOTIMPL;
+            break;
     }
-    *pcreds = (APR_SUCCESS == rv)? creds : NULL;
+leave:
+    *ppubcert = (APR_SUCCESS == rv)? pubcert : NULL;
     return rv;
 }
 
-apr_status_t md_reg_creds_get(const md_creds_t **pcreds, md_reg_t *reg, 
-                              md_store_group_t group, const md_t *md, apr_pool_t *p)
+apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg, 
+                                const md_t *md, apr_pool_t *p)
 {
     apr_status_t rv = APR_SUCCESS;
-    md_creds_t *creds;
-    
-    rv = md_util_pool_vdo(creds_load, reg, p, &creds, group, md, NULL);
-    *pcreds = (APR_SUCCESS == rv)? creds : NULL;
+    const md_pubcert_t *pubcert;
+    const char *name;
+
+    pubcert = apr_hash_get(reg->certs, md->name, (apr_ssize_t)strlen(md->name));
+    if (!pubcert && !reg->domains_frozen) {
+        rv = md_util_pool_vdo(pubcert_load, reg, reg->p, &pubcert, MD_SG_DOMAINS, md, NULL);
+        if (APR_STATUS_IS_ENOENT(rv)) {
+            /* We cache it missing with an empty record */
+            pubcert = apr_pcalloc(reg->p, sizeof(*pubcert));
+        }
+        else if (APR_SUCCESS != rv) goto leave;
+        name = (p != reg->p)? apr_pstrdup(reg->p, md->name) : md->name;
+        apr_hash_set(reg->certs, name, (apr_ssize_t)strlen(name), pubcert);
+    }
+leave:
+    if (APR_SUCCESS == rv && (!pubcert || !pubcert->certs)) {
+        rv = APR_ENOENT;
+    }
+    *ppubcert = (APR_SUCCESS == rv)? pubcert : NULL;
     return rv;
 }
 
+apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile,
+                                   md_reg_t *reg, md_store_group_t group, 
+                                   const md_t *md, apr_pool_t *p)
+{
+    apr_status_t rv;
+    
+    if (md->cert_file) {
+        /* With fixed files configured, we use those without further checking them ourself */
+        *pcertfile = md->cert_file;
+        *pkeyfile = md->pkey_file;
+        return APR_SUCCESS;
+    }
+    rv = md_store_get_fname(pkeyfile, reg->store, group, md->name, MD_FN_PRIVKEY, p);
+    if (APR_SUCCESS != rv) return rv;
+    if (!md_file_exists(*pkeyfile, p)) return APR_ENOENT;
+    rv = md_store_get_fname(pcertfile, reg->store, group, md->name, MD_FN_PUBCERT, p);
+    if (APR_SUCCESS != rv) return rv;
+    if (!md_file_exists(*pcertfile, p)) return APR_ENOENT;
+    return APR_SUCCESS;
+}
+
+int md_reg_should_renew(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;
+    rv = md_reg_get_pubcert(&pub, reg, md, p);
+    if (APR_STATUS_IS_ENOENT(rv)) return 1;
+    if (APR_SUCCESS == rv) {
+        cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+        certlife.start = md_cert_get_not_before(cert);
+        certlife.end = md_cert_get_not_after(cert);
+
+        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[%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 0;
+}
+
+int md_reg_should_warn(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, warn;
+    apr_status_t rv;
+    
+    if (md->state == MD_S_INCOMPLETE) return 0;
+    rv = md_reg_get_pubcert(&pub, reg, md, p);
+    if (APR_STATUS_IS_ENOENT(rv)) return 0;
+    if (APR_SUCCESS == rv) {
+        cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+        certlife.start = md_cert_get_not_before(cert);
+        certlife.end = md_cert_get_not_after(cert);
+
+        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[%s]: cert-life[%s] warn[%s]", md->name, 
+                          md_timeperiod_print(p, &certlife),
+                          md_timeperiod_print(p, &warn));
+        }
+        return md_timeperiod_has_started(&warn, apr_time_now());
+    }
+    return 0;
+}
+
 /**************************************************************************************************/
 /* synching */
 
@@ -654,7 +690,7 @@ static apr_status_t read_store_mds(md_re
     
     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)) {
+    if (APR_STATUS_IS_ENOENT(rv) || APR_STATUS_IS_EINVAL(rv)) {
         rv = APR_SUCCESS;
     }
     return rv;
@@ -665,6 +701,7 @@ apr_status_t md_reg_set_props(md_reg_t *
     if (reg->can_http != can_http || reg->can_https != can_https) {
         md_json_t *json;
         
+        if (reg->domains_frozen) return APR_EACCES; 
         reg->can_http = can_http;
         reg->can_https = can_https;
         
@@ -676,6 +713,10 @@ 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:
@@ -700,11 +741,12 @@ apr_status_t md_reg_sync(md_reg_t *reg,
     apr_status_t rv;
 
     ctx.p = ptemp;
-    ctx.store_mds = apr_array_make(ptemp,100, sizeof(md_t *));
+    ctx.store_mds = apr_array_make(ptemp, 100, sizeof(md_t *));
     rv = read_store_mds(reg, &ctx);
     
     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;
@@ -718,8 +760,11 @@ apr_status_t md_reg_sync(md_reg_t *reg,
             if (smd) {
                 fields = 0;
                 
-                /* Once stored, we keep the name */
+                /* 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);
                 }
                 
@@ -758,11 +803,17 @@ apr_status_t md_reg_sync(md_reg_t *reg,
                             md_reg_remove(reg, ptemp, omd->name, 1); /* best effort */
                         }
                         else {
-                            rv = md_reg_update(reg, ptemp, omd->name, omd, MD_UPD_DOMAINS);
+                            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;
@@ -779,8 +830,8 @@ apr_status_t md_reg_sync(md_reg_t *reg,
                     smd->transitive = md->transitive;
                     fields |= MD_UPD_TRANSITIVE;
                 }
-                if (MD_VAL_UPDATE(md, smd, drive_mode)) {
-                    smd->drive_mode = md->drive_mode;
+                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) 
@@ -788,15 +839,14 @@ apr_status_t md_reg_sync(md_reg_t *reg,
                     smd->contacts = md->contacts;
                     fields |= MD_UPD_CONTACTS;
                 }
-                if (MD_VAL_UPDATE(md, smd, renew_window) 
-                    || MD_VAL_UPDATE(md, smd, renew_norm)) {
-                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                                  "%s: update renew norm=%ld, window=%ld", 
-                                  smd->name, (long)md->renew_norm, (long)md->renew_window);
-                    smd->renew_norm = md->renew_norm;
+                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 
@@ -824,15 +874,24 @@ apr_status_t md_reg_sync(md_reg_t *reg,
                     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 = md_reg_update(reg, ptemp, smd->name, smd, 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 */
-                rv = md_reg_add(reg, md, ptemp);
+                /* 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);
             }
         }
@@ -846,159 +905,257 @@ apr_status_t md_reg_sync(md_reg_t *reg,
 
 apr_status_t md_reg_remove(md_reg_t *reg, apr_pool_t *p, const char *name, int archive)
 {
+    if (reg->domains_frozen) return APR_EACCES; 
     return md_store_move(reg->store, p, MD_SG_DOMAINS, MD_SG_ARCHIVE, name, archive);
 }
 
+typedef struct {
+    md_reg_t *reg;
+    apr_pool_t *p;
+    apr_array_header_t *mds;
+} cleanup_challenge_ctx;
+ 
+static apr_status_t cleanup_challenge_inspector(void *baton, const char *dir, const char *name, 
+                                                md_store_vtype_t vtype, void *value, 
+                                                apr_pool_t *ptemp)
+{
+    cleanup_challenge_ctx *ctx = baton;
+    const md_t *md;
+    int i, used;
+    apr_status_t rv;
+    
+    (void)value;
+    (void)vtype;
+    (void)dir;
+    for (used = 0, i = 0; i < ctx->mds->nelts && !used; ++i) {
+        md = APR_ARRAY_IDX(ctx->mds, i, const md_t *);
+        used = !strcmp(name, md->name);
+    }
+    if (!used) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, 
+                      "challenges/%s: not in use, purging", name);
+        rv = md_store_purge(ctx->reg->store, ctx->p, MD_SG_CHALLENGES, name);
+        if (APR_SUCCESS != rv) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, ptemp, 
+                          "challenges/%s: unable to purge", name);
+        }
+    }
+    return APR_SUCCESS;
+}
+
+apr_status_t md_reg_cleanup_challenges(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, 
+                                       apr_array_header_t *mds)
+{
+    apr_status_t rv;
+    cleanup_challenge_ctx ctx;
+
+    (void)p;
+    ctx.reg = reg;
+    ctx.p = ptemp;
+    ctx.mds = mds;
+    rv = md_store_iter_names(cleanup_challenge_inspector, &ctx, reg->store, ptemp, 
+                             MD_SG_CHALLENGES, "*");
+    return rv;
+}
+
 
 /**************************************************************************************************/
 /* driving */
 
-static apr_status_t init_proto_driver(md_proto_driver_t *driver, const md_proto_t *proto, 
-                                      md_reg_t *reg, const md_t *md, 
-                                      const char *challenge, int reset, apr_pool_t *p) 
+static apr_status_t run_init(void *baton, apr_pool_t *p, ...)
 {
-    apr_status_t rv = APR_SUCCESS;
+    va_list ap;
+    md_reg_t *reg = baton;
+    const md_t *md;
+    md_proto_driver_t *driver, **pdriver;
+    md_result_t *result;
+    apr_table_t *env;
+    
+    (void)p;
+    va_start(ap, p);
+    pdriver = va_arg(ap, md_proto_driver_t **);
+    md = va_arg(ap, const md_t *);
+    env = va_arg(ap, apr_table_t *);
+    result = va_arg(ap, md_result_t *); 
+    va_end(ap);
+    
+    *pdriver = driver = apr_pcalloc(p, sizeof(*driver));
 
-    /* If this registry instance was not synched before (and obtained server
-     * properties that way), read them from the store.
-     */
-    driver->proto = proto;
     driver->p = p;
-    driver->challenge = challenge;
-    driver->can_http = reg->can_http;
-    driver->can_https = reg->can_https;
+    driver->env = env? apr_table_copy(p, env) : apr_table_make(p, 10);
     driver->reg = reg;
     driver->store = md_reg_store_get(reg);
     driver->proxy_url = reg->proxy_url;
     driver->md = md;
-    driver->reset = reset;
+    driver->can_http = reg->can_http;
+    driver->can_https = reg->can_https;
 
-    return rv;
+    if (!md->ca_proto) {
+        md_result_printf(result, APR_EGENERAL, "CA protocol is not defined"); 
+        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "md[%s]: %s", md->name, result->detail);
+        goto leave;
+    }
+    
+    driver->proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto));
+    if (!driver->proto) {
+        md_result_printf(result, APR_EGENERAL, "Unknown CA protocol '%s'", md->ca_proto); 
+        goto leave;
+    }
+    
+    result->status = driver->proto->init(driver, result);
+
+leave:
+    if (APR_SUCCESS != result->status) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, result->status, p, "md[%s]: %s", md->name, 
+                      result->detail? result->detail : "<see error log for details>");
+    }
+    else {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: init done", md->name);
+    }
+    return result->status;
 }
 
-static apr_status_t run_stage(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+static apr_status_t run_test_init(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+    const md_t *md;
+    apr_table_t *env;
+    md_result_t *result;
+    md_proto_driver_t *driver;
+    
+    (void)p;
+    md = va_arg(ap, const md_t *);
+    env = va_arg(ap, apr_table_t *);
+    result = va_arg(ap, md_result_t *); 
+
+    return run_init(baton, ptemp, &driver, md, env, result, NULL);
+}
+
+apr_status_t md_reg_test_init(md_reg_t *reg, const md_t *md, struct apr_table_t *env, 
+                              md_result_t *result, apr_pool_t *p)
+{
+    return md_util_pool_vdo(run_test_init, reg, p, md, env, result, NULL);
+}
+
+static apr_status_t run_renew(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
 {
-    md_reg_t *reg = baton;
-    const md_proto_t *proto;
     const md_t *md;
     int reset;
     md_proto_driver_t *driver;
-    const char *challenge;
-    apr_time_t *pvalid_from;
+    apr_table_t *env;
     apr_status_t rv;
+    md_result_t *result;
     
     (void)p;
-    proto = va_arg(ap, const md_proto_t *);
     md = va_arg(ap, const md_t *);
-    challenge = va_arg(ap, const char *);
+    env = va_arg(ap, apr_table_t *);
     reset = va_arg(ap, int); 
-    pvalid_from = va_arg(ap, apr_time_t*);
-    
-    driver = apr_pcalloc(ptemp, sizeof(*driver));
-    rv = init_proto_driver(driver, proto, reg, md, challenge, reset, ptemp);
-    if (APR_SUCCESS == rv && 
-        APR_SUCCESS == (rv = proto->init(driver))) {
-        
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run staging", md->name);
-        rv = proto->stage(driver);
+    result = va_arg(ap, md_result_t *); 
 
-        if (APR_SUCCESS == rv && pvalid_from) {
-            *pvalid_from = driver->stage_valid_from;
-        }
+    rv = run_init(baton, ptemp, &driver, md, 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;
+        rv = driver->proto->renew(driver, result);
     }
     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: staging done", md->name);
     return rv;
 }
 
-apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md, const char *challenge, 
-                          int reset, apr_time_t *pvalid_from, apr_pool_t *p)
+apr_status_t md_reg_renew(md_reg_t *reg, const md_t *md, apr_table_t *env, 
+                          int reset, md_result_t *result, apr_pool_t *p)
 {
-    const md_proto_t *proto;
-    
-    if (!md->ca_proto) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "md %s has no CA protocol", md->name);
-        ((md_t *)md)->state = MD_S_ERROR;
-        return APR_SUCCESS;
-    }
-    
-    proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto));
-    if (!proto) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, 
-                      "md %s has unknown CA protocol: %s", md->name, md->ca_proto);
-        ((md_t *)md)->state = MD_S_ERROR;
-        return APR_EINVAL;
-    }
-    
-    return md_util_pool_vdo(run_stage, reg, p, proto, md, challenge, reset, pvalid_from, NULL);
+    return md_util_pool_vdo(run_renew, reg, p, md, env, reset, result, NULL);
 }
 
-static apr_status_t run_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+static apr_status_t run_load_staging(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
 {
     md_reg_t *reg = baton;
-    const char *name;
-    const md_proto_t *proto;
-    const md_t *md, *nmd;
+    const md_t *md;
     md_proto_driver_t *driver;
+    md_result_t *result;
+    apr_table_t *env;
+    md_job_t *job;
     apr_status_t rv;
     
-    name = va_arg(ap, const char *);
+    /* For the MD,  check if something is in the STAGING area. If none is there, 
+     * return that status. Otherwise ask the protocol driver to preload it into
+     * a new, temporary area. 
+     * If that succeeds, we move the TEMP area over the DOMAINS (causing the 
+     * existing one go to ARCHIVE).
+     * Finally, we clean up the data from CHALLENGES and STAGING.
+     */
+    md = va_arg(ap, const md_t*);
+    env =  va_arg(ap, apr_table_t*);
+    result =  va_arg(ap, md_result_t*);
     
-    if (APR_STATUS_IS_ENOENT(rv = md_load(reg->store, MD_SG_STAGING, name, NULL, ptemp))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp, "%s: nothing staged", name);
-        return APR_ENOENT;
+    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);
+        goto out;
     }
     
-    md = md_reg_get(reg, name, p);
-    if (!md) {
-        return APR_ENOENT;
-    }
+    rv = run_init(baton, ptemp, &driver, md, env, result, NULL);
+    if (APR_SUCCESS != rv) goto out;
     
-    if (!md->ca_proto) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "md %s has no CA protocol", name);
-        ((md_t *)md)->state = MD_S_ERROR;
-        return APR_EINVAL;
+    apr_hash_set(reg->certs, md->name, (apr_ssize_t)strlen(md->name), NULL);
+    md_result_activity_setn(result, "preloading staged to tmp");
+    rv = driver->proto->preload(driver, MD_SG_TMP, result);
+    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);
     }
     
-    proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto));
-    if (!proto) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, 
-                      "md %s has unknown CA protocol: %s", md->name, md->ca_proto);
-        ((md_t *)md)->state = MD_S_ERROR;
-        return APR_EINVAL;
+    /* swap */
+    md_result_activity_setn(result, "moving tmp to become new domains");
+    rv = md_store_move(reg->store, p, MD_SG_TMP, MD_SG_DOMAINS, md->name, 1);
+    if (APR_SUCCESS != rv) {
+        md_result_set(result, rv, NULL);
+        goto out;
     }
     
-    driver = apr_pcalloc(ptemp, sizeof(*driver));
-    init_proto_driver(driver, proto, reg, md, NULL, 0, ptemp);
+    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");
 
-    if (APR_SUCCESS == (rv = proto->init(driver))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run load", md->name);
-        
-        if (APR_SUCCESS == (rv = proto->preload(driver, MD_SG_TMP))) {
-            /* swap */
-            rv = md_store_move(reg->store, p, MD_SG_TMP, MD_SG_DOMAINS, md->name, 1);
-            if (APR_SUCCESS == rv) {
-                /* load again */
-                nmd = md_reg_get(reg, md->name, p);
-                if (!nmd) {
-                    rv = APR_ENOENT;
-                    md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "loading md after staging");
-                }
-                else if (nmd->state != MD_S_COMPLETE) {
-                    md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, 
-                                  "md has state %d after load", nmd->state);
-                }
-                
-                md_store_purge(reg->store, p, MD_SG_STAGING, md->name);
-                md_store_purge(reg->store, p, MD_SG_CHALLENGES, md->name);
-            }
-        }
-    }
+out:
     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: load done", md->name);
     return rv;
 }
 
-apr_status_t md_reg_load(md_reg_t *reg, const char *name, apr_pool_t *p)
+apr_status_t md_reg_load_staging(md_reg_t *reg, const md_t *md, apr_table_t *env, 
+                                 md_result_t *result, apr_pool_t *p)
 {
-    return md_util_pool_vdo(run_load, reg, p, name, NULL);
+    if (reg->domains_frozen) return APR_EACCES;
+    return md_util_pool_vdo(run_load_staging, reg, p, md, env, result, NULL);
 }
 
+apr_status_t md_reg_freeze_domains(md_reg_t *reg, apr_array_header_t *mds)
+{
+    apr_status_t rv = APR_SUCCESS;
+    md_t *md;
+    const md_pubcert_t *pubcert;
+    int i;
+    
+    assert(!reg->domains_frozen);
+    /* prefill the certs cache for all mds */
+    for (i = 0; i < mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mds, i, md_t*);
+        rv = md_reg_get_pubcert(&pubcert, reg, md, reg->p);
+        if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) goto leave;
+    }
+    reg->domains_frozen = 1;
+leave:
+    return rv;
+}
+
+void md_reg_set_renew_window_default(md_reg_t *reg, const md_timeslice_t *renew_window)
+{
+    reg->renew_window = renew_window;
+}
+
+void md_reg_set_warn_window_default(md_reg_t *reg, const md_timeslice_t *warn_window)
+{
+    reg->warn_window = warn_window;
+}

Modified: httpd/httpd/trunk/modules/md/md_reg.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_reg.h?rev=1862013&r1=1862012&r2=1862013&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_reg.h (original)
+++ httpd/httpd/trunk/modules/md/md_reg.h Mon Jun 24 16:04:32 2019
@@ -22,6 +22,7 @@ struct apr_array_header_t;
 struct md_store_t;
 struct md_pkey_t;
 struct md_cert_t;
+struct md_result_t;
 
 /**
  * A registry for managed domains with a md_store_t as persistence.
@@ -30,11 +31,10 @@ struct md_cert_t;
 typedef struct md_reg_t md_reg_t;
 
 /**
- * Initialize the registry, using the pool and loading any existing information
- * from the store.
+ * Create the MD registry, using the pool and store.
  */
-apr_status_t md_reg_init(md_reg_t **preg, apr_pool_t *pm, struct md_store_t *store,
-                         const char *proxy_url);
+apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *pm, struct md_store_t *store,
+                           const char *proxy_url);
 
 struct md_store_t *md_reg_store_get(md_reg_t *reg);
 
@@ -66,9 +66,9 @@ 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);
 
 /**
- * Assess the capability and need to driving this managed domain.
+ * Re-compute the state of the MD, given current store contents.
  */
-apr_status_t md_reg_assess(md_reg_t *reg, md_t *md, int *perrored, int *prenew, apr_pool_t *p);
+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.
@@ -91,7 +91,6 @@ int md_reg_do(md_reg_do_cb *cb, void *ba
 #define MD_UPD_CA_ACCOUNT    0x0008
 #define MD_UPD_CONTACTS      0x0010
 #define MD_UPD_AGREEMENT     0x0020
-#define MD_UPD_CERT_URL      0x0040
 #define MD_UPD_DRIVE_MODE    0x0080
 #define MD_UPD_RENEW_WINDOW  0x0100
 #define MD_UPD_CA_CHALLENGES 0x0200
@@ -99,6 +98,8 @@ int md_reg_do(md_reg_do_cb *cb, void *ba
 #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_ALL           0x7FFFFFFF
 
 /**
@@ -109,14 +110,19 @@ apr_status_t md_reg_update(md_reg_t *reg
                            const char *name, const md_t *md, int fields);
 
 /**
- * Get the credentials available for the managed domain md. Returns APR_ENOENT
- * when none is available. The returned values are immutable. 
+ * Get the chain of public certificates of the managed domain md, starting with the cert
+ * of the domain and going up the issuers. Returns APR_ENOENT when not available. 
  */
-apr_status_t md_reg_creds_get(const md_creds_t **pcreds, md_reg_t *reg, 
-                              md_store_group_t group, const md_t *md, apr_pool_t *p);
+apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg, 
+                                const md_t *md, apr_pool_t *p);
 
-apr_status_t md_reg_get_cred_files(md_reg_t *reg, const md_t *md, apr_pool_t *p,
-                                   const char **pkeyfile, const char **pcertfile);
+/**
+ * Get the filenames of private key and pubcert of the MD - if they exist.
+ * @return APR_ENOENT if one or both do not exist.
+ */
+apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile,
+                                   md_reg_t *reg, md_store_group_t group, 
+                                   const md_t *md, apr_pool_t *p);
 
 /**
  * Synchronise the give master mds with the store.
@@ -126,6 +132,44 @@ apr_status_t md_reg_sync(md_reg_t *reg,
 
 apr_status_t md_reg_remove(md_reg_t *reg, apr_pool_t *p, const char *name, int archive);
 
+/**
+ * Delete the account from the local store.
+ */
+apr_status_t md_reg_delete_acct(md_reg_t *reg, apr_pool_t *p, const char *acct_id);
+
+
+/**
+ * Cleanup any challenges that are no longer in use.
+ * 
+ * @param reg   the registry
+ * @param p     pool for permament storage
+ * @param ptemp pool for temporary storage
+ * @param mds   the list of configured MDs
+ */
+apr_status_t md_reg_cleanup_challenges(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, 
+                                       apr_array_header_t *mds);
+
+/**
+ * Mark all information from group MD_SG_DOMAINS as readonly, deny future modifications 
+ * (MD_SG_STAGING and MD_SG_CHALLENGES remain writeable). For the given MDs, cache
+ * the public information (MDs themselves and their pubcerts or lack of).
+ */
+apr_status_t md_reg_freeze_domains(md_reg_t *reg, apr_array_header_t *mds);
+
+/**
+ * Return if the certificate of the MD shoud be renewed. This includes reaching
+ * the renewal window of an otherwise valid certificate. It return also !0 iff
+ * no certificate has been obtained yet.
+ */
+int md_reg_should_renew(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.
+ */
+int md_reg_should_warn(md_reg_t *reg, const md_t *md, apr_pool_t *p);
+
 /**************************************************************************************************/
 /* protocol drivers */
 
@@ -133,47 +177,69 @@ typedef struct md_proto_t md_proto_t;
 
 typedef struct md_proto_driver_t md_proto_driver_t;
 
+/** 
+ * Operating environment for a protocol driver. This is valid only for the
+ * duration of one run (init + renew, init + preload).
+ */
 struct md_proto_driver_t {
     const md_proto_t *proto;
     apr_pool_t *p;
-    const char *challenge;
-    int can_http;
-    int can_https;
-    struct md_store_t *store;
+    void *baton;
+    struct apr_table_t *env;
+
     md_reg_t *reg;
+    struct md_store_t *store;
+    const char *proxy_url;
     const md_t *md;
-    void *baton;
+
+    int can_http;
+    int can_https;
     int reset;
-    apr_time_t stage_valid_from;
-    const char *proxy_url;
 };
 
-typedef apr_status_t md_proto_init_cb(md_proto_driver_t *driver);
-typedef apr_status_t md_proto_stage_cb(md_proto_driver_t *driver);
-typedef apr_status_t md_proto_preload_cb(md_proto_driver_t *driver, md_store_group_t group);
+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_preload_cb(md_proto_driver_t *driver, 
+                                         md_store_group_t group, struct md_result_t *result);
 
 struct md_proto_t {
     const char *protocol;
     md_proto_init_cb *init;
-    md_proto_stage_cb *stage;
+    md_proto_renew_cb *renew;
     md_proto_preload_cb *preload;
 };
 
+/**
+ * Run a test intialization of the renew protocol for the given MD. This verifies
+ * basic parameter settings and is expected to return a description of encountered
+ * problems in <pmessage> when != APR_SUCCESS.
+ * A message return is allocated fromt the given pool.
+ */
+apr_status_t md_reg_test_init(md_reg_t *reg, const md_t *md, struct apr_table_t *env, 
+                              struct md_result_t *result, apr_pool_t *p);
 
 /**
- * Stage a new credentials set for the given managed domain in a separate location
- * without interfering with any existing credentials.
+ * Obtain new credentials for the given managed domain in STAGING.
+ *
+ * @return APR_SUCCESS if new credentials have been staged successfully
  */
-apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md, 
-                          const char *challenge, int reset, 
-                          apr_time_t *pvalid_from, apr_pool_t *p);
+apr_status_t md_reg_renew(md_reg_t *reg, const md_t *md, 
+                          struct apr_table_t *env, int reset, 
+                          struct md_result_t *result, apr_pool_t *p);
 
 /**
- * Load a staged set of new credentials for the managed domain. This will archive
- * any existing credential data and make the staged set the new live one.
+ * Load a new set of credentials for the managed domain from STAGING - if it exists. 
+ * This will archive any existing credential data and make the staged set the new one
+ * in DOMAINS.
  * If staging is incomplete or missing, the load will fail and all credentials remain
  * as they are.
+ *
+ * @return APR_SUCCESS on loading new data, APR_ENOENT when nothing is staged, error otherwise.
  */
-apr_status_t md_reg_load(md_reg_t *reg, const char *name, apr_pool_t *p);
+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);
 
 #endif /* mod_md_md_reg_h */

Added: httpd/httpd/trunk/modules/md/md_result.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_result.c?rev=1862013&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/md/md_result.c (added)
+++ httpd/httpd/trunk/modules/md/md_result.c Mon Jun 24 16:04:32 2019
@@ -0,0 +1,245 @@
+/* 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 <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_date.h>
+#include <apr_time.h>
+#include <apr_strings.h>
+
+#include "md.h"
+#include "md_json.h"
+#include "md_log.h"
+#include "md_result.h"
+
+static const char *dup_trim(apr_pool_t *p, const char *s)
+{
+    char *d = apr_pstrdup(p, s);
+    apr_collapse_spaces(d, d);
+    return d;
+}
+
+md_result_t *md_result_make(apr_pool_t *p, apr_status_t status)
+{
+    md_result_t *result;
+    
+    result = apr_pcalloc(p, sizeof(*result));
+    result->p = p;
+    result->status = status;
+    return result;
+}
+
+md_result_t *md_result_md_make(apr_pool_t *p, const struct md_t *md)
+{
+    md_result_t *result = md_result_make(p, APR_SUCCESS);
+    result->md = md;
+    return result;
+}
+
+void md_result_reset(md_result_t *result)
+{
+    apr_pool_t *p = result->p;
+    memset(result, 0, sizeof(*result));
+    result->p = p;
+}
+
+static void on_change(md_result_t *result)
+{
+    if (result->on_change) result->on_change(result, result->on_change_data);
+}
+
+void md_result_activity_set(md_result_t *result, const char *activity)
+{
+    md_result_activity_setn(result, activity? apr_pstrdup(result->p, activity) : NULL);
+}
+
+void md_result_activity_setn(md_result_t *result, const char *activity)
+{
+    result->activity = activity;
+    result->problem = result->detail = NULL;
+    on_change(result);
+}
+
+void md_result_activity_printf(md_result_t *result, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    md_result_activity_setn(result, apr_pvsprintf(result->p, fmt, ap));
+    va_end(ap);
+}
+
+void md_result_set(md_result_t *result, apr_status_t status, const char *detail)
+{
+    result->status = status;
+    result->problem = NULL;
+    result->detail = detail? apr_pstrdup(result->p, detail) : NULL;
+    on_change(result);
+}
+
+void md_result_problem_set(md_result_t *result, apr_status_t status,
+                           const char *problem, const char *detail)
+{
+    result->status = status;
+    result->problem = dup_trim(result->p, problem);
+    result->detail = apr_pstrdup(result->p, detail);
+    on_change(result);
+}
+
+void md_result_problem_printf(md_result_t *result, apr_status_t status,
+                              const char *problem, const char *fmt, ...)
+{
+    va_list ap;
+
+    result->status = status;
+    result->problem = dup_trim(result->p, problem);
+
+    va_start(ap, fmt);
+    result->detail = apr_pvsprintf(result->p, fmt, ap);
+    va_end(ap);
+    on_change(result);
+}
+
+void md_result_printf(md_result_t *result, apr_status_t status, const char *fmt, ...)
+{
+    va_list ap;
+
+    result->status = status;
+    va_start(ap, fmt);
+    result->detail = apr_pvsprintf(result->p, fmt, ap);
+    va_end(ap);
+    on_change(result);
+}
+
+void md_result_delay_set(md_result_t *result, apr_time_t ready_at)
+{
+    result->ready_at = ready_at;
+    on_change(result);
+}
+
+md_result_t*md_result_from_json(const struct md_json_t *json, apr_pool_t *p)
+{
+    md_result_t *result;
+    const char *s;
+    
+    result = md_result_make(p, APR_SUCCESS);
+    result->status = (int)md_json_getl(json, MD_KEY_STATUS, NULL);
+    result->problem = md_json_dups(p, json, MD_KEY_PROBLEM, NULL);
+    result->detail = md_json_dups(p, json, MD_KEY_DETAIL, NULL);
+    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);
+
+    return result;
+}
+
+struct md_json_t *md_result_to_json(const md_result_t *result, apr_pool_t *p)
+{
+    md_json_t *json;
+    char ts[APR_RFC822_DATE_LEN];
+   
+    json = md_json_create(p);
+    md_json_setl(result->status, json, MD_KEY_STATUS, NULL);
+    if (result->status > 0) {
+        char buffer[HUGE_STRING_LEN];
+        apr_strerror(result->status, buffer, sizeof(buffer));
+        md_json_sets(buffer, json, "status-description", NULL);
+    }
+    if (result->problem) md_json_sets(result->problem, json, MD_KEY_PROBLEM, NULL);
+    if (result->detail) md_json_sets(result->detail, json, MD_KEY_DETAIL, NULL);
+    if (result->activity) md_json_sets(result->activity, json, MD_KEY_ACTIVITY, NULL);
+    if (result->ready_at > 0) {
+        apr_rfc822_date(ts, result->ready_at);
+        md_json_sets(ts, json, MD_KEY_VALID_FROM, NULL);
+    }
+    return json;
+}
+
+static int str_cmp(const char *s1, const char *s2)
+{
+    if (s1 == s2) return 0;
+    if (!s1) return -1;
+    if (!s2) return 1;
+    return strcmp(s1, s2);
+}
+
+int md_result_cmp(const md_result_t *r1, const md_result_t *r2)
+{
+    int n;
+    if (r1 == r2) return 0;
+    if (!r1) return -1;
+    if (!r2) return 1;
+    if ((n = r1->status - r2->status)) return n;
+    if ((n = str_cmp(r1->problem, r2->problem))) return n;
+    if ((n = str_cmp(r1->detail, r2->detail))) return n;
+    if ((n = str_cmp(r1->activity, r2->activity))) return n;
+    return (int)(r1->ready_at - r2->ready_at);
+}
+
+void md_result_assign(md_result_t *dest, const md_result_t *src)
+{
+   dest->status = src->status;
+   dest->problem = src->problem;
+   dest->detail = src->detail;
+   dest->activity = src->activity;
+   dest->ready_at = src->ready_at;
+}
+
+void md_result_dup(md_result_t *dest, const md_result_t *src)
+{
+   dest->status = src->status;
+   dest->problem = src->problem? dup_trim(dest->p, src->problem) : NULL; 
+   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;
+   on_change(dest);
+}
+
+void md_result_log(md_result_t *result, 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);
+            sep = " ";
+        }
+        if (result->activity) {
+            msg = apr_psprintf(result->p, "%s%swhile[%s]", msg, sep, result->activity);
+            sep = " ";
+        }
+        if (result->problem) {
+            msg = apr_psprintf(result->p, "%s%sproblem[%s]", msg, sep, result->problem);
+            sep = " ";
+        }
+        if (result->detail) {
+            msg = apr_psprintf(result->p, "%s%sdetail[%s]", msg, sep, result->detail);
+            sep = " ";
+        }
+        md_log_perror(MD_LOG_MARK, (md_log_level_t)level, result->status, result->p, "%s", msg);
+    }
+}
+
+void md_result_on_change(md_result_t *result, md_result_change_cb *cb, void *data)
+{
+    result->on_change = cb;
+    result->on_change_data = data;
+}

Added: httpd/httpd/trunk/modules/md/md_result.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_result.h?rev=1862013&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/md/md_result.h (added)
+++ httpd/httpd/trunk/modules/md/md_result.h Mon Jun 24 16:04:32 2019
@@ -0,0 +1,71 @@
+/* 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 mod_md_md_result_h
+#define mod_md_md_result_h
+
+struct md_json_t;
+struct md_t;
+
+typedef struct md_result_t md_result_t;
+
+typedef void md_result_change_cb(md_result_t *result, void *data);
+
+struct md_result_t {
+    apr_pool_t *p;
+    const struct md_t *md;
+    apr_status_t status;
+    const char *problem;
+    const char *detail;
+    const char *activity;
+    apr_time_t ready_at;
+    md_result_change_cb *on_change;
+    void *on_change_data;
+};
+
+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);
+void md_result_reset(md_result_t *result);
+
+void md_result_activity_set(md_result_t *result, const char *activity);
+void md_result_activity_setn(md_result_t *result, const char *activity);
+void md_result_activity_printf(md_result_t *result, const char *fmt, ...);
+
+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);
+void md_result_problem_printf(md_result_t *result, apr_status_t status,
+                              const char *problem, const char *fmt, ...);
+
+#define MD_RESULT_LOG_ID(logno)       "urn:org:apache:httpd:log:"logno
+
+void md_result_printf(md_result_t *result, apr_status_t status, const char *fmt, ...);
+
+void md_result_delay_set(md_result_t *result, apr_time_t ready_at);
+
+md_result_t*md_result_from_json(const struct md_json_t *json, apr_pool_t *p);
+struct md_json_t *md_result_to_json(const md_result_t *result, apr_pool_t *p);
+
+int md_result_cmp(const md_result_t *r1, const md_result_t *r2);
+
+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_on_change(md_result_t *result, md_result_change_cb *cb, void *data);
+
+#endif /* mod_md_md_result_h */

Added: httpd/httpd/trunk/modules/md/md_status.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_status.c?rev=1862013&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/md/md_status.c (added)
+++ httpd/httpd/trunk/modules/md/md_status.c Mon Jun 24 16:04:32 2019
@@ -0,0 +1,364 @@
+/* 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 <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+#include <apr_time.h>
+#include <apr_date.h>
+
+#include "md_json.h"
+#include "md.h"
+#include "md_crypt.h"
+#include "md_log.h"
+#include "md_store.h"
+#include "md_result.h"
+#include "md_reg.h"
+#include "md_util.h"
+#include "md_status.h"
+
+#define MD_STATUS_WITH_SCTS     0
+
+/**************************************************************************************************/
+/* certificate status information */
+
+static apr_status_t status_get_cert_json(md_json_t **pjson, const md_cert_t *cert, apr_pool_t *p)
+{
+    char ts[APR_RFC822_DATE_LEN];
+    const char *finger;
+    apr_status_t rv = APR_SUCCESS;
+    md_json_t *json;
+    
+    json = md_json_create(p);
+    apr_rfc822_date(ts, md_cert_get_not_before(cert));
+    md_json_sets(ts, json, MD_KEY_VALID_FROM, NULL);
+    apr_rfc822_date(ts, md_cert_get_not_after(cert));
+    md_json_sets(ts, json, MD_KEY_VALID_UNTIL, NULL);
+    md_json_sets(md_cert_get_serial_number(cert, p), json, MD_KEY_SERIAL, NULL);
+    if (APR_SUCCESS != (rv = md_cert_to_sha256_fingerprint(&finger, cert, p))) goto leave;
+    md_json_sets(finger, json, MD_KEY_SHA256_FINGERPRINT, NULL);
+
+#if MD_STATUS_WITH_SCTS
+    do {
+        apr_array_header_t *scts;
+        const char *hex;
+        const md_sct *sct;
+        md_json_t *sctj;
+        int i;
+        
+        scts = apr_array_make(p, 5, sizeof(const md_sct*));
+        if (APR_SUCCESS == md_cert_get_ct_scts(scts, p, cert)) {
+            for (i = 0; i < scts->nelts; ++i) {
+                sct = APR_ARRAY_IDX(scts, i, const md_sct*);
+                sctj = md_json_create(p);
+                
+                apr_rfc822_date(ts, sct->timestamp);
+                md_json_sets(ts, sctj, "signed", NULL);
+                md_json_setl(sct->version, sctj, MD_KEY_VERSION, NULL);
+                md_data_to_hex(&hex, 0, p, sct->logid);
+                md_json_sets(hex, sctj, "logid", NULL);
+                md_data_to_hex(&hex, 0, p, sct->signature);
+                md_json_sets(hex, sctj, "signature", NULL);
+                md_json_sets(md_nid_get_sname(sct->signature_type_nid), sctj, "signature-type", NULL);
+                md_json_addj(sctj, json, "scts", NULL);
+            }
+        }
+    while (0);
+#endif
+leave:
+    *pjson = (APR_SUCCESS == rv)? json : NULL;
+    return rv;
+}
+
+/**************************************************************************************************/
+/* md status information */
+
+static apr_status_t get_staging_cert_json(md_json_t **pjson, apr_pool_t *p, 
+                                          md_reg_t *reg, const md_t *md)
+{ 
+    md_json_t *json = NULL;
+    apr_array_header_t *certs;
+    md_cert_t *cert;
+    apr_status_t rv = APR_SUCCESS;
+    
+    rv = md_pubcert_load(md_reg_store_get(reg), MD_SG_STAGING, md->name, &certs, p);
+    if (APR_STATUS_IS_ENOENT(rv) || certs->nelts == 0) {
+        rv = APR_SUCCESS;
+        goto leave;
+    }
+    else if (APR_SUCCESS != rv) {
+        goto leave;
+    }
+    cert = APR_ARRAY_IDX(certs, 0, md_cert_t *);
+    rv = status_get_cert_json(&json, cert, p);
+leave:
+    *pjson = (APR_SUCCESS == rv)? json : NULL;
+    return rv;
+}
+
+static apr_status_t job_loadj(md_json_t **pjson, const char *name, 
+                              struct md_reg_t *reg, apr_pool_t *p)
+{
+    md_store_t *store = md_reg_store_get(reg);
+    return md_store_load_json(store, MD_SG_STAGING, name, MD_FN_JOB, pjson, p);
+}
+
+apr_status_t md_status_get_md_json(md_json_t **pjson, const md_t *md, 
+                                   md_reg_t *reg, apr_pool_t *p)
+{
+    md_json_t *mdj, *jobj, *certj;
+    int renew;
+    const md_pubcert_t *pubcert;
+    const md_cert_t *cert;
+    apr_status_t rv = APR_SUCCESS;
+
+    mdj = md_to_json(md, p);
+    if (APR_SUCCESS == md_reg_get_pubcert(&pubcert, reg, md, p)) {
+        cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*);
+        if (APR_SUCCESS != (rv = status_get_cert_json(&certj, cert, p))) goto leave;
+        md_json_setj(certj, mdj, MD_KEY_CERT, NULL);
+    }
+    
+    renew = md_reg_should_renew(reg, md, p);
+    md_json_setb(renew, mdj, MD_KEY_RENEW, NULL);
+    if (renew) {
+        rv = job_loadj(&jobj, md->name, reg, p);
+        if (APR_SUCCESS == rv) {
+            rv = get_staging_cert_json(&certj, p, reg, md);
+            if (APR_SUCCESS != rv) goto leave;
+            if (certj) md_json_setj(certj, jobj, MD_KEY_CERT, NULL);
+            md_json_setj(jobj, mdj, MD_KEY_RENEWAL, NULL);
+        }
+        else if (APR_STATUS_IS_ENOENT(rv)) rv = APR_SUCCESS;
+        else goto leave;
+    }
+leave:
+    *pjson = (APR_SUCCESS == rv)? mdj : NULL;
+    return rv;
+}
+
+apr_status_t md_status_get_json(md_json_t **pjson, apr_array_header_t *mds, 
+                                md_reg_t *reg, apr_pool_t *p) 
+{
+    md_json_t *json, *mdj;
+    apr_status_t rv = APR_SUCCESS;
+    const md_t *md;
+    int i;
+    
+    json = md_json_create(p);
+    md_json_sets(MOD_MD_VERSION, json, MD_KEY_VERSION, NULL);
+    for (i = 0; i < mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mds, i, const md_t *);
+        rv = md_status_get_md_json(&mdj, md, reg, p);
+        if (APR_SUCCESS != rv) goto leave;
+        md_json_addj(mdj, json, MD_KEY_MDS, NULL);
+    }
+leave:
+    *pjson = (APR_SUCCESS == rv)? json : NULL;
+    return rv;
+}
+
+/**************************************************************************************************/
+/* drive job persistence */
+
+md_job_t *md_job_make(apr_pool_t *p, const char *name)
+{
+    md_job_t *job = apr_pcalloc(p, sizeof(*job));
+    job->name = apr_pstrdup(p, name);
+    job->p = p;
+    return job;
+}
+
+static void md_job_from_json(md_job_t *job, md_json_t *json, apr_pool_t *p)
+{
+    const char *s;
+    
+    /* not good, this is malloced from a temp pool */
+    /*job->name = md_json_gets(json, MD_KEY_NAME, NULL);*/
+    job->finished = md_json_getb(json, MD_KEY_FINISHED, NULL);
+    s = md_json_dups(p, json, MD_KEY_NEXT_RUN, NULL);
+    if (s && *s) job->next_run = apr_date_parse_rfc(s);
+    s = md_json_dups(p, json, MD_KEY_LAST_RUN, NULL);
+    if (s && *s) job->last_run = apr_date_parse_rfc(s);
+    s = md_json_dups(p, json, MD_KEY_VALID_FROM, NULL);
+    if (s && *s) job->valid_from = apr_date_parse_rfc(s);
+    job->error_runs = (int)md_json_getl(json, MD_KEY_ERRORS, NULL);
+    if (md_json_has_key(json, MD_KEY_LAST, NULL)) {
+        job->last_result = md_result_from_json(md_json_getcj(json, MD_KEY_LAST, NULL), p);
+    }
+    job->log = md_json_getj(json, MD_KEY_LOG, NULL);
+}
+
+static void job_to_json(md_json_t *json, const md_job_t *job, 
+                        md_result_t *result, apr_pool_t *p)
+{
+    char ts[APR_RFC822_DATE_LEN];
+
+    md_json_sets(job->name, json, MD_KEY_NAME, NULL);
+    md_json_setb(job->finished, json, MD_KEY_FINISHED, NULL);
+    if (job->next_run > 0) {
+        apr_rfc822_date(ts, job->next_run);
+        md_json_sets(ts, json, MD_KEY_NEXT_RUN, NULL);
+    }
+    if (job->last_run > 0) {
+        apr_rfc822_date(ts, job->last_run);
+        md_json_sets(ts, json, MD_KEY_LAST_RUN, NULL);
+    }
+    if (job->valid_from > 0) {
+        apr_rfc822_date(ts, job->valid_from);
+        md_json_sets(ts, json, MD_KEY_VALID_FROM, NULL);
+    }
+    md_json_setl(job->error_runs, json, MD_KEY_ERRORS, NULL);
+    if (!result) result = job->last_result;
+    if (result) {
+        md_json_setj(md_result_to_json(result, p), json, MD_KEY_LAST, NULL);
+    }
+    if (job->log) md_json_setj(job->log, json, MD_KEY_LOG, NULL);
+}
+
+apr_status_t md_job_load(md_job_t *job, md_reg_t *reg, 
+                         md_store_group_t group, apr_pool_t *p)
+{
+    md_store_t *store = md_reg_store_get(reg);
+    md_json_t *jprops;
+    apr_status_t rv;
+    
+    rv = md_store_load_json(store, group, job->name, MD_FN_JOB, &jprops, p);
+    if (APR_SUCCESS == rv) {
+        md_job_from_json(job, jprops, p);
+    }
+    return rv;
+}
+
+apr_status_t md_job_save(md_job_t *job, struct md_reg_t *reg, 
+                         md_store_group_t group, md_result_t *result, 
+                         apr_pool_t *p)
+{
+    md_store_t *store = md_reg_store_get(reg);
+    md_json_t *jprops;
+    apr_status_t rv;
+    
+    jprops = md_json_create(p);
+    job_to_json(jprops, job, result, p);
+    rv = md_store_save_json(store, p, group, job->name, MD_FN_JOB, jprops, 0);
+    return rv;
+}
+
+void md_job_log_append(md_job_t *job, const char *type, 
+                       const char *status, const char *detail)
+{
+    md_json_t *entry;
+    char ts[APR_RFC822_DATE_LEN];
+    
+    entry = md_json_create(job->p);
+    apr_rfc822_date(ts, apr_time_now());
+    md_json_sets(ts, entry, MD_KEY_WHEN, NULL);
+    md_json_sets(type, entry, MD_KEY_TYPE, NULL);
+    if (status) md_json_sets(status, entry, MD_KEY_STATUS, NULL);
+    if (detail) md_json_sets(detail, entry, MD_KEY_DETAIL, NULL);
+    if (!job->log) job->log = md_json_create(job->p);
+    md_json_insertj(entry, 0, job->log, MD_KEY_ENTRIES, NULL);
+}
+
+typedef struct {
+    md_job_t *job;
+    const char *type;
+    md_json_t *entry;
+    size_t index;
+} log_find_ctx;
+
+static int find_first_log_entry(void *baton, size_t index, md_json_t *entry)
+{
+    log_find_ctx *ctx = baton;
+    const char *etype;
+    
+    etype = md_json_gets(entry, MD_KEY_TYPE, NULL);
+    if (etype == ctx->type || (etype && ctx->type && !strcmp(etype, ctx->type))) {
+        ctx->entry = entry;
+        ctx->index = index;
+        return 0;
+    }
+    return 1;
+}
+
+md_json_t *md_job_log_get_latest(md_job_t *job, const char *type)
+
+{
+    log_find_ctx ctx;
+    ctx.job = job;
+    ctx.type = type;
+    memset(&ctx, 0, sizeof(ctx));
+    if (job->log) md_json_itera(find_first_log_entry, &ctx, job->log, MD_KEY_ENTRIES, NULL);
+    return ctx.entry;
+}
+
+apr_time_t md_job_log_get_time_of_latest(md_job_t *job, const char *type)
+{
+    md_json_t *entry;
+    const char *s;
+    
+    entry = md_job_log_get_latest(job, type);
+    if (entry) {
+        s = md_json_gets(entry, MD_KEY_WHEN, NULL);
+        if (s) return apr_date_parse_rfc(s);
+    }
+    return 0;
+}
+
+void  md_status_take_stock(md_json_t **pjson, apr_array_header_t *mds, 
+                           md_reg_t *reg, apr_pool_t *p)
+{
+    const md_t *md;
+    md_job_t job;
+    int i, complete, renewing, errored, ready, total;
+    md_json_t *json;
+
+    json = md_json_create(p);
+    complete = renewing = errored = ready = total = 0;
+    for (i = 0; i < mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mds, i, const md_t *);
+        ++total;
+        switch (md->state) {
+            case MD_S_COMPLETE: ++complete; /* fall through */
+            case MD_S_INCOMPLETE:
+                if (md_reg_should_renew(reg, md, p)) {
+                    ++renewing;
+                    memset(&job, 0, sizeof(job));
+                    job.name = md->name;
+                    if (APR_SUCCESS == md_job_load(&job, reg, MD_SG_STAGING, p)) {
+                        if (job.error_runs > 0 
+                            || (job.last_result && job.last_result->status != APR_SUCCESS)) {
+                            ++errored;
+                        }
+                        else if (job.finished) {
+                            ++ready;
+                        }
+                    }
+                }
+                break;
+            default: ++errored; break;
+        }
+    }
+    md_json_setl(total, json, MD_KEY_TOTAL, NULL);
+    md_json_setl(complete, json, MD_KEY_COMPLETE, NULL);
+    md_json_setl(renewing, json, MD_KEY_RENEWING, NULL);
+    md_json_setl(errored, json, MD_KEY_ERRORED, NULL);
+    md_json_setl(ready, json, MD_KEY_READY, NULL);
+    *pjson = json;
+}

Added: httpd/httpd/trunk/modules/md/md_status.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_status.h?rev=1862013&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/md/md_status.h (added)
+++ httpd/httpd/trunk/modules/md/md_status.h Mon Jun 24 16:04:32 2019
@@ -0,0 +1,97 @@
+/* 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_status_h
+#define md_status_h
+
+struct md_json_t;
+struct md_reg_t;
+struct md_result_t;
+
+/** 
+ * Get a JSON summary of the MD and its status (certificates, jobs, etc.).
+ */
+apr_status_t md_status_get_md_json(struct md_json_t **pjson, const md_t *md, 
+                                   struct md_reg_t *reg, apr_pool_t *p);
+
+/** 
+ * Get a JSON summary of all MDs and their status.
+ */
+apr_status_t md_status_get_json(struct md_json_t **pjson, apr_array_header_t *mds, 
+                                struct md_reg_t *reg, apr_pool_t *p);
+
+/**
+ * Take stock of all MDs given for a short overview. The JSON returned
+ * will carry intergers for MD_KEY_COMPLETE, MD_KEY_RENEWING, 
+ * MD_KEY_ERRORED, MD_KEY_READY and MD_KEY_TOTAL.
+ */
+void  md_status_take_stock(struct md_json_t **pjson, apr_array_header_t *mds, 
+                           struct md_reg_t *reg, apr_pool_t *p);
+
+typedef struct md_job_t md_job_t;
+struct md_job_t {
+    const char *name;      /* Name of the MD this job is about */
+    apr_pool_t *p;     
+    apr_time_t next_run;   /* Time this job wants to be processed next */
+    apr_time_t last_run;   /* Time this job ran last (or 0) */
+    struct md_result_t *last_result; /* Result from last run */
+    int finished;          /* true iff the job finished successfully */
+    apr_time_t valid_from; /* at which time the finished job results become valid, 0 if immediate */
+    int error_runs;        /* Number of errored runs of an unfinished job */
+    md_json_t *log;        /* array of log objects with minimum fields
+                              MD_KEY_WHEN (timestamp) and MD_KEY_TYPE (string) */   
+};
+
+/**
+ * Create a new job instance for the given MD name. Job load/save will work
+ * on the MD_SG_STAGING for the name.
+ */
+md_job_t *md_job_make(apr_pool_t *p, const char *name);
+
+/**
+ * Update the job from storage in <group>/job->name.
+ */
+apr_status_t md_job_load(md_job_t *job, struct md_reg_t *reg, 
+                         md_store_group_t group, apr_pool_t *p);
+
+/**
+ * Update storage from job in <group>/job->name.
+ */
+apr_status_t md_job_save(md_job_t *job, struct md_reg_t *reg, 
+                         md_store_group_t group, struct md_result_t *result, 
+                         apr_pool_t *p);
+
+/**
+ * Append to the job's log. Timestamp is automatically added.
+ * @param type          type of log entry
+ * @param status        status of entry (maybe NULL)
+ * @param detail        description of what happened
+ */
+void md_job_log_append(md_job_t *job, const char *type, 
+                       const char *status, const char *detail);
+
+/**
+ * Retrieve the lastest log entry of a certain type.
+ */
+md_json_t *md_job_log_get_latest(md_job_t *job, const char *type);
+
+/**
+ * Get the time the latest log entry of the given type happened, or 0 if
+ * none is found.
+ */
+apr_time_t md_job_log_get_time_of_latest(md_job_t *job, const char *type);
+
+#endif /* md_status_h */

Modified: httpd/httpd/trunk/modules/md/md_store.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_store.c?rev=1862013&r1=1862012&r2=1862013&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_store.c (original)
+++ httpd/httpd/trunk/modules/md/md_store.c Mon Jun 24 16:04:32 2019
@@ -145,6 +145,12 @@ int md_store_is_newer(md_store_t *store,
     return store->is_newer(store, group1, group2, name, aspect, p);
 }
 
+apr_status_t md_store_iter_names(md_store_inspect *inspect, void *baton, md_store_t *store, 
+                                 apr_pool_t *p, md_store_group_t group, const char *pattern)
+{
+    return store->iterate_names(inspect, baton, store, p, group, pattern);
+}
+
 /**************************************************************************************************/
 /* convenience */
 
@@ -243,32 +249,6 @@ apr_status_t md_pkey_save(md_store_t *st
     return md_store_save(store, p, group, name, MD_FN_PRIVKEY, MD_SV_PKEY, pkey, create);
 }
 
-apr_status_t md_cert_load(md_store_t *store, md_store_group_t group, const char *name, 
-                          struct md_cert_t **pcert, apr_pool_t *p)
-{
-    return md_store_load(store, group, name, MD_FN_CERT, MD_SV_CERT, (void**)pcert, p);
-}
-
-apr_status_t md_cert_save(md_store_t *store, apr_pool_t *p, 
-                          md_store_group_t group, const char *name, 
-                          struct md_cert_t *cert, int create)
-{
-    return md_store_save(store, p, group, name, MD_FN_CERT, MD_SV_CERT, cert, create);
-}
-
-apr_status_t md_chain_load(md_store_t *store, md_store_group_t group, const char *name, 
-                           struct apr_array_header_t **pchain, apr_pool_t *p)
-{
-    return md_store_load(store, group, name, MD_FN_CHAIN, MD_SV_CHAIN, (void**)pchain, p);
-}
-
-apr_status_t md_chain_save(md_store_t *store, apr_pool_t *p, 
-                           md_store_group_t group, const char *name, 
-                           struct apr_array_header_t *chain, int create)
-{
-    return md_store_save(store, p, group, name, MD_FN_CHAIN, MD_SV_CHAIN, chain, create);
-}
-
 apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name, 
                              struct apr_array_header_t **ppubcert, apr_pool_t *p)
 {

Modified: httpd/httpd/trunk/modules/md/md_store.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/md/md_store.h?rev=1862013&r1=1862012&r2=1862013&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/md/md_store.h (original)
+++ httpd/httpd/trunk/modules/md/md_store.h Mon Jun 24 16:04:32 2019
@@ -49,6 +49,9 @@ typedef apr_status_t md_store_iter_cb(md
                                       apr_pool_t *p, md_store_group_t group, const char *pattern,
                                       const char *aspect, md_store_vtype_t vtype);
 
+typedef apr_status_t md_store_names_iter_cb(md_store_inspect *inspect, void *baton, md_store_t *store, 
+                                            apr_pool_t *p, md_store_group_t group, const char *pattern);
+
 typedef apr_status_t md_store_move_cb(md_store_t *store, apr_pool_t *p, md_store_group_t from, 
                                       md_store_group_t to, const char *name, int archive);
 
@@ -69,6 +72,7 @@ struct md_store_t {
     md_store_remove_cb *remove;
     md_store_move_cb *move;
     md_store_iter_cb *iterate;
+    md_store_names_iter_cb *iterate_names;
     md_store_purge_cb *purge;
     md_store_get_fname_cb *get_fname;
     md_store_is_newer_cb *is_newer;
@@ -115,6 +119,10 @@ apr_status_t md_store_get_fname(const ch
 int md_store_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,  
                       const char *name, const char *aspect, apr_pool_t *p);
 
+apr_status_t md_store_iter_names(md_store_inspect *inspect, void *baton, md_store_t *store, 
+                                 apr_pool_t *p, md_store_group_t group, const char *pattern);
+
+
 /**************************************************************************************************/
 /* Storage handling utils */
 
@@ -138,14 +146,6 @@ apr_status_t md_pkey_load(md_store_t *st
                           const char *name, struct md_pkey_t **ppkey, apr_pool_t *p);
 apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                           const char *name, struct md_pkey_t *pkey, int create);
-apr_status_t md_cert_load(md_store_t *store, md_store_group_t group, 
-                          const char *name, struct md_cert_t **pcert, apr_pool_t *p);
-apr_status_t md_cert_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
-                          const char *name, struct md_cert_t *cert, int create);
-apr_status_t md_chain_load(md_store_t *store, md_store_group_t group, 
-                           const char *name, struct apr_array_header_t **pchain, apr_pool_t *p);
-apr_status_t md_chain_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
-                           const char *name, struct apr_array_header_t *chain, int create);
 
 apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name, 
                              struct apr_array_header_t **ppubcert, apr_pool_t *p);