httpd-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ic...@apache.org
Subject svn commit: r1804123 [2/6] - in /httpd/httpd/branches/trunk-md: ./ modules/http2/ modules/md/
Date Fri, 04 Aug 2017 13:47:26 GMT
Added: httpd/httpd/branches/trunk-md/modules/md/md_acme_authz.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/trunk-md/modules/md/md_acme_authz.c?rev=1804123&view=auto
==============================================================================
--- httpd/httpd/branches/trunk-md/modules/md/md_acme_authz.c (added)
+++ httpd/httpd/branches/trunk-md/modules/md/md_acme_authz.c Fri Aug  4 13:47:25 2017
@@ -0,0 +1,682 @@
+/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_file_info.h>
+#include <apr_file_io.h>
+#include <apr_fnmatch.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_jws.h"
+#include "md_store.h"
+#include "md_util.h"
+
+#include "md_acme.h"
+#include "md_acme_authz.h"
+
+md_acme_authz_t *md_acme_authz_create(apr_pool_t *p)
+{
+    md_acme_authz_t *authz;
+    authz = apr_pcalloc(p, sizeof(*authz));
+    
+    return authz;
+}
+
+md_acme_authz_set_t *md_acme_authz_set_create(apr_pool_t *p, md_acme_t *acme)
+{
+    md_acme_authz_set_t *authz_set;
+    
+    authz_set = apr_pcalloc(p, sizeof(*authz_set));
+    authz_set->authzs = apr_array_make(p, 5, sizeof(md_acme_authz_t *));
+    
+    return authz_set;
+}
+
+md_acme_authz_t *md_acme_authz_set_get(md_acme_authz_set_t *set, const char *domain)
+{
+    md_acme_authz_t *authz;
+    int i;
+    
+    assert(domain);
+    for (i = 0; i < set->authzs->nelts; ++i) {
+        authz = APR_ARRAY_IDX(set->authzs, i, md_acme_authz_t *);
+        if (!apr_strnatcasecmp(domain, authz->domain)) {
+            return authz;
+        }
+    }
+    return NULL;
+}
+
+apr_status_t md_acme_authz_set_add(md_acme_authz_set_t *set, md_acme_authz_t *authz)
+{
+    md_acme_authz_t *existing;
+    
+    assert(authz->domain);
+    if (NULL != (existing = md_acme_authz_set_get(set, authz->domain))) {
+        return APR_EINVAL;
+    }
+    APR_ARRAY_PUSH(set->authzs, md_acme_authz_t*) = authz;
+    return APR_SUCCESS;
+}
+
+apr_status_t md_acme_authz_set_remove(md_acme_authz_set_t *set, const char *domain)
+{
+    md_acme_authz_t *authz;
+    int i;
+    
+    assert(domain);
+    for (i = 0; i < set->authzs->nelts; ++i) {
+        authz = APR_ARRAY_IDX(set->authzs, i, md_acme_authz_t *);
+        if (!apr_strnatcasecmp(domain, authz->domain)) {
+            int n = i +1;
+            if (n < set->authzs->nelts) {
+                void **elems = (void **)set->authzs->elts;
+                memmove(elems + i, elems + n, set->authzs->nelts - n); 
+            }
+            --set->authzs->nelts;
+            return APR_SUCCESS;
+        }
+    }
+    return APR_ENOENT;
+}
+
+/**************************************************************************************************/
+/* Register a new authorization */
+
+typedef struct {
+    size_t index;
+    const char *type;
+    const char *uri;
+    const char *token;
+    const char *key_authz;
+} md_acme_authz_cha_t;
+
+typedef struct {
+    apr_pool_t *p;
+    md_acme_t *acme;
+    const char *domain;
+    md_acme_authz_t *authz;
+    md_acme_authz_cha_t *challenge;
+} authz_req_ctx;
+
+static void authz_req_ctx_init(authz_req_ctx *ctx, md_acme_t *acme, 
+                               const char *domain, md_acme_authz_t *authz, apr_pool_t *p)
+{
+    memset(ctx, 0, sizeof(*ctx));
+    ctx->p = p;
+    ctx->acme = acme;
+    ctx->domain = domain;
+    ctx->authz = authz;
+}
+
+static apr_status_t on_init_authz(md_acme_req_t *req, void *baton)
+{
+    authz_req_ctx *ctx = baton;
+    md_json_t *jpayload;
+
+    jpayload = md_json_create(req->p);
+    md_json_sets("new-authz", jpayload, MD_KEY_RESOURCE, NULL);
+    md_json_sets("dns", jpayload, MD_KEY_IDENTIFIER, MD_KEY_TYPE, NULL);
+    md_json_sets(ctx->domain, jpayload, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL);
+    
+    return md_acme_req_body_init(req, jpayload);
+} 
+
+static apr_status_t authz_created(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs, 
+                                  md_json_t *body, void *baton)
+{
+    authz_req_ctx *ctx = baton;
+    const char *location = apr_table_get(hdrs, "location");
+    apr_status_t rv = APR_SUCCESS;
+    
+    if (location) {
+        ctx->authz = md_acme_authz_create(ctx->p);
+        ctx->authz->domain = apr_pstrdup(ctx->p, ctx->domain);
+        ctx->authz->location = apr_pstrdup(ctx->p, location);
+        ctx->authz->resource = md_json_clone(ctx->p, body);
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ctx->p, "authz_new at %s", location);
+    }
+    else {
+        rv = APR_EINVAL;
+        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, ctx->p, "new authz, no location header");
+    }
+    return rv;
+}
+
+apr_status_t md_acme_authz_register(struct md_acme_authz_t **pauthz, md_acme_t *acme, 
+                                    md_store_t *store, const char *domain, apr_pool_t *p)
+{
+    apr_status_t rv;
+    authz_req_ctx ctx;
+    
+    authz_req_ctx_init(&ctx, acme, domain, NULL, p);
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "create new authz");
+    rv = md_acme_POST(acme, acme->new_authz, on_init_authz, authz_created, NULL, &ctx);
+    
+    *pauthz = (APR_SUCCESS == rv)? ctx.authz : NULL;
+    return rv;
+}
+
+/**************************************************************************************************/
+/* Update an exiosting authorization */
+
+apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, 
+                                  md_store_t *store, apr_pool_t *p)
+{
+    md_json_t *json;
+    const char *s;
+    apr_status_t rv;
+    
+    assert(acme);
+    assert(acme->http);
+    assert(authz);
+    assert(authz->location);
+
+    if (APR_SUCCESS != (rv = md_acme_get_json(&json, acme, authz->location, p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "update authz for %s at %s",
+                      authz->domain, authz->location);
+        return rv;
+    }
+    
+    authz->resource = json;
+    s = md_json_gets(json, "identifier", "type", NULL);
+    if (!s || strcmp(s, "dns")) return APR_EINVAL;
+    s = md_json_gets(json, "identifier", "value", NULL);
+    if (!s || strcmp(s, authz->domain)) return APR_EINVAL;
+    
+    authz->state = MD_ACME_AUTHZ_S_UNKNOWN;
+    s = md_json_gets(json, "status", NULL);
+    if (s && !strcmp(s, "pending")) {
+        authz->state = MD_ACME_AUTHZ_S_PENDING;
+    }
+    else if (s && !strcmp(s, "valid")) {
+        authz->state = MD_ACME_AUTHZ_S_VALID;
+    }
+    else if (s && !strcmp(s, "invalid")) {
+        authz->state = MD_ACME_AUTHZ_S_INVALID;
+    }
+    else if (s) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "unknown authz state '%s' "
+                      "for %s in %s", s, authz->domain, authz->location);
+        return APR_EINVAL;
+    }
+    return rv;
+}
+
+/**************************************************************************************************/
+/* response to a challenge */
+
+static md_acme_authz_cha_t *cha_from_json(apr_pool_t *p, size_t index, md_json_t *json)
+{
+    md_acme_authz_cha_t * cha;
+    
+    cha = apr_pcalloc(p, sizeof(*cha));
+    cha->index = index;
+    cha->type = md_json_dups(p, json, MD_KEY_TYPE, NULL);
+    cha->uri = md_json_dups(p, json, MD_KEY_URI, NULL);
+    cha->token = md_json_dups(p, json, MD_KEY_TOKEN, NULL);
+    cha->key_authz = md_json_dups(p, json, MD_KEY_KEYAUTHZ, NULL);
+
+    return cha;
+}
+
+static apr_status_t on_init_authz_resp(md_acme_req_t *req, void *baton)
+{
+    authz_req_ctx *ctx = baton;
+    md_json_t *jpayload;
+
+    jpayload = md_json_create(req->p);
+    md_json_sets("challenge", jpayload, MD_KEY_RESOURCE, NULL);
+    md_json_sets(ctx->challenge->key_authz, jpayload, MD_KEY_KEYAUTHZ, NULL);
+    
+    return md_acme_req_body_init(req, jpayload);
+} 
+
+static apr_status_t authz_http_set(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs, 
+                                   md_json_t *body, void *baton)
+{
+    authz_req_ctx *ctx = baton;
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "updated authz %s", ctx->authz->location);
+    return APR_SUCCESS;
+}
+
+static apr_status_t setup_key_authz(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
+                                    md_acme_t *acme, apr_pool_t *p, int *pchanged)
+{
+    const char *thumb64, *key_authz;
+    apr_status_t rv;
+    
+    assert(cha);
+    assert(cha->token);
+    
+    *pchanged = 0;
+    if (APR_SUCCESS == (rv = md_jws_pkey_thumb(&thumb64, p, acme->acct_key))) {
+        key_authz = apr_psprintf(p, "%s.%s", cha->token, thumb64);
+        if (cha->key_authz) {
+            if (strcmp(key_authz, cha->key_authz)) {
+                /* Hu? Did the account change key? */
+                cha->key_authz = NULL;
+            }
+        }
+        if (!cha->key_authz) {
+            cha->key_authz = key_authz;
+            *pchanged = 1;
+        }
+    }
+    return rv;
+}
+
+static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, 
+                                      md_acme_t *acme, md_store_t *store, apr_pool_t *p)
+{
+    const char *data;
+    apr_status_t rv;
+    int notify_server;
+    
+    if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, &notify_server))) {
+        goto out;
+    }
+    
+    rv = md_store_load(store, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01,
+                       MD_SV_TEXT, (void**)&data, p);
+    if ((APR_SUCCESS == rv && strcmp(cha->key_authz, data)) || APR_STATUS_IS_ENOENT(rv)) {
+        rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01,
+                           MD_SV_TEXT, (void*)cha->key_authz, 0);
+        authz->dir = authz->domain;
+        notify_server = 1;
+    }
+    
+    if (APR_SUCCESS == rv && notify_server) {
+        authz_req_ctx ctx;
+
+        /* challenge is setup or was changed from previous data, tell ACME server
+         * so it may (re)try verification */        
+        authz_req_ctx_init(&ctx, acme, NULL, authz, p);
+        ctx.challenge = cha;
+        rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, &ctx);
+    }
+out:
+    return rv;
+}
+
+static apr_status_t setup_cha_dns(const char **pdns, md_acme_authz_cha_t *cha, apr_pool_t *p)
+{
+    const char *dhex;
+    char *dns;
+    apr_size_t dhex_len;
+    apr_status_t rv;
+    
+    rv = md_crypt_sha256_digest_hex(&dhex, p, cha->key_authz, strlen(cha->key_authz));
+    if (APR_SUCCESS == rv) {
+        dhex = md_util_str_tolower((char*)dhex);
+        dhex_len = strlen(dhex); 
+        assert(dhex_len > 32);
+        dns = apr_pcalloc(p, dhex_len + 1 + sizeof(MD_TLSSNI01_DNS_SUFFIX));
+        strncpy(dns, dhex, 32);
+        dns[32] = '.';
+        strncpy(dns+33, dhex+32, dhex_len-32);
+        memcpy(dns+(dhex_len+1), MD_TLSSNI01_DNS_SUFFIX, sizeof(MD_TLSSNI01_DNS_SUFFIX));
+    }
+    *pdns = (APR_SUCCESS == rv)? dns : NULL;
+    return rv;
+}
+
+static apr_status_t cha_tls_sni_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, 
+                                         md_acme_t *acme, md_store_t *store, apr_pool_t *p)
+{
+    md_cert_t *cha_cert;
+    md_pkey_t *cha_key;
+    const char *cha_dns;
+    apr_status_t rv;
+    int notify_server;
+    
+    if (   APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, &notify_server))
+        || APR_SUCCESS != (rv = setup_cha_dns(&cha_dns, cha, p))) {
+        goto out;
+    }
+
+    rv = md_store_load(store, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_CERT,
+                       MD_SV_CERT, (void**)&cha_cert, p);
+    if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, cha_dns)) 
+        || APR_STATUS_IS_ENOENT(rv)) {
+        
+        if (APR_SUCCESS != (rv = md_pkey_gen_rsa(&cha_key, p, acme->pkey_bits))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-sni-01 challgenge key",
+                          authz->domain);
+            goto out;
+        }
+
+        /* setup a certificate containing the challenge dns */
+        rv = md_cert_self_sign(&cha_cert, authz->domain, cha_dns, cha_key, 
+                               apr_time_from_sec(7 * MD_SECS_PER_DAY), p);
+        
+        if (APR_SUCCESS != rv) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: setup self signed cert for %s",
+                          authz->domain, cha_dns);
+            goto out;
+        }
+        
+        rv = md_store_save(store, p, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_PKEY,
+                           MD_SV_PKEY, (void*)cha_key, 0);
+        if (APR_SUCCESS == rv) {
+            rv = md_store_save(store, p, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_CERT,
+                               MD_SV_CERT, (void*)cha_cert, 0);
+        }
+        authz->dir = cha_dns;
+        notify_server = 1;
+    }
+    
+    if (APR_SUCCESS == rv && notify_server) {
+        authz_req_ctx ctx;
+
+        /* challenge is setup or was changed from previous data, tell ACME server
+         * so it may (re)try verification */        
+        authz_req_ctx_init(&ctx, acme, NULL, authz, p);
+        ctx.challenge = cha;
+        rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, &ctx);
+    }
+out:    
+    return rv;
+}
+
+typedef apr_status_t cha_starter(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, 
+                                 md_acme_t *acme, md_store_t *store, apr_pool_t *p);
+                                 
+typedef struct {
+    const char *name;
+    cha_starter *start;
+} cha_type;
+
+static const cha_type CHA_TYPES[] = {
+    { MD_AUTHZ_TYPE_HTTP01,     cha_http_01_setup },
+    { MD_AUTHZ_TYPE_TLSSNI01,   cha_tls_sni_01_setup },
+};
+static const apr_size_t CHA_TYPES_LEN = (sizeof(CHA_TYPES)/sizeof(CHA_TYPES[0]));
+
+typedef struct {
+    apr_pool_t *p;
+    const char *type;
+    md_acme_authz_cha_t *accepted;
+    apr_array_header_t *offered;
+} cha_find_ctx;
+
+static apr_status_t collect_offered(void *baton, size_t index, md_json_t *json)
+{
+    cha_find_ctx *ctx = baton;
+    
+    const char *ctype = md_json_gets(json, MD_KEY_TYPE, NULL);
+    if (ctype) {
+        APR_ARRAY_PUSH(ctx->offered, const char*) = apr_pstrdup(ctx->p, ctype);
+    }
+    return 1;
+}
+
+static apr_status_t find_type(void *baton, size_t index, md_json_t *json)
+{
+    cha_find_ctx *ctx = baton;
+    
+    const char *ctype = md_json_gets(json, MD_KEY_TYPE, NULL);
+    if (ctype && !apr_strnatcasecmp(ctx->type, ctype)) {
+        ctx->accepted = cha_from_json(ctx->p, index, json);
+        return 0;
+    }
+    return 1;
+}
+
+apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store, 
+                                   apr_array_header_t *challenges, apr_pool_t *p)
+{
+    apr_status_t rv;
+    int i;
+    cha_find_ctx fctx;
+    
+    assert(acme);
+    assert(authz);
+    assert(authz->resource);
+
+    fctx.p = p;
+    fctx.accepted = NULL;
+    
+    /* Look in the order challenge types are defined */
+    for (i = 0; i < challenges->nelts && !fctx.accepted; ++i) {
+        fctx.type = APR_ARRAY_IDX(challenges, i, const char *);
+        md_json_itera(find_type, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL);
+    }
+    
+    if (!fctx.accepted) {
+        rv = APR_EINVAL;
+        fctx.offered = apr_array_make(p, 5, sizeof(const char*));
+        md_json_itera(collect_offered, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL);
+        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, 
+                      "%s: the server offers no ACME challenge that is configured "
+                      "for this MD. The server offered '%s' and available for this "
+                      "MD are: '%s' (via %s).",
+                      authz->domain, 
+                      apr_array_pstrcat(p, fctx.offered, ' '),
+                      apr_array_pstrcat(p, challenges, ' '),
+                      authz->location);
+        return rv;
+    }
+    
+    for (i = 0; i < CHA_TYPES_LEN; ++i) {
+        if (!apr_strnatcasecmp(CHA_TYPES[i].name, fctx.accepted->type)) {
+            return CHA_TYPES[i].start(fctx.accepted, authz, acme, store, p);
+        }
+    }
+    
+    rv = APR_ENOTIMPL;
+    md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, 
+                  "%s: no implementation found for challenge '%s'",
+                  authz->domain, fctx.accepted->type);
+    return rv;
+}
+
+/**************************************************************************************************/
+/* Delete an existing authz resource */
+
+typedef struct {
+    apr_pool_t *p;
+    md_acme_authz_t *authz;
+} del_ctx;
+
+static apr_status_t on_init_authz_del(md_acme_req_t *req, void *baton)
+{
+    md_json_t *jpayload;
+
+    jpayload = md_json_create(req->p);
+    md_json_sets("deactivated", jpayload, MD_KEY_STATUS, NULL);
+    
+    return md_acme_req_body_init(req, jpayload);
+} 
+
+static apr_status_t authz_del(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs, 
+                              md_json_t *body, void *baton)
+{
+    authz_req_ctx *ctx = baton;
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "deleted authz %s", ctx->authz->location);
+    acme->acct = NULL;
+    return APR_SUCCESS;
+}
+
+apr_status_t md_acme_authz_del(md_acme_authz_t *authz, md_acme_t *acme, 
+                               md_store_t *store, apr_pool_t *p)
+{
+    authz_req_ctx ctx;
+    
+    ctx.p = p;
+    ctx.authz = authz;
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "delete authz for %s from %s", 
+                  authz->domain, authz->location);
+    return md_acme_POST(acme, authz->location, on_init_authz_del, authz_del, NULL, &ctx);
+}
+
+/**************************************************************************************************/
+/* authz conversion */
+
+md_json_t *md_acme_authz_to_json(md_acme_authz_t *a, apr_pool_t *p)
+{
+    md_json_t *json = md_json_create(p);
+    if (json) {
+        md_json_sets(a->domain, json, MD_KEY_DOMAIN, NULL);
+        md_json_sets(a->location, json, MD_KEY_LOCATION, NULL);
+        md_json_sets(a->dir, json, MD_KEY_DIR, NULL);
+        md_json_setl(a->state, json, MD_KEY_STATE, NULL);
+        return json;
+    }
+    return NULL;
+}
+
+md_acme_authz_t *md_acme_authz_from_json(struct md_json_t *json, apr_pool_t *p)
+{
+    md_acme_authz_t *authz = md_acme_authz_create(p);
+    if (authz) {
+        authz->domain = md_json_dups(p, json, MD_KEY_DOMAIN, NULL);            
+        authz->location = md_json_dups(p, json, MD_KEY_LOCATION, NULL);            
+        authz->dir = md_json_dups(p, json, MD_KEY_DIR, NULL);            
+        authz->state = (int)md_json_getl(json, MD_KEY_STATE, NULL);            
+        return authz;
+    }
+    return NULL;
+}
+
+/**************************************************************************************************/
+/* authz_set conversion */
+
+#define MD_KEY_ACCOUNT          "account"
+#define MD_KEY_AUTHZS           "authorizations"
+
+static apr_status_t authz_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton)
+{
+    return md_json_setj(md_acme_authz_to_json(value, p), json, NULL);
+}
+
+static apr_status_t authz_from_json(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
+{
+    *pvalue = md_acme_authz_from_json(json, p);
+    return (*pvalue)? APR_SUCCESS : APR_EINVAL;
+}
+
+md_json_t *md_acme_authz_set_to_json(md_acme_authz_set_t *set, apr_pool_t *p)
+{
+    md_json_t *json = md_json_create(p);
+    if (json) {
+        md_json_seta(set->authzs, authz_to_json, NULL, json, MD_KEY_AUTHZS, NULL);
+        return json;
+    }
+    return NULL;
+}
+
+md_acme_authz_set_t *md_acme_authz_set_from_json(md_json_t *json, apr_pool_t *p)
+{
+    md_acme_authz_set_t *set = md_acme_authz_set_create(p, NULL);
+    if (set) {
+        md_json_geta(set->authzs, authz_from_json, NULL, json, MD_KEY_AUTHZS, NULL);
+        return set;
+    }
+    return NULL;
+}
+
+/**************************************************************************************************/
+/* persistence */
+
+apr_status_t md_acme_authz_set_load(struct md_store_t *store, md_store_group_t group, 
+                                    const char *md_name, md_acme_authz_set_t **pauthz_set, 
+                                    apr_pool_t *p)
+{
+    apr_status_t rv;
+    md_json_t *json;
+    md_acme_authz_set_t *authz_set;
+    
+    rv = md_store_load_json(store, group, md_name, MD_FN_AUTHZ, &json, p);
+    if (APR_SUCCESS == rv) {
+        authz_set = md_acme_authz_set_from_json(json, p);
+    }
+    *pauthz_set = (APR_SUCCESS == rv)? authz_set : NULL;
+    return rv;  
+}
+
+static apr_status_t p_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+    md_store_t *store = baton;
+    md_json_t *json;
+    md_store_group_t group;
+    md_acme_authz_set_t *set;
+    const char *md_name;
+    int create;
+    
+    group = va_arg(ap, int);
+    md_name = va_arg(ap, const char *);
+    set = va_arg(ap, md_acme_authz_set_t *);
+    create = va_arg(ap, int);
+
+    json = md_acme_authz_set_to_json(set, ptemp);
+    assert(json);
+    return md_store_save_json(store, ptemp, group, md_name, MD_FN_AUTHZ, json, create);
+}
+
+apr_status_t md_acme_authz_set_save(struct md_store_t *store, apr_pool_t *p,
+                                    md_store_group_t group, const char *md_name, 
+                                    md_acme_authz_set_t *authz_set, int create)
+{
+    return md_util_pool_vdo(p_save, store, p, group, md_name, authz_set, create, NULL);
+}
+
+static apr_status_t p_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+    md_store_t *store = baton;
+    md_acme_authz_set_t *authz_set;
+    const md_acme_authz_t *authz;
+    md_store_group_t group;
+    const char *md_name;
+    int i;
+
+    group = va_arg(ap, int);
+    md_name = va_arg(ap, const char *);
+
+    if (APR_SUCCESS == md_acme_authz_set_load(store, group, md_name, &authz_set, p)) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz_set loaded for %s", md_name);
+        for (i = 0; i < authz_set->authzs->nelts; ++i) {
+            authz = APR_ARRAY_IDX(authz_set->authzs, i, const md_acme_authz_t*);
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz check %s", authz->domain);
+            if (authz->dir) {
+                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz purge %s", authz->dir);
+                md_store_purge(store, p, MD_SG_CHALLENGES, authz->dir);
+            }
+        }
+    }
+    return md_store_remove(store, group, md_name, MD_FN_AUTHZ, ptemp, 1);
+}
+
+apr_status_t md_acme_authz_set_purge(md_store_t *store, md_store_group_t group,
+                                     apr_pool_t *p, const char *md_name)
+{
+    return md_util_pool_vdo(p_purge, store, p, group, md_name, NULL);
+}
+

Added: httpd/httpd/branches/trunk-md/modules/md/md_acme_authz.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/trunk-md/modules/md/md_acme_authz.h?rev=1804123&view=auto
==============================================================================
--- httpd/httpd/branches/trunk-md/modules/md/md_acme_authz.h (added)
+++ httpd/httpd/branches/trunk-md/modules/md/md_acme_authz.h Fri Aug  4 13:47:25 2017
@@ -0,0 +1,102 @@
+/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_acme_authz_h
+#define mod_md_md_acme_authz_h
+
+struct apr_array_header_t;
+struct md_acme_t;
+struct md_acme_acct_t;
+struct md_json_t;
+struct md_store_t;
+
+typedef struct md_acme_challenge_t md_acme_challenge_t;
+
+/**************************************************************************************************/
+/* authorization request for a specific domain name */
+
+#define MD_AUTHZ_TYPE_HTTP01        "http-01"
+#define MD_AUTHZ_TYPE_TLSSNI01      "tls-sni-01"
+
+typedef enum {
+    MD_ACME_AUTHZ_S_UNKNOWN,
+    MD_ACME_AUTHZ_S_PENDING,
+    MD_ACME_AUTHZ_S_VALID,
+    MD_ACME_AUTHZ_S_INVALID,
+} md_acme_authz_state_t;
+
+typedef struct md_acme_authz_t md_acme_authz_t;
+
+struct md_acme_authz_t {
+    const char *domain;
+    const char *location;
+    const char *dir;
+    md_acme_authz_state_t state;
+    apr_time_t expires;
+    struct md_json_t *resource;
+};
+
+#define MD_FN_HTTP01            "acme-http-01.txt"
+#define MD_FN_TLSSNI01_CERT     "acme-tls-sni-01.cert.pem"
+#define MD_FN_TLSSNI01_PKEY     "acme-tls-sni-01.key.pem"
+#define MD_FN_AUTHZ             "authz.json"
+
+
+md_acme_authz_t *md_acme_authz_create(apr_pool_t *p);
+
+struct md_json_t *md_acme_authz_to_json(md_acme_authz_t *a, apr_pool_t *p);
+md_acme_authz_t *md_acme_authz_from_json(struct md_json_t *json, apr_pool_t *p);
+
+/* authz interaction with ACME server */
+apr_status_t md_acme_authz_register(struct md_acme_authz_t **pauthz, struct md_acme_t *acme,
+                                    struct md_store_t *store, const char *domain, apr_pool_t *p);
+
+apr_status_t md_acme_authz_update(md_acme_authz_t *authz, struct md_acme_t *acme, 
+                                  struct md_store_t *store, apr_pool_t *p);
+
+apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, struct md_acme_t *acme, 
+                                   struct md_store_t *store, 
+                                   apr_array_header_t *challenges, apr_pool_t *p);
+apr_status_t md_acme_authz_del(md_acme_authz_t *authz, struct md_acme_t *acme, 
+                               struct md_store_t *store, apr_pool_t *p);
+
+/**************************************************************************************************/
+/* set of authz data for a managed domain */
+
+typedef struct md_acme_authz_set_t md_acme_authz_set_t;
+
+struct md_acme_authz_set_t {
+    struct apr_array_header_t *authzs;
+};
+
+md_acme_authz_set_t *md_acme_authz_set_create(apr_pool_t *p, struct md_acme_t *acme);
+md_acme_authz_t *md_acme_authz_set_get(md_acme_authz_set_t *set, const char *domain);
+apr_status_t md_acme_authz_set_add(md_acme_authz_set_t *set, md_acme_authz_t *authz);
+apr_status_t md_acme_authz_set_remove(md_acme_authz_set_t *set, const char *domain);
+
+struct md_json_t *md_acme_authz_set_to_json(md_acme_authz_set_t *set, apr_pool_t *p);
+md_acme_authz_set_t *md_acme_authz_set_from_json(struct md_json_t *json, apr_pool_t *p);
+
+apr_status_t md_acme_authz_set_load(struct md_store_t *store, md_store_group_t group, 
+                                    const char *md_name, md_acme_authz_set_t **pauthz_set, 
+                                    apr_pool_t *p);
+apr_status_t md_acme_authz_set_save(struct md_store_t *store, apr_pool_t *p, 
+                                    md_store_group_t group, const char *md_name, 
+                                    md_acme_authz_set_t *authz_set, int create);
+
+apr_status_t md_acme_authz_set_purge(struct md_store_t *store, md_store_group_t group,
+                                     apr_pool_t *p, const char *md_name);
+
+#endif /* md_acme_authz_h */

Added: httpd/httpd/branches/trunk-md/modules/md/md_acme_drive.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/trunk-md/modules/md/md_acme_drive.c?rev=1804123&view=auto
==============================================================================
--- httpd/httpd/branches/trunk-md/modules/md/md_acme_drive.c (added)
+++ httpd/httpd/branches/trunk-md/modules/md/md_acme_drive.c Fri Aug  4 13:47:25 2017
@@ -0,0 +1,926 @@
+/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_buckets.h>
+#include <apr_hash.h>
+#include <apr_uri.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_jws.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_reg.h"
+#include "md_store.h"
+#include "md_util.h"
+
+#include "md_acme.h"
+#include "md_acme_acct.h"
+#include "md_acme_authz.h"
+
+typedef struct {
+    md_proto_driver_t *driver;
+    
+    const char *phase;
+    int complete;
+
+    md_pkey_t *pkey;
+    md_cert_t *cert;
+    apr_array_header_t *chain;
+
+    md_acme_t *acme;
+    md_t *md;
+    const md_creds_t *ncreds;
+    
+    apr_array_header_t *ca_challenges;
+    md_acme_authz_set_t *authz_set;
+    apr_interval_time_t authz_monitor_timeout;
+    
+    const char *csr_der_64;
+    apr_interval_time_t cert_poll_timeout;
+    
+    const char *chain_url;
+    
+} md_acme_driver_t;
+
+/**************************************************************************************************/
+/* account setup */
+
+static apr_status_t ad_set_acct(md_proto_driver_t *d) 
+{
+    md_acme_driver_t *ad = d->baton;
+    md_t *md = ad->md;
+    apr_status_t rv = APR_SUCCESS;
+    int update = 0, acct_installed = 0;
+    
+    ad->phase = "setup acme";
+    if (!ad->acme 
+        && APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, md->ca_url))) {
+        goto out;
+    }
+
+    ad->phase = "choose account";
+    /* Do we have a staged (modified) account? */
+    if (APR_SUCCESS == (rv = md_acme_use_acct_staged(ad->acme, d->store, md, d->p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "re-using staged account");
+        md->ca_account = MD_ACME_ACCT_STAGED;
+        acct_installed = 1;
+    }
+    else if (APR_STATUS_IS_ENOENT(rv)) {
+        rv = APR_SUCCESS;
+    }
+    
+    /* Get an account for the ACME server for this MD */
+    if (md->ca_account && !acct_installed) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "re-use account '%s'", md->ca_account);
+        rv = md_acme_use_acct(ad->acme, d->store, d->p, md->ca_account);
+        if (APR_STATUS_IS_ENOENT(rv) || APR_STATUS_IS_EINVAL(rv)) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "rejected %s", md->ca_account);
+            md->ca_account = NULL;
+            update = 1;
+            rv = APR_SUCCESS;
+        }
+    }
+
+    if (APR_SUCCESS == rv && !md->ca_account) {
+        /* Find a local account for server, store at MD */ 
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: looking at existing accounts",
+                      d->proto->protocol);
+        if (APR_SUCCESS == md_acme_find_acct(ad->acme, d->store, d->p)) {
+            md->ca_account = md_acme_get_acct(ad->acme, d->p);
+            update = 1;
+        }
+    }
+    
+    if (APR_SUCCESS == rv && !md->ca_account) {
+        /* 2.2 No local account exists, create a new one */
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: creating new account", 
+                      d->proto->protocol);
+        
+        if (!ad->md->contacts || apr_is_empty_array(md->contacts)) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, d->p, 
+                          "no contact information for md %s", md->name);            
+            rv = APR_EINVAL;
+            goto out;
+        }
+    
+        if (APR_SUCCESS == (rv = md_acme_create_acct(ad->acme, d->p, 
+                                                     md->contacts, md->ca_agreement))
+            && APR_SUCCESS == (rv = md_acme_acct_save_staged(ad->acme, d->store, md, d->p))) {
+            md->ca_account = MD_ACME_ACCT_STAGED;
+            update = 1;
+        }
+    }
+    
+out:
+    if (APR_SUCCESS == rv) {
+        const char *agreement = md_acme_get_agreement(ad->acme);
+        /* Persist the account chosen at the md so we use the same on future runs */
+        if (agreement && (!md->ca_agreement || strcmp(agreement, md->ca_agreement))) { 
+            md->ca_agreement = agreement;
+            update = 1;
+        }
+        if (update) {
+            rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
+        }
+    }
+    return rv;
+}
+
+/**************************************************************************************************/
+/* authz/challenge setup */
+
+/**
+ * Pre-Req: we have an account for the ACME server that has accepted the current license agreement
+ * For each domain in MD: 
+ * - check if there already is a valid AUTHZ resource
+ * - if ot, create an AUTHZ resource with challenge data 
+ */
+static apr_status_t ad_setup_authz(md_proto_driver_t *d)
+{
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv;
+    md_t *md = ad->md;
+    md_acme_authz_t *authz;
+    int i, changed;
+    
+    assert(ad->md);
+    assert(ad->acme);
+
+    ad->phase = "check authz";
+    
+    /* For each domain in MD: AUTHZ setup
+     * if an AUTHZ resource is known, check if it is still valid
+     * if known AUTHZ resource is not valid, remove, goto 4.1.1
+     * if no AUTHZ available, create a new one for the domain, store it
+     */
+    rv = md_acme_authz_set_load(d->store, MD_SG_STAGING, md->name, &ad->authz_set, d->p);
+    if (!ad->authz_set || APR_STATUS_IS_ENOENT(rv)) {
+        ad->authz_set = md_acme_authz_set_create(d->p, ad->acme);
+        rv = APR_SUCCESS;
+    }
+    else if (APR_SUCCESS != rv) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: loading authz data", md->name);
+        md_acme_authz_set_purge(d->store, MD_SG_STAGING, d->p, md->name);
+        return APR_EAGAIN;
+    }
+    
+    /* 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)) {
+            md_acme_authz_set_remove(ad->authz_set, authz->domain);
+            changed = 1;
+        }
+    }
+    
+    /* Add anything we do not already have */
+    for (i = 0; i < md->domains->nelts && APR_SUCCESS == rv; ++i) {
+        const char *domain = APR_ARRAY_IDX(md->domains, i, const char *);
+        changed = 0;
+        authz = md_acme_authz_set_get(ad->authz_set, domain);
+        if (authz) {
+            /* check valid */
+            rv = md_acme_authz_update(authz, ad->acme, d->store, d->p);
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: updated authz for %s", 
+                          md->name, domain);
+            if (APR_SUCCESS != rv) {
+                md_acme_authz_set_remove(ad->authz_set, domain);
+                authz = NULL;
+                changed = 1;
+            }
+        }
+        if (!authz) {
+            /* create new one */
+            rv = md_acme_authz_register(&authz, ad->acme, d->store, domain, d->p);
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: created authz for %s", 
+                          md->name, domain);
+            if (APR_SUCCESS == rv) {
+                rv = md_acme_authz_set_add(ad->authz_set, authz);
+                changed = 1;
+            }
+        }
+    }
+    
+    /* Save any changes */
+    if (APR_SUCCESS == rv && changed) {
+        rv = md_acme_authz_set_save(d->store, d->p, MD_SG_STAGING, md->name, ad->authz_set, 0);
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, "%s: saved", md->name);
+    }
+    
+    return rv;
+}
+
+/**
+ * Pre-Req: all domains have a AUTHZ resources at the ACME server
+ * For each domain in MD: 
+ * - if AUTHZ resource is 'valid' -> continue
+ * - if AUTHZ resource is 'pending':
+ *   - find preferred challenge choice
+ *   - calculate challenge data for httpd to find
+ *   - POST challenge start to ACME server
+ * For each domain in MD where AUTHZ is 'pending', until overall timeout: 
+ *   - wait a certain time, check status again
+ * If not all AUTHZ are valid, fail
+ */
+static apr_status_t ad_start_challenges(md_proto_driver_t *d)
+{
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv = APR_SUCCESS;
+    md_acme_authz_t *authz;
+    int i, changed = 0;
+    
+    assert(ad->md);
+    assert(ad->acme);
+    assert(ad->authz_set);
+
+    ad->phase = "start challenges";
+
+    for (i = 0; i < ad->authz_set->authzs->nelts && APR_SUCCESS == rv; ++i) {
+        authz = APR_ARRAY_IDX(ad->authz_set->authzs, i, md_acme_authz_t*);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: check AUTHZ for %s", 
+                      ad->md->name, authz->domain);
+        if (APR_SUCCESS != (rv = md_acme_authz_update(authz, ad->acme, d->store, d->p))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: check authz for %s",
+                          ad->md->name, authz->domain);
+            break;
+        }
+
+        switch (authz->state) {
+            case MD_ACME_AUTHZ_S_VALID:
+                break;
+            case MD_ACME_AUTHZ_S_PENDING:
+                
+                rv = md_acme_authz_respond(authz, ad->acme, d->store, ad->ca_challenges, d->p);
+                changed = 1;
+                break;
+            default:
+                rv = APR_EINVAL;
+                md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, 
+                              "%s: unexpected AUTHZ state %d at %s", 
+                              authz->domain, authz->state, authz->location);
+                break;
+        }
+    }
+    
+    if (APR_SUCCESS == rv && changed) {
+        rv = md_acme_authz_set_save(d->store, d->p, MD_SG_STAGING, ad->md->name, ad->authz_set, 0);
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, "%s: saved", ad->md->name);
+    }
+    return rv;
+}
+
+static apr_status_t check_challenges(void *baton, int attemmpt)
+{
+    md_proto_driver_t *d = baton;
+    md_acme_driver_t *ad = d->baton;
+    md_acme_authz_t *authz;
+    apr_status_t rv = APR_SUCCESS;
+    int i;
+    
+    for (i = 0; i < ad->authz_set->authzs->nelts && APR_SUCCESS == rv; ++i) {
+        authz = APR_ARRAY_IDX(ad->authz_set->authzs, i, md_acme_authz_t*);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: check AUTHZ for %s", 
+                      ad->md->name, authz->domain);
+        if (APR_SUCCESS == (rv = md_acme_authz_update(authz, ad->acme, d->store, d->p))) {
+            switch (authz->state) {
+                case MD_ACME_AUTHZ_S_VALID:
+                    break;
+                case MD_ACME_AUTHZ_S_PENDING:
+                    rv = APR_EAGAIN;
+                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, 
+                                  "%s: status pending at %s", authz->domain, authz->location);
+                    break;
+                default:
+                    rv = APR_EINVAL;
+                    md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, 
+                                  "%s: unexpected AUTHZ state %d at %s", 
+                                  authz->domain, authz->state, authz->location);
+                    break;
+            }
+        }
+    }
+    return rv;
+}
+
+static apr_status_t ad_monitor_challenges(md_proto_driver_t *d)
+{
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv;
+    
+    assert(ad->md);
+    assert(ad->acme);
+    assert(ad->authz_set);
+
+    ad->phase = "monitor challenges";
+    rv = md_util_try(check_challenges, d, 0, ad->authz_monitor_timeout, 0, 0, 1);
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, d->p, 
+                  "%s: checked all domain authorizations", ad->md->name);
+    return rv;
+}
+
+/**************************************************************************************************/
+/* poll cert */
+
+
+static apr_status_t read_http_cert(md_cert_t **pcert, apr_pool_t *p,
+                                   const md_http_response_t *res)
+{
+    apr_status_t rv = APR_SUCCESS;
+    
+    if (APR_SUCCESS != (rv = md_cert_read_http(pcert, p, res))
+        && APR_STATUS_IS_ENOENT(rv)) {
+        rv = APR_EAGAIN;
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
+                      "cert not in response from %s", res->req->url);
+    }
+    return rv;
+}
+
+static apr_status_t on_got_cert(md_acme_t *acme, const md_http_response_t *res, void *baton)
+{
+    md_proto_driver_t *d = baton;
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv = APR_SUCCESS;
+    
+    
+    if (APR_SUCCESS == (rv = read_http_cert(&ad->cert, d->p, res))) {
+        rv = md_store_save(d->store, d->p, MD_SG_STAGING, ad->md->name, MD_FN_CERT, 
+                           MD_SV_CERT, ad->cert, 0);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "cert parsed and saved");
+    }
+    return rv;
+}
+
+static apr_status_t get_cert(void *baton, int attempt)
+{
+    md_proto_driver_t *d = baton;
+    md_acme_driver_t *ad = d->baton;
+    
+    return md_acme_GET(ad->acme, ad->md->cert_url, NULL, NULL, on_got_cert, d);
+}
+
+static apr_status_t ad_cert_poll(md_proto_driver_t *d, int only_once)
+{
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv;
+    
+    assert(ad->md);
+    assert(ad->acme);
+    assert(ad->md->cert_url);
+    
+    ad->phase = "poll certificate";
+    if (only_once) {
+        rv = get_cert(d, 0);
+    }
+    else {
+        rv = md_util_try(get_cert, d, 1, ad->cert_poll_timeout, 0, 0, 1);
+    }
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "poll for cert at %s", ad->md->cert_url);
+    return rv;
+}
+
+/**************************************************************************************************/
+/* cert setup */
+
+static apr_status_t on_init_csr_req(md_acme_req_t *req, void *baton)
+{
+    md_proto_driver_t *d = baton;
+    md_acme_driver_t *ad = d->baton;
+    md_json_t *jpayload;
+
+    jpayload = md_json_create(req->p);
+    md_json_sets("new-cert", jpayload, MD_KEY_RESOURCE, NULL);
+    md_json_sets(ad->csr_der_64, jpayload, MD_KEY_CSR, NULL);
+    
+    return md_acme_req_body_init(req, jpayload);
+} 
+
+static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void *baton)
+{
+    md_proto_driver_t *d = baton;
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv = APR_SUCCESS;
+    
+    ad->md->cert_url = apr_table_get(res->headers, "location");
+    if (!ad->md->cert_url) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, d->p, 
+                      "cert created without giving its location header");
+        return APR_EINVAL;
+    }
+    if (APR_SUCCESS != (rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, d->p, 
+                      "%s: saving cert url %s", ad->md->name, ad->md->cert_url);
+        return rv;
+    }
+    
+    /* Check if it already was sent with this response */
+    if (APR_SUCCESS == (rv = md_cert_read_http(&ad->cert, d->p, res))) {
+        rv = md_cert_save(d->store, d->p, MD_SG_STAGING, ad->md->name, ad->cert, 0);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "cert parsed and saved");
+    }
+    else if (APR_STATUS_IS_ENOENT(rv)) {
+        rv = APR_SUCCESS;
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, 
+                      "cert not in response, need to poll %s", ad->md->cert_url);
+    }
+    
+    return rv;
+}
+
+/**
+ * Pre-Req: all domains have been validated by the ACME server, e.g. all have AUTHZ
+ * resources that have status 'valid'
+ * - Setup private key, if not already there
+ * - Generate a CSR with org, contact, etc
+ * - Optionally enable must-staple OCSP extension
+ * - Submit CSR, expect 201 with location
+ * - POLL location for certificate
+ * - store certificate
+ * - retrieve cert chain information from cert
+ * - GET cert chain
+ * - store cert chain
+ */
+static apr_status_t ad_setup_certificate(md_proto_driver_t *d)
+{
+    md_acme_driver_t *ad = d->baton;
+    md_pkey_t *pkey;
+    apr_status_t rv;
+
+    ad->phase = "setup cert pkey";
+    
+    rv = md_pkey_load(d->store, MD_SG_STAGING, ad->md->name, &pkey, d->p);
+    if (APR_STATUS_IS_ENOENT(rv)) {
+        if (APR_SUCCESS == (rv = md_pkey_gen_rsa(&pkey, d->p, ad->acme->pkey_bits))) {
+            rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, ad->md->name, pkey, 1);
+        }
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: generate pkey", ad->md->name);
+    }
+
+    if (APR_SUCCESS == rv) {
+        ad->phase = "setup csr";
+        rv = md_cert_req_create(&ad->csr_der_64, ad->md, pkey, d->p);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: create CSR", ad->md->name);
+    }
+
+    if (APR_SUCCESS == rv) {
+        ad->phase = "submit csr";
+        rv = md_acme_POST(ad->acme, ad->acme->new_cert, on_init_csr_req, NULL, csr_req, d);
+    }
+
+    if (APR_SUCCESS == rv) {
+        if (!ad->cert) {
+            rv = ad_cert_poll(d, 0);
+        }
+    }
+    return rv;
+}
+
+/**************************************************************************************************/
+/* cert chain retrieval */
+
+static apr_status_t on_add_chain(md_acme_t *acme, const md_http_response_t *res, void *baton)
+{
+    md_proto_driver_t *d = baton;
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv = APR_SUCCESS;
+    md_cert_t *cert;
+    const char *ct;
+    
+    ct = apr_table_get(res->headers, "Content-Type");
+    if (ct && !strcmp("application/x-pkcs7-mime", ct)) {
+        /* root cert most likely, end it here */
+        return APR_SUCCESS;
+    }
+    
+    if (APR_SUCCESS == (rv = read_http_cert(&cert, d->p, res))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "chain cert parsed");
+        APR_ARRAY_PUSH(ad->chain, md_cert_t *) = cert;
+    }
+    return rv;
+}
+
+static apr_status_t get_chain(void *baton, int attempt)
+{
+    md_proto_driver_t *d = baton;
+    md_acme_driver_t *ad = d->baton;
+    md_cert_t *cert;
+    const char *url, *last_url = NULL;
+    apr_status_t rv = APR_SUCCESS;
+    
+    while (APR_SUCCESS == rv && ad->chain->nelts < 10) {
+        int nelts = ad->chain->nelts;
+        if (ad->chain && nelts > 0) {
+            cert = APR_ARRAY_IDX(ad->chain, nelts - 1, md_cert_t *);
+        }
+        else {
+            cert = ad->cert;
+        }
+        
+        if (APR_SUCCESS == (rv = md_cert_get_issuers_uri(&url, cert, d->p))
+            && (!last_url || strcmp(last_url, url))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "next issuer is  %s", url);
+#if MD_EXPERIMENTAL
+            if (!strncmp("http://127.0.0.1:", url, sizeof("http://127.0.0.1:")-1)) {
+                /* test boulder instance always reports issuer cert on localhost, but we
+                 * may use a different address to reach the boulder server */
+                apr_uri_t curi, ca;
+                
+                if (APR_SUCCESS == apr_uri_parse(d->p, url, &curi)
+                    && APR_SUCCESS == apr_uri_parse(d->p, ad->acme->url, &ca)) {
+                    url = apr_psprintf(d->p, "%s://%s:%s%s", 
+                                       ca.scheme, ca.hostname, ca.port_str, curi.path);
+                }
+            }
+#endif
+            rv = md_acme_GET(ad->acme, url, NULL, NULL, on_add_chain, d);
+            
+            if (APR_SUCCESS == rv && nelts == ad->chain->nelts) {
+                break;
+            }
+        }
+        else if (APR_STATUS_IS_ENOENT(rv) || !url || !strlen(url)) {
+            rv = APR_SUCCESS;
+            break;
+        }
+    }
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, 
+                  "got chain with %d certs", ad->chain->nelts);
+    return rv;
+}
+
+static apr_status_t ad_chain_install(md_proto_driver_t *d)
+{
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv;
+    
+    ad->chain = apr_array_make(d->p, 5, sizeof(md_cert_t *));
+    if (APR_SUCCESS == (rv = md_util_try(get_chain, d, 0, ad->cert_poll_timeout, 0, 0, 0))) {
+        rv = md_store_save(d->store, d->p, MD_SG_STAGING, ad->md->name, MD_FN_CHAIN, 
+                           MD_SV_CHAIN, ad->chain, 0);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "chain fetched and saved");
+    }
+    return rv;
+}
+
+/**************************************************************************************************/
+/* ACME driver init */
+
+static apr_status_t acme_driver_init(md_proto_driver_t *d)
+{
+    md_acme_driver_t *ad;
+    apr_status_t rv;
+
+    ad = apr_pcalloc(d->p, sizeof(*ad));
+    
+    d->baton = ad;
+    ad->driver = d;
+    
+    ad->authz_monitor_timeout = apr_time_from_sec(30);
+    ad->cert_poll_timeout = apr_time_from_sec(30);
+
+    /* We can only support challenges if the server is reachable from the outside
+     * via port 80 and/or 443. These ports might be mapped for httpd to something
+     * else, but a mapping needs to exist. */
+    ad->ca_challenges = apr_array_make(d->p, 3, sizeof(const char *)); 
+    if (d->challenge) {
+        /* we have been told to use this type */
+        APR_ARRAY_PUSH(ad->ca_challenges, const char*) = apr_pstrdup(d->p, d->challenge);
+    }
+    else if (d->md->ca_challenges && d->md->ca_challenges->nelts > 0) {
+        /* pre-configured set for this managed domain */
+        apr_array_cat(ad->ca_challenges, d->md->ca_challenges);
+    }
+    else {
+        /* free to chose. Add all we support and see what we get offered */
+        APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_TLSSNI01;
+        APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_HTTP01;
+    }
+    
+    if (!d->can_http && !d->can_https) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, d->p, "%s: the server seems neither "
+                      "reachable via http (port 80) nor https (port 443). The ACME protocol "
+                      "needs at least one of those so the CA can talk to the server and verify "
+                      "a domain ownership.", d->md->name);
+        return APR_EGENERAL;
+    }
+    
+    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;
+}
+
+/**************************************************************************************************/
+/* ACME staging */
+
+static apr_status_t acme_stage(md_proto_driver_t *d)
+{
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv = APR_SUCCESS;
+    int renew = 1;
+
+    if (md_log_is_level(d->p, MD_LOG_DEBUG)) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: staging started, "
+                      "state=%d, can_http=%d, can_https=%d, challenges='%s'",
+                      d->md->name, d->md->state, d->can_http, d->can_https,
+                      apr_array_pstrcat(d->p, ad->ca_challenges, ' '));
+    }
+
+    /* 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 */
+        md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: all data staged", d->md->name);
+        renew = 0;
+    }
+    
+    if (renew) {
+        if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, d->md->ca_url)) 
+            || APR_SUCCESS != (rv = md_acme_setup(ad->acme))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, "%s: setup ACME(%s)", 
+                          d->md->name, d->md->ca_url);
+            return rv;
+        }
+
+        if (!ad->md) {
+            /* re-initialize staging */
+            md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: setup staging", d->md->name);
+            md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
+            ad->md = md_copy(d->p, d->md);
+            ad->md->cert_url = NULL; /* do not retrieve the old cert */
+            rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: save staged md", 
+                          ad->md->name);
+        }
+
+        if (APR_SUCCESS == rv && !ad->cert) {
+            md_cert_load(d->store, MD_SG_STAGING, ad->md->name, &ad->cert, d->p);
+        }
+
+        if (APR_SUCCESS == rv && !ad->cert) {
+            ad->phase = "get certificate";
+            md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: need certificate", d->md->name);
+            
+            /* Chose (or create) and ACME account to use */
+            rv = ad_set_acct(d);
+            
+            /* Check that the account agreed to the terms-of-service, otherwise
+             * requests for new authorizations are denied. ToS may change during the
+             * lifetime of an account */
+            if (APR_SUCCESS == rv) {
+                ad->phase = "check agreement";
+                md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
+                              "%s: check Terms-of-Service agreement", d->md->name);
+                
+                rv = md_acme_check_agreement(ad->acme, d->p, ad->md->ca_agreement);
+            }
+            
+            /* If we know a cert's location, try to get it. Previous download might
+             * have failed. If server 404 it, we clear our memory of it. */
+            if (APR_SUCCESS == rv && ad->md->cert_url) {
+                md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
+                              "%s: polling certificate", d->md->name);
+                rv = ad_cert_poll(d, 1);
+                if (APR_STATUS_IS_ENOENT(rv)) {
+                    /* Server reports to know nothing about it. */
+                    ad->md->cert_url = NULL;
+                    rv = md_reg_update(d->reg, d->p, ad->md->name, ad->md, MD_UPD_CERT_URL);
+                }
+            }
+            
+            if (APR_SUCCESS == rv && !ad->cert) {
+                md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
+                              "%s: setup new authorization", d->md->name);
+                if (APR_SUCCESS != (rv = ad_setup_authz(d))) {
+                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: setup authz resource", 
+                                  ad->md->name);
+                    goto out;
+                }
+                md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
+                              "%s: setup new challenges", d->md->name);
+                if (APR_SUCCESS != (rv = ad_start_challenges(d))) {
+                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: start challenges", 
+                                  ad->md->name);
+                    goto out;
+                }
+                md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
+                              "%s: monitoring challenge status", d->md->name);
+                if (APR_SUCCESS != (rv = ad_monitor_challenges(d))) {
+                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: monitor challenges", 
+                                  ad->md->name);
+                    goto out;
+                }
+                md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
+                              "%s: creating certificate request", d->md->name);
+                if (APR_SUCCESS != (rv = ad_setup_certificate(d))) {
+                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: setup certificate", 
+                                  ad->md->name);
+                    goto out;
+                }
+                md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
+                              "%s: received certificate", d->md->name);
+            }
+            
+        }
+        
+        if (APR_SUCCESS == rv && !ad->chain) {
+            ad->phase = "install chain";
+            md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
+                          "%s: retrieving certificate chain", d->md->name);
+            rv = ad_chain_install(d);
+        }
+    }
+out:    
+    return rv;
+}
+
+static apr_status_t acme_driver_stage(md_proto_driver_t *d)
+{
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv;
+
+    ad->phase = "ACME staging";
+    if (APR_SUCCESS == (rv = acme_stage(d))) {
+        ad->phase = "staging done";
+    }
+        
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: %s, %s", 
+                  d->md->name, d->proto->protocol, ad->phase);
+    return rv;
+}
+
+/**************************************************************************************************/
+/* ACME preload */
+
+static apr_status_t acme_preload(md_store_t *store, md_store_group_t load_group, 
+                                 const char *name, apr_pool_t *p) 
+{
+    apr_status_t rv;
+    md_pkey_t *pkey, *acct_key;
+    md_t *md;
+    md_cert_t *cert;
+    apr_array_header_t *chain;
+    struct md_acme_acct_t *acct;
+
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: preload start", name);
+    /* Load all data which will be taken into the DOMAIN storage group.
+     * This serves several purposes:
+     *  1. It's a format check on the input data. 
+     *  2. We write back what we read, creating data with our own access permissions
+     *  3. We ignore any other accumulated data in STAGING
+     *  4. Once TMP is verified, we can swap/archive groups with a rename
+     *  5. Reading/Writing the data will apply/remove any group specific data encryption.
+     *     With the exemption that DOMAINS and TMP must apply the same policy/keys.
+     */
+    if (APR_SUCCESS != (rv = md_load(store, MD_SG_STAGING, name, &md, p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading md json", name);
+        return rv;
+    }
+    if (APR_SUCCESS != (rv = md_cert_load(store, MD_SG_STAGING, name, &cert, p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading certificate", name);
+        return rv;
+    }
+    if (APR_SUCCESS != (rv = md_chain_load(store, MD_SG_STAGING, name, &chain, p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading cert chain", name);
+        return rv;
+    }
+    if (APR_SUCCESS != (rv = md_pkey_load(store, MD_SG_STAGING, name, &pkey, p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading staging private key", name);
+        return rv;
+    }
+
+    /* See if staging holds a new or modified account data */
+    rv = md_acme_acct_load(&acct, &acct_key, store, MD_SG_STAGING, name, p);
+    if (APR_STATUS_IS_ENOENT(rv)) {
+        acct = NULL;
+        acct_key = NULL;
+        rv = APR_SUCCESS;
+    }
+    else if (APR_SUCCESS != rv) {
+        return rv; 
+    }
+
+    /* Remove any authz information we have here or in MD_SG_CHALLENGES */
+    md_acme_authz_set_purge(store, MD_SG_STAGING, p, name);
+
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
+                  "%s: staged data load, purging tmp space", name);
+    rv = md_store_purge(store, p, load_group, name);
+    if (APR_SUCCESS != rv) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: error purging preload storage", name);
+        return rv;
+    }
+    
+    if (acct) {
+        md_acme_t *acme;
+        
+        if (APR_SUCCESS != (rv = md_acme_create(&acme, p, md->ca_url))
+            || APR_SUCCESS != (rv = md_acme_acct_save(store, p, acme, acct, acct_key))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: error saving acct", name);
+            return rv;
+        }
+        md->ca_account = acct->id;
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: saved ACME account %s", 
+                      name, acct->id);
+    }
+    
+    if (APR_SUCCESS != (rv = md_save(store, p, load_group, md, 1))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving md json", name);
+        return rv;
+    }
+    if (APR_SUCCESS != (rv = md_cert_save(store, p, load_group, name, cert, 1))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving certificate", name);
+        return rv;
+    }
+    if (APR_SUCCESS != (rv = md_chain_save(store, p, load_group, name, chain, 1))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving cert chain", name);
+        return rv;
+    }
+    if (APR_SUCCESS != (rv = md_pkey_save(store, p, load_group, name, pkey, 1))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving domain private key", name);
+        return rv;
+    }
+    
+    return rv;
+}
+
+static apr_status_t acme_driver_preload(md_proto_driver_t *d, md_store_group_t group)
+{
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv;
+
+    ad->phase = "ACME preload";
+    if (APR_SUCCESS == (rv = acme_preload(d->store, group, d->md->name, d->p))) {
+        ad->phase = "preload done";
+    }
+        
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: %s, %s", 
+                  d->md->name, d->proto->protocol, ad->phase);
+    return rv;
+}
+
+static md_proto_t ACME_PROTO = {
+    MD_PROTO_ACME, acme_driver_init, acme_driver_stage, acme_driver_preload
+};
+ 
+apr_status_t md_acme_protos_add(apr_hash_t *protos, apr_pool_t *p)
+{
+    apr_hash_set(protos, MD_PROTO_ACME, sizeof(MD_PROTO_ACME)-1, &ACME_PROTO);
+    return APR_SUCCESS;
+}

Added: httpd/httpd/branches/trunk-md/modules/md/md_config.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/trunk-md/modules/md/md_config.c?rev=1804123&view=auto
==============================================================================
--- httpd/httpd/branches/trunk-md/modules/md/md_config.c (added)
+++ httpd/httpd/branches/trunk-md/modules/md/md_config.c Fri Aug  4 13:47:25 2017
@@ -0,0 +1,583 @@
+/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_log.h>
+#include <http_vhost.h>
+
+#include "md.h"
+#include "md_config.h"
+#include "md_util.h"
+
+extern module AP_MODULE_DECLARE_DATA md_module;
+APLOG_USE_MODULE(md);
+
+#define DEF_VAL     (-1)
+
+static md_config_t defconf = {
+    "default",
+    NULL,
+    80,
+    443,
+    NULL,
+    MD_ACME_DEF_URL,
+    "ACME",
+    NULL, 
+    NULL,
+    MD_DRIVE_AUTO,
+    apr_time_from_sec(14 * MD_SECS_PER_DAY), 
+    NULL, 
+    "md",
+    NULL
+};
+
+#define CONF_S_NAME(s)  (s && s->server_hostname? s->server_hostname : "default")
+
+void *md_config_create_svr(apr_pool_t *pool, server_rec *s)
+{
+    md_config_t *conf = (md_config_t *)apr_pcalloc(pool, sizeof(md_config_t));
+
+    conf->name = apr_pstrcat(pool, "srv[", CONF_S_NAME(s), "]", NULL);
+    conf->s = s;
+    conf->local_80 = DEF_VAL;
+    conf->local_443 = DEF_VAL;
+    conf->drive_mode = DEF_VAL;
+    conf->mds = apr_array_make(pool, 5, sizeof(const md_t *));
+    conf->renew_window = DEF_VAL;
+    
+    return conf;
+}
+
+static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv)
+{
+    md_config_t *base = (md_config_t *)basev;
+    md_config_t *add = (md_config_t *)addv;
+    md_config_t *n = (md_config_t *)apr_pcalloc(pool, sizeof(md_config_t));
+    char *name = apr_pstrcat(pool, "[", CONF_S_NAME(add->s), ", ", CONF_S_NAME(base->s), "]", NULL);
+    md_t *md;
+    int i;
+    
+    n->name = name;
+    n->local_80 = (add->local_80 != DEF_VAL)? add->local_80 : base->local_80;
+    n->local_443 = (add->local_443 != DEF_VAL)? add->local_443 : base->local_443;
+
+    /* I think we should not merge md definitions. They should reside where
+     * they were defined */
+    n->mds = apr_array_make(pool, add->mds->nelts, sizeof(const md_t *));
+    for (i = 0; i < add->mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(add->mds, i, md_t*);
+        APR_ARRAY_PUSH(n->mds, md_t *) = md_clone(pool, md);
+    }
+    n->ca_url = add->ca_url? add->ca_url : base->ca_url;
+    n->ca_proto = add->ca_proto? add->ca_proto : base->ca_proto;
+    n->ca_agreement = add->ca_agreement? add->ca_agreement : base->ca_agreement;
+    n->drive_mode = (add->drive_mode != DEF_VAL)? add->drive_mode : base->drive_mode;
+    n->md = NULL;
+    n->base_dir = add->base_dir? add->base_dir : base->base_dir;
+    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));
+    return n;
+}
+
+void *md_config_merge_svr(apr_pool_t *pool, void *basev, void *addv)
+{
+    return md_config_merge(pool, basev, addv);
+}
+
+void *md_config_create_dir(apr_pool_t *pool, char *dummy)
+{
+    md_config_dir_t *conf = apr_pcalloc(pool, sizeof(*conf));
+    return conf;
+}
+
+void *md_config_merge_dir(apr_pool_t *pool, void *basev, void *addv)
+{
+    md_config_dir_t *base = basev;
+    md_config_dir_t *add = addv;
+    md_config_dir_t *n = apr_pcalloc(pool, sizeof(*n));
+    n->md = add->md? add->md : base->md;
+    return n;
+}
+
+static int inside_section(cmd_parms *cmd) {
+    return (cmd->directive->parent 
+            && !ap_cstr_casecmp(cmd->directive->parent->directive, "<ManagedDomain"));
+}
+
+static const char *md_section_check(cmd_parms *cmd) {
+    if (!inside_section(cmd)) {
+        return apr_pstrcat(cmd->pool, cmd->cmd->name, 
+                           " is only valid inside a <ManagedDomain context, not ", 
+                           cmd->directive->parent? cmd->directive->parent->directive : "root", 
+                           NULL);
+    }
+    return NULL;
+}
+
+static void add_domain_name(apr_array_header_t *domains, const char *name, apr_pool_t *p)
+{
+    if (md_array_str_index(domains, name, 0, 0) < 0) {
+        APR_ARRAY_PUSH(domains, char *) = md_util_str_tolower(apr_pstrdup(p, name));
+    }
+}
+
+static const char *md_config_sec_start(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+    md_config_t *sconf = ap_get_module_config(cmd->server->module_config, &md_module);
+    const char *endp = ap_strrchr_c(arg, '>');
+    ap_conf_vector_t *new_dir_conf = ap_create_per_dir_config(cmd->pool);
+    int old_overrides = cmd->override;
+    char *old_path = cmd->path;
+    const char *err, *name;
+    md_config_dir_t *dconf;
+    md_t *md;
+
+    if (NULL != (err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE))) {
+        return err;
+    }
+        
+    if (endp == NULL) {
+        return apr_pstrcat(cmd->pool, cmd->cmd->name, "> directive missing closing '>'", NULL);
+    }
+
+    arg = apr_pstrndup(cmd->pool, arg, endp-arg);
+    if (!arg || !*arg) {
+        return "<ManagedDomain > block must specify a unique domain name";
+    }
+
+    cmd->path = ap_getword_white(cmd->pool, &arg);
+    name = cmd->path;
+    
+    md = md_create_empty(cmd->pool);
+    md->name = name;
+    APR_ARRAY_PUSH(md->domains, const char*) = name;
+    md->drive_mode = DEF_VAL;
+    
+    while (*arg != '\0') {
+        name = ap_getword_white(cmd->pool, &arg);
+        APR_ARRAY_PUSH(md->domains, const char*) = name;
+    }
+
+    dconf = ap_set_config_vectors(cmd->server, new_dir_conf, cmd->path, &md_module, cmd->pool);
+    dconf->md = md;
+    
+    if (NULL == (err = ap_walk_config(cmd->directive->first_child, cmd, new_dir_conf))) {
+        APR_ARRAY_PUSH(sconf->mds, const md_t *) = md;
+    }
+    
+    cmd->path = old_path;
+    cmd->override = old_overrides;
+
+    return err;
+}
+
+static const char *md_config_sec_add_members(cmd_parms *cmd, void *dc, 
+                                             int argc, char *const argv[])
+{
+    md_config_dir_t *dconfig = dc;
+    apr_array_header_t *domains;
+    const char *err;
+    int i;
+    
+    if (NULL != (err = md_section_check(cmd))) {
+        return err;
+    }
+    
+    domains = dconfig->md->domains;
+    for (i = 0; i < argc; ++i) {
+        add_domain_name(domains, argv[i], cmd->pool);
+    }
+    return NULL;
+}
+
+static const char *md_config_set_names(cmd_parms *cmd, void *arg, 
+                                       int argc, char *const argv[])
+{
+    md_config_t *config = (md_config_t *)md_config_get(cmd->server);
+    apr_array_header_t *domains = apr_array_make(cmd->pool, 5, sizeof(const char *));
+    const char *err;
+    md_t *md;
+    int i;
+
+    err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE);
+    if (err) {
+        return err;
+    }
+
+    for (i = 0; i < argc; ++i) {
+        add_domain_name(domains, argv[i], cmd->pool);
+    }
+    err = md_create(&md, cmd->pool, domains);
+    if (err) {
+        return err;
+    }
+    
+    if (cmd->config_file) {
+        md->defn_name = cmd->config_file->name;
+        md->defn_line_number = cmd->config_file->line_number;
+    }
+
+    APR_ARRAY_PUSH(config->mds, md_t *) = md;
+
+    return NULL;
+}
+
+static const char *md_config_set_ca(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_config_t *config = (md_config_t *)md_config_get(cmd->server);
+    const char *err;
+
+    if (inside_section(cmd)) {
+        md_config_dir_t *dconf = dc;
+        dconf->md->ca_url = value;
+    }
+    else {
+        if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+            return err;
+        }
+        config->ca_url = value;
+    }
+    return NULL;
+}
+
+static const char *md_config_set_ca_proto(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_config_t *config = (md_config_t *)md_config_get(cmd->server);
+    const char *err;
+
+    if (inside_section(cmd)) {
+        md_config_dir_t *dconf = dc;
+        dconf->md->ca_proto = value;
+    }
+    else {
+        if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+            return err;
+        }
+        config->ca_proto = value;
+    }
+    return NULL;
+}
+
+static const char *md_config_set_agreement(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_config_t *config = (md_config_t *)md_config_get(cmd->server);
+    const char *err;
+
+    if (inside_section(cmd)) {
+        md_config_dir_t *dconf = dc;
+        dconf->md->ca_agreement = value;
+    }
+    else {
+        if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+            return err;
+        }
+        config->ca_agreement = value;
+    }
+    return NULL;
+}
+
+static const char *md_config_set_drive_mode(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_config_t *config = (md_config_t *)md_config_get(cmd->server);
+    const char *err;
+    md_drive_mode_t drive_mode;
+
+    if (!apr_strnatcasecmp("auto", value) || !apr_strnatcasecmp("automatic", value)) {
+        drive_mode = MD_DRIVE_AUTO;
+    }
+    else if (!apr_strnatcasecmp("always", value)) {
+        drive_mode = MD_DRIVE_ALWAYS;
+    }
+    else if (!apr_strnatcasecmp("manual", value) || !apr_strnatcasecmp("stick", value)) {
+        drive_mode = MD_DRIVE_MANUAL;
+    }
+    else {
+        return apr_pstrcat(cmd->pool, "unknown MDDriveMode ", value, NULL);
+    }
+    
+    if (inside_section(cmd)) {
+        md_config_dir_t *dconf = dc;
+        dconf->md->drive_mode = drive_mode;
+    }
+    else {
+        if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+            return err;
+        }
+        config->drive_mode = drive_mode;
+    }
+    return NULL;
+}
+
+static apr_status_t duration_parse(const char *value, apr_interval_time_t *ptimeout, 
+                                   const char *def_unit)
+{
+    char *endp;
+    long funits = 1;
+    apr_status_t rv;
+    apr_int64_t n;
+    
+    n = apr_strtoi64(value, &endp, 10);
+    if (errno) {
+        return errno;
+    }
+    if (!endp || !*endp) {
+        if (strcmp(def_unit, "d") == 0) {
+            def_unit = "s";
+            funits = MD_SECS_PER_DAY;
+        }
+    }
+    else if (*endp == 'd') {
+        *ptimeout = apr_time_from_sec(n * MD_SECS_PER_DAY);
+        return APR_SUCCESS;
+    }
+    else {
+        def_unit = endp;
+    }
+    rv = ap_timeout_parameter_parse(value, ptimeout, def_unit);
+    if (APR_SUCCESS == rv && funits > 1) {
+        *ptimeout *= funits;
+    }
+    return rv;
+}
+
+static const char *md_config_set_renew_window(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_config_t *config = (md_config_t *)md_config_get(cmd->server);
+    const char *err;
+    apr_interval_time_t timeout;
+
+    /* Inspired by http_core.c */
+    if (duration_parse(value, &timeout, "d") != APR_SUCCESS) {
+        return "MDRenewWindow has wrong format";
+    }
+        
+    if (inside_section(cmd)) {
+        md_config_dir_t *dconf = dc;
+        dconf->md->renew_window = timeout;
+    }
+    else {
+        if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+            return err;
+        }
+        config->renew_window = timeout;
+    }
+    return NULL;
+}
+
+static const char *md_config_set_store_dir(cmd_parms *cmd, void *arg, const char *value)
+{
+    md_config_t *config = (md_config_t *)md_config_get(cmd->server);
+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+    if (err) {
+        return err;
+    }
+    config->base_dir = value;
+    (void)arg;
+    return NULL;
+}
+
+static const char *set_port_map(md_config_t *config, const char *value)
+{
+    int net_port, local_port;
+    char *endp;
+
+    net_port = (int)apr_strtoi64(value, &endp, 10);
+    if (errno) {
+        return "unable to parse first port number";
+    }
+    if (!endp || *endp != ':') {
+        return "no ':' after first port number";
+    }
+    ++endp;
+    if (*endp == '-') {
+        local_port = 0;
+    }
+    else {
+        local_port = (int)apr_strtoi64(endp, &endp, 10);
+        if (errno) {
+            return "unable to parse second port number";
+        }
+        if (local_port <= 0 || local_port > 65535) {
+            return "invalid number for port map, must be in ]0,65535]";
+        }
+    }
+    switch (net_port) {
+        case 80:
+            config->local_80 = local_port;
+            break;
+        case 443:
+            config->local_443 = local_port;
+            break;
+        default:
+            return "mapped port number must be 80 or 443";
+    }
+    return NULL;
+}
+
+static const char *md_config_set_port_map(cmd_parms *cmd, void *arg, 
+                                          const char *v1, const char *v2)
+{
+    md_config_t *config = (md_config_t *)md_config_get(cmd->server);
+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+    (void)arg;
+    if (!err) {
+        err = set_port_map(config, v1);
+    }
+    if (!err && v2) {
+        err = set_port_map(config, v2);
+    }
+    return err;
+}
+
+static const char *md_config_set_cha_tyes(cmd_parms *cmd, void *dc, 
+                                          int argc, char *const argv[])
+{
+    md_config_t *config = (md_config_t *)md_config_get(cmd->server);
+    apr_array_header_t **pcha, *ca_challenges;
+    const char *err;
+    int i;
+
+    if (inside_section(cmd)) {
+        md_config_dir_t *dconf = dc;
+        pcha = &dconf->md->ca_challenges;
+    }
+    else {
+        if (NULL != (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+            return err;
+        }
+        pcha = &config->ca_challenges; 
+    }
+    
+    ca_challenges = *pcha;
+    if (!ca_challenges) {
+        *pcha = ca_challenges = apr_array_make(cmd->pool, 5, sizeof(const char *));
+    }
+    for (i = 0; i < argc; ++i) {
+        APR_ARRAY_PUSH(ca_challenges, const char *) = argv[i];
+    }
+    
+    return NULL;
+}
+
+
+#define AP_END_CMD     AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
+
+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("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, 
+                  "URL of CA issueing the certificates"),
+    AP_INIT_TAKE1("MDStoreDir", md_config_set_store_dir, NULL, RSRC_CONF, 
+                  "the directory for file system storage of managed domain data."),
+    AP_INIT_TAKE1("MDCertificateProtocol", md_config_set_ca_proto, NULL, RSRC_CONF, 
+                  "Protocol used to obtain/renew certificates"),
+    AP_INIT_TAKE1("MDCertificateAgreement", md_config_set_agreement, NULL, RSRC_CONF, 
+                  "URL of CA Terms-of-Service agreement you accept"),
+    AP_INIT_TAKE1("MDDriveMode", md_config_set_drive_mode, NULL, RSRC_CONF, 
+                  "method of obtaining certificates for the managed domain"),
+    AP_INIT_TAKE1("MDRenewWindow", md_config_set_renew_window, NULL, RSRC_CONF, 
+                  "Time length for renewal before certificate expires (defaults to days)"),
+    AP_INIT_TAKE12("MDPortMap", md_config_set_port_map, NULL, RSRC_CONF, 
+                  "Declare the mapped ports 80 and 443 on the local server. E.g. 80:8000 "
+                  "to indicate that the server port 8000 is reachable as port 80 from the "
+                  "internet. Use 80:- to indicate that port 80 is not reachable from "
+                  "the outside."),
+    AP_INIT_TAKE_ARGV("MDCAChallenges", md_config_set_cha_tyes, NULL, RSRC_CONF, 
+                      "A list of challenge types to be used."),
+    AP_END_CMD
+};
+
+
+static const md_config_t *config_get_int(server_rec *s, apr_pool_t *p)
+{
+    md_config_t *cfg = (md_config_t *)ap_get_module_config(s->module_config, &md_module);
+    ap_assert(cfg);
+    if (cfg->s != s && p) {
+        cfg = md_config_merge(p, &defconf, cfg);
+        cfg->name = apr_pstrcat(p, CONF_S_NAME(s), cfg->name, NULL);
+        ap_set_module_config(s->module_config, &md_module, cfg);
+    }
+    return cfg;
+}
+
+const md_config_t *md_config_get(server_rec *s)
+{
+    return config_get_int(s, NULL);
+}
+
+const md_config_t *md_config_get_unique(server_rec *s, apr_pool_t *p)
+{
+    assert(p);
+    return config_get_int(s, p);
+}
+
+const md_config_t *md_config_cget(conn_rec *c)
+{
+    return md_config_get(c->base_server);
+}
+
+const char *md_config_gets(const md_config_t *config, md_config_var_t var)
+{
+    switch (var) {
+        case MD_CONFIG_CA_URL:
+            return config->ca_url? config->ca_url : defconf.ca_url;
+        case MD_CONFIG_CA_PROTO:
+            return config->ca_proto? config->ca_proto : defconf.ca_proto;
+        case MD_CONFIG_BASE_DIR:
+            return config->base_dir? config->base_dir : defconf.base_dir;
+        case MD_CONFIG_CA_AGREEMENT:
+            return config->ca_agreement? config->ca_agreement : defconf.ca_agreement;
+        default:
+            return NULL;
+    }
+}
+
+int md_config_geti(const md_config_t *config, md_config_var_t var)
+{
+    switch (var) {
+        case MD_CONFIG_DRIVE_MODE:
+            return (config->drive_mode != DEF_VAL)? config->drive_mode : defconf.drive_mode;
+        case MD_CONFIG_LOCAL_80:
+            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;
+        default:
+            return 0;
+    }
+}
+
+apr_interval_time_t md_config_get_interval(const md_config_t *config, md_config_var_t var)
+{
+    switch (var) {
+        case MD_CONFIG_RENEW_WINDOW:
+            return (config->renew_window != DEF_VAL)? config->renew_window : defconf.renew_window;
+        default:
+            return 0;
+    }
+}

Added: httpd/httpd/branches/trunk-md/modules/md/md_config.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/trunk-md/modules/md/md_config.h?rev=1804123&view=auto
==============================================================================
--- httpd/httpd/branches/trunk-md/modules/md/md_config.h (added)
+++ httpd/httpd/branches/trunk-md/modules/md/md_config.h Fri Aug  4 13:47:25 2017
@@ -0,0 +1,77 @@
+/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed 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_config_h
+#define mod_md_md_config_h
+
+struct md_store_t;
+
+typedef enum {
+    MD_CONFIG_CA_URL,
+    MD_CONFIG_CA_PROTO,
+    MD_CONFIG_BASE_DIR,
+    MD_CONFIG_CA_AGREEMENT,
+    MD_CONFIG_DRIVE_MODE,
+    MD_CONFIG_LOCAL_80,
+    MD_CONFIG_LOCAL_443,
+    MD_CONFIG_RENEW_WINDOW,
+} md_config_var_t;
+
+typedef struct {
+    const char *name;
+    const server_rec *s;
+    
+    int local_80;
+    int local_443;
+    
+    apr_array_header_t *mds;           /* array of md_t pointers */
+    const char *ca_url;
+    const char *ca_proto;
+    const char *ca_agreement;
+    apr_array_header_t *ca_challenges; /* challenge types allowed */
+    
+    int drive_mode;
+    apr_interval_time_t renew_window;  /* time for renewal before expiry */
+    
+    const md_t *md;
+    const char *base_dir;
+    struct md_store_t *store;
+
+} md_config_t;
+
+typedef struct {
+    md_t *md;
+} md_config_dir_t;
+
+void *md_config_create_svr(apr_pool_t *pool, server_rec *s);
+void *md_config_merge_svr(apr_pool_t *pool, void *basev, void *addv);
+void *md_config_create_dir(apr_pool_t *pool, char *dummy);
+void *md_config_merge_dir(apr_pool_t *pool, void *basev, void *addv);
+
+extern const command_rec md_cmds[];
+
+/* Get the effective md configuration for the connection */
+const md_config_t *md_config_cget(conn_rec *c);
+/* Get the effective md configuration for the server */
+const md_config_t *md_config_get(server_rec *s);
+/* Get the effective md configuration for the server, but make it
+ * unique to this server_rec, so that any changes only affect this server */
+const md_config_t *md_config_get_unique(server_rec *s, apr_pool_t *p);
+
+const char *md_config_gets(const md_config_t *config, md_config_var_t var);
+int md_config_geti(const md_config_t *config, md_config_var_t var);
+apr_interval_time_t md_config_get_interval(const md_config_t *config, md_config_var_t var);
+
+#endif /* md_config_h */



Mime
View raw message