mod_md: v0.6.1 from github

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/trunk-md@1804529 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/modules/md/md.h b/modules/md/md.h
index 35bab07..7f543a0 100644
--- a/modules/md/md.h
+++ b/modules/md/md.h
@@ -69,6 +69,7 @@
     apr_interval_time_t renew_window;/* time before expiration that starts renewal */
     
     struct apr_array_header_t *domains; /* all DNS names this MD includes */
+    int transitive;                 /* != 0 iff VirtualHost names/aliases are auto-added */
     md_drive_mode_t drive_mode;     /* mode of obtaining credentials */
     int must_staple;                /* certificates should set the OCSP Must Staple extension */
     
@@ -116,6 +117,7 @@
 #define MD_KEY_STATUS           "status"
 #define MD_KEY_STORE            "store"
 #define MD_KEY_TOKEN            "token"
+#define MD_KEY_TRANSITIVE       "transitive"
 #define MD_KEY_TYPE             "type"
 #define MD_KEY_URL              "url"
 #define MD_KEY_URI              "uri"
@@ -140,7 +142,7 @@
 /**
  * Determine if the Managed Domain contains a specific domain name.
  */
-int md_contains(const md_t *md, const char *domain);
+int md_contains(const md_t *md, const char *domain, int case_sensitive);
 
 /**
  * Determine if the names of the two managed domains overlap.
@@ -150,7 +152,7 @@
 /**
  * Determine if the domain names are equal.
  */
-int md_equal_domains(const md_t *md1, const md_t *md2);
+int md_equal_domains(const md_t *md1, const md_t *md2, int case_sensitive);
 
 /**
  * Determine if the domains in md1 contain all domains of md2.
@@ -184,8 +186,8 @@
 md_t *md_get_by_dns_overlap(struct apr_array_header_t *mds, const md_t *md);
 
 /**
- * Find the managed domain in the list that has the most overlaps in domains to the
- * given md.
+ * Find the managed domain in the list that, for the given md, 
+ * has the same name, or the most number of overlaps in domains
  */
 md_t *md_find_closest_match(apr_array_header_t *mds, const md_t *md);
 
diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c
index f72e6bd..533f60d 100644
--- a/modules/md/md_acme_drive.c
+++ b/modules/md/md_acme_drive.c
@@ -186,7 +186,7 @@
     /* Remove anything we no longer need */
     for (i = 0; i < ad->authz_set->authzs->nelts; ++i) {
         authz = APR_ARRAY_IDX(ad->authz_set->authzs, i, md_acme_authz_t*);
-        if (!md_contains(md, authz->domain)) {
+        if (!md_contains(md, authz->domain, 0)) {
             md_acme_authz_set_remove(ad->authz_set, authz->domain);
             changed = 1;
         }
@@ -589,7 +589,7 @@
 static apr_status_t acme_driver_init(md_proto_driver_t *d)
 {
     md_acme_driver_t *ad;
-    apr_status_t rv;
+    apr_status_t rv = APR_SUCCESS;
 
     ad = apr_pcalloc(d->p, sizeof(*ad));
     
@@ -627,43 +627,6 @@
     
     md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, d->p, "%s: init driver", d->md->name);
     
-    rv = md_load(d->store, MD_SG_STAGING, d->md->name, &ad->md, d->p);
-    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, "%s: checked stage md", d->md->name);
-    if (d->reset || APR_STATUS_IS_ENOENT(rv)) {
-        /* reset the staging area for this domain */
-        rv = md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
-        if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) {
-            return rv;
-        }
-        rv = APR_SUCCESS;
-    }
-    
-    if (ad->md) {
-        /* staging in progress.
-         * There are certain md properties that are updated in staging, others are only
-         * updated in the domains store. Are these still the same? If not, we better
-         * start anew.
-         */
-        if (strcmp(d->md->ca_url, ad->md->ca_url)
-            || strcmp(d->md->ca_proto, ad->md->ca_proto)) {
-            /* reject staging info in this case */
-            ad->md = NULL;
-            return APR_SUCCESS;
-        }
-        
-        if (d->md->ca_agreement 
-            && (!ad->md->ca_agreement || strcmp(d->md->ca_agreement, ad->md->ca_agreement))) {
-            ad->md->ca_agreement = d->md->ca_agreement;
-        }
-        
-        /* look for new ACME account information collected there */
-        rv = md_reg_creds_get(&ad->ncreds, d->reg, MD_SG_STAGING, d->md, d->p);
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: checked creds", d->md->name);
-        if (APR_STATUS_IS_ENOENT(rv)) {
-            rv = APR_SUCCESS;
-        }
-    }
-    
     return rv;
 }
 
@@ -673,6 +636,7 @@
 static apr_status_t acme_stage(md_proto_driver_t *d)
 {
     md_acme_driver_t *ad = d->baton;
+    int reset_staging = d->reset;
     apr_status_t rv = APR_SUCCESS;
     int renew = 1;
 
@@ -683,6 +647,42 @@
                       apr_array_pstrcat(d->p, ad->ca_challenges, ' '));
     }
 
+    if (!reset_staging) {
+        rv = md_load(d->store, MD_SG_STAGING, d->md->name, &ad->md, d->p);
+        if (APR_SUCCESS == rv) {
+            /* So, we have a copy in staging, but is it a recent or an old one? */
+            if (!md_is_newer(d->store, MD_SG_STAGING, MD_SG_DOMAINS, d->md->name, d->p)) {
+                reset_staging = 1;
+            }
+        }
+        else if (APR_STATUS_IS_ENOENT(rv)) {
+            reset_staging = 1;
+            rv = APR_SUCCESS;
+        }
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, 
+                      "%s: checked staging area, will%s reset",
+                      d->md->name, reset_staging? "" : " not");
+    }
+    
+    if (reset_staging) {
+        /* reset the staging area for this domain */
+        rv = md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
+        if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) {
+            return rv;
+        }
+        rv = APR_SUCCESS;
+        ad->md = NULL;
+    }
+    
+    if (ad->md) {
+        /* staging in progress. look for new ACME account information collected there */
+        rv = md_reg_creds_get(&ad->ncreds, d->reg, MD_SG_STAGING, d->md, d->p);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: checked creds", d->md->name);
+        if (APR_STATUS_IS_ENOENT(rv)) {
+            rv = APR_SUCCESS;
+        }
+    }
+    
     /* Find out where we're at with this managed domain */
     if (ad->ncreds && ad->ncreds->pkey && ad->ncreds->cert && ad->ncreds->chain) {
         /* There is a full set staged, to be loaded */
diff --git a/modules/md/md_core.c b/modules/md/md_core.c
index a65c639..0a67e85 100644
--- a/modules/md/md_core.c
+++ b/modules/md/md_core.c
@@ -29,9 +29,9 @@
 #include "md_util.h"
 
 
-int md_contains(const md_t *md, const char *domain)
+int md_contains(const md_t *md, const char *domain, int case_sensitive)
 {
-   return md_array_str_index(md->domains, domain, 0, 0) >= 0;
+   return md_array_str_index(md->domains, domain, 0, case_sensitive) >= 0;
 }
 
 const char *md_common_name(const md_t *md1, const md_t *md2)
@@ -45,7 +45,7 @@
     
     for (i = 0; i < md1->domains->nelts; ++i) {
         const char *name1 = APR_ARRAY_IDX(md1->domains, i, const char*);
-        if (md_contains(md2, name1)) {
+        if (md_contains(md2, name1, 0)) {
             return name1;
         }
     }
@@ -70,7 +70,7 @@
     hits = 0;
     for (i = 0; i < md1->domains->nelts; ++i) {
         const char *name1 = APR_ARRAY_IDX(md1->domains, i, const char*);
-        if (md_contains(md2, name1)) {
+        if (md_contains(md2, name1, 0)) {
             ++hits;
         }
     }
@@ -84,19 +84,20 @@
         md->domains = apr_array_make(p, 5, sizeof(const char *));
         md->contacts = apr_array_make(p, 5, sizeof(const char *));
         md->drive_mode = MD_DRIVE_DEFAULT;
+        md->transitive = -1;
         md->defn_name = "unknown";
         md->defn_line_number = 0;
     }
     return md;
 }
 
-int md_equal_domains(const md_t *md1, const md_t *md2)
+int md_equal_domains(const md_t *md1, const md_t *md2, int case_sensitive)
 {
     int i;
     if (md1->domains->nelts == md2->domains->nelts) {
         for (i = 0; i < md1->domains->nelts; ++i) {
             const char *name1 = APR_ARRAY_IDX(md1->domains, i, const char*);
-            if (!md_contains(md2, name1)) {
+            if (!md_contains(md2, name1, case_sensitive)) {
                 return 0;
             }
         }
@@ -111,7 +112,7 @@
     if (md1->domains->nelts >= md2->domains->nelts) {
         for (i = 0; i < md2->domains->nelts; ++i) {
             const char *name2 = APR_ARRAY_IDX(md2->domains, i, const char*);
-            if (!md_contains(md1, name2)) {
+            if (!md_contains(md1, name2, 0)) {
                 return 0;
             }
         }
@@ -169,7 +170,7 @@
     int i;
     for (i = 0; i < mds->nelts; ++i) {
         md_t *md = APR_ARRAY_IDX(mds, i, md_t *);
-        if (md_contains(md, domain)) {
+        if (md_contains(md, domain, 0)) {
             return md;
         }
     }
@@ -264,6 +265,7 @@
         md_json_sets(md->name, json, MD_KEY_NAME, NULL);
         md_json_setsa(domains, json, MD_KEY_DOMAINS, NULL);
         md_json_setsa(md->contacts, json, MD_KEY_CONTACTS, NULL);
+        md_json_setl(md->transitive, json, MD_KEY_TRANSITIVE, NULL);
         md_json_sets(md->ca_account, json, MD_KEY_CA, MD_KEY_ACCOUNT, NULL);
         md_json_sets(md->ca_proto, json, MD_KEY_CA, MD_KEY_PROTO, NULL);
         md_json_sets(md->ca_url, json, MD_KEY_CA, MD_KEY_URL, NULL);
@@ -305,6 +307,7 @@
         md->state = (int)md_json_getl(json, MD_KEY_STATE, NULL);
         md->drive_mode = (int)md_json_getl(json, MD_KEY_DRIVE_MODE, NULL);
         md->domains = md_array_str_compact(p, md->domains, 0);
+        md->transitive = (int)md_json_getl(json, MD_KEY_TRANSITIVE, NULL);
         s = md_json_dups(p, json, MD_KEY_CERT, MD_KEY_EXPIRES, NULL);
         if (s && *s) {
             md->expires = apr_date_parse_rfc(s);
diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c
index b2953fb..791565f 100644
--- a/modules/md/md_reg.c
+++ b/modules/md/md_reg.c
@@ -357,7 +357,7 @@
 {
     find_domain_ctx *ctx = baton;
     
-    if (md_contains(md, ctx->domain)) {
+    if (md_contains(md, ctx->domain, 0)) {
         ctx->md = md;
         return 0;
     }
@@ -685,7 +685,7 @@
     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
                   "sync: found %d mds in store", ctx.store_mds->nelts);
     if (APR_SUCCESS == rv) {
-        int i, added, fields;
+        int i, fields;
         md_t *md, *config_md, *smd, *omd;
         const char *common;
         
@@ -696,11 +696,18 @@
             smd = md_find_closest_match(ctx.store_mds, md);
             if (smd) {
                 fields = 0;
-                /* add any newly configured domains to the store md */
-                added = md_array_str_add_missing(smd->domains, md->domains, 0);
-                if (added) {
+                
+                /* Once stored, we keep the name */
+                if (strcmp(md->name, smd->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: %d domains added", smd->name, added);
+                                 "%s: %d domains changed", smd->name);
+                    smd->domains = md_array_str_clone(ptemp, md->domains);
                     fields |= MD_UPD_DOMAINS;
                 }
                 
@@ -712,7 +719,7 @@
                     
                     /* Is this md still configured or has it been abandoned in the config? */
                     config_md = md_get_by_name(ctx.conf_mds, omd->name);
-                    if (config_md && md_contains(config_md, common)) {
+                    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, 
diff --git a/modules/md/md_store.c b/modules/md/md_store.c
index 9028845..f94e959 100644
--- a/modules/md/md_store.c
+++ b/modules/md/md_store.c
@@ -138,6 +138,12 @@
     return APR_ENOTIMPL;
 }
 
+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)
+{
+    return store->is_newer(store, group1, group2, name, aspect, p);
+}
+
 /**************************************************************************************************/
 /* convenience */
 
@@ -211,6 +217,13 @@
     return md_util_pool_vdo(p_remove, &ctx, p, name, force, NULL);
 }
 
+int md_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,  
+                      const char *name, apr_pool_t *p)
+{
+    return md_store_is_newer(store, group1, group2, name, MD_FN_MD, p);
+}
+
+
 typedef struct {
     apr_pool_t *p;
     apr_array_header_t *mds;
diff --git a/modules/md/md_store.h b/modules/md/md_store.h
index 7806504..86a8639 100644
--- a/modules/md/md_store.h
+++ b/modules/md/md_store.h
@@ -56,6 +56,10 @@
                                            const char *name, const char *aspect, 
                                            apr_pool_t *p);
 
+typedef int md_store_is_newer_cb(md_store_t *store, 
+                                 md_store_group_t group1, md_store_group_t group2,  
+                                 const char *name, const char *aspect, apr_pool_t *p);
+
 struct md_store_t {
     md_store_destroy_cb *destroy;
 
@@ -66,6 +70,7 @@
     md_store_iter_cb *iterate;
     md_store_purge_cb *purge;
     md_store_get_fname_cb *get_fname;
+    md_store_is_newer_cb *is_newer;
 };
 
 void md_store_destroy(md_store_t *store);
@@ -106,6 +111,9 @@
                                 const char *name, const char *aspect, 
                                 apr_pool_t *p);
 
+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);
+
 /**************************************************************************************************/
 /* Storage handling utils */
 
@@ -116,6 +124,9 @@
 apr_status_t md_remove(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                      const char *name, int force);
 
+int md_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,  
+                const char *name, apr_pool_t *p);
+
 typedef int md_store_md_inspect(void *baton, md_store_t *store, md_t *md, apr_pool_t *ptemp);
 
 apr_status_t md_store_md_iter(md_store_md_inspect *inspect, void *baton, md_store_t *store, 
diff --git a/modules/md/md_store_fs.c b/modules/md/md_store_fs.c
index 09a4eb2..2626fde 100644
--- a/modules/md/md_store_fs.c
+++ b/modules/md/md_store_fs.c
@@ -90,6 +90,8 @@
                                  md_store_t *store, md_store_group_t group, 
                                  const char *name, const char *aspect, 
                                  apr_pool_t *p);
+static int fs_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);
 
 static apr_status_t init_store_file(md_store_fs_t *s_fs, const char *fname, 
                                     apr_pool_t *p, apr_pool_t *ptemp)
@@ -129,7 +131,7 @@
                                     apr_pool_t *p, apr_pool_t *ptemp)
 {
     md_json_t *json;
-    const char *s, *key64;
+    const char *key64;
     apr_status_t rv;
     double store_version;
     
@@ -140,7 +142,7 @@
             store_version = 1.0;
         }
         if (store_version > MD_STORE_VERSION) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "version too new: %s", s);
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "version too new: %s", store_version);
             return APR_EINVAL;
         }
         else if (store_version > MD_STORE_VERSION) {
@@ -204,6 +206,7 @@
     s_fs->s.purge = fs_purge;
     s_fs->s.iterate = fs_iterate;
     s_fs->s.get_fname = fs_get_fname;
+    s_fs->s.is_newer = fs_is_newer;
     
     /* by default, everything is only readable by the current user */ 
     s_fs->def_perms.dir = MD_FPROT_D_UONLY;
@@ -425,8 +428,48 @@
     md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "mk_group_dir %d %s", group, name);
     return rv;
 }
+
+static apr_status_t pfs_is_newer(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+    md_store_fs_t *s_fs = baton;
+    const char *fname1, *fname2, *name, *aspect;
+    md_store_group_t group1, group2;
+    apr_finfo_t inf1, inf2;
+    int *pnewer;
+    apr_status_t rv;
+    
+    group1 = va_arg(ap, int);
+    group2 = va_arg(ap, int);
+    name = va_arg(ap, const char*);
+    aspect = va_arg(ap, const char*);
+    pnewer = va_arg(ap, int*);
+    
+    *pnewer = 0;
+    if (   APR_SUCCESS == (rv = fs_get_fname(&fname1, &s_fs->s, group1, name, aspect, ptemp))
+        && APR_SUCCESS == (rv = fs_get_fname(&fname2, &s_fs->s, group2, name, aspect, ptemp))
+        && APR_SUCCESS == (rv = apr_stat(&inf1, fname1, APR_FINFO_MTIME, ptemp))
+        && APR_SUCCESS == (rv = apr_stat(&inf2, fname2, APR_FINFO_MTIME, ptemp))) {
+        *pnewer = inf1.mtime > inf2.mtime;
+    }
+
+    return rv;
+}
+
  
- 
+static int fs_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)
+{
+    md_store_fs_t *s_fs = FS_STORE(store);
+    int newer = 0;
+    apr_status_t rv;
+    
+    rv = md_util_pool_vdo(pfs_is_newer, s_fs, p, group1, group2, name, aspect, &newer, NULL);
+    if (APR_SUCCESS == rv) {
+        return newer;
+    }
+    return 0;
+}
+
 static apr_status_t pfs_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
 {
     md_store_fs_t *s_fs = baton;
diff --git a/modules/md/md_version.h b/modules/md/md_version.h
index 405734c..7b20638 100644
--- a/modules/md/md_version.h
+++ b/modules/md/md_version.h
@@ -26,7 +26,7 @@
  * @macro
  * Version number of the md module as c string
  */
-#define MOD_MD_VERSION "0.6.0"
+#define MOD_MD_VERSION "0.6.1"
 
 /**
  * @macro
@@ -34,7 +34,7 @@
  * release. This is a 24 bit number with 8 bits for major number, 8 bits
  * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
  */
-#define MOD_MD_VERSION_NUM 0x000600
+#define MOD_MD_VERSION_NUM 0x000601
 
 #define MD_EXPERIMENTAL 1
 #define MD_ACME_DEF_URL    "https://acme-staging.api.letsencrypt.org/directory"
diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c
index caee792..b5f391f 100644
--- a/modules/md/mod_md.c
+++ b/modules/md/mod_md.c
@@ -149,6 +149,9 @@
                 if (nmd->renew_window <= 0) {
                     nmd->renew_window = md_config_get_interval(config, MD_CONFIG_RENEW_WINDOW);
                 }
+                if (nmd->transitive < 0) {
+                    nmd->transitive = md_config_geti(config, MD_CONFIG_TRANSITIVE);
+                }
                 if (!nmd->ca_challenges && config->ca_challenges) {
                     nmd->ca_challenges = apr_array_copy(p, config->ca_challenges);
                 }
@@ -161,17 +164,37 @@
             }
         }
     }
+    
     ctx->mds = (APR_SUCCESS == rv)? mds : NULL;
     return rv;
 }
 
+static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s, apr_pool_t *p)
+{
+    if (md_contains(md, domain, 0)) {
+        return APR_SUCCESS;
+    }
+    else if (md->transitive) {
+        APR_ARRAY_PUSH(md->domains, const char*) = apr_pstrdup(p, domain);
+        return APR_SUCCESS;
+    }
+    else {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO()
+                     "Virtual Host %s:%d matches Managed Domain '%s', but the "
+                     "name/alias %s itself is not managed. A requested MD certificate "
+                     "will not match ServerName.",
+                     s->server_hostname, s->port, md->name, domain);
+        return APR_EINVAL;
+    }
+}
+
 static apr_status_t md_check_vhost_mapping(md_ctx *ctx, apr_pool_t *p, apr_pool_t *plog,
                                            apr_pool_t *ptemp, server_rec *base_server)
 {
     server_rec *s;
     request_rec r;
     md_config_t *config;
-    apr_status_t rv = APR_SUCCESS;
+    apr_status_t rv = APR_SUCCESS, rv2;
     md_t *md;
     int i, j, k;
     const char *domain, *name;
@@ -227,30 +250,17 @@
 
                     /* This server matches a managed domain. If it contains names or
                      * alias that are not in this md, a generated certificate will not match. */
-                    if (!md_contains(md, s->server_hostname)) {
-                        ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO()
-                                     "Virtual Host %s:%d matches Managed Domain '%s', but the name"
-                                     " itself is not managed. A requested MD certificate will "
-                                     "not match ServerName.",
-                                     s->server_hostname, s->port, md->name);
-                        rv = APR_EINVAL;
-                        goto next_server;
-                    }
-                    else {
+                    if (APR_SUCCESS == (rv2 = check_coverage(md, s->server_hostname, s, p))) {
                         for (k = 0; k < s->names->nelts; ++k) {
                             name = APR_ARRAY_IDX(s->names, k, const char*);
-                            if (!md_contains(md, name)) {
-                                ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO()
-                                             "Virtual Host %s:%d matches Managed Domain '%s', but "
-                                             "the ServerAlias %s is not covered by the MD. "
-                                             "A requested MD certificate will not match this " 
-                                             "alias.", s->server_hostname, s->port, md->name,
-                                             name);
-                                rv = APR_EINVAL;
-                                goto next_server;
+                            if (APR_SUCCESS != (rv2 = check_coverage(md, name, s, p))) {
+                                break;
                             }
                         }
                     }
+                    if (APR_SUCCESS != rv2) {
+                        rv = rv2;
+                    }
                     goto next_server;
                 }
             }
@@ -470,7 +480,9 @@
         else if (renew) {
             ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO() 
                          "md(%s): state=%d, driving", md->name, md->state);
+                         
             rv = md_reg_stage(wd->reg, md, NULL, 0, ptemp);
+            
             if (APR_SUCCESS == rv) {
                 md->state = MD_S_COMPLETE;
                 md->expires = 0;
diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c
index c5c3d7d..2a70683 100644
--- a/modules/md/mod_md_config.c
+++ b/modules/md/mod_md_config.c
@@ -43,7 +43,8 @@
     NULL, 
     NULL,
     MD_DRIVE_AUTO,
-    apr_time_from_sec(14 * MD_SECS_PER_DAY), 
+    apr_time_from_sec(14 * MD_SECS_PER_DAY),
+    1,  
     NULL, 
     "md",
     NULL
@@ -62,6 +63,7 @@
     conf->drive_mode = DEF_VAL;
     conf->mds = apr_array_make(pool, 5, sizeof(const md_t *));
     conf->renew_window = DEF_VAL;
+    conf->transitive = DEF_VAL;
     
     return conf;
 }
@@ -95,6 +97,7 @@
     n->renew_window = (add->renew_window != DEF_VAL)? add->renew_window : base->renew_window;
     n->ca_challenges = (add->ca_challenges? apr_array_copy(pool, add->ca_challenges) 
                     : (base->ca_challenges? apr_array_copy(pool, base->ca_challenges) : NULL));
+    n->transitive = (add->transitive != DEF_VAL)? add->transitive : base->transitive;
     return n;
 }
 
@@ -190,21 +193,41 @@
     return err;
 }
 
+static const char *set_transitive(int *ptransitive, const char *value)
+{
+    if (!apr_strnatcasecmp("auto", value)) {
+        *ptransitive = 1;
+        return NULL;
+    }
+    else if (!apr_strnatcasecmp("manual", value)) {
+        *ptransitive = 0;
+        return NULL;
+    }
+    return "unknown value, use \"auto|manual\"";
+}
+
 static const char *md_config_sec_add_members(cmd_parms *cmd, void *dc, 
                                              int argc, char *const argv[])
 {
+    md_config_t *config = (md_config_t *)md_config_get(cmd->server);
     md_config_dir_t *dconfig = dc;
     apr_array_header_t *domains;
     const char *err;
     int i;
     
     if (NULL != (err = md_section_check(cmd))) {
+        if (argc == 1) {
+            /* only allowed value outside a section */
+            return set_transitive(&config->transitive, argv[0]);
+        }
         return err;
     }
     
     domains = dconfig->md->domains;
     for (i = 0; i < argc; ++i) {
-        add_domain_name(domains, argv[i], cmd->pool);
+        if (NULL != set_transitive(&dconfig->md->transitive, argv[i])) {
+            add_domain_name(domains, argv[i], cmd->pool);
+        }
     }
     return NULL;
 }
@@ -216,7 +239,7 @@
     apr_array_header_t *domains = apr_array_make(cmd->pool, 5, sizeof(const char *));
     const char *err;
     md_t *md;
-    int i;
+    int i, transitive = -1;
 
     err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE);
     if (err) {
@@ -224,12 +247,18 @@
     }
 
     for (i = 0; i < argc; ++i) {
-        add_domain_name(domains, argv[i], cmd->pool);
+        if (NULL != set_transitive(&transitive, argv[i])) {
+            add_domain_name(domains, argv[i], cmd->pool);
+        }
     }
     err = md_create(&md, cmd->pool, domains);
     if (err) {
         return err;
     }
+
+    if (transitive >= 0) {
+        md->transitive = transitive;
+    }
     
     if (cmd->config_file) {
         md->defn_name = cmd->config_file->name;
@@ -486,8 +515,12 @@
 const command_rec md_cmds[] = {
     AP_INIT_RAW_ARGS("<ManagedDomain", md_config_sec_start, NULL, RSRC_CONF, 
                       "Container for a manged domain with common settings and certificate."),
-    AP_INIT_TAKE_ARGV("MDMember", md_config_sec_add_members, NULL, OR_ALL, 
-                      "Define domain name(s) part of the Managed Domain"),
+    AP_INIT_TAKE_ARGV("MDMember", md_config_sec_add_members, NULL, RSRC_CONF, 
+                      "Define domain name(s) part of the Managed Domain. Use 'auto' or "
+                      "'manual' to enable/disable auto adding names from virtual hosts."),
+    AP_INIT_TAKE_ARGV("MDMembers", md_config_sec_add_members, NULL, RSRC_CONF, 
+                      "Define domain name(s) part of the Managed Domain. Use 'auto' or "
+                      "'manual' to enable/disable auto adding names from virtual hosts."),
     AP_INIT_TAKE_ARGV("ManagedDomain", md_config_set_names, NULL, RSRC_CONF, 
                       "A group of server names with one certificate"),
     AP_INIT_TAKE1("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF, 
@@ -566,6 +599,8 @@
             return (config->local_80 != DEF_VAL)? config->local_80 : 80;
         case MD_CONFIG_LOCAL_443:
             return (config->local_443 != DEF_VAL)? config->local_443 : 443;
+        case MD_CONFIG_TRANSITIVE:
+            return (config->transitive != DEF_VAL)? config->transitive : defconf.transitive;
         default:
             return 0;
     }
diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h
index 3568f7c..c0ec6cc 100644
--- a/modules/md/mod_md_config.h
+++ b/modules/md/mod_md_config.h
@@ -27,6 +27,7 @@
     MD_CONFIG_LOCAL_80,
     MD_CONFIG_LOCAL_443,
     MD_CONFIG_RENEW_WINDOW,
+    MD_CONFIG_TRANSITIVE,
 } md_config_var_t;
 
 typedef struct {
@@ -44,6 +45,7 @@
     
     int drive_mode;
     apr_interval_time_t renew_window;  /* time for renewal before expiry */
+    int transitive;
     
     const md_t *md;
     const char *base_dir;