]> granicus.if.org Git - apache/commitdiff
*) mod_md: bringing over v2.0.6 from github.
authorStefan Eissing <icing@apache.org>
Mon, 24 Jun 2019 16:04:32 +0000 (16:04 +0000)
committerStefan Eissing <icing@apache.org>
Mon, 24 Jun 2019 16:04:32 +0000 (16:04 +0000)
     - supports the ACMEv2 protocol
     - supports the new challenge method 'tls-alpn-01'
     - supports command configuration to setup/teardown 'dns-01' challenges
     - supports wildcard certificates when dns challenges are configured
     - ACMEv2 is the new default and will be used on the next certificate renewal,
       unless another MDCertificateAuthority is configured
     - challenge type 'tls-sni-01' has been removed as CAs do not offer this any longer
     - a domain exposes its status at https://<domain>/.httpd/certificate-status
     - Managed Domains are now in Apache's 'server-status' page
     - A new handler 'md-status' exposes verbose status information in JSON format
     - new directives "MDCertificateFile" and "MDCertificateKeyFile" to configure a
       Managed Domain that uses static files. Auto-renewal is turned off for those.
     - new MDMessageCmd that is invoked on several events: 'renewed', 'expiring' and
       'errored'. New 'MDWarnWindow' directive to configure when expiration warnings
       shall be issued.
     - ACMEv2 endpoints use the GET via empty POST way of accessing resources, see
       announcement by Let's Encrypt:
       https://community.letsencrypt.org/t/acme-v2-scheduled-deprecation-of-unauthenticated-resource-gets/74380

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1862013 13f79535-47bb-0310-9956-ffa450edef68

49 files changed:
CHANGES
modules/md/config2.m4
modules/md/md.h
modules/md/md_acme.c
modules/md/md_acme.h
modules/md/md_acme_acct.c
modules/md/md_acme_acct.h
modules/md/md_acme_authz.c
modules/md/md_acme_authz.h
modules/md/md_acme_drive.c
modules/md/md_acme_drive.h [new file with mode: 0644]
modules/md/md_acme_order.c [new file with mode: 0644]
modules/md/md_acme_order.h [new file with mode: 0644]
modules/md/md_acmev1_drive.c [new file with mode: 0644]
modules/md/md_acmev1_drive.h [new file with mode: 0644]
modules/md/md_acmev2_drive.c [new file with mode: 0644]
modules/md/md_acmev2_drive.h [new file with mode: 0644]
modules/md/md_core.c
modules/md/md_crypt.c
modules/md/md_crypt.h
modules/md/md_curl.c
modules/md/md_http.c
modules/md/md_http.h
modules/md/md_json.c
modules/md/md_json.h
modules/md/md_jws.c
modules/md/md_reg.c
modules/md/md_reg.h
modules/md/md_result.c [new file with mode: 0644]
modules/md/md_result.h [new file with mode: 0644]
modules/md/md_status.c [new file with mode: 0644]
modules/md/md_status.h [new file with mode: 0644]
modules/md/md_store.c
modules/md/md_store.h
modules/md/md_store_fs.c
modules/md/md_time.c [new file with mode: 0644]
modules/md/md_time.h [new file with mode: 0644]
modules/md/md_util.c
modules/md/md_util.h
modules/md/md_version.h
modules/md/mod_md.c
modules/md/mod_md.h
modules/md/mod_md_config.c
modules/md/mod_md_config.h
modules/md/mod_md_drive.c [new file with mode: 0644]
modules/md/mod_md_drive.h [new file with mode: 0644]
modules/md/mod_md_os.c
modules/md/mod_md_status.c [new file with mode: 0644]
modules/md/mod_md_status.h [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index c603a7b006b71d4ba667ad8b0d189fe56e9d2333..57e179d906a3f4053c43f219fe560b52d00b155f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,27 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.5.1
 
+  *) mod_md: bringing over v2.0.6 from github.
+     - supports the ACMEv2 protocol
+     - supports the new challenge method 'tls-alpn-01' 
+     - supports command configuration to setup/teardown 'dns-01' challenges
+     - supports wildcard certificates when dns challenges are configured
+     - ACMEv2 is the new default and will be used on the next certificate renewal,
+       unless another MDCertificateAuthority is configured
+     - challenge type 'tls-sni-01' has been removed as CAs do not offer this any longer
+     - a domain exposes its status at https://<domain>/.httpd/certificate-status
+     - Managed Domains are now in Apache's 'server-status' page
+     - A new handler 'md-status' exposes verbose status information in JSON format
+     - new directives "MDCertificateFile" and "MDCertificateKeyFile" to configure a
+       Managed Domain that uses static files. Auto-renewal is turned off for those.
+     - new MDMessageCmd that is invoked on several events: 'renewed', 'expiring' and
+       'errored'. New 'MDWarnWindow' directive to configure when expiration warnings
+       shall be issued.
+     - ACMEv2 endpoints use the GET via empty POST way of accessing resources, see
+       announcement by Let's Encrypt: 
+       https://community.letsencrypt.org/t/acme-v2-scheduled-deprecation-of-unauthenticated-resource-gets/74380
+     [Stefan Eissing]
+
   *) mod_ssl: use OPENSSL_init_ssl() to initialise OpenSSL on versions 1.1+.
      [Graham Leggett]
 
index 354885f02de436f7f0de38611f673bfef9566066..0d8d678780113d53211fcd178135c2137eb785f2 100644 (file)
@@ -140,6 +140,9 @@ md_acme.lo dnl
 md_acme_acct.lo dnl
 md_acme_authz.lo dnl
 md_acme_drive.lo dnl
+md_acmev1_drive.lo dnl
+md_acmev2_drive.lo dnl
+md_acme_order.lo dnl
 md_core.lo dnl
 md_curl.lo dnl
 md_crypt.lo dnl
@@ -147,13 +150,18 @@ md_http.lo dnl
 md_json.lo dnl
 md_jws.lo dnl
 md_log.lo dnl
+md_result.lo dnl
 md_reg.lo dnl
+md_status.lo dnl
 md_store.lo dnl
 md_store_fs.lo dnl
+md_time.lo dnl
 md_util.lo dnl
 mod_md.lo dnl
 mod_md_config.lo dnl
+mod_md_drive.lo dnl
 mod_md_os.lo dnl
+mod_md_status.lo dnl
 "
 
 # Ensure that other modules can pick up mod_md.h
index 60f8852b57b25286650da28a346c7d4f07bcd299..a2d874551a9edcf0e8ebb045955024a98eda2625 100644 (file)
@@ -17,6 +17,7 @@
 #ifndef mod_md_md_h
 #define mod_md_md_h
 
+#include "md_time.h"
 #include "md_version.h"
 
 struct apr_array_header_t;
@@ -37,13 +38,19 @@ struct md_pkey_spec_t;
 #define MD_HSTS_HEADER             "Strict-Transport-Security"
 #define MD_HSTS_MAX_AGE_DEFAULT    15768000
 
+#define PROTO_ACME_TLS_1        "acme-tls/1"
+
+#define MD_TIME_LIFE_NORM           (apr_time_from_sec(100 * MD_SECS_PER_DAY))
+#define MD_TIME_RENEW_WINDOW_DEF    (apr_time_from_sec(33 * MD_SECS_PER_DAY))
+#define MD_TIME_WARN_WINDOW_DEF     (apr_time_from_sec(10 * MD_SECS_PER_DAY))
+
 typedef enum {
-    MD_S_UNKNOWN,                   /* MD has not been analysed yet */
-    MD_S_INCOMPLETE,                /* MD is missing necessary information, cannot go live */
-    MD_S_COMPLETE,                  /* MD has all necessary information, can go live */
-    MD_S_EXPIRED,                   /* MD is complete, but credentials have expired */
-    MD_S_ERROR,                     /* MD data is flawed, unable to be processed as is */ 
-    MD_S_MISSING,                   /* MD is missing config information, cannot proceed */
+    MD_S_UNKNOWN = 0,               /* MD has not been analysed yet */
+    MD_S_INCOMPLETE = 1,            /* MD is missing necessary information, cannot go live */
+    MD_S_COMPLETE = 2,              /* MD has all necessary information, can go live */
+    MD_S_EXPIRED_DEPRECATED = 3,    /* deprecated */
+    MD_S_ERROR = 4,                 /* MD data is flawed, unable to be processed as is */ 
+    MD_S_MISSING_INFORMATION = 5,     /* User has not agreed to ToS */
 } md_state_t;
 
 typedef enum {
@@ -73,11 +80,11 @@ typedef enum {
 } md_store_group_t;
 
 typedef enum {
-    MD_DRIVE_DEFAULT = -1,          /* default value */
-    MD_DRIVE_MANUAL,                /* manually triggered transmission of credentials */
-    MD_DRIVE_AUTO,                  /* automatic process performed by httpd */
-    MD_DRIVE_ALWAYS,                /* always driven by httpd, even if not used in any vhost */
-} md_drive_mode_t;
+    MD_RENEW_DEFAULT = -1,          /* default value */
+    MD_RENEW_MANUAL,                /* manually triggered renewal of certificate */
+    MD_RENEW_AUTO,                  /* automatic process performed by httpd */
+    MD_RENEW_ALWAYS,                /* always renewed by httpd, even if not necessary */
+} md_renew_mode_t;
 
 typedef struct md_t md_t;
 struct md_t {
@@ -88,35 +95,46 @@ struct md_t {
     int transitive;                 /* != 0 iff VirtualHost names/aliases are auto-added */
     md_require_t require_https;     /* Iff https: is required for this MD */
     
-    int drive_mode;                 /* mode of obtaining credentials */
+    int renew_mode;                 /* mode of obtaining credentials */
     struct md_pkey_spec_t *pkey_spec;/* specification for generating new private keys */
     int must_staple;                /* certificates should set the OCSP Must Staple extension */
-    apr_interval_time_t renew_norm; /* if > 0, normalized cert lifetime */
-    apr_interval_time_t renew_window;/* time before expiration that starts renewal */
+    const md_timeslice_t *renew_window;  /* time before expiration that starts renewal */
+    const md_timeslice_t *warn_window;   /* time before expiration that warnings are sent out */
     
     const char *ca_url;             /* url of CA certificate service */
     const char *ca_proto;           /* protocol used vs CA (e.g. ACME) */
     const char *ca_account;         /* account used at CA */
     const char *ca_agreement;       /* accepted agreement uri between CA and user */ 
     struct apr_array_header_t *ca_challenges; /* challenge types configured for this MD */
-
+    const char *cert_file;          /* != NULL iff pubcert file explicitly configured */
+    const char *pkey_file;          /* != NULL iff privkey file explicitly configured */
+    
     md_state_t state;               /* state of this MD */
-    apr_time_t valid_from;          /* When the credentials start to be valid. 0 if unknown */
-    apr_time_t expires;             /* When the credentials expire. 0 if unknown */
-    const char *cert_url;           /* url where cert has been created, remember during drive */ 
+    
+    struct apr_array_header_t *acme_tls_1_domains; /* domains supporting "acme-tls/1" protocol */
     
     const struct md_srv_conf_t *sc; /* server config where it was defined or NULL */
     const char *defn_name;          /* config file this MD was defined */
     unsigned defn_line_number;      /* line number of definition */
+    
+    const char *configured_name;    /* name this MD was configured with, if different */
 };
 
 #define MD_KEY_ACCOUNT          "account"
+#define MD_KEY_ACME_TLS_1       "acme-tls/1"
+#define MD_KEY_ACTIVITY         "activity"
 #define MD_KEY_AGREEMENT        "agreement"
+#define MD_KEY_AUTHORIZATIONS   "authorizations"
 #define MD_KEY_BITS             "bits"
 #define MD_KEY_CA               "ca"
 #define MD_KEY_CA_URL           "ca-url"
 #define MD_KEY_CERT             "cert"
+#define MD_KEY_CERT_FILE        "cert-file"
+#define MD_KEY_CERTIFICATE      "certificate"
+#define MD_KEY_CHALLENGE        "challenge"
 #define MD_KEY_CHALLENGES       "challenges"
+#define MD_KEY_CMD_DNS01        "cmd-dns-01"
+#define MD_KEY_COMPLETE         "complete"
 #define MD_KEY_CONTACT          "contact"
 #define MD_KEY_CONTACTS         "contacts"
 #define MD_KEY_CSR              "csr"
@@ -125,46 +143,67 @@ struct md_t {
 #define MD_KEY_DIR              "dir"
 #define MD_KEY_DOMAIN           "domain"
 #define MD_KEY_DOMAINS          "domains"
-#define MD_KEY_DRIVE_MODE       "drive-mode"
+#define MD_KEY_ENTRIES          "entries"
+#define MD_KEY_ERRORED          "errored"
 #define MD_KEY_ERRORS           "errors"
 #define MD_KEY_EXPIRES          "expires"
+#define MD_KEY_FINALIZE         "finalize"
+#define MD_KEY_FINISHED         "finished"
 #define MD_KEY_HTTP             "http"
 #define MD_KEY_HTTPS            "https"
 #define MD_KEY_ID               "id"
 #define MD_KEY_IDENTIFIER       "identifier"
 #define MD_KEY_KEY              "key"
 #define MD_KEY_KEYAUTHZ         "keyAuthorization"
+#define MD_KEY_LAST             "last"
+#define MD_KEY_LAST_RUN         "last-run"
 #define MD_KEY_LOCATION         "location"
+#define MD_KEY_LOG              "log"
+#define MD_KEY_MDS              "managed-domains"
+#define MD_KEY_MESSAGE          "message"
 #define MD_KEY_MUST_STAPLE      "must-staple"
 #define MD_KEY_NAME             "name"
+#define MD_KEY_NEXT_RUN         "next-run"
+#define MD_KEY_NOTIFIED         "notified"
+#define MD_KEY_ORDERS           "orders"
 #define MD_KEY_PERMANENT        "permanent"
 #define MD_KEY_PKEY             "privkey"
-#define MD_KEY_PROCESSED        "processed"
+#define MD_KEY_PKEY_FILE        "pkey-file"
+#define MD_KEY_PROBLEM          "problem"
 #define MD_KEY_PROTO            "proto"
+#define MD_KEY_READY            "ready"
 #define MD_KEY_REGISTRATION     "registration"
 #define MD_KEY_RENEW            "renew"
+#define MD_KEY_RENEW_MODE       "renew-mode"
+#define MD_KEY_RENEWAL          "renewal"
+#define MD_KEY_RENEWING         "renewing"
 #define MD_KEY_RENEW_WINDOW     "renew-window"
 #define MD_KEY_REQUIRE_HTTPS    "require-https"
 #define MD_KEY_RESOURCE         "resource"
+#define MD_KEY_SERIAL           "serial"
+#define MD_KEY_SHA256_FINGERPRINT  "sha256-fingerprint"
 #define MD_KEY_STATE            "state"
 #define MD_KEY_STATUS           "status"
 #define MD_KEY_STORE            "store"
 #define MD_KEY_TEMPORARY        "temporary"
 #define MD_KEY_TOKEN            "token"
+#define MD_KEY_TOTAL            "total"
 #define MD_KEY_TRANSITIVE       "transitive"
 #define MD_KEY_TYPE             "type"
 #define MD_KEY_URL              "url"
 #define MD_KEY_URI              "uri"
-#define MD_KEY_VALID_FROM       "validFrom"
+#define MD_KEY_VALID_FROM       "valid-from"
+#define MD_KEY_VALID_UNTIL      "valid-until"
 #define MD_KEY_VALUE            "value"
 #define MD_KEY_VERSION          "version"
+#define MD_KEY_WHEN             "when"
+#define MD_KEY_WARN_WINDOW      "warn-window"
 
 #define MD_FN_MD                "md.json"
 #define MD_FN_JOB               "job.json"
 #define MD_FN_PRIVKEY           "privkey.pem"
 #define MD_FN_PUBCERT           "pubcert.pem"
 #define MD_FN_CERT              "cert.pem"
-#define MD_FN_CHAIN             "chain.pem"
 #define MD_FN_HTTPD_JSON        "httpd.json"
 
 #define MD_FN_FALLBACK_PKEY     "fallback-privkey.pem"
@@ -226,7 +265,7 @@ 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, 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);
+md_t *md_find_closest_match(struct apr_array_header_t *mds, const md_t *md);
 
 /**
  * Create and empty md record, structures initialized.
@@ -248,11 +287,6 @@ md_t *md_clone(apr_pool_t *p, const md_t *src);
  */
 md_t *md_copy(apr_pool_t *p, const md_t *src);
 
-/**
- * Create a merged md with the settings of add overlaying the ones from base.
- */
-md_t *md_merge(apr_pool_t *p, const md_t *add, const md_t *base);
-
 /** 
  * Convert the managed domain into a JSON representation and vice versa. 
  *
@@ -261,30 +295,26 @@ md_t *md_merge(apr_pool_t *p, const md_t *add, const md_t *base);
 struct md_json_t *md_to_json (const md_t *md, apr_pool_t *p);
 md_t *md_from_json(struct md_json_t *json, apr_pool_t *p);
 
-/**
- * Determine if MD should renew its cert (if it has one)
- */
-int md_should_renew(const md_t *md);
+int md_is_covered_by_alt_names(const md_t *md, const struct apr_array_header_t* alt_names);
+
+#define LE_ACMEv1_PROD      "https://acme-v01.api.letsencrypt.org/directory"
+#define LE_ACMEv1_STAGING   "https://acme-staging.api.letsencrypt.org/directory"
+
+#define LE_ACMEv2_PROD      "https://acme-v02.api.letsencrypt.org/directory"  
+#define LE_ACMEv2_STAGING   "https://acme-staging-v02.api.letsencrypt.org/directory"
+
 
 /**************************************************************************************************/
 /* domain credentials */
 
-typedef struct md_creds_t md_creds_t;
-struct md_creds_t {
-    struct md_pkey_t *privkey;
-    struct apr_array_header_t *pubcert;    /* complete md_cert* chain */
-    struct md_cert_t *cert;
-    int expired;
+typedef struct md_pubcert_t md_pubcert_t;
+struct md_pubcert_t {
+    struct apr_array_header_t *certs;     /* chain of const md_cert*, leaf cert first */
+    struct apr_array_header_t *alt_names; /* alt-names of leaf cert */
+    const char *cert_file;                /* file path of chain */
+    const char *key_file;                 /* file path of key for leaf cert */
 };
 
-/* TODO: not sure this is a good idea, testing some readability and debuggabiltiy of
- * cascaded apr_status_t checks. */
-#define MD_CHK_VARS                 const char *md_chk_
-#define MD_LAST_CHK                 md_chk_
-#define MD_CHK_STEP(c, status, s)   (md_chk_ = s, (void)md_chk_, status == (rv = (c)))
-#define MD_CHK(c, status)           MD_CHK_STEP(c, status, #c)
-#define MD_IS_ERR(c, err)           (md_chk_ = #c, APR_STATUS_IS_##err((rv = (c))))
-#define MD_CHK_SUCCESS(c)           MD_CHK(c, APR_SUCCESS)
-#define MD_OK(c)                    MD_CHK_SUCCESS(c)
+#define MD_OK(c)                    (APR_SUCCESS == (rv = c))
 
 #endif /* mod_md_md_h */
index 3fbd365f9c26ed897832b40b04a787a52faacb8d..d2cc00a10ef4ac4cb648156ab595d24d9c7249e7 100644 (file)
@@ -30,6 +30,7 @@
 #include "md_http.h"
 #include "md_log.h"
 #include "md_store.h"
+#include "md_result.h"
 #include "md_util.h"
 #include "md_version.h"
 
@@ -37,7 +38,7 @@
 #include "md_acme_acct.h"
 
 
-static const char *base_product;
+static const char *base_product= "-";
 
 typedef struct acme_problem_status_t acme_problem_status_t;
 
@@ -85,91 +86,6 @@ static apr_status_t problem_status_get(const char *type) {
     return APR_EGENERAL;
 }
 
-apr_status_t md_acme_init(apr_pool_t *p, const char *base)
-{
-    base_product = base;
-    return md_crypt_init(p);
-}
-
-apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
-                            const char *proxy_url)
-{
-    md_acme_t *acme;
-    const char *err = NULL;
-    apr_status_t rv;
-    apr_uri_t uri_parsed;
-    size_t len;
-    
-    if (!url) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p, "create ACME without url");
-        return APR_EINVAL;
-    }
-    
-    if (APR_SUCCESS != (rv = md_util_abs_uri_check(p, url, &err))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "invalid ACME uri (%s): %s", err, url);
-        return rv;
-    }
-    
-    acme = apr_pcalloc(p, sizeof(*acme));
-    acme->url = url;
-    acme->p = p;
-    acme->user_agent = apr_psprintf(p, "%s mod_md/%s", 
-                                    base_product, MOD_MD_VERSION);
-    acme->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
-    acme->max_retries = 3;
-    
-    if (APR_SUCCESS != (rv = apr_uri_parse(p, url, &uri_parsed))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "parsing ACME uri: %s", url);
-        return APR_EINVAL;
-    }
-    
-    len = strlen(uri_parsed.hostname);
-    acme->sname = (len <= 16)? uri_parsed.hostname : apr_pstrdup(p, uri_parsed.hostname + len - 16);
-    
-    *pacme = (APR_SUCCESS == rv)? acme : NULL;
-    return rv;
-}
-
-apr_status_t md_acme_setup(md_acme_t *acme)
-{
-    apr_status_t rv;
-    md_json_t *json;
-    
-    assert(acme->url);
-    if (!acme->http && APR_SUCCESS != (rv = md_http_create(&acme->http, acme->p,
-                                                           acme->user_agent, acme->proxy_url))) {
-        return rv;
-    }
-    md_http_set_response_limit(acme->http, 1024*1024);
-    
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "get directory from %s", acme->url);
-    
-    rv = md_acme_get_json(&json, acme, acme->url, acme->p);
-    if (APR_SUCCESS == rv) {
-        acme->new_authz = md_json_gets(json, "new-authz", NULL);
-        acme->new_cert = md_json_gets(json, "new-cert", NULL);
-        acme->new_reg = md_json_gets(json, "new-reg", NULL);
-        acme->revoke_cert = md_json_gets(json, "revoke-cert", NULL);
-        if (acme->new_authz && acme->new_cert && acme->new_reg && acme->revoke_cert) {
-            return APR_SUCCESS;
-        }
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, acme->p,
-                      "Unable to understand ACME server response. Wrong ACME protocol version?");
-        rv = APR_EINVAL;
-    }
-    else {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, acme->p, "unsuccessful in contacting ACME "
-                      "server at %s. If this problem persists, please check your network "
-                      "connectivity from your Apache server to the ACME server. Also, older "
-                      "servers might have trouble verifying the certificates of the ACME "
-                      "server. You can check if you are able to contact it manually via the "
-                      "curl command. Sometimes, the ACME server might be down for maintenance, "
-                      "so failing to contact it is not an immediate problem. mod_md will "
-                      "continue retrying this.", acme->url);
-    }
-    return rv;
-}
-
 /**************************************************************************************************/
 /* acme requests */
 
@@ -195,16 +111,6 @@ static apr_status_t http_update_nonce(const md_http_response_t *res)
     return res->rv;
 }
 
-static apr_status_t md_acme_new_nonce(md_acme_t *acme)
-{
-    apr_status_t rv;
-    long id;
-    
-    rv = md_http_HEAD(acme->http, acme->new_reg, NULL, http_update_nonce, acme, &id);
-    md_http_await(acme->http, id);
-    return rv;
-}
-
 static md_acme_req_t *md_acme_req_create(md_acme_t *acme, const char *method, const char *url)
 {
     apr_pool_t *pool;
@@ -232,31 +138,26 @@ static md_acme_req_t *md_acme_req_create(md_acme_t *acme, const char *method, co
         return NULL;
     }
     req->max_retries = acme->max_retries;
-    
+    req->result = md_result_make(req->p, APR_SUCCESS);
     return req;
 }
  
-apr_status_t md_acme_req_body_init(md_acme_req_t *req, md_json_t *jpayload)
+static apr_status_t acmev1_new_nonce(md_acme_t *acme)
 {
-    const char *payload;
-    size_t payload_len;
-
-    if (!req->acme->acct) {
-        return APR_EINVAL;
-    }
+    return md_http_HEAD(acme->http, acme->api.v1.new_reg, NULL, http_update_nonce, acme);
+}
 
-    payload = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
-    if (!payload) {
-        return APR_EINVAL;
-    }
+static apr_status_t acmev2_new_nonce(md_acme_t *acme)
+{
+    return md_http_HEAD(acme->http, acme->api.v2.new_nonce, NULL, http_update_nonce, acme);
+}
 
-    payload_len = strlen(payload);
-    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p, 
-                  "acct payload(len=%" APR_SIZE_T_FMT "): %s", payload_len, payload);
-    return md_jws_sign(&req->req_json, req->p, payload, payload_len,
-                       req->prot_hdrs, req->acme->acct_key, NULL);
-} 
 
+apr_status_t md_acme_init(apr_pool_t *p, const char *base,  int init_ssl)
+{
+    base_product = base;
+    return init_ssl? md_crypt_init(p) : APR_SUCCESS;
+}
 
 static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t *res)
 {
@@ -274,6 +175,7 @@ static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t
             ptype = md_json_gets(problem, MD_KEY_TYPE, NULL); 
             pdetail = md_json_gets(problem, MD_KEY_DETAIL, NULL);
             req->rv = problem_status_get(ptype);
+            md_result_problem_set(req->result, req->rv, ptype, pdetail);
             
             if (APR_STATUS_IS_EAGAIN(req->rv)) {
                 md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, req->rv, req->p,
@@ -298,7 +200,9 @@ static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t
             default:
                 md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, req->p,
                               "acme problem unknown: http status %d", res->status);
-                return APR_EGENERAL;
+                md_result_printf(req->result, APR_EGENERAL, "unexpected http status: %d",
+                                 res->status);
+                return req->result->status;
         }
     }
     return res->rv;
@@ -307,9 +211,73 @@ static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t
 /**************************************************************************************************/
 /* ACME requests with nonce handling */
 
-static apr_status_t md_acme_req_done(md_acme_req_t *req)
+static apr_status_t acmev1_req_init(md_acme_req_t *req, md_json_t *jpayload)
+{
+    const char *payload;
+    size_t payload_len;
+    
+    if (!req->acme->acct) {
+        return APR_EINVAL;
+    }
+    if (jpayload) {
+        payload = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
+        if (!payload) {
+            return APR_EINVAL;
+        }
+    }
+    else {
+        payload = "";
+    }
+
+    payload_len = strlen(payload);
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p, 
+                  "acme payload(len=%" APR_SIZE_T_FMT "): %s", payload_len, payload);
+    return md_jws_sign(&req->req_json, req->p, payload, payload_len,
+                       req->prot_hdrs, req->acme->acct_key, NULL);
+}
+
+static apr_status_t acmev2_req_init(md_acme_req_t *req, md_json_t *jpayload)
 {
-    apr_status_t rv = req->rv;
+    const char *payload;
+    size_t payload_len;
+    
+    if (!req->acme->acct) {
+        return APR_EINVAL;
+    }
+    if (jpayload) {
+        payload = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
+        if (!payload) {
+            return APR_EINVAL;
+        }
+    }
+    else {
+        payload = "";
+    }
+
+    payload_len = strlen(payload);
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->p, 
+                  "acme payload(len=%" APR_SIZE_T_FMT "): %s", payload_len, payload);
+    return md_jws_sign(&req->req_json, req->p, payload, payload_len,
+                       req->prot_hdrs, req->acme->acct_key, req->acme->acct->url);
+}
+
+apr_status_t md_acme_req_body_init(md_acme_req_t *req, md_json_t *payload)
+{
+    return req->acme->req_init_fn(req, payload);
+}
+
+static apr_status_t md_acme_req_done(md_acme_req_t *req, apr_status_t rv)
+{
+    if (req->result->status != APR_SUCCESS) {
+        if (req->on_err) {
+            req->on_err(req, req->result, req->baton);
+        }
+    }
+    /* An error in rv superceeds the result->status */
+    if (APR_SUCCESS != rv) req->result->status = rv;
+    rv = req->result->status;
+    /* transfer results into the acme's central result for longer life and later inspection */
+    md_result_dup(req->acme->last, req->result);
     if (req->p) {
         apr_pool_destroy(req->p);
     }
@@ -361,9 +329,10 @@ static apr_status_t on_response(const md_http_response_t *res)
         
         if (!processed) {
             rv = APR_EINVAL;
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, req->p, 
-                          "response: %d, content-type=%s", res->status, 
-                          apr_table_get(res->headers, "Content-Type"));
+            md_result_printf(req->result, rv, "unable to process the response: "
+                             "http-status=%d, content-type=%s", 
+                             res->status, apr_table_get(res->headers, "Content-Type"));
+            md_result_log(req->result, MD_LOG_ERR);
         }
     }
     else if (APR_EAGAIN == (rv = inspect_problem(req, res))) {
@@ -372,84 +341,111 @@ static apr_status_t on_response(const md_http_response_t *res)
     }
 
 out:
-    md_acme_req_done(req);
+    md_acme_req_done(req, rv);
     return rv;
 }
 
+static apr_status_t acmev2_GET_as_POST_init(md_acme_req_t *req, void *baton)
+{
+    (void)baton;
+    return md_acme_req_body_init(req, NULL);
+}
+
 static apr_status_t md_acme_req_send(md_acme_req_t *req)
 {
     apr_status_t rv;
     md_acme_t *acme = req->acme;
     const char *body = NULL;
+    md_result_t *result;
 
     assert(acme->url);
     
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->p, 
+                  "sending req: %s %s", req->method, req->url);
+    md_result_reset(req->acme->last);
+    result = md_result_make(req->p, APR_SUCCESS);
+    
+    /* Whom are we talking to? */
+    if (acme->version == MD_ACME_VERSION_UNKNOWN) {
+        rv = md_acme_setup(acme, result);
+        if (APR_SUCCESS != rv) goto leave;
+    }
+    
+    if (!strcmp("GET", req->method) && !req->on_init && !req->req_json 
+        && MD_ACME_VERSION_MAJOR(acme->version) > 1) {
+        /* See <https://ietf-wg-acme.github.io/acme/draft-ietf-acme-acme.html#rfc.section.6.3>
+         * and <https://mailarchive.ietf.org/arch/msg/acme/sotffSQ0OWV-qQJodLwWYWcEVKI>
+         * and <https://community.letsencrypt.org/t/acme-v2-scheduled-deprecation-of-unauthenticated-resource-gets/74380>
+         * We implement this change in ACMEv2 and higher as keeping the md_acme_GET() methods,
+         * but switching them to POSTs with a empty, JWS signed, body when we call
+         * our HTTP client. */
+        req->method = "POST";
+        req->on_init = acmev2_GET_as_POST_init;
+    }
+    
+    /* Besides GET/HEAD, we always need a fresh nonce */
     if (strcmp("GET", req->method) && strcmp("HEAD", req->method)) {
-        if (!acme->new_authz) {
-            if (APR_SUCCESS != (rv = md_acme_setup(acme))) {
-                return rv;
-            }
+        if (acme->version == MD_ACME_VERSION_UNKNOWN) {
+            rv = md_acme_setup(acme, result);
+            if (APR_SUCCESS != rv) goto leave;
         }
-        if (!acme->nonce) {
-            if (APR_SUCCESS != (rv = md_acme_new_nonce(acme))) {
-                md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, req->p, 
-                              "error retrieving new nonce from ACME server");
-                return rv;
-            }
+        if (!acme->nonce && (APR_SUCCESS != (rv = acme->new_nonce_fn(acme)))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, req->p, 
+                          "error retrieving new nonce from ACME server");
+            goto leave;
         }
         
         apr_table_set(req->prot_hdrs, "nonce", acme->nonce);
+        if (MD_ACME_VERSION_MAJOR(acme->version) > 1) {
+            apr_table_set(req->prot_hdrs, "url", req->url);
+        }
         acme->nonce = NULL;
     }
     
     rv = req->on_init? req->on_init(req, req->baton) : APR_SUCCESS;
+    if (APR_SUCCESS != rv) goto leave;
     
-    if ((rv == APR_SUCCESS) && req->req_json) {
+    if (req->req_json) {
         body = md_json_writep(req->req_json, req->p, MD_JSON_FMT_INDENT);
         if (!body) {
-            rv = APR_EINVAL;
+            rv = APR_EINVAL; goto leave;
         }
     }
 
-    if (rv == APR_SUCCESS) {
-        long id = 0;
-        
-        if (body && md_log_is_level(req->p, MD_LOG_TRACE2)) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, req->p, 
-                          "req: POST %s, body:\n%s", req->url, body);
-        }
-        else {
-            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->p, 
-                          "req: POST %s", req->url);
-        }
-        if (!strcmp("GET", req->method)) {
-            rv = md_http_GET(req->acme->http, req->url, NULL, on_response, req, &id);
-        }
-        else if (!strcmp("POST", req->method)) {
-            rv = md_http_POSTd(req->acme->http, req->url, NULL, "application/json",  
-                               body, body? strlen(body) : 0, on_response, req, &id);
-        }
-        else if (!strcmp("HEAD", req->method)) {
-            rv = md_http_HEAD(req->acme->http, req->url, NULL, on_response, req, &id);
-        }
-        else {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, req->p, 
-                          "HTTP method %s against: %s", req->method, req->url);
-            rv = APR_ENOTIMPL;
-        }
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, req->p, "req sent");
-        md_http_await(acme->http, id);
-        
-        if (APR_EAGAIN == rv && req->max_retries > 0) {
-            --req->max_retries;
-            return md_acme_req_send(req);
-        }
-        req = NULL;
+    if (body && md_log_is_level(req->p, MD_LOG_TRACE2)) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, req->p, 
+                      "req: %s %s, body:\n%s", req->method, req->url, body);
     }
-
-    if (req) {
-        md_acme_req_done(req);
+    else {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->p, 
+                      "req: %s %s", req->method, req->url);
+    }
+    
+    if (!strcmp("GET", req->method)) {
+        rv = md_http_GET(req->acme->http, req->url, NULL, on_response, req);
+    }
+    else if (!strcmp("POST", req->method)) {
+        rv = md_http_POSTd(req->acme->http, req->url, NULL, "application/jose+json",  
+                           body, body? strlen(body) : 0, on_response, req);
+    }
+    else if (!strcmp("HEAD", req->method)) {
+        rv = md_http_HEAD(req->acme->http, req->url, NULL, on_response, req);
+    }
+    else {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, req->p, 
+                      "HTTP method %s against: %s", req->method, req->url);
+        rv = APR_ENOTIMPL;
+    }
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, req->p, "req sent");
+    
+    if (APR_EAGAIN == rv && req->max_retries > 0) {
+        --req->max_retries;
+        rv = md_acme_req_send(req);
     }
+    req = NULL;
+
+leave:
+    if (req) md_acme_req_done(req, rv);
     return rv;
 }
 
@@ -457,6 +453,7 @@ apr_status_t md_acme_POST(md_acme_t *acme, const char *url,
                           md_acme_req_init_cb *on_init,
                           md_acme_req_json_cb *on_json,
                           md_acme_req_res_cb *on_res,
+                          md_acme_req_err_cb *on_err,
                           void *baton)
 {
     md_acme_req_t *req;
@@ -469,6 +466,7 @@ apr_status_t md_acme_POST(md_acme_t *acme, const char *url,
     req->on_init = on_init;
     req->on_json = on_json;
     req->on_res = on_res;
+    req->on_err = on_err;
     req->baton = baton;
     
     return md_acme_req_send(req);
@@ -478,6 +476,7 @@ apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
                          md_acme_req_init_cb *on_init,
                          md_acme_req_json_cb *on_json,
                          md_acme_req_res_cb *on_res,
+                          md_acme_req_err_cb *on_err,
                          void *baton)
 {
     md_acme_req_t *req;
@@ -490,11 +489,22 @@ apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
     req->on_init = on_init;
     req->on_json = on_json;
     req->on_res = on_res;
+    req->on_err = on_err;
     req->baton = baton;
     
     return md_acme_req_send(req);
 }
 
+void md_acme_report_result(md_acme_t *acme, apr_status_t rv, struct md_result_t *result)
+{
+    if (acme->last->status == APR_SUCCESS) {
+        md_result_set(result, rv, NULL);
+    }
+    else {
+        md_result_problem_set(result, acme->last->status, acme->last->problem, acme->last->detail);
+    }
+}
+
 /**************************************************************************************************/
 /* GET JSON */
 
@@ -524,8 +534,255 @@ apr_status_t md_acme_get_json(struct md_json_t **pjson, md_acme_t *acme,
     ctx.pool = p;
     ctx.json = NULL;
     
-    rv = md_acme_GET(acme, url, NULL, on_got_json, NULL, &ctx);
+    rv = md_acme_GET(acme, url, NULL, on_got_json, NULL, NULL, &ctx);
     *pjson = (APR_SUCCESS == rv)? ctx.json : NULL;
     return rv;
 }
 
+/**************************************************************************************************/
+/* Generic ACME operations */
+
+void md_acme_clear_acct(md_acme_t *acme)
+{
+    acme->acct_id = NULL;
+    acme->acct = NULL;
+    acme->acct_key = NULL;
+}
+
+const char *md_acme_acct_id_get(md_acme_t *acme)
+{
+    return acme->acct_id;
+}
+
+const char *md_acme_acct_url_get(md_acme_t *acme)
+{
+    return acme->acct? acme->acct->url : NULL;
+}
+
+apr_status_t md_acme_use_acct(md_acme_t *acme, md_store_t *store,
+                              apr_pool_t *p, const char *acct_id)
+{
+    md_acme_acct_t *acct;
+    md_pkey_t *pkey;
+    apr_status_t rv;
+    
+    if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey, 
+                                               store, MD_SG_ACCOUNTS, acct_id, acme->p))) {
+        if (acct->ca_url && !strcmp(acct->ca_url, acme->url)) {
+            acme->acct_id = apr_pstrdup(p, acct_id);
+            acme->acct = acct;
+            acme->acct_key = pkey;
+            rv = md_acme_acct_validate(acme, store, p);
+        }
+        else {
+            /* account is from a nother server or, more likely, from another
+             * protocol endpoint on the same server */
+            rv = APR_ENOENT;
+        }
+    }
+    return rv;
+}
+
+apr_status_t md_acme_save_acct(md_acme_t *acme, apr_pool_t *p, md_store_t *store)
+{
+    return md_acme_acct_save(store, p, acme, &acme->acct_id, acme->acct, acme->acct_key);
+}
+
+static apr_status_t acmev1_POST_new_account(md_acme_t *acme, 
+                                            md_acme_req_init_cb *on_init,
+                                            md_acme_req_json_cb *on_json,
+                                            md_acme_req_res_cb *on_res,
+                                            md_acme_req_err_cb *on_err,
+                                            void *baton)
+{
+    return md_acme_POST(acme, acme->api.v1.new_reg, on_init, on_json, on_res, on_err, baton);
+}
+
+static apr_status_t acmev2_POST_new_account(md_acme_t *acme, 
+                                            md_acme_req_init_cb *on_init,
+                                            md_acme_req_json_cb *on_json,
+                                            md_acme_req_res_cb *on_res,
+                                            md_acme_req_err_cb *on_err,
+                                            void *baton)
+{
+    return md_acme_POST(acme, acme->api.v2.new_account, on_init, on_json, on_res, on_err, baton);
+}
+
+apr_status_t md_acme_POST_new_account(md_acme_t *acme, 
+                                      md_acme_req_init_cb *on_init,
+                                      md_acme_req_json_cb *on_json,
+                                      md_acme_req_res_cb *on_res,
+                                      md_acme_req_err_cb *on_err,
+                                      void *baton)
+{
+    return acme->post_new_account_fn(acme, on_init, on_json, on_res, on_err, baton);
+}
+
+/**************************************************************************************************/
+/* ACME setup */
+
+apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
+                            const char *proxy_url)
+{
+    md_acme_t *acme;
+    const char *err = NULL;
+    apr_status_t rv;
+    apr_uri_t uri_parsed;
+    size_t len;
+    
+    if (!url) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p, "create ACME without url");
+        return APR_EINVAL;
+    }
+    
+    if (APR_SUCCESS != (rv = md_util_abs_uri_check(p, url, &err))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "invalid ACME uri (%s): %s", err, url);
+        return rv;
+    }
+    
+    acme = apr_pcalloc(p, sizeof(*acme));
+    acme->url = url;
+    acme->p = p;
+    acme->user_agent = apr_psprintf(p, "%s mod_md/%s", 
+                                    base_product, MOD_MD_VERSION);
+    acme->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
+    acme->max_retries = 3;
+    
+    if (APR_SUCCESS != (rv = apr_uri_parse(p, url, &uri_parsed))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "parsing ACME uri: %s", url);
+        return APR_EINVAL;
+    }
+    
+    len = strlen(uri_parsed.hostname);
+    acme->sname = (len <= 16)? uri_parsed.hostname : apr_pstrdup(p, uri_parsed.hostname + len - 16);
+    acme->version = MD_ACME_VERSION_UNKNOWN;
+    acme->last = md_result_make(acme->p, APR_SUCCESS);
+    
+    *pacme = (APR_SUCCESS == rv)? acme : NULL;
+    return rv;
+}
+
+typedef struct {
+    md_acme_t *acme;
+    md_result_t *result;
+} update_dir_ctx;
+
+static apr_status_t update_directory(const md_http_response_t *res)
+{
+    md_http_request_t *req = res->req;
+    md_acme_t *acme = ((update_dir_ctx *)req->baton)->acme;
+    md_result_t *result = ((update_dir_ctx *)req->baton)->result;
+    apr_status_t rv = res->rv;
+    md_json_t *json;
+    const char *s;
+    
+    if (APR_SUCCESS != rv) goto leave;
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, req->pool, "directory lookup response: %d", res->status);
+    if (res->status == 503) {
+        md_result_printf(result, APR_EAGAIN,
+            "The ACME server at <%s> reports that Service is Unavailable (503). This "
+            "may happen during maintenance for short periods of time.", acme->url); 
+        md_result_log(result, MD_LOG_INFO);
+        rv = result->status;
+        goto leave;
+    }
+    else if (res->status < 200 || res->status >= 300) {
+        md_result_printf(result, APR_EAGAIN,
+            "The ACME server at <%s> responded with HTTP status %d. This "
+            "is unusual. Please verify that the URL is correct and that you can indeed "
+            "make request from the server to it by other means, e.g. invoking curl/wget.", 
+            acme->url, res->status); 
+        goto leave;
+    }
+    
+    rv = md_json_read_http(&json, req->pool, res);
+    if (APR_SUCCESS != rv) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, req->pool, "reading JSON body");
+        goto leave;
+    }
+    
+    if (md_log_is_level(acme->p, MD_LOG_TRACE2)) {
+        s = md_json_writep(json, req->pool, MD_JSON_FMT_INDENT);
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, req->pool,
+                      "response: %s", s ? s : "<failed to serialize!>");
+    }
+    
+    /* What have we got? */
+    if ((s = md_json_dups(acme->p, json, "new-authz", NULL))) {
+        acme->api.v1.new_authz = s;
+        acme->api.v1.new_cert = md_json_dups(acme->p, json, "new-cert", NULL);
+        acme->api.v1.new_reg = md_json_dups(acme->p, json, "new-reg", NULL);
+        acme->api.v1.revoke_cert = md_json_dups(acme->p, json, "revoke-cert", NULL);
+        if (acme->api.v1.new_authz && acme->api.v1.new_cert 
+            && acme->api.v1.new_reg && acme->api.v1.revoke_cert) {
+            acme->version = MD_ACME_VERSION_1;
+        }
+        acme->ca_agreement = md_json_dups(acme->p, json, "meta", "terms-of-service", NULL);
+        acme->new_nonce_fn = acmev1_new_nonce;
+        acme->req_init_fn = acmev1_req_init;
+        acme->post_new_account_fn = acmev1_POST_new_account;
+    }
+    else if ((s = md_json_dups(acme->p, json, "newAccount", NULL))) {
+        acme->api.v2.new_account = s;
+        acme->api.v2.new_order = md_json_dups(acme->p, json, "newOrder", NULL);
+        acme->api.v2.revoke_cert = md_json_dups(acme->p, json, "revokeCert", NULL);
+        acme->api.v2.key_change = md_json_dups(acme->p, json, "keyChange", NULL);
+        acme->api.v2.new_nonce = md_json_dups(acme->p, json, "newNonce", NULL);
+        if (acme->api.v2.new_account && acme->api.v2.new_order 
+            && acme->api.v2.revoke_cert && acme->api.v2.key_change
+            && acme->api.v2.new_nonce) {
+            acme->version = MD_ACME_VERSION_2;
+        }
+        acme->ca_agreement = md_json_dups(acme->p, json, "meta", "termsOfService", NULL);
+        acme->new_nonce_fn = acmev2_new_nonce;
+        acme->req_init_fn = acmev2_req_init;
+        acme->post_new_account_fn = acmev2_POST_new_account;
+    }
+    
+    if (MD_ACME_VERSION_UNKNOWN == acme->version) {
+        md_result_printf(result, APR_EINVAL,
+            "Unable to understand ACME server response from <%s>. "
+            "Wrong ACME protocol version or link?", acme->url); 
+        md_result_log(result, MD_LOG_WARNING);
+        rv = result->status;
+    }
+leave:
+    return rv;
+}
+
+apr_status_t md_acme_setup(md_acme_t *acme, md_result_t *result)
+{
+    apr_status_t rv;
+    update_dir_ctx ctx;
+   
+    assert(acme->url);
+    acme->version = MD_ACME_VERSION_UNKNOWN;
+    
+    if (!acme->http && APR_SUCCESS != (rv = md_http_create(&acme->http, acme->p,
+                                                           acme->user_agent, acme->proxy_url))) {
+        return rv;
+    }
+    md_http_set_response_limit(acme->http, 1024*1024);
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "get directory from %s", acme->url);
+    
+    ctx.acme = acme;
+    ctx.result = result;
+    rv = md_http_GET(acme->http, acme->url, NULL, update_directory, &ctx);
+    
+    if (APR_SUCCESS != rv && APR_SUCCESS == result->status) {
+        /* If the result reports no error, we never got a response from the server */
+        md_result_printf(result, rv, 
+            "Unsuccessful in contacting ACME server at <%s>. If this problem persists, "
+            "please check your network connectivity from your Apache server to the "
+            "ACME server. Also, older servers might have trouble verifying the certificates "
+            "of the ACME server. You can check if you are able to contact it manually via the "
+            "curl command. Sometimes, the ACME server might be down for maintenance, "
+            "so failing to contact it is not an immediate problem. Apache will "
+            "continue retrying this.", acme->url);
+        md_result_log(result, MD_LOG_WARNING);
+    }
+    return rv;
+}
+
+
index 2dcbee6a7b028b5891430d91bc10cc077722e167..f6af75fb049ccf9c35b31613b5fc0eafbccedbc3 100644 (file)
@@ -26,14 +26,21 @@ struct md_json_t;
 struct md_pkey_t;
 struct md_t;
 struct md_acme_acct_t;
-struct md_proto_t;
+struct md_acmev2_acct_t;
 struct md_store_t;
+struct md_result_t;
 
 #define MD_PROTO_ACME               "ACME"
 
 #define MD_AUTHZ_CHA_HTTP_01        "http-01"
 #define MD_AUTHZ_CHA_SNI_01         "tls-sni-01"
 
+#define MD_ACME_VERSION_UNKNOWN    0x0
+#define MD_ACME_VERSION_1          0x010000
+#define MD_ACME_VERSION_2          0x020000
+
+#define MD_ACME_VERSION_MAJOR(i)    (((i)&0xFF0000) >> 16)
+
 typedef enum {
     MD_ACME_S_UNKNOWN,              /* MD has not been analysed yet */
     MD_ACME_S_REGISTERED,           /* MD is registered at CA, but not more */
@@ -46,30 +53,90 @@ typedef enum {
 
 typedef struct md_acme_t md_acme_t;
 
+typedef struct md_acme_req_t md_acme_req_t;
+/**
+ * Request callback on a successful HTTP response (status 2xx).
+ */
+typedef apr_status_t md_acme_req_res_cb(md_acme_t *acme, 
+                                        const struct md_http_response_t *res, void *baton);
+
+/**
+ * Request callback to initialize before sending. May be invoked more than once in
+ * case of retries.
+ */
+typedef apr_status_t md_acme_req_init_cb(md_acme_req_t *req, void *baton);
+
+/**
+ * Request callback on a successful response (HTTP response code 2xx) and content
+ * type matching application/.*json.
+ */
+typedef apr_status_t md_acme_req_json_cb(md_acme_t *acme, apr_pool_t *p, 
+                                         const apr_table_t *headers, 
+                                         struct md_json_t *jbody, void *baton);
+
+/**
+ * Request callback on detected errors.
+ */
+typedef apr_status_t md_acme_req_err_cb(md_acme_req_t *req, 
+                                        const struct md_result_t *result, void *baton);
+
+
+typedef apr_status_t md_acme_new_nonce_fn(md_acme_t *acme);
+typedef apr_status_t md_acme_req_init_fn(md_acme_req_t *req, struct md_json_t *jpayload);
+
+typedef apr_status_t md_acme_post_fn(md_acme_t *acme, 
+                                     md_acme_req_init_cb *on_init,
+                                     md_acme_req_json_cb *on_json,
+                                     md_acme_req_res_cb *on_res,
+                                     md_acme_req_err_cb *on_err,
+                                     void *baton);
+
 struct md_acme_t {
     const char *url;                /* directory url of the ACME service */
     const char *sname;              /* short name for the service, not necessarily unique */
     apr_pool_t *p;
     const char *user_agent;
     const char *proxy_url;
-    struct md_acme_acct_t *acct;
-    struct md_pkey_t *acct_key;
     
-    const char *new_authz;
-    const char *new_cert;
-    const char *new_reg;
-    const char *revoke_cert;
+    const char *acct_id;            /* local storage id account was loaded from or NULL */
+    struct md_acme_acct_t *acct;    /* account at ACME server to use for requests */
+    struct md_pkey_t *acct_key;     /* private RSA key belonging to account */
+    
+    int version;                    /* as detected from the server */
+    union {
+        struct {
+            const char *new_authz;
+            const char *new_cert;
+            const char *new_reg;
+            const char *revoke_cert;
+            
+        } v1;
+        struct {
+            const char *new_account;
+            const char *new_order;
+            const char *key_change;
+            const char *revoke_cert;
+            const char *new_nonce;
+        } v2;
+    } api;
+    const char *ca_agreement;
+    const char *acct_name;
+    
+    md_acme_new_nonce_fn *new_nonce_fn;
+    md_acme_req_init_fn *req_init_fn;
+    md_acme_post_fn *post_new_account_fn;
     
     struct md_http_t *http;
     
     const char *nonce;
     int max_retries;
+    struct md_result_t *last;      /* result of last request */
 };
 
 /**
  * Global init, call once at start up.
  */
-apr_status_t md_acme_init(apr_pool_t *pool, const char *base_version);
+apr_status_t md_acme_init(apr_pool_t *pool, const char *base_version, int init_ssl);
 
 /**
  * Create a new ACME server instance. If path is not NULL, will use that directory
@@ -89,16 +156,31 @@ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
  * 
  * @param acme    the ACME server to contact
  */
-apr_status_t md_acme_setup(md_acme_t *acme);
+apr_status_t md_acme_setup(md_acme_t *acme, struct md_result_t *result);
+
+void md_acme_report_result(md_acme_t *acme, apr_status_t rv, struct md_result_t *result);
 
 /**************************************************************************************************/
 /* account handling */
 
-#define MD_ACME_ACCT_STAGED     "staged"
+/**
+ * Clear any existing account data from acme instance.
+ */
+void md_acme_clear_acct(md_acme_t *acme);
+
+apr_status_t md_acme_POST_new_account(md_acme_t *acme, 
+                                      md_acme_req_init_cb *on_init,
+                                      md_acme_req_json_cb *on_json,
+                                      md_acme_req_res_cb *on_res,
+                                      md_acme_req_err_cb *on_err,
+                                      void *baton);
 
-apr_status_t md_acme_acct_load(struct md_acme_acct_t **pacct, struct md_pkey_t **ppkey,
-                               struct md_store_t *store, md_store_group_t group, 
-                               const char *name, apr_pool_t *p);
+/**
+ * Get the local name of the account currently used by the acme instance.
+ * Will be NULL if no account has been setup successfully.
+ */
+const char *md_acme_acct_id_get(md_acme_t *acme);
+const char *md_acme_acct_url_get(md_acme_t *acme);
 
 /** 
  * Specify the account to use by name in local store. On success, the account
@@ -107,14 +189,11 @@ apr_status_t md_acme_acct_load(struct md_acme_acct_t **pacct, struct md_pkey_t *
 apr_status_t md_acme_use_acct(md_acme_t *acme, struct md_store_t *store, 
                               apr_pool_t *p, const char *acct_id);
 
-apr_status_t md_acme_use_acct_staged(md_acme_t *acme, struct md_store_t *store, 
-                                     md_t *md, apr_pool_t *p);
-
 /**
  * Get the local name of the account currently used by the acme instance.
  * Will be NULL if no account has been setup successfully.
  */
-const char *md_acme_get_acct_id(md_acme_t *acme);
+const char *md_acme_acct_id_get(md_acme_t *acme);
 
 /**
  * Agree to the given Terms-of-Service url for the current account.
@@ -136,71 +215,16 @@ apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *tos);
 apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, 
                                      const char *agreement, const char **prequired);
 
-/**
- * Get the ToS agreement for current account.
- */
-const char *md_acme_get_agreement(md_acme_t *acme);
-
-
-/** 
- * Find an existing account in the local store. On APR_SUCCESS, the acme
- * instance will have a current, validated account to use.
- */ 
-apr_status_t md_acme_find_acct(md_acme_t *acme, struct md_store_t *store, apr_pool_t *p);
-
-/**
- * Create a new account at the ACME server. The
- * new account is the one used by the acme instance afterwards, on success.
- */
-apr_status_t md_acme_create_acct(md_acme_t *acme, apr_pool_t *p, apr_array_header_t *contacts, 
-                                 const char *agreement);
-
-apr_status_t md_acme_acct_save(struct md_store_t *store, apr_pool_t *p, md_acme_t *acme,  
-                               struct md_acme_acct_t *acct, struct md_pkey_t *acct_key);
+apr_status_t md_acme_save_acct(md_acme_t *acme, apr_pool_t *p, struct md_store_t *store);
                                
-apr_status_t md_acme_save(md_acme_t *acme, struct md_store_t *store, apr_pool_t *p);
-
-apr_status_t md_acme_acct_save_staged(md_acme_t *acme, struct md_store_t *store, 
-                                      md_t *md, apr_pool_t *p);
-
-/**
- * Delete the current account at the ACME server and remove it from store. 
- */
-apr_status_t md_acme_delete_acct(md_acme_t *acme, struct md_store_t *store, apr_pool_t *p);
-
 /**
- * Delete the account from the local store without contacting the ACME server.
+ * Deactivate the current account at the ACME server.. 
  */
-apr_status_t md_acme_unstore_acct(struct md_store_t *store, apr_pool_t *p, const char *acct_id);
+apr_status_t md_acme_acct_deactivate(md_acme_t *acme, apr_pool_t *p);
 
 /**************************************************************************************************/
 /* request handling */
 
-/**
- * Request callback on a successful HTTP response (status 2xx).
- */
-typedef apr_status_t md_acme_req_res_cb(md_acme_t *acme, 
-                                        const struct md_http_response_t *res, void *baton);
-
-/**
- * A request against an ACME server
- */
-typedef struct md_acme_req_t md_acme_req_t;
-
-/**
- * Request callback to initialize before sending. May be invoked more than once in
- * case of retries.
- */
-typedef apr_status_t md_acme_req_init_cb(md_acme_req_t *req, void *baton);
-
-/**
- * Request callback on a successful response (HTTP response code 2xx) and content
- * type matching application/.*json.
- */
-typedef apr_status_t md_acme_req_json_cb(md_acme_t *acme, apr_pool_t *p, 
-                                         const apr_table_t *headers, 
-                                         struct md_json_t *jbody, void *baton);
-
 struct md_acme_req_t {
     md_acme_t *acme;               /* the ACME server to talk to */
     apr_pool_t *p;                 /* pool for the request duration */
@@ -218,14 +242,19 @@ struct md_acme_req_t {
     md_acme_req_init_cb *on_init;  /* callback to initialize the request before submit */
     md_acme_req_json_cb *on_json;  /* callback on successful JSON response */
     md_acme_req_res_cb *on_res;    /* callback on generic HTTP response */
+    md_acme_req_err_cb *on_err;    /* callback on encountered error */
     int max_retries;               /* how often this might be retried */
     void *baton;                   /* userdata for callbacks */
+    struct md_result_t *result;    /* result of this request */
 };
 
+apr_status_t md_acme_req_body_init(md_acme_req_t *req, struct md_json_t *payload);
+
 apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
                          md_acme_req_init_cb *on_init,
                          md_acme_req_json_cb *on_json,
                          md_acme_req_res_cb *on_res,
+                         md_acme_req_err_cb *on_err,
                          void *baton);
 /**
  * Perform a POST against the ACME url. If a on_json callback is given and
@@ -245,14 +274,9 @@ apr_status_t md_acme_POST(md_acme_t *acme, const char *url,
                           md_acme_req_init_cb *on_init,
                           md_acme_req_json_cb *on_json,
                           md_acme_req_res_cb *on_res,
+                          md_acme_req_err_cb *on_err,
                           void *baton);
 
-apr_status_t md_acme_GET(md_acme_t *acme, const char *url,
-                         md_acme_req_init_cb *on_init,
-                         md_acme_req_json_cb *on_json,
-                         md_acme_req_res_cb *on_res,
-                         void *baton);
-
 /**
  * Retrieve a JSON resource from the ACME server 
  */
index c4a2b5fe71fc2f4ffce608c8ed2cff2ed52ed375..0f55ab258f4417d03a482c3f1a96c638ffe38b16 100644 (file)
 #include "md_acme_acct.h"
 
 static apr_status_t acct_make(md_acme_acct_t **pacct, apr_pool_t *p, 
-                              const char *ca_url, const char *id, apr_array_header_t *contacts)
+                              const char *ca_url, apr_array_header_t *contacts)
 {
     md_acme_acct_t *acct;
     
     acct = apr_pcalloc(p, sizeof(*acct));
 
-    acct->id = id? apr_pstrdup(p, id) : NULL;
     acct->ca_url = ca_url;
-    
     if (!contacts || apr_is_empty_array(contacts)) {
         acct->contacts = apr_array_make(p, 5, sizeof(const char *));
     }
@@ -72,53 +70,102 @@ static const char *mk_acct_pattern(apr_pool_t *p, md_acme_t *acme)
 /**************************************************************************************************/
 /* json load/save */
 
-static md_json_t *acct_to_json(md_acme_acct_t *acct, apr_pool_t *p)
+static md_acme_acct_st acct_st_from_str(const char *s) 
+{
+    if (s) {
+        if (!strcmp("valid", s)) {
+            return MD_ACME_ACCT_ST_VALID;
+        }
+        else if (!strcmp("deactivated", s)) {
+            return MD_ACME_ACCT_ST_DEACTIVATED;
+        }
+        else if (!strcmp("revoked", s)) {
+            return MD_ACME_ACCT_ST_REVOKED;
+        }
+    }
+    return MD_ACME_ACCT_ST_UNKNOWN;
+}
+
+md_json_t *md_acme_acct_to_json(md_acme_acct_t *acct, apr_pool_t *p)
 {
     md_json_t *jacct;
+    const char *s;
 
     assert(acct);
     jacct = md_json_create(p);
-    md_json_sets(acct->id, jacct, MD_KEY_ID, NULL);
-    md_json_setb(acct->disabled, jacct, MD_KEY_DISABLED, NULL);
+    switch (acct->status) {
+        case MD_ACME_ACCT_ST_VALID:
+            s = "valid";
+            break;
+        case MD_ACME_ACCT_ST_DEACTIVATED:
+            s = "deactivated";
+            break;
+        case MD_ACME_ACCT_ST_REVOKED:
+            s = "revoked";
+            break;
+        default:
+            s = NULL;
+            break;
+    }    
+    if (s) {
+        md_json_sets(s, jacct, MD_KEY_STATUS, NULL);
+    }
     md_json_sets(acct->url, jacct, MD_KEY_URL, NULL);
     md_json_sets(acct->ca_url, jacct, MD_KEY_CA_URL, NULL);
+    md_json_setsa(acct->contacts, jacct, MD_KEY_CONTACT, NULL);
     md_json_setj(acct->registration, jacct, MD_KEY_REGISTRATION, NULL);
     if (acct->agreement) {
         md_json_sets(acct->agreement, jacct, MD_KEY_AGREEMENT, NULL);
     }
+    if (acct->orders) {
+        md_json_sets(acct->orders, jacct, MD_KEY_ORDERS, NULL);
+    }
     
     return jacct;
 }
 
-static apr_status_t acct_from_json(md_acme_acct_t **pacct, md_json_t *json, apr_pool_t *p)
+apr_status_t md_acme_acct_from_json(md_acme_acct_t **pacct, md_json_t *json, apr_pool_t *p)
 {
     apr_status_t rv = APR_EINVAL;
     md_acme_acct_t *acct;
-    int disabled;
-    const char *ca_url, *url, *id;
+    md_acme_acct_st status = MD_ACME_ACCT_ST_UNKNOWN;
+    const char *ca_url, *url;
     apr_array_header_t *contacts;
     
-    id = md_json_gets(json, MD_KEY_ID, NULL);
-    disabled = md_json_getb(json, MD_KEY_DISABLED, NULL);
-    ca_url = md_json_gets(json, MD_KEY_CA_URL, NULL);
-    if (!ca_url) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no CA url: %s", id);
-        goto out;
+    if (md_json_has_key(json, MD_KEY_STATUS, NULL)) {
+        status = acct_st_from_str(md_json_gets(json, MD_KEY_STATUS, NULL));
+    }
+    else {
+        /* old accounts only had disabled boolean field */
+        status = md_json_getb(json, MD_KEY_DISABLED, NULL)? 
+            MD_ACME_ACCT_ST_DEACTIVATED : MD_ACME_ACCT_ST_VALID;
     }
     
     url = md_json_gets(json, MD_KEY_URL, NULL);
     if (!url) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no url: %s", id);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no url");
         goto out;
     }
 
+    ca_url = md_json_gets(json, MD_KEY_CA_URL, NULL);
+    if (!ca_url) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "account has no CA url: %s", url);
+        goto out;
+    }
+    
     contacts = apr_array_make(p, 5, sizeof(const char *));
-    md_json_getsa(contacts, json, MD_KEY_REGISTRATION, MD_KEY_CONTACT, NULL);
-    rv = acct_make(&acct, p, ca_url, id, contacts);
+    if (md_json_has_key(json, MD_KEY_CONTACT, NULL)) {
+        md_json_getsa(contacts, json, MD_KEY_CONTACT, NULL);
+    }
+    else {
+        md_json_getsa(contacts, json, MD_KEY_REGISTRATION, MD_KEY_CONTACT, NULL);
+    }
+    rv = acct_make(&acct, p, ca_url, contacts);
     if (APR_SUCCESS == rv) {
-        acct->disabled = disabled;
+        acct->status = status;
         acct->url = url;
         acct->agreement = md_json_gets(json, "terms-of-service", NULL);
+        acct->orders = md_json_gets(json, MD_KEY_ORDERS, NULL);
     }
 
 out:
@@ -126,33 +173,15 @@ out:
     return rv;
 }
 
-apr_status_t md_acme_acct_save_staged(md_acme_t *acme, md_store_t *store, md_t *md, apr_pool_t *p)
-{
-    md_acme_acct_t *acct = acme->acct;
-    md_json_t *jacct;
-    apr_status_t rv;
-    
-    jacct = acct_to_json(acct, p);
-    
-    rv = md_store_save(store, p, MD_SG_STAGING, md->name, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 0);
-    if (APR_SUCCESS == rv) {
-        rv = md_store_save(store, p, MD_SG_STAGING, md->name, MD_FN_ACCT_KEY, 
-                           MD_SV_PKEY, acme->acct_key, 0);
-    }
-    return rv;
-}
-
 apr_status_t md_acme_acct_save(md_store_t *store, apr_pool_t *p, md_acme_t *acme, 
-                               md_acme_acct_t *acct, md_pkey_t *acct_key)
+                               const char **pid, md_acme_acct_t *acct, md_pkey_t *acct_key)
 {
     md_json_t *jacct;
     apr_status_t rv;
     int i;
-    const char *id;
-    
-    jacct = acct_to_json(acct, p);
-    id = acct->id;
+    const char *id = pid? *pid : NULL;
     
+    jacct = md_acme_acct_to_json(acct, p);
     if (id) {
         rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 0);
     }
@@ -160,23 +189,16 @@ apr_status_t md_acme_acct_save(md_store_t *store, apr_pool_t *p, md_acme_t *acme
         rv = APR_EAGAIN;
         for (i = 0; i < 1000 && APR_SUCCESS != rv; ++i) {
             id = mk_acct_id(p, acme, i);
-            md_json_sets(id, jacct, MD_KEY_ID, NULL);
             rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 1);
         }
-        
     }
     if (APR_SUCCESS == rv) {
-        acct->id = id;
+        if (pid) *pid = id;
         rv = md_store_save(store, p, MD_SG_ACCOUNTS, id, MD_FN_ACCT_KEY, MD_SV_PKEY, acct_key, 0);
     }
     return rv;
 }
 
-apr_status_t md_acme_save(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
-{
-    return md_acme_acct_save(store, p, acme, acme->acct, acme->acct_key); 
-}
-
 apr_status_t md_acme_acct_load(md_acme_acct_t **pacct, md_pkey_t **ppkey,
                                md_store_t *store, md_store_group_t group, 
                                const char *name, apr_pool_t *p)
@@ -193,11 +215,11 @@ apr_status_t md_acme_acct_load(md_acme_acct_t **pacct, md_pkey_t **ppkey,
         goto out;
     }
     
-    rv = acct_from_json(pacct, json, p);
+    rv = md_acme_acct_from_json(pacct, json, p);
     if (APR_SUCCESS == rv) {
         rv = md_store_load(store, group, name, MD_FN_ACCT_KEY, MD_SV_PKEY, (void**)ppkey, p);
         if (APR_SUCCESS != rv) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "loading key: %s", name);
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "loading key: %s", name);
             goto out;
         }
     }
@@ -215,6 +237,7 @@ out:
 typedef struct {
     apr_pool_t *p;
     md_acme_t *acme;
+    int url_match;
     const char *id;
 } find_ctx;
 
@@ -223,30 +246,34 @@ static int find_acct(void *baton, const char *name, const char *aspect,
 {
     find_ctx *ctx = baton;
     int disabled;
-    const char *ca_url, *id;
+    const char *ca_url, *status;
     
     (void)aspect;
     (void)ptemp;
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p, "account candidate %s/%s", name, aspect); 
     if (MD_SV_JSON == vtype) {
         md_json_t *json = value;
         
-        id = md_json_gets(json, MD_KEY_ID, NULL);
+        status = md_json_gets(json, MD_KEY_STATUS, NULL);
         disabled = md_json_getb(json, MD_KEY_DISABLED, NULL);
         ca_url = md_json_gets(json, MD_KEY_CA_URL, NULL);
         
-        if (!disabled && ca_url && !strcmp(ctx->acme->url, ca_url)) {
+        if ((!status || !strcmp("valid", status)) && !disabled 
+            && (!ctx->url_match || (ca_url && !strcmp(ctx->acme->url, ca_url)))) {
             md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p, 
-                          "found account %s for %s: %s, disabled=%d, ca-url=%s", 
-                          name, ctx->acme->url, id, disabled, ca_url);
-            ctx->id = id;
+                          "found account %s for %s: %s, status=%s, disabled=%d, ca-url=%s", 
+                          name, ctx->acme->url, aspect, status, disabled, ca_url);
+            ctx->id = apr_pstrdup(ctx->p, name);
             return 0;
         }
     }
     return 1;
 }
 
-static apr_status_t acct_find(md_acme_acct_t **pacct, md_pkey_t **ppkey, 
-                              md_store_t *store, md_acme_t *acme, apr_pool_t *p)
+static apr_status_t acct_find(const char **pid, md_acme_acct_t **pacct, md_pkey_t **ppkey, 
+                              md_store_t *store, md_store_group_t group,
+                              const char *name_pattern, int url_match, 
+                              md_acme_t *acme, apr_pool_t *p)
 {
     apr_status_t rv;
     find_ctx ctx;
@@ -254,200 +281,207 @@ static apr_status_t acct_find(md_acme_acct_t **pacct, md_pkey_t **ppkey,
     ctx.p = p;
     ctx.acme = acme;
     ctx.id = NULL;
+    ctx.url_match = url_match;
+    *pid = NULL;
     
-    rv = md_store_iter(find_acct, &ctx, store, p, MD_SG_ACCOUNTS, mk_acct_pattern(p, acme),
-                       MD_FN_ACCOUNT, MD_SV_JSON);
+    rv = md_store_iter(find_acct, &ctx, store, p, group, name_pattern, MD_FN_ACCOUNT, MD_SV_JSON);
     if (ctx.id) {
-        rv = md_acme_acct_load(pacct, ppkey, store, MD_SG_ACCOUNTS, ctx.id, p);
+        *pid = ctx.id;
+        rv = md_acme_acct_load(pacct, ppkey, store, group, ctx.id, p);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "loading account %s", ctx.id);
     }
     else {
         *pacct = NULL;
         rv = APR_ENOENT;
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, "acct_find: none found"); 
     }
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                  "acct_find %s", (*pacct)? (*pacct)->id : "NULL"); 
     return rv;
 }
 
-/**************************************************************************************************/
-/* Register a new account */
-
-typedef struct {
-    md_acme_t *acme;
-    apr_pool_t *p;
-} acct_ctx_t;
-
-static apr_status_t on_init_acct_new(md_acme_req_t *req, void *baton)
+static apr_status_t acct_find_and_verify(md_store_t *store, md_store_group_t group, 
+                                         const char *name_pattern, md_acme_t *acme, apr_pool_t *p)
 {
-    acct_ctx_t *ctx = baton;
-    md_json_t *jpayload;
-
-    jpayload = md_json_create(req->p);
-    md_json_sets("new-reg", jpayload, MD_KEY_RESOURCE, NULL);
-    md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL);
-    if (ctx->acme->acct->agreement) {
-        md_json_sets(ctx->acme->acct->agreement, jpayload, MD_KEY_AGREEMENT, NULL);
-    }
-    
-    return md_acme_req_body_init(req, jpayload);
-} 
+    md_acme_acct_t *acct;
+    md_pkey_t *pkey;
+    const char *id;
+    apr_status_t rv;
 
-static apr_status_t acct_upd(md_acme_t *acme, apr_pool_t *p, 
-                             const apr_table_t *hdrs, md_json_t *body, void *baton)
-{
-    acct_ctx_t *ctx = baton;
-    apr_status_t rv = APR_SUCCESS;
-    md_acme_acct_t *acct = acme->acct;
+    if (APR_SUCCESS == (rv = acct_find(&id, &acct, &pkey, store, group, name_pattern, 1, acme, p))) {
+        acme->acct_id = (MD_SG_STAGING == group)? NULL : id;
+        acme->acct = acct;
+        acme->acct_key = pkey;
+        rv = md_acme_acct_validate(acme, NULL, p);
     
-    if (!acct->url) {
-        const char *location = apr_table_get(hdrs, "location");
-        if (!location) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, APR_EINVAL, p, "new acct without location");
-            return APR_EINVAL;
-        }
-        acct->url = apr_pstrdup(ctx->p, location);
-    }
-    if (!acct->tos_required) {
-        acct->tos_required = md_link_find_relation(hdrs, ctx->p, "terms-of-service");
-        if (acct->tos_required) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
-                          "server requires agreement to <%s>", acct->tos_required);
+        if (APR_SUCCESS != rv) {
+            acme->acct_id = NULL;
+            acme->acct = NULL;
+            acme->acct_key = NULL;
+            if (APR_STATUS_IS_ENOENT(rv)) {
+                /* verification failed and account has been disabled.
+                   Indicate to caller that he may try again. */
+                rv = APR_EAGAIN;
+            }
         }
     }
-    
-    apr_array_clear(acct->contacts);
-    md_json_getsa(acct->contacts, body, MD_KEY_CONTACT, NULL);
-    acct->registration = md_json_clone(ctx->p, body);
-    
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "updated acct %s", acct->url);
     return rv;
 }
 
-static apr_status_t acct_register(md_acme_t *acme, apr_pool_t *p,  
-                                  apr_array_header_t *contacts, const char *agreement)
+apr_status_t md_acme_find_acct(md_acme_t *acme, md_store_t *store)
 {
     apr_status_t rv;
-    md_pkey_t *pkey;
-    const char *err = NULL, *uri;
-    md_pkey_spec_t spec;
-    int i;
-    
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "create new account");
     
-    if (agreement) {
-        if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, agreement, &err))) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, 
-                          "invalid agreement uri (%s): %s", err, agreement);
-            goto out;
-        }
+    while (APR_EAGAIN == (rv = acct_find_and_verify(store, MD_SG_ACCOUNTS, 
+                                                    mk_acct_pattern(acme->p, acme), 
+                                                    acme, acme->p))) {
+        /* nop */
     }
-    for (i = 0; i < contacts->nelts; ++i) {
-        uri = APR_ARRAY_IDX(contacts, i, const char *);
-        if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, uri, &err))) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, 
-                          "invalid contact uri (%s): %s", err, uri);
-            goto out;
+    
+    if (APR_STATUS_IS_ENOENT(rv)) {
+        /* No suitable account found in MD_SG_ACCOUNTS. Maybe a new account
+         * can already be found in MD_SG_STAGING? */
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, 
+                      "no account found, looking in STAGING");
+        while (APR_EAGAIN == (rv = acct_find_and_verify(store, MD_SG_STAGING, "*", 
+                                                        acme, acme->p))) {
+            /* nop */
         }
     }
-    
-    spec.type = MD_PKEY_TYPE_RSA;
-    spec.params.rsa.bits = MD_ACME_ACCT_PKEY_BITS;
-    
-    if (APR_SUCCESS == (rv = md_pkey_gen(&pkey, acme->p, &spec))
-        && APR_SUCCESS == (rv = acct_make(&acme->acct,  p, acme->url, NULL, contacts))) {
-        acct_ctx_t ctx;
+    return rv;
+}
 
-        acme->acct_key = pkey;
-        if (agreement) {
-            acme->acct->agreement = agreement;
-        }
+typedef struct {
+    apr_pool_t *p;
+    const char *url;
+    const char *id;
+} load_ctx;
 
-        ctx.acme = acme;
-        ctx.p = p;
-        rv = md_acme_POST(acme, acme->new_reg, on_init_acct_new, acct_upd, NULL, &ctx);
-        if (APR_SUCCESS == rv) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p, 
-                          "registered new account %s", acme->acct->url);
+static int id_by_url(void *baton, const char *name, const char *aspect,
+                     md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
+{
+    load_ctx *ctx = baton;
+    int disabled;
+    const char *acct_url, *status;
+    
+    (void)aspect;
+    (void)ptemp;
+    if (MD_SV_JSON == vtype) {
+        md_json_t *json = value;
+        
+        status = md_json_gets(json, MD_KEY_STATUS, NULL);
+        disabled = md_json_getb(json, MD_KEY_DISABLED, NULL);
+        acct_url = md_json_gets(json, MD_KEY_URL, NULL);
+        
+        if ((!status || !strcmp("valid", status)) && !disabled 
+            && acct_url && !strcmp(ctx->url, acct_url)) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ctx->p, 
+                          "found account %s for url %s: %s, status=%s, disabled=%d", 
+                          name, ctx->url, aspect, status, disabled);
+            ctx->id = apr_pstrdup(ctx->p, name);
+            return 0;
         }
     }
+    return 1;
+}
 
-out:    
-    if (APR_SUCCESS != rv && acme->acct) {
-        acme->acct = NULL;
-    }
+apr_status_t md_acme_acct_id_for_url(const char **pid, md_store_t *store, 
+                                     md_store_group_t group, const char *url, apr_pool_t *p)
+{
+    apr_status_t rv;
+    load_ctx ctx;
+    
+    ctx.p = p;
+    ctx.url = url;
+    ctx.id = NULL;
+    
+    rv = md_store_iter(id_by_url, &ctx, store, p, group, "*", MD_FN_ACCOUNT, MD_SV_JSON);
+    *pid = (APR_SUCCESS == rv)? ctx.id : NULL;
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "acct_id_by_url %s -> %s", url, *pid);
     return rv;
 }
 
 /**************************************************************************************************/
-/* acct validation */
+/* acct operation context */
+typedef struct {
+    md_acme_t *acme;
+    apr_pool_t *p;
+    const char *agreement;
+} acct_ctx_t;
 
-static apr_status_t on_init_acct_valid(md_acme_req_t *req, void *baton)
+/**************************************************************************************************/
+/* acct update */
+
+static apr_status_t on_init_acct_upd(md_acme_req_t *req, void *baton)
 {
     md_json_t *jpayload;
 
     (void)baton;
     jpayload = md_json_create(req->p);
-    md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
-    
+    switch (MD_ACME_VERSION_MAJOR(req->acme->version)) {
+        case 1:
+            md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
+            break;
+        default:
+            break;
+    }
     return md_acme_req_body_init(req, jpayload);
 } 
 
-static apr_status_t acct_valid(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs
-                               md_json_t *body, void *baton)
+static apr_status_t acct_upd(md_acme_t *acme, apr_pool_t *p
+                             const apr_table_t *hdrs, md_json_t *body, void *baton)
 {
-    md_acme_acct_t *acct = acme->acct;
+    acct_ctx_t *ctx = baton;
     apr_status_t rv = APR_SUCCESS;
-    const char *body_str;
-    const char *tos_required;
+    md_acme_acct_t *acct = acme->acct;
+    
+    if (!acct->url) {
+        const char *location = apr_table_get(hdrs, "location");
+        if (!location) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, APR_EINVAL, p, "new acct without location");
+            return APR_EINVAL;
+        }
+        acct->url = apr_pstrdup(ctx->p, location);
+    }
     
-    (void)p;
-    (void)baton;
     apr_array_clear(acct->contacts);
     md_json_getsa(acct->contacts, body, MD_KEY_CONTACT, NULL);
-    acct->registration = md_json_clone(acme->p, body);
-    
-    body_str = md_json_writep(body, acme->p, MD_JSON_FMT_INDENT);
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p, "validate acct %s: %s", 
-                  acct->url, body_str ? body_str : "<failed to serialize!>");
-    
-    acct->agreement = md_json_gets(acct->registration, MD_KEY_AGREEMENT, NULL);
-    tos_required = md_link_find_relation(hdrs, acme->p, "terms-of-service");
-    
-    if (tos_required) {
-        if (!acct->agreement || strcmp(tos_required, acct->agreement)) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p, 
-                          "needs to agree to terms-of-service '%s', "
-                          "has already agreed to '%s'", 
-                          tos_required, acct->agreement);
-        }
-        acct->tos_required = tos_required;
+    if (md_json_has_key(body, MD_KEY_STATUS, NULL)) {
+        acct->status = acct_st_from_str(md_json_gets(body, MD_KEY_STATUS, NULL));
     }
+    if (md_json_has_key(body, MD_KEY_AGREEMENT, NULL)) {
+        acct->agreement = md_json_dups(acme->p, body, MD_KEY_AGREEMENT, NULL);
+    }
+    if (md_json_has_key(body, MD_KEY_ORDERS, NULL)) {
+        acct->orders = md_json_dups(acme->p, body, MD_KEY_ORDERS, NULL);
+    }
+    acct->registration = md_json_clone(ctx->p, body);
     
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "updated acct %s", acct->url);
     return rv;
 }
 
-static apr_status_t md_acme_validate_acct(md_acme_t *acme)
+apr_status_t md_acme_acct_update(md_acme_t *acme)
 {
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "acct validation");
+    acct_ctx_t ctx;
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "acct update");
     if (!acme->acct) {
         return APR_EINVAL;
     }
-    return md_acme_POST(acme, acme->acct->url, on_init_acct_valid, acct_valid, NULL, NULL);
+    ctx.acme = acme;
+    ctx.p = acme->p;
+    return md_acme_POST(acme, acme->acct->url, on_init_acct_upd, acct_upd, NULL, NULL, &ctx);
 }
 
-/**************************************************************************************************/
-/* account setup */
-
-static apr_status_t acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
+apr_status_t md_acme_acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
 {
     apr_status_t rv;
     
-    if (APR_SUCCESS != (rv = md_acme_validate_acct(acme))) {
+    if (APR_SUCCESS != (rv = md_acme_acct_update(acme))) {
         if (acme->acct && (APR_ENOENT == rv || APR_EACCES == rv)) {
-            if (!acme->acct->disabled) {
-                acme->acct->disabled = 1;
+            if (MD_ACME_ACCT_ST_VALID == acme->acct->status) {
+                acme->acct->status = MD_ACME_ACCT_ST_UNKNOWN;
                 if (store) {
-                    md_acme_save(acme, store, p);
+                    md_acme_acct_save(store, p, acme, &acme->acct_id, acme->acct, acme->acct_key); 
                 }
             }
             acme->acct = NULL;
@@ -458,133 +492,152 @@ static apr_status_t acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_t
     return rv;
 }
 
-apr_status_t md_acme_use_acct(md_acme_t *acme, md_store_t *store,
-                              apr_pool_t *p, const char *acct_id)
+/**************************************************************************************************/
+/* Register a new account */
+
+static apr_status_t on_init_acct_new(md_acme_req_t *req, void *baton)
 {
-    md_acme_acct_t *acct;
-    md_pkey_t *pkey;
-    apr_status_t rv;
+    acct_ctx_t *ctx = baton;
+    md_json_t *jpayload;
+
+    jpayload = md_json_create(req->p);
     
-    if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey, 
-                                               store, MD_SG_ACCOUNTS, acct_id, acme->p))) {
-        if (acct->ca_url && !strcmp(acct->ca_url, acme->url)) {
-            acme->acct = acct;
-            acme->acct_key = pkey;
-            rv = acct_validate(acme, store, p);
-        }
-        else {
-            /* account is from a nother server or, more likely, from another
-             * protocol endpoint on the same server */
-            rv = APR_ENOENT;
-        }
+    switch (MD_ACME_VERSION_MAJOR(req->acme->version)) {
+        case 1:
+            md_json_sets("new-reg", jpayload, MD_KEY_RESOURCE, NULL);
+            md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL);
+            if (ctx->agreement) {
+                md_json_sets(ctx->agreement, jpayload, MD_KEY_AGREEMENT, NULL);
+            }
+            break;
+        default:
+            md_json_setsa(ctx->acme->acct->contacts, jpayload, MD_KEY_CONTACT, NULL);
+            if (ctx->agreement) {
+                md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL);
+            }
+        break;
     }
-    return rv;
-}
+    
+    return md_acme_req_body_init(req, jpayload);
+} 
 
-apr_status_t md_acme_use_acct_staged(md_acme_t *acme, struct md_store_t *store
-                                     md_t *md, apr_pool_t *p)
+apr_status_t md_acme_acct_register(md_acme_t *acme, md_store_t *store, apr_pool_t *p
+                                   apr_array_header_t *contacts, const char *agreement)
 {
-    md_acme_acct_t *acct;
-    md_pkey_t *pkey;
     apr_status_t rv;
+    md_pkey_t *pkey;
+    const char *err = NULL, *uri;
+    md_pkey_spec_t spec;
+    int i;
+    acct_ctx_t ctx;
     
-    if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey, 
-                                               store, MD_SG_STAGING, md->name, acme->p))) {
-        acme->acct = acct;
-        acme->acct_key = pkey;
-        rv = acct_validate(acme, NULL, p);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "create new account");
+    
+    ctx.acme = acme;
+    ctx.p = p;
+    /* The agreement URL is submitted when the ACME server announces Terms-of-Service
+     * in its directory meta data. The magic value "accepted" will always use the
+     * advertised URL. */
+    ctx.agreement = NULL;
+    if (acme->ca_agreement && agreement) {
+        ctx.agreement = !strcmp("accepted", agreement)? acme->ca_agreement : agreement;
     }
-    return rv;
-}
-
-const char *md_acme_get_acct_id(md_acme_t *acme)
-{
-    return acme->acct? acme->acct->id : NULL;
-}
-
-const char *md_acme_get_agreement(md_acme_t *acme)
-{
-    return acme->acct? acme->acct->agreement : NULL;
-}
-
-apr_status_t md_acme_find_acct(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
-{
-    md_acme_acct_t *acct;
-    md_pkey_t *pkey;
-    apr_status_t rv;
     
-    while (APR_SUCCESS == acct_find(&acct, &pkey, store, acme, acme->p)) {
-        acme->acct = acct;
-        acme->acct_key = pkey;
-        rv = acct_validate(acme, store, p);
-        
-        if (APR_SUCCESS == rv) {
-            return rv;
+    if (ctx.agreement) {
+        if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, ctx.agreement, &err))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, 
+                          "invalid agreement uri (%s): %s", err, ctx.agreement);
+            goto out;
         }
-        else {
-            acme->acct = NULL;
-            acme->acct_key = NULL;
-            if (!APR_STATUS_IS_ENOENT(rv)) {
-                /* encountered error with server */
-                return rv;
+    }
+    
+    for (i = 0; i < contacts->nelts; ++i) {
+        uri = APR_ARRAY_IDX(contacts, i, const char *);
+        if (APR_SUCCESS != (rv = md_util_abs_uri_check(acme->p, uri, &err))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, 
+                          "invalid contact uri (%s): %s", err, uri);
+            goto out;
+        }
+    }
+    
+    /* If there is no key selected yet, try to find an existing one for the same host. 
+     * Let's Encrypt identifies accounts by their key for their ACMEv1 and v2 services.
+     * Although the account appears on both services with different urls, it is 
+     * internally the same one.
+     * I think this is beneficial if someone migrates from ACMEv1 to v2 and not a leak
+     * of identifying information.
+     */
+    if (!acme->acct_key) {
+        find_ctx fctx;
+    
+        fctx.p = p;
+        fctx.acme = acme;
+        fctx.id = NULL;
+        fctx.url_match = 0;
+        
+        md_store_iter(find_acct, &fctx, store, p, MD_SG_ACCOUNTS, 
+                      mk_acct_pattern(p, acme), MD_FN_ACCOUNT, MD_SV_JSON);
+        if (fctx.id) {
+            rv = md_store_load(store, MD_SG_ACCOUNTS, fctx.id, MD_FN_ACCT_KEY, MD_SV_PKEY, 
+                               (void**)&acme->acct_key, p);
+            if (APR_SUCCESS == rv) {
+                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "reusing key from account %s", fctx.id);
+            }
+            else {
+                acme->acct_key = NULL;
             }
         }
     }
-    return APR_ENOENT;
-}
-
-apr_status_t md_acme_create_acct(md_acme_t *acme, apr_pool_t *p, apr_array_header_t *contacts, 
-                                 const char *agreement)
-{
-    return acct_register(acme, p, contacts, agreement);
-}
-
-/**************************************************************************************************/
-/* Delete the account */
-
-apr_status_t md_acme_unstore_acct(md_store_t *store, apr_pool_t *p, const char *acct_id) 
-{
-    apr_status_t rv = APR_SUCCESS;
     
-    rv = md_store_remove(store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCOUNT, p, 1);
+    /* If we still have no key, generate a new one */
+    if (!acme->acct_key) {
+        spec.type = MD_PKEY_TYPE_RSA;
+        spec.params.rsa.bits = MD_ACME_ACCT_PKEY_BITS;
+        
+        if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, acme->p, &spec))) goto out;
+        acme->acct_key = pkey;
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "created new account key");
+    }
+    
+    if (APR_SUCCESS != (rv = acct_make(&acme->acct,  p, acme->url, contacts))) goto out;
+    rv = md_acme_POST_new_account(acme,  on_init_acct_new, acct_upd, NULL, NULL, &ctx);
     if (APR_SUCCESS == rv) {
-        md_store_remove(store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCT_KEY, p, 1);
+        md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p, 
+                      "registered new account %s", acme->acct->url);
+    }
+
+out:    
+    if (APR_SUCCESS != rv && acme->acct) {
+        acme->acct = NULL;
     }
     return rv;
 }
 
+/**************************************************************************************************/
+/* Deactivate the account */
+
 static apr_status_t on_init_acct_del(md_acme_req_t *req, void *baton)
 {
     md_json_t *jpayload;
 
     (void)baton;
     jpayload = md_json_create(req->p);
-    md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
-    md_json_setb(1, jpayload, "delete", NULL);
-    
+    switch (MD_ACME_VERSION_MAJOR(req->acme->version)) {
+        case 1:
+            md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
+            md_json_setb(1, jpayload, "delete", NULL);
+            break;
+        default:
+            md_json_sets("deactivated", jpayload, MD_KEY_STATUS, NULL);
+            break;
+    }
     return md_acme_req_body_init(req, jpayload);
 } 
 
-static apr_status_t acct_del(md_acme_t *acme, apr_pool_t *p,
-                             const apr_table_t *hdrs, md_json_t *body, void *baton)
-{
-    md_store_t *store = baton;
-    apr_status_t rv = APR_SUCCESS;
-
-    (void)hdrs;
-    (void)body;
-    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p, "deleted account %s", acme->acct->url);
-    if (store) {
-        rv = md_acme_unstore_acct(store, p, acme->acct->id);
-        acme->acct = NULL;
-        acme->acct_key = NULL;
-    }
-    return rv;
-}
-
-apr_status_t md_acme_delete_acct(md_acme_t *acme, md_store_t *store, apr_pool_t *p)
+apr_status_t md_acme_acct_deactivate(md_acme_t *acme, apr_pool_t *p)
 {
     md_acme_acct_t *acct = acme->acct;
+    acct_ctx_t ctx;
     
     (void)p;
     if (!acct) {
@@ -592,7 +645,9 @@ apr_status_t md_acme_delete_acct(md_acme_t *acme, md_store_t *store, apr_pool_t
     }
     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "delete account %s from %s", 
                   acct->url, acct->ca_url);
-    return md_acme_POST(acme, acct->url, on_init_acct_del, acct_del, NULL, store);
+    ctx.acme = acme;
+    ctx.p = p;
+    return md_acme_POST(acme, acct->url, on_init_acct_del, acct_upd, NULL, NULL, &ctx);
 }
 
 /**************************************************************************************************/
@@ -604,9 +659,17 @@ static apr_status_t on_init_agree_tos(md_acme_req_t *req, void *baton)
     md_json_t *jpayload;
 
     jpayload = md_json_create(req->p);
-    md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
-    md_json_sets(ctx->acme->acct->agreement, jpayload, MD_KEY_AGREEMENT, NULL);
-    
+    switch (MD_ACME_VERSION_MAJOR(req->acme->version)) {
+        case 1:
+            md_json_sets("reg", jpayload, MD_KEY_RESOURCE, NULL);
+            md_json_sets(ctx->acme->acct->agreement, jpayload, MD_KEY_AGREEMENT, NULL);
+            break;
+        default:
+            if (ctx->acme->acct->agreement) {
+                md_json_setb(1, jpayload, "termsOfServiceAgreed", NULL);
+            }
+            break;
+    }
     return md_acme_req_body_init(req, jpayload);
 } 
 
@@ -615,21 +678,13 @@ apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *agreement
     acct_ctx_t ctx;
     
     acme->acct->agreement = agreement;
+    if (!strcmp("accepted", agreement) && acme->ca_agreement) {
+        acme->acct->agreement = acme->ca_agreement;
+    }
+    
     ctx.acme = acme;
     ctx.p = p;
-    return md_acme_POST(acme, acme->acct->url, on_init_agree_tos, acct_upd, NULL, &ctx);
-}
-
-static int agreement_required(md_acme_acct_t *acct)
-{
-    /* We used to really check if the account agreement and the one
-     * indicated as valid are the very same:
-     * return (!acct->agreement 
-     *       || (acct->tos_required && strcmp(acct->tos_required, acct->agreement)));
-     * However, LE is happy if the account has agreed to a ToS in the past and
-     * does not required a renewed acceptance.
-     */
-     return !acct->agreement; 
+    return md_acme_POST(acme, acme->acct->url, on_init_agree_tos, acct_upd, NULL, NULL, &ctx);
 }
 
 apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, 
@@ -637,32 +692,17 @@ apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p,
 {
     apr_status_t rv = APR_SUCCESS;
     
-    /* Check if (correct) Terms-of-Service for account were accepted */
+    /* We used to really check if the account agreement and the one indicated in meta
+     * are the very same. However, LE is happy if the account has agreed to a ToS in 
+     * the past and does not require a renewed acceptance.
+     */
     *prequired = NULL;
-    if (agreement_required(acme->acct)) {
-        const char *tos = acme->acct->tos_required;
-        if (!tos) {
-            if (APR_SUCCESS != (rv = md_acme_validate_acct(acme))) {
-                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p, 
-                              "validate for account %s", acme->acct->id); 
-                return rv;
-            }
-            tos = acme->acct->tos_required; 
-            if (!tos) {
-                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, acme->p, "unknown terms-of-service "
-                              "required after validation of account %s", acme->acct->id); 
-                return APR_EGENERAL;
-            }
-        }
-        
-        if (acme->acct->agreement && !strcmp(tos, acme->acct->agreement)) {
-            rv = md_acme_agree(acme, p, tos);
-        }
-        else if (agreement && !strcmp(tos, agreement)) {
-            rv = md_acme_agree(acme, p, tos);
+    if (!acme->acct->agreement && acme->ca_agreement) {
+        if (agreement) {
+            rv = md_acme_agree(acme, p, acme->ca_agreement);
         }
         else {
-            *prequired = apr_pstrdup(p, tos);
+            *prequired = acme->ca_agreement;
             rv = APR_INCOMPLETE;
         }
     }
index e200da37ebe134165ea2f1835d01eb7442d5eb1c..2b552add758708421aa0d6b10e358d2d652a3d25 100644 (file)
@@ -27,16 +27,23 @@ struct md_pkey_t;
  */
 typedef struct md_acme_acct_t md_acme_acct_t;
 
+typedef enum {
+    MD_ACME_ACCT_ST_UNKNOWN,
+    MD_ACME_ACCT_ST_VALID,
+    MD_ACME_ACCT_ST_DEACTIVATED,
+    MD_ACME_ACCT_ST_REVOKED,
+} md_acme_acct_st;
+
 struct md_acme_acct_t {
     const char *id;                 /* short, unique id for the account */
     const char *url;                /* url of the account, once registered */
     const char *ca_url;             /* url of the ACME protocol endpoint */
+    md_acme_acct_st status;         /* status of this account */
     apr_array_header_t *contacts;   /* list of contact uris, e.g. mailto:xxx */
     const char *tos_required;       /* terms of service asked for by CA */
     const char *agreement;          /* terms of service agreed to by user */
-    
+    const char *orders;             /* URL where certificate orders are found (ACMEv2) */
     struct md_json_t *registration; /* data from server registration */
-    int disabled;
 };
 
 #define MD_FN_ACCOUNT           "account.json"
@@ -46,4 +53,83 @@ struct md_acme_acct_t {
  * are expected to live long, better err on the safe side. */
 #define MD_ACME_ACCT_PKEY_BITS  3072
 
+#define MD_ACME_ACCT_STAGED     "staged"
+
+/**
+ * Convert an ACME account form/to JSON.
+ */
+struct md_json_t *md_acme_acct_to_json(md_acme_acct_t *acct, apr_pool_t *p);
+apr_status_t md_acme_acct_from_json(md_acme_acct_t **pacct, struct md_json_t *json, apr_pool_t *p);
+
+/**
+ * Update the account from the ACME server.
+ * - Will update acme->acct structure from server on success
+ * - Will return error status when request failed or account is not known.
+ */
+apr_status_t md_acme_acct_update(md_acme_t *acme);
+
+/**
+ * Update the account and persist changes in the store, if given (and not NULL).
+ */
+apr_status_t md_acme_acct_validate(md_acme_t *acme, struct md_store_t *store, apr_pool_t *p);
+
+/**
+ * Agree to the given Terms-of-Service url for the current account.
+ */
+apr_status_t md_acme_agree(md_acme_t *acme, apr_pool_t *p, const char *tos);
+
+/**
+ * Confirm with the server that the current account agrees to the Terms-of-Service
+ * given in the agreement url.
+ * If the known agreement is equal to this, nothing is done.
+ * If it differs, the account is re-validated in the hope that the server
+ * announces the Tos URL it wants. If this is equal to the agreement specified,
+ * the server is notified of this. If the server requires a ToS that the account
+ * thinks it has already given, it is resend.
+ *
+ * If an agreement is required, different from the current one, APR_INCOMPLETE is
+ * returned and the agreement url is returned in the parameter.
+ */
+apr_status_t md_acme_check_agreement(md_acme_t *acme, apr_pool_t *p, 
+                                     const char *agreement, const char **prequired);
+
+/**
+ * Get the ToS agreement for current account.
+ */
+const char *md_acme_get_agreement(md_acme_t *acme);
+
+
+/** 
+ * Find an existing account in the local store. On APR_SUCCESS, the acme
+ * instance will have a current, validated account to use.
+ */ 
+apr_status_t md_acme_find_acct(md_acme_t *acme, struct md_store_t *store);
+
+/**
+ * Find the account id for a given account url. 
+ */
+apr_status_t md_acme_acct_id_for_url(const char **pid, struct md_store_t *store, 
+                                     md_store_group_t group, const char *url, apr_pool_t *p);
+
+/**
+ * Create a new account at the ACME server. The
+ * new account is the one used by the acme instance afterwards, on success.
+ */
+apr_status_t md_acme_acct_register(md_acme_t *acme, struct md_store_t *store, 
+                                   apr_pool_t *p, apr_array_header_t *contacts, 
+                                   const char *agreement);
+
+apr_status_t md_acme_acct_save(struct md_store_t *store, apr_pool_t *p, md_acme_t *acme,  
+                               const char **pid, struct md_acme_acct_t *acct, 
+                               struct md_pkey_t *acct_key);
+                               
+/**
+ * Deactivate the current account at the ACME server. 
+ */
+apr_status_t md_acme_acct_deactivate(md_acme_t *acme, apr_pool_t *p);
+
+apr_status_t md_acme_acct_load(struct md_acme_acct_t **pacct, struct md_pkey_t **ppkey,
+                               struct md_store_t *store, md_store_group_t group, 
+                               const char *name, apr_pool_t *p);
+
 #endif /* md_acme_acct_h */
index 2295745b7b4b0f1f874a109d6ce7029e6bd891f5..ddb4e91973d41787844a5b5ae5daa3fb53c0447a 100644 (file)
@@ -32,6 +32,7 @@
 #include "md_http.h"
 #include "md_log.h"
 #include "md_jws.h"
+#include "md_result.h"
 #include "md_store.h"
 #include "md_util.h"
 
@@ -46,64 +47,6 @@ md_acme_authz_t *md_acme_authz_create(apr_pool_t *p)
     return authz;
 }
 
-md_acme_authz_set_t *md_acme_authz_set_create(apr_pool_t *p)
-{
-    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, (size_t)(set->authzs->nelts - n) * sizeof(*elems));
-            }
-            --set->authzs->nelts;
-            return APR_SUCCESS;
-        }
-    }
-    return APR_ENOENT;
-}
-
 /**************************************************************************************************/
 /* Register a new authorization */
 
@@ -158,7 +101,7 @@ static apr_status_t authz_created(md_acme_t *acme, apr_pool_t *p, const apr_tabl
     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->url = 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);
     }
@@ -170,16 +113,15 @@ static apr_status_t authz_created(md_acme_t *acme, apr_pool_t *p, const apr_tabl
 }
 
 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)
+                                    const char *domain, apr_pool_t *p)
 {
     apr_status_t rv;
     authz_req_ctx ctx;
     
-    (void)store;
     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);
+    rv = md_acme_POST(acme, acme->api.v1.new_authz, on_init_authz, authz_created, NULL, NULL, &ctx);
     
     *pauthz = (APR_SUCCESS == rv)? ctx.authz : NULL;
     return rv;
@@ -188,33 +130,41 @@ apr_status_t md_acme_authz_register(struct md_acme_authz_t **pauthz, md_acme_t *
 /**************************************************************************************************/
 /* Update an existing 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)
+apr_status_t md_acme_authz_retrieve(md_acme_t *acme, apr_pool_t *p, const char *url, 
+                                    md_acme_authz_t **pauthz)
+{
+    md_acme_authz_t *authz;
+    apr_status_t rv;
+    
+    authz = apr_pcalloc(p, sizeof(*authz));
+    authz->url = apr_pstrdup(p, url);
+    rv = md_acme_authz_update(authz, acme, p);
+    
+    *pauthz = (APR_SUCCESS == rv)? authz : NULL;
+    return rv;
+}
+
+apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, apr_pool_t *p)
 {
     md_json_t *json;
     const char *s, *err;
     md_log_level_t log_level;
     apr_status_t rv;
-    MD_CHK_VARS;
     
-    (void)store;
     assert(acme);
     assert(acme->http);
     assert(authz);
-    assert(authz->location);
+    assert(authz->url);
 
     authz->state = MD_ACME_AUTHZ_S_UNKNOWN;
     json = NULL;
     err = "unable to parse response";
     log_level = MD_LOG_ERR;
     
-    if (MD_OK(md_acme_get_json(&json, acme, authz->location, p))
-        && (s = md_json_gets(json, MD_KEY_IDENTIFIER, MD_KEY_TYPE, NULL))
-        && !strcmp(s, "dns")
-        && (s = md_json_gets(json, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL))
-        && !strcmp(s, authz->domain)
+    if (APR_SUCCESS == (rv = md_acme_get_json(&json, acme, authz->url, p))
         && (s = md_json_gets(json, MD_KEY_STATUS, NULL))) {
-        
+            
+        authz->domain = md_json_gets(json, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL); 
         authz->resource = json;
         if (!strcmp(s, "pending")) {
             authz->state = MD_ACME_AUTHZ_S_PENDING;
@@ -239,7 +189,7 @@ apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme,
     
     if (md_log_is_level(p, log_level)) {
         md_log_perror(MD_LOG_MARK, log_level, rv, p, "ACME server authz: %s for %s at %s. "
-                      "Exact response was: %s", err? err : "", authz->domain, authz->location,
+                      "Exact response was: %s", err? err : "", authz->domain, authz->url,
                       json? md_json_writep(json, p, MD_JSON_FMT_COMPACT) : "not available");
     }
     
@@ -256,7 +206,12 @@ static md_acme_authz_cha_t *cha_from_json(apr_pool_t *p, size_t index, md_json_t
     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);
+    if (md_json_has_key(json, MD_KEY_URL, NULL)) { /* ACMEv2 */
+        cha->uri = md_json_dups(p, json, MD_KEY_URL, NULL);
+    }
+    else {                                         /* ACMEv1 */
+        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);
 
@@ -269,8 +224,12 @@ static apr_status_t on_init_authz_resp(md_acme_req_t *req, void *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);
+    if (MD_ACME_VERSION_MAJOR(req->acme->version) <= 1) {
+        md_json_sets(MD_KEY_CHALLENGE, jpayload, MD_KEY_RESOURCE, NULL);
+    }
+    if (ctx->challenge->key_authz) {
+        md_json_sets(ctx->challenge->key_authz, jpayload, MD_KEY_KEYAUTHZ, NULL);
+    }
     
     return md_acme_req_body_init(req, jpayload);
 } 
@@ -284,7 +243,7 @@ static apr_status_t authz_http_set(md_acme_t *acme, apr_pool_t *p, const apr_tab
     (void)p;
     (void)hdrs;
     (void)body;
-    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "updated authz %s", ctx->authz->location);
+    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "updated authz %s", ctx->authz->url);
     return APR_SUCCESS;
 }
 
@@ -293,14 +252,13 @@ static apr_status_t setup_key_authz(md_acme_authz_cha_t *cha, md_acme_authz_t *a
 {
     const char *thumb64, *key_authz;
     apr_status_t rv;
-    MD_CHK_VARS;
     
     (void)authz;
     assert(cha);
     assert(cha->token);
     
     *pchanged = 0;
-    if (MD_OK(md_jws_pkey_thumb(&thumb64, p, acme->acct_key))) {
+    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)) {
@@ -318,15 +276,18 @@ static apr_status_t setup_key_authz(md_acme_authz_cha_t *cha, md_acme_authz_t *a
 
 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, 
-                                      md_pkey_spec_t *key_spec, apr_pool_t *p)
+                                      md_pkey_spec_t *key_spec, 
+                                      apr_array_header_t *acme_tls_1_domains, 
+                                      apr_table_t *env, apr_pool_t *p)
 {
     const char *data;
     apr_status_t rv;
     int notify_server;
-    MD_CHK_VARS;
     
     (void)key_spec;
-    if (!MD_OK(setup_key_authz(cha, authz, acme, p, &notify_server))) {
+    (void)env;
+    (void)acme_tls_1_domains;
+    if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, &notify_server))) {
         goto out;
     }
     
@@ -335,7 +296,6 @@ static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t
     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;
     }
     
@@ -346,78 +306,72 @@ static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t
          * 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);
+        rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, 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, 
-                                         md_pkey_spec_t *key_spec, apr_pool_t *p)
+static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, 
+                                          md_acme_t *acme, md_store_t *store, 
+                                          md_pkey_spec_t *key_spec,  
+                                          apr_array_header_t *acme_tls_1_domains, 
+                                          apr_table_t *env, apr_pool_t *p)
 {
     md_cert_t *cha_cert;
     md_pkey_t *cha_key;
-    const char *cha_dns;
+    const char *acme_id, *token;
     apr_status_t rv;
     int notify_server;
-    apr_array_header_t *domains;
-    MD_CHK_VARS;
-    
-    if (   !MD_OK(setup_key_authz(cha, authz, acme, p, &notify_server))
-        || !MD_OK(setup_cha_dns(&cha_dns, cha, p))) {
+    md_data data;
+    
+    (void)env;
+    if (md_array_str_index(acme_tls_1_domains, authz->domain, 0, 0) < 0) {
+        rv = APR_ENOTIMPL;
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
+                      "%s: protocol 'acme-tls/1' not enabled for this domain.", 
+                      authz->domain);
         goto out;
     }
-
-    rv = md_store_load(store, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_CERT,
+    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_TLSALPN01_CERT,
                        MD_SV_CERT, (void**)&cha_cert, p);
-    if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, cha_dns)) 
+    if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, authz->domain)) 
         || APR_STATUS_IS_ENOENT(rv)) {
         
         if (APR_SUCCESS != (rv = md_pkey_gen(&cha_key, p, key_spec))) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-sni-01 challenge key",
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 challenge key",
                           authz->domain);
             goto out;
         }
 
-        /* setup a certificate containing the challenge dns */
-        domains = apr_array_make(p, 5, sizeof(const char*));
-        APR_ARRAY_PUSH(domains, const char*) = cha_dns;
-        if (!MD_OK(md_cert_self_sign(&cha_cert, authz->domain, domains, cha_key, 
-                                     apr_time_from_sec(7 * MD_SECS_PER_DAY), p))) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: setup self signed cert for %s",
-                          authz->domain, cha_dns);
+        /* Create a "tls-alpn-01" certificate for the domain we want to authenticate.
+         * The server will need to answer a TLS connection with SNI == authz->domain
+         * and ALPN procotol "acme-tls/1" with this certificate.
+         */
+        MD_DATA_SET_STR(&data, cha->key_authz);
+        rv = md_crypt_sha256_digest_hex(&token, p, &data);
+        if (APR_SUCCESS != rv) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 cert",
+                          authz->domain);
             goto out;
         }
         
-        if (MD_OK(md_store_save(store, p, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_PKEY,
+        acme_id = apr_psprintf(p, "critical,DER:04:20:%s", token);
+        if (APR_SUCCESS != (rv = md_cert_make_tls_alpn_01(&cha_cert, authz->domain, acme_id, cha_key, 
+                                            apr_time_from_sec(7 * MD_SECS_PER_DAY), p))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 cert",
+                          authz->domain);
+            goto out;
+        }
+        
+        if (APR_SUCCESS == (rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_TLSALPN01_PKEY,
                                 MD_SV_PKEY, (void*)cha_key, 0))) {
-            rv = md_store_save(store, p, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_CERT,
+            rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_TLSALPN01_CERT,
                                MD_SV_CERT, (void*)cha_cert, 0);
         }
-        authz->dir = cha_dns;
         notify_server = 1;
     }
     
@@ -428,24 +382,130 @@ static apr_status_t cha_tls_sni_01_setup(md_acme_authz_cha_t *cha, md_acme_authz
          * 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);
+        rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, 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, 
-                                 md_pkey_spec_t *key_spec, apr_pool_t *p);
+static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, 
+                                     md_acme_t *acme, md_store_t *store, 
+                                     md_pkey_spec_t *key_spec, 
+                                     apr_array_header_t *acme_tls_1_domains, 
+                                     apr_table_t *env, apr_pool_t *p)
+{
+    const char *token;
+    const char * const *argv;
+    const char *cmdline, *dns01_cmd;
+    apr_status_t rv;
+    int exit_code, notify_server;
+    authz_req_ctx ctx;
+    md_data data;
+    
+    (void)store;
+    (void)key_spec;
+    (void)acme_tls_1_domains;
+    
+    dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01);
+    if (!dns01_cmd) {
+        rv = APR_ENOTIMPL;
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 command not set", 
+                      authz->domain);
+        goto out;
+    }
+    
+    if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, &notify_server))) {
+        goto out;
+    }
+    
+    MD_DATA_SET_STR(&data, cha->key_authz);
+    rv = md_crypt_sha256_digest64(&token, p, &data);
+    if (APR_SUCCESS != rv) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create dns-01 token",
+                      authz->domain);
+        goto out;
+    }
+
+    cmdline = apr_psprintf(p, "%s setup %s %s", dns01_cmd, authz->domain, token); 
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                  "%s: dns-01 setup command: %s", authz->domain, cmdline);
+    apr_tokenize_to_argv(cmdline, (char***)&argv, p);
+    if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, &exit_code))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, 
+                      "%s: dns-01 setup command failed to execute", authz->domain);
+        goto out;
+    }
+    if (exit_code) {
+        rv = APR_EGENERAL;
+        md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, 
+                      "%s: dns-01 setup command returns %d", authz->domain, exit_code);
+        goto out;
+    }
+    
+    /* challenge is setup, tell ACME server so it may (re)try verification */        
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 setup succeeded", authz->domain);
+    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, NULL, &ctx);
+    
+out:    
+    return rv;
+}
+
+static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, 
+                                        apr_table_t *env, apr_pool_t *p)
+{
+    const char * const *argv;
+    const char *cmdline, *dns01_cmd;
+    apr_status_t rv;
+    int exit_code;
+    
+    (void)store;
+    
+    dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01);
+    if (!dns01_cmd) {
+        rv = APR_ENOTIMPL;
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: dns-01 command not set", domain);
+        goto out;
+    }
+    
+    cmdline = apr_psprintf(p, "%s teardown %s", dns01_cmd, domain); 
+    apr_tokenize_to_argv(cmdline, (char***)&argv, p);
+    if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, &exit_code)) || exit_code) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, 
+                      "%s: dns-01 teardown command failed (exit code=%d)",
+                      domain, exit_code);
+    }
+out:    
+    return rv;
+}
+
+static apr_status_t cha_teardown_dir(md_store_t *store, const char *domain, 
+                                     apr_table_t *env, apr_pool_t *p)
+{
+    (void)env;
+    return md_store_purge(store, p, MD_SG_CHALLENGES, domain);
+}
+
+typedef apr_status_t cha_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, 
+                               md_acme_t *acme, md_store_t *store, 
+                               md_pkey_spec_t *key_spec, 
+                               apr_array_header_t *acme_tls_1_domains, 
+                               apr_table_t *env, apr_pool_t *p);
+                               
+typedef apr_status_t cha_teardown(md_store_t *store, const char *domain, 
+                                  apr_table_t *env, apr_pool_t *p);
                                  
 typedef struct {
     const char *name;
-    cha_starter *start;
+    cha_setup *setup;
+    cha_teardown *teardown;
 } 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 },
+    { MD_AUTHZ_TYPE_HTTP01,     cha_http_01_setup,      cha_teardown_dir },
+    { MD_AUTHZ_TYPE_TLSALPN01,  cha_tls_alpn_01_setup,  cha_teardown_dir },
+    { MD_AUTHZ_TYPE_DNS01,      cha_dns_01_setup,       cha_dns_01_teardown },
 };
 static const apr_size_t CHA_TYPES_LEN = (sizeof(CHA_TYPES)/sizeof(CHA_TYPES[0]));
 
@@ -481,12 +541,15 @@ static apr_status_t find_type(void *baton, size_t index, md_json_t *json)
 }
 
 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, 
-                                   md_pkey_spec_t *key_spec, apr_pool_t *p)
+                                   apr_array_header_t *challenges, md_pkey_spec_t *key_spec,
+                                   apr_array_header_t *acme_tls_1_domains, 
+                                   apr_table_t *env, apr_pool_t *p, const char **psetup_token,
+                                   md_result_t *result)
 {
     apr_status_t rv;
     int i;
     cha_find_ctx fctx;
+    const char *challenge_setup;
     
     assert(acme);
     assert(authz);
@@ -495,229 +558,91 @@ apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_s
     fctx.p = p;
     fctx.accepted = NULL;
     
-    /* Look in the order challenge types are defined */
+    /* Look in the order challenge types are defined:
+     * - if they are offered by the CA, try to set it up
+     * - if setup was successful, we are done and the CA will evaluate us
+     * - if setup failed, continue to look for another supported challenge type
+     * - if there is no overlap in types, tell the user that she has to configure
+     *   either more types (dns, tls-alpn-01), make ports available or refrain
+     *   from useing wildcard domains when dns is not available. etc.
+     * - if there was an overlap, but no setup was successfull, report that. We
+     *   will retry this, maybe the failure is temporary (e.g. command to setup DNS
+     */
+    rv = APR_ENOTIMPL;
+    challenge_setup = NULL;
     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) {
+            for (i = 0; i < (int)CHA_TYPES_LEN; ++i) {
+                if (!apr_strnatcasecmp(CHA_TYPES[i].name, fctx.accepted->type)) {
+                    md_result_activity_printf(result, "Setting up challenge '%s' for domain %s", 
+                                              fctx.accepted->type, authz->domain);
+                    rv = CHA_TYPES[i].setup(fctx.accepted, authz, acme, store, key_spec, 
+                                            acme_tls_1_domains, env, p);
+                    if (APR_SUCCESS == rv) {
+                        md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, 
+                                      "%s: set up challenge '%s'", 
+                                      authz->domain, fctx.accepted->type);
+                        challenge_setup = CHA_TYPES[i].name; 
+                        goto out;
+                    }
+                    md_result_printf(result, rv, "error setting up challenge '%s', "
+                                     "for domain %s, looking for other option",
+                                     fctx.accepted->type, authz->domain);
+                    md_result_log(result, MD_LOG_INFO);
+                }
+            }
+        }
     }
     
-    if (!fctx.accepted) {
+out:
+    *psetup_token = (APR_SUCCESS == rv)? apr_psprintf(p, "%s:%s", challenge_setup, authz->domain) : NULL;
+    if (!fctx.accepted || APR_ENOTIMPL == rv) {
         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).",
+        md_result_printf(result, rv, "None of offered challenge types for domain %s are supported. "
+                      "The server offered '%s' and available are: '%s'.",
                       authz->domain, 
                       apr_array_pstrcat(p, fctx.offered, ' '),
-                      apr_array_pstrcat(p, challenges, ' '),
-                      authz->location);
-        return rv;
+                      apr_array_pstrcat(p, challenges, ' '));
+        result->problem = "challenge-mismatch";
+        md_result_log(result, MD_LOG_ERR);
     }
-    
-    for (i = 0; i < (int)CHA_TYPES_LEN; ++i) {
-        if (!apr_strnatcasecmp(CHA_TYPES[i].name, fctx.accepted->type)) {
-            return CHA_TYPES[i].start(fctx.accepted, authz, acme, store, key_spec, p);
-        }
+    else if (APR_SUCCESS != rv) {
+        fctx.offered = apr_array_make(p, 5, sizeof(const char*));
+        md_json_itera(collect_offered, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL);
+        md_result_printf(result, rv, "None of the offered challenge types %s offered "
+                         "for domain %s could be setup successfully. Please check the "
+                         "log for errors.", authz->domain, 
+                         apr_array_pstrcat(p, fctx.offered, ' '));
+        result->problem = "challenge-setup-failure";
+        md_result_log(result, MD_LOG_ERR);
     }
-    
-    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;
-
-    (void)baton;
-    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;
-    
-    (void)p;
-    (void)body;
-    (void)hdrs;
-    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;
-    
-    (void)store;
-    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)
+apr_status_t md_acme_authz_teardown(struct md_store_t *store, 
+                                    const char *token, apr_table_t *env, 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 = (md_acme_authz_state_t)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)
-{
-    (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)
-{
-    (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);
-    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;
-    (void)p;   
-    group = (md_store_group_t)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;
+    char *challenge, *domain;
     int i;
-
-    group = (md_store_group_t)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);
+    
+    if (strchr(token, ':')) {
+        challenge = apr_pstrdup(p, token);
+        domain = strchr(challenge, ':');
+        *domain = '\0'; domain++;
+        for (i = 0; i < (int)CHA_TYPES_LEN; ++i) {
+            if (!apr_strnatcasecmp(CHA_TYPES[i].name, challenge)) {
+                if (CHA_TYPES[i].teardown) {
+                    return CHA_TYPES[i].teardown(store, domain, env, p);
+                }
+                break;
             }
         }
     }
-    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);
+    return APR_SUCCESS;
 }
 
index aa33f23d23f735a2d94fe76dab5d815f9ac3525c..4a3b453b29160fa68ac0a62da416a16bc26857b8 100644 (file)
 #define mod_md_md_acme_authz_h
 
 struct apr_array_header_t;
+struct apr_table_t;
 struct md_acme_t;
 struct md_acme_acct_t;
 struct md_json_t;
 struct md_store_t;
 struct md_pkey_spec_t;
+struct md_result_t;
 
 typedef struct md_acme_challenge_t md_acme_challenge_t;
 
 /**************************************************************************************************/
 /* authorization request for a specific domain name */
 
+#define MD_AUTHZ_TYPE_DNS01         "dns-01"
 #define MD_AUTHZ_TYPE_HTTP01        "http-01"
-#define MD_AUTHZ_TYPE_TLSSNI01      "tls-sni-01"
+#define MD_AUTHZ_TYPE_TLSALPN01     "tls-alpn-01"
 
 typedef enum {
     MD_ACME_AUTHZ_S_UNKNOWN,
@@ -43,8 +46,7 @@ typedef struct md_acme_authz_t md_acme_authz_t;
 
 struct md_acme_authz_t {
     const char *domain;
-    const char *location;
-    const char *dir;
+    const char *url;
     md_acme_authz_state_t state;
     apr_time_t expires;
     struct md_json_t *resource;
@@ -53,52 +55,29 @@ struct md_acme_authz_t {
 #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"
+#define MD_FN_TLSALPN01_CERT    "acme-tls-alpn-01.cert.pem"
+#define MD_FN_TLSALPN01_PKEY    "acme-tls-alpn-01.key.pem"
 
 
 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);
+                                    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_retrieve(md_acme_t *acme, apr_pool_t *p, const char *url, 
+                                    md_acme_authz_t **pauthz);
+apr_status_t md_acme_authz_update(md_acme_authz_t *authz, struct md_acme_t *acme, 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, 
-                                   struct md_pkey_spec_t *key_spec, 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);
-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);
+                                   struct md_pkey_spec_t *key_spec,
+                                   apr_array_header_t *acme_tls_1_domains, 
+                                   struct apr_table_t *env,
+                                   apr_pool_t *p, const char **setup_token,
+                                   struct md_result_t *result);
+
+apr_status_t md_acme_authz_teardown(struct md_store_t *store, const char *setup_token, 
+                                    struct apr_table_t *env, apr_pool_t *p);
 
 #endif /* md_acme_authz_h */
index f62fd929e1929d8199f27d1b8ab293b1961662d6..0e1d84602f09f31c7789afa78ee35c7efbde73ef 100644 (file)
@@ -29,6 +29,7 @@
 #include "md_jws.h"
 #include "md_http.h"
 #include "md_log.h"
+#include "md_result.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"
+#include "md_acme_order.h"
 
-typedef struct {
-    md_proto_driver_t *driver;
-    
-    const char *phase;
-    int complete;
+#include "md_acme_drive.h"
+#include "md_acmev1_drive.h"
+#include "md_acmev2_drive.h"
 
-    md_pkey_t *privkey;              /* the new private key */
-    apr_array_header_t *pubcert;     /* the new certificate + chain certs */
-    
-    md_cert_t *cert;                 /* the new certificate */
-    apr_array_header_t *chain;       /* the chain certificates */
-    const char *next_up_link;        /* where the next chain cert is */
-    
-    md_acme_t *acme;
-    md_t *md;
-    const md_creds_t *ncreds;
+/**************************************************************************************************/
+/* account setup */
+
+static apr_status_t use_staged_acct(md_acme_t *acme, struct md_store_t *store, 
+                                    const char *md_name, apr_pool_t *p)
+{
+    md_acme_acct_t *acct;
+    md_pkey_t *pkey;
+    apr_status_t rv;
     
-    apr_array_header_t *ca_challenges;
-    md_acme_authz_set_t *authz_set;
-    apr_interval_time_t authz_monitor_timeout;
+    if (APR_SUCCESS == (rv = md_acme_acct_load(&acct, &pkey, store, 
+                                               MD_SG_STAGING, md_name, acme->p))) {
+        acme->acct_id = NULL;
+        acme->acct = acct;
+        acme->acct_key = pkey;
+        rv = md_acme_acct_validate(acme, NULL, p);
+    }
+    return rv;
+}
+
+static apr_status_t save_acct_staged(md_acme_t *acme, md_store_t *store, 
+                                     const char *md_name, apr_pool_t *p)
+{
+    md_json_t *jacct;
+    apr_status_t rv;
     
-    const char *csr_der_64;
-    apr_interval_time_t cert_poll_timeout;
+    jacct = md_acme_acct_to_json(acme->acct, p);
     
-} md_acme_driver_t;
-
-/**************************************************************************************************/
-/* account setup */
+    rv = md_store_save(store, p, MD_SG_STAGING, md_name, MD_FN_ACCOUNT, MD_SV_JSON, jacct, 0);
+    if (APR_SUCCESS == rv) {
+        rv = md_store_save(store, p, MD_SG_STAGING, md_name, MD_FN_ACCT_KEY, 
+                           MD_SV_PKEY, acme->acct_key, 0);
+    }
+    return rv;
+}
 
-static apr_status_t ad_set_acct(md_proto_driver_t *d
+apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result
 {
     md_acme_driver_t *ad = d->baton;
     md_t *md = ad->md;
     apr_status_t rv = APR_SUCCESS;
-    int update = 0, acct_installed = 0;
+    int update_md = 0, update_acct = 0;
+    
+    md_result_activity_printf(result, "Selecting account to use for %s", d->md->name);
+    md_acme_clear_acct(ad->acme);
     
-    ad->phase = "setup acme";
-    if (!ad->acme 
-        && APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, md->ca_url, d->proxy_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))) {
+    if (APR_SUCCESS == (rv = use_staged_acct(ad->acme, d->store, md->name, 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;
+    else if (!APR_STATUS_IS_ENOENT(rv)) {
+        goto out;
     }
     
     /* Get an account for the ACME server for this MD */
-    if (md->ca_account && !acct_installed) {
+    if (!ad->acme->acct && md->ca_account) {
         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;
+            update_md = 1;
+        }
+        else if (APR_SUCCESS != rv) {
+            goto out;
         }
     }
 
-    if (APR_SUCCESS == rv && !md->ca_account) {
+    if (!ad->acme->acct && !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_id(ad->acme);
-            update = 1;
+        if (APR_SUCCESS == (rv = md_acme_find_acct(ad->acme, d->store))) {
+            md->ca_account = md_acme_acct_id_get(ad->acme);
+            update_md = 1;
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: using account %s (id=%s)",
+                          d->proto->protocol, ad->acme->acct->url, md->ca_account);
         }
     }
     
-    if (APR_SUCCESS == rv && !md->ca_account) {
-        /* 2.2 No local account exists, create a new one */
+    if (!ad->acme->acct) {
+        /* No account staged, no suitable found in store, register a new one */
+        md_result_activity_printf(result, "Creating new ACME account for %s", d->md->name);
         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: creating new account", 
                       d->proto->protocol);
         
@@ -123,228 +135,44 @@ static apr_status_t ad_set_acct(md_proto_driver_t *d)
             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) { 
-            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;
-    int changed = 0;
-    
-    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);
-        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;) {
-        authz = APR_ARRAY_IDX(ad->authz_set->authzs, i, md_acme_authz_t*);
-        if (!md_contains(md, authz->domain, 0)) {
-            md_acme_authz_set_remove(ad->authz_set, authz->domain);
-            changed = 1;
-        }
-        else {
-            ++i;
-        }
-    }
-    
-    /* 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 *);
-        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;
-            }
+        
+        /* ACMEv1 allowed registration of accounts without accepted Terms-of-Service.
+         * ACMEv2 requires it. Fail early in this case with a meaningful error message.
+         */ 
+        if (!md->ca_agreement && MD_ACME_VERSION_MAJOR(ad->acme->version) > 1) {
+            md_result_printf(result, APR_EINVAL,
+                  "the CA requires you to accept the terms-of-service "
+                  "as specified in <%s>. "
+                  "Please read the document that you find at that URL and, "
+                  "if you agree to the conditions, configure "
+                  "\"MDCertificateAgreement accepted\" "
+                  "in your Apache. Then (graceful) restart the server to activate.", 
+                  ad->acme->ca_agreement);
+            md_result_log(result, MD_LOG_ERR);
+            rv = result->status;
+            goto out;
         }
-    }
-    
-    /* 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->md->pkey_spec, 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;
+        rv = md_acme_acct_register(ad->acme, d->store, d->p, md->contacts, md->ca_agreement);
+        if (APR_SUCCESS == rv) {
+            md->ca_account = NULL;
+            update_md = 1;
+            update_acct = 1;
         }
     }
     
-    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);
+out:
+    /* Persist MD changes in STAGING, so we pick them up on next run */
+    if (APR_SUCCESS == rv&& update_md) {
+        rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
     }
-    return rv;
-}
-
-static apr_status_t check_challenges(void *baton, int attempt)
-{
-    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(%d. attempt)", 
-                      ad->md->name, authz->domain, attempt);
-        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;
-            }
-        }
+    /* Persist account changes in STAGING, so we pick them up on next run */
+    if (APR_SUCCESS == rv&& update_acct) {
+        rv = save_acct_staged(ad->acme, d->store, md->name, d->p);
     }
     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 */
 
@@ -359,34 +187,42 @@ static void get_up_link(md_proto_driver_t *d, apr_table_t *headers)
     }
 } 
 
-static apr_status_t read_http_cert(md_cert_t **pcert, apr_pool_t *p,
+static apr_status_t add_http_certs(apr_array_header_t *chain, apr_pool_t *p,
                                    const md_http_response_t *res)
 {
     apr_status_t rv = APR_SUCCESS;
+    const char *ct;
     
-    if (APR_SUCCESS != (rv = md_cert_read_http(pcert, p, res))
+    ct = apr_table_get(res->headers, "Content-Type");
+    if (ct && !strcmp("application/x-pkcs7-mime", ct)) {
+        /* this looks like a root cert and we do not want those in our chain */
+        goto out; 
+    }
+
+    /* Lets try to read one or more certificates */
+    if (APR_SUCCESS != (rv = md_cert_chain_read_http(chain, 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);
     }
+out:
     return rv;
 }
 
-static apr_status_t on_got_cert(md_acme_t *acme, const md_http_response_t *res, void *baton)
+static apr_status_t on_add_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;
+    int count;
     
     (void)acme;
-    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");
-        if (APR_SUCCESS == rv) {
-            get_up_link(d, res->headers);
-        }
+    count = ad->certs->nelts;
+    if (APR_SUCCESS == (rv = add_http_certs(ad->certs, d->p, res))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%d certs parsed", 
+                      ad->certs->nelts - count);
+        get_up_link(d, res->headers);
     }
     return rv;
 }
@@ -397,19 +233,21 @@ static apr_status_t get_cert(void *baton, int attempt)
     md_acme_driver_t *ad = d->baton;
     
     (void)attempt;
-    return md_acme_GET(ad->acme, ad->md->cert_url, NULL, NULL, on_got_cert, d);
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, d->p, "retrieving cert from %s",
+                  ad->order->certificate);
+    return md_acme_GET(ad->acme, ad->order->certificate, NULL, NULL, on_add_cert, NULL, d);
 }
 
-static apr_status_t ad_cert_poll(md_proto_driver_t *d, int only_once)
+apr_status_t md_acme_drive_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);
+    assert(ad->order);
+    assert(ad->order->certificate);
     
-    ad->phase = "poll certificate";
     if (only_once) {
         rv = get_cert(d, 0);
     }
@@ -417,12 +255,12 @@ static apr_status_t ad_cert_poll(md_proto_driver_t *d, int only_once)
         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);
+    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "poll for cert at %s", ad->order->certificate);
     return rv;
 }
 
 /**************************************************************************************************/
-/* cert setup */
+/* order finalization */
 
 static apr_status_t on_init_csr_req(md_acme_req_t *req, void *baton)
 {
@@ -431,7 +269,9 @@ static apr_status_t on_init_csr_req(md_acme_req_t *req, void *baton)
     md_json_t *jpayload;
 
     jpayload = md_json_create(req->p);
-    md_json_sets("new-cert", jpayload, MD_KEY_RESOURCE, NULL);
+    if (MD_ACME_VERSION_MAJOR(req->acme->version) == 1) {
+        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);
@@ -441,34 +281,47 @@ static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void
 {
     md_proto_driver_t *d = baton;
     md_acme_driver_t *ad = d->baton;
+    const char *location;
+    md_cert_t *cert;
     apr_status_t rv = APR_SUCCESS;
     
     (void)acme;
-    ad->md->cert_url = apr_table_get(res->headers, "location");
-    if (!ad->md->cert_url) {
+    location = apr_table_get(res->headers, "location");
+    if (!location) {
         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))) {
+    ad->order->certificate = apr_pstrdup(d->p, location);
+    if (APR_SUCCESS != (rv = md_acme_order_save(d->store, d->p, MD_SG_STAGING, 
+                                                d->md->name, ad->order, 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);
+                      "%s: saving cert url %s", d->md->name, location);
         return rv;
     }
     
     /* Check if it already was sent with this response */
     ad->next_up_link = NULL;
-    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");
+    if (APR_SUCCESS == (rv = md_cert_read_http(&cert, d->p, res))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "cert parsed");
+        if (ad->certs) {
+            apr_array_clear(ad->certs);
+        }
+        else {
+            ad->certs = apr_array_make(d->p, 5, sizeof(md_cert_t*));
+        }
+        APR_ARRAY_PUSH(ad->certs, md_cert_t*) = cert;
+        
         if (APR_SUCCESS == rv) {
             get_up_link(d, res->headers);
         }
     }
     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);
+        if (location) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, 
+                          "cert not in response, need to poll %s", location);
+        }
     }
     
     return rv;
@@ -487,38 +340,41 @@ static apr_status_t csr_req(md_acme_t *acme, const md_http_response_t *res, void
  * - GET cert chain
  * - store cert chain
  */
-static apr_status_t ad_setup_certificate(md_proto_driver_t *d)
+apr_status_t md_acme_drive_setup_certificate(md_proto_driver_t *d, md_result_t *result)
 {
     md_acme_driver_t *ad = d->baton;
     md_pkey_t *privkey;
     apr_status_t rv;
 
-    ad->phase = "setup cert privkey";
+    md_result_activity_printf(result, "Finalizing order for %s", ad->md->name);
     
-    rv = md_pkey_load(d->store, MD_SG_STAGING, ad->md->name, &privkey, d->p);
+    rv = md_pkey_load(d->store, MD_SG_STAGING, d->md->name, &privkey, d->p);
     if (APR_STATUS_IS_ENOENT(rv)) {
         if (APR_SUCCESS == (rv = md_pkey_gen(&privkey, d->p, d->md->pkey_spec))) {
-            rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, ad->md->name, privkey, 1);
+            rv = md_pkey_save(d->store, d->p, MD_SG_STAGING, d->md->name, privkey, 1);
         }
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: generate privkey", ad->md->name);
-    }
-
-    if (APR_SUCCESS == rv) {
-        ad->phase = "setup csr";
-        rv = md_cert_req_create(&ad->csr_der_64, ad->md, privkey, 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);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: generate privkey", d->md->name);
     }
+    if (APR_SUCCESS != rv) goto leave;
+    
+    md_result_activity_printf(result, "Creating CSR for %s", d->md->name);
+    rv = md_cert_req_create(&ad->csr_der_64, d->md->name, ad->domains, 
+                            ad->md->must_staple, privkey, d->p);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: create CSR", d->md->name);
+    if (APR_SUCCESS != rv) goto leave;
 
-    if (APR_SUCCESS == rv) {
-        if (!ad->cert) {
-            rv = ad_cert_poll(d, 0);
-        }
+    md_result_activity_printf(result, "Submitting CSR to CA for %s", d->md->name);
+    switch (MD_ACME_VERSION_MAJOR(ad->acme->version)) {
+        case 1:
+            rv = md_acme_POST(ad->acme, ad->acme->api.v1.new_cert, on_init_csr_req, NULL, csr_req, NULL, d);
+            break;
+        default:
+            assert(ad->order->finalize);
+            rv = md_acme_POST(ad->acme, ad->order->finalize, on_init_csr_req, NULL, csr_req, NULL, d);
+            break;
     }
+leave:
+    md_acme_report_result(ad->acme, rv, result);
     return rv;
 }
 
@@ -530,7 +386,6 @@ static apr_status_t on_add_chain(md_acme_t *acme, const md_http_response_t *res,
     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;
     
     (void)acme;
@@ -540,12 +395,9 @@ static apr_status_t on_add_chain(md_acme_t *acme, const md_http_response_t *res,
         return APR_SUCCESS;
     }
     
-    if (APR_SUCCESS == (rv = read_http_cert(&cert, d->p, res))) {
+    if (APR_SUCCESS == (rv = add_http_certs(ad->certs, 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;
-        if (APR_SUCCESS == rv) {
-            get_up_link(d, res->headers);
-        }
+        get_up_link(d, res->headers);
     }
     return rv;
 }
@@ -557,68 +409,93 @@ static apr_status_t get_chain(void *baton, int attempt)
     const char *prev_link = NULL;
     apr_status_t rv = APR_SUCCESS;
 
-    while (APR_SUCCESS == rv && ad->chain->nelts < 10) {
-        int nelts = ad->chain->nelts;
+    while (APR_SUCCESS == rv && ad->certs->nelts < 10) {
+        int nelts = ad->certs->nelts;
         
         if (ad->next_up_link && (!prev_link || strcmp(prev_link, ad->next_up_link))) {
             prev_link = ad->next_up_link;
 
             md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, 
-                          "next issuer is  %s", ad->next_up_link);
-            rv = md_acme_GET(ad->acme, ad->next_up_link, NULL, NULL, on_add_chain, d);
+                          "next chain cert at  %s", ad->next_up_link);
+            rv = md_acme_GET(ad->acme, ad->next_up_link, NULL, NULL, on_add_chain, NULL, d);
             
-            if (APR_SUCCESS == rv && nelts == ad->chain->nelts) {
+            if (APR_SUCCESS == rv && nelts == ad->certs->nelts) {
                 break;
             }
         }
+        else if (ad->certs->nelts <= 1) {
+            /* This cannot be the complete chain (no one signs new web certs with their root)
+             * and we did not see a "Link: ...rel=up", so we do not know how to continue. */
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, 
+                          "no link header 'up' for new certificate, unable to retrieve chain");
+            rv = APR_EINVAL;
+            break;
+        }
         else {
             rv = APR_SUCCESS;
             break;
         }
     }
     md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, 
-                  "got chain with %d certs (%d. attempt)", ad->chain->nelts, attempt);
+                  "got chain with %d certs (%d. attempt)", ad->certs->nelts, attempt);
     return rv;
 }
 
-static apr_status_t ad_chain_install(md_proto_driver_t *d)
+static apr_status_t ad_chain_retrieve(md_proto_driver_t *d)
 {
     md_acme_driver_t *ad = d->baton;
     apr_status_t rv;
     
-    /* We should have that from initial cert retrieval, but if we restarted
-     * or switched child process, we need to retrieve this again from the 
-     * certificate resources. */
-    if (!ad->next_up_link) {
-        if (APR_SUCCESS != (rv = ad_cert_poll(d, 0))) {
-            return rv;
+    /* This may be called repeatedly and needs to progress. The relevant state is in
+     * ad->certs                the certificate chain, starting with the new cert for the md
+     * ad->order->certificate   the url where ACME offers us the new md certificate. This may
+     *                          be a single one or even the complete chain
+     * ad->next_up_link         in case the last certificate retrieval did not end the chain,
+     *                          the link header with relation "up" gives us the location
+     *                          for the next cert in the chain
+     */
+    if (!ad->certs) {
+        ad->certs = apr_array_make(d->p, 5, sizeof(md_cert_t *));
+    }
+    if (md_array_is_empty(ad->certs)) {
+        /* Need to start at the order */
+        ad->next_up_link = NULL;
+        if (!ad->order) {
+            rv = APR_EGENERAL;
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, 
+                "%s: asked to retrieve chain, but no order in context", d->md->name);
+            goto out;
         }
-        if (!ad->next_up_link) {
+        if (!ad->order->certificate) {
+            rv = APR_EGENERAL;
             md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, 
-                "server reports no link header 'up' for certificate at %s", ad->md->cert_url);
-            return APR_EINVAL;
+                "%s: asked to retrieve chain, but no certificate url part of order", d->md->name);
+            goto out;
+        }
+        
+        if (APR_SUCCESS != (rv = md_acme_drive_cert_poll(d, 0))) {
+            goto out;
         }
     }
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, 
-                  "chain starts at %s", ad->next_up_link);
     
-    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");
-    }
+    rv = md_util_try(get_chain, d, 0, ad->cert_poll_timeout, 0, 0, 0);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "chain retrieved");
+    
+out:
     return rv;
 }
 
 /**************************************************************************************************/
 /* ACME driver init */
 
-static apr_status_t acme_driver_init(md_proto_driver_t *d)
+static apr_status_t acme_driver_init(md_proto_driver_t *d, md_result_t *result)
 {
     md_acme_driver_t *ad;
-    apr_status_t rv = APR_SUCCESS;
-
+    int dis_http, dis_https, dis_alpn_acme, dis_dns;
+    const char *challenge;
+    
+    md_result_set(result, APR_SUCCESS, NULL);
+    
     ad = apr_pcalloc(d->p, sizeof(*ad));
     
     d->baton = ad;
@@ -626,14 +503,14 @@ static apr_status_t acme_driver_init(md_proto_driver_t *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);
+    ad->ca_challenges = apr_array_make(d->p, 3, sizeof(const char *));
+    challenge = apr_table_get(d->env, MD_KEY_CHALLENGE); 
+    if (challenge) {
+        APR_ARRAY_PUSH(ad->ca_challenges, const char*) = apr_pstrdup(d->p, challenge);
     }
     else if (d->md->ca_challenges && d->md->ca_challenges->nelts > 0) {
         /* pre-configured set for this managed domain */
@@ -642,54 +519,66 @@ static apr_status_t acme_driver_init(md_proto_driver_t *d)
     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_HTTP01;
-        APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_TLSSNI01;
+        APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_TLSALPN01;
+        APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_DNS01;
     }
     
-    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;
+    if (!d->can_http && !d->can_https 
+        && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_DNS01, 0, 0) < 0) {
+        md_result_printf(result, APR_EGENERAL,
+            "the server seems neither reachable via http (port 80) nor https (port 443). "
+            "Please look at the MDPortMap configuration directive on how to correct this. "
+            "The ACME protocol needs at least one of those so the CA can talk to the server "
+            "and verify a domain ownership. Alternatively, you may configure support "
+            "for the %s challenge directive.", MD_AUTHZ_TYPE_DNS01);
+        goto leave;
     }
     
-    if (!d->can_http) {
+    dis_http = dis_https = dis_alpn_acme = dis_dns = 0;
+    if (!d->can_http && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_HTTP01, 0, 1) >= 0) {
         ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_HTTP01, 0);
+        dis_http = 1;
+    }
+    if (!d->can_https && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0, 1) >= 0) {
+        ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0);
+        dis_https = 1;
+    }
+    if (apr_is_empty_array(d->md->acme_tls_1_domains)
+        && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0, 1) >= 0) {
+        ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0);
+        dis_alpn_acme = 1;
     }
-    if (!d->can_https) {
-        ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_TLSSNI01, 0);
+    if (!apr_table_get(d->env, MD_KEY_CMD_DNS01) && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_DNS01, 0, 1) >= 0) {
+        ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_DNS01, 0);
+        dis_dns = 1;
     }
 
     if (apr_is_empty_array(ad->ca_challenges)) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, d->p, "%s: specific CA challenge methods "
-                      "have been configured, but the server is unable to use any of those. "
-                      "For 'http-01' it needs to be reachable on port 80, for 'tls-sni-01'"
-                      " port 443 is needed.", d->md->name);
-        return APR_EGENERAL;
-    }
-    else if (ad->ca_challenges->nelts == 1 
-        && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_TLSSNI01, 0, 0) >= 0) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, d->p, "%s: only challenge type '%s' "
-                      "is available. This method of obtaining certificates will be "
-                      "discontinued by Let's Encrypt and other CAs from early 2019 on, "
-                      "if it is not already disabled for you.", 
-                      d->md->name, MD_AUTHZ_TYPE_TLSSNI01);
-    } 
-    
-    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, d->p, "%s: init driver", d->md->name);
-    
-    return rv;
+        md_result_printf(result, APR_EGENERAL, 
+            "None of the ACME challenge methods configured for this domain are suitable.%s%s%s%s",
+            dis_http? " The http: challenge 'http-01' is disabled because the server seems not reachable on port 80." : "",
+            dis_https? " The https: challenge 'tls-alpn-01' is disabled because the server seems not reachable on port 443." : "",
+            dis_alpn_acme? "The https: challenge 'tls-alpn-01' is disabled because the Protocols configuration does not include the 'acme-tls/1' protocol." : "",
+            dis_dns? "The DNS challenge 'dns-01' is disabled because the directive 'MDChallengeDns01' is not configured." : ""
+            );
+        goto leave;
+    }
+
+leave:    
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, result->status, d->p, "%s: init driver", d->md->name);
+    return result->status;
 }
 
 /**************************************************************************************************/
 /* ACME staging */
 
-static apr_status_t acme_stage(md_proto_driver_t *d)
+static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
 {
     md_acme_driver_t *ad = d->baton;
     int reset_staging = d->reset;
     apr_status_t rv = APR_SUCCESS;
-    int renew = 1;
+    apr_time_t now;
+    char ts[APR_RFC822_DATE_LEN];
 
     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, "
@@ -698,7 +587,10 @@ static apr_status_t acme_stage(md_proto_driver_t *d)
                       apr_array_pstrcat(d->p, ad->ca_challenges, ' '));
     }
 
+    /* When not explicitly told to reset, we check the existing data. If
+     * it is incomplete or old, we trigger the reset for a clean start. */
     if (!reset_staging) {
+        md_result_activity_setn(result, "Checking staging area");
         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? */
@@ -710,220 +602,181 @@ static apr_status_t acme_stage(md_proto_driver_t *d)
             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) {
+        md_result_activity_setn(result, "Resetting staging area");
         /* reset the staging area for this domain */
         rv = md_store_purge(d->store, d->p, MD_SG_STAGING, d->md->name);
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, 
+                      "%s: reset staging area, will", d->md->name);
         if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) {
-            return rv;
+            md_result_printf(result, rv, "resetting staging area");
+            goto out;
         }
         rv = APR_SUCCESS;
         ad->md = NULL;
+        ad->order = NULL;
     }
     
-    if (ad->md && ad->md->state == MD_S_MISSING) {
-        /* There is config information missing. It makes no sense to drive this MD further */
-        rv = APR_INCOMPLETE;
+    md_result_activity_setn(result, "Assessing current status");
+    if (ad->md && ad->md->state == MD_S_MISSING_INFORMATION) {
+        /* ToS agreement is missing. It makes no sense to drive this MD further */
+        md_result_printf(result, APR_INCOMPLETE, 
+            "The managed domain %s is missing required information", d->md->name);
         goto out;
     }
     
     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;
+        const char *keyfile, *certfile;
+
+        rv = md_reg_get_cred_files(&keyfile, &certfile, d->reg, MD_SG_STAGING, d->md, d->p);
+        if (APR_SUCCESS == rv) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: all data staged", d->md->name);
+            goto ready;
         }
     }
     
-    /* Find out where we're at with this managed domain */
-    if (ad->ncreds && ad->ncreds->privkey && ad->ncreds->pubcert) {
-        /* 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;
+    /* Need to renew */
+    md_result_activity_printf(result, "Contacting ACME server for %s at %s", 
+                              d->md->name, d->md->ca_url);
+    if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, d->md->ca_url, d->proxy_url))) {
+        md_result_printf(result, rv, "setup ACME communications");
+        md_result_log(result, MD_LOG_ERR);
+        goto out;
+    } 
+    if (APR_SUCCESS != (rv = md_acme_setup(ad->acme, result))) {
+        md_result_log(result, MD_LOG_ERR);
+        goto out;
     }
     
-    if (renew) {
-        if (APR_SUCCESS != (rv = md_acme_create(&ad->acme, d->p, d->md->ca_url, d->proxy_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 (!ad->md || strcmp(ad->md->ca_url, d->md->ca_url)) {
+        md_result_activity_printf(result, "Resetting staging for %s", d->md->name);
+        /* 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->order = NULL;
+        rv = md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
+        if (APR_SUCCESS != rv) {
+            md_result_printf(result, rv, "Saving MD information in staging area.");
+            md_result_log(result, MD_LOG_ERR);
+            goto out;
         }
-
-        if (APR_SUCCESS == rv && !ad->cert) {
-            md_cert_load(d->store, MD_SG_STAGING, ad->md->name, &ad->cert, d->p);
+    }
+    if (!ad->domains) {
+        ad->domains = md_dns_make_minimal(d->p, ad->md->domains);
+    }
+    if (md_array_is_empty(ad->certs)) {
+        /* have we created this already? */
+        md_pubcert_load(d->store, MD_SG_STAGING, d->md->name, &ad->certs, d->p);
+    }
+    
+    if (md_array_is_empty(ad->certs)) {
+        md_result_activity_printf(result, "Driving ACME protocol for renewal of %s", d->md->name);
+        /* The process of setting up challenges and verifying domain
+         * names differs between ACME versions. */
+        switch (MD_ACME_VERSION_MAJOR(ad->acme->version)) {
+                case 1:
+                rv = md_acmev1_drive_renew(ad, d, result);
+                break;
+                case 2:
+                rv = md_acmev2_drive_renew(ad, d, result);
+                break;
+            default:
+                md_result_printf(result, APR_EINVAL,
+                    "ACME server has unknown major version %d (%x)",
+                    MD_ACME_VERSION_MAJOR(ad->acme->version), ad->acme->version);
+                rv = result->status;
+                break;
         }
-
-        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) {
-                const char *required;
-                
-                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, &required);
-                
-                if (APR_STATUS_IS_INCOMPLETE(rv) && required) {
-                    /* The CA wants the user to agree to Terms-of-Services. Until the user
-                     * has reconfigured and restarted the server, this MD cannot be
-                     * driven further */
-                    ad->md->state = MD_S_MISSING;
-                    md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
-
-                    md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, d->p, 
-                                  "%s: the CA requires you to accept the terms-of-service "
-                                  "as specified in <%s>. "
-                                  "Please read the document that you find at that URL and, "
-                                  "if you agree to the conditions, configure "
-                                  "\"MDCertificateAgreement url\" "
-                                  "with exactly that URL in your Apache. "
-                                  "Then (graceful) restart the server to activate.", 
-                                  ad->md->name, required);
-                    goto out;
-                }
-            }
-            
-            /* 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) goto out;
+    }
+    
+    if (md_array_is_empty(ad->certs) || ad->next_up_link) {
+        md_result_activity_printf(result, "Retrieving certificate chain for %s", d->md->name);
+        md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
+                      "%s: retrieving certificate chain", d->md->name);
+        rv = ad_chain_retrieve(d);
+        if (APR_SUCCESS != rv) {
+            md_result_printf(result, rv, "Unable to retrive certificate chain.");
+            goto out;
         }
         
-        if (APR_SUCCESS == rv && !ad->chain) {
-            /* have we created this already? */
-            md_chain_load(d->store, MD_SG_STAGING, ad->md->name, &ad->chain, d->p);
-        }
-        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);
+        if (!md_array_is_empty(ad->certs)) {
+            rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, d->md->name, ad->certs, 0);
+            if (APR_SUCCESS != rv) {
+                md_result_printf(result, rv, "Saving new certificate chain.");
+                goto out;
+            }
         }
+    }
+    
+    /* As last step, cleanup any order we created so that challenge data
+     * may be removed asap. */
+    md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md->name, d->env);
 
-        if (APR_SUCCESS == rv && !ad->pubcert) {
-            /* have we created this already? */
-            md_pubcert_load(d->store, MD_SG_STAGING, ad->md->name, &ad->pubcert, d->p);
-        }
-        if (APR_SUCCESS == rv && !ad->pubcert) {
-            /* combine cert + chain into the pubcert */
-            ad->pubcert = apr_array_make(d->p, ad->chain->nelts + 1, sizeof(md_cert_t*));
-            APR_ARRAY_PUSH(ad->pubcert, md_cert_t *) = ad->cert;
-            apr_array_cat(ad->pubcert, ad->chain);
-            rv = md_pubcert_save(d->store, d->p, MD_SG_STAGING, ad->md->name, ad->pubcert, 0);
-        }
+ready:
+    md_result_activity_setn(result, NULL);
+    /* we should have the complete cert chain now */
+    assert(!md_array_is_empty(ad->certs));
+    assert(ad->certs->nelts > 1);
+    
+    /* determine when it should be activated */
+    md_result_delay_set(result, md_cert_get_not_before(APR_ARRAY_IDX(ad->certs, 0, md_cert_t*)));
 
-        if (APR_SUCCESS == rv && ad->cert) {
-            apr_time_t now = apr_time_now();
-            apr_interval_time_t max_delay, delay_activation; 
-            
-            /* determine when this cert should be activated */
-            d->stage_valid_from = md_cert_get_not_before(ad->cert);
-            if (d->md->state == MD_S_COMPLETE && d->md->expires > now) {            
-                /**
-                 * The MD is complete and un-expired. This is a renewal run. 
-                 * Give activation 24 hours leeway (if we have that time) to
-                 * accommodate for clients with somewhat weird clocks.
-                 */
+    /* If the existing MD is complete and un-expired, delay the activation
+     * to 24 hours after new cert is valid (if there is enough time left), so
+     * that cients with skewed clocks do not see a problem. */
+    now = apr_time_now();
+    if (d->md->state == MD_S_COMPLETE) {
+        const md_pubcert_t *pub;
+        apr_time_t valid_until, delay_activation;
+        
+        if (APR_SUCCESS == md_reg_get_pubcert(&pub, d->reg, d->md, d->p)) {
+            valid_until = md_cert_get_not_after(APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*));
+            if (valid_until > now) {            
                 delay_activation = apr_time_from_sec(MD_SECS_PER_DAY);
-                if (delay_activation > (max_delay = d->md->expires - now)) {
-                    delay_activation = max_delay;
+                if (delay_activation > (valid_until - now)) {
+                    delay_activation = (valid_until - now);
                 }
-                d->stage_valid_from += delay_activation;
+                md_result_delay_set(result, result->ready_at + delay_activation);
             }
         }
     }
-out:    
+    
+    /* There is a full set staged, to be loaded */
+    apr_rfc822_date(ts, result->ready_at);
+    if (result->ready_at > now) {
+        md_result_printf(result, APR_SUCCESS, 
+            "The certificate for the managed domain has been renewed successfully and can "
+            "be used from %s on. A graceful server restart in %s is recommended.",
+            ts, md_duration_print(d->p, result->ready_at - now));
+    }
+    else {
+        md_result_printf(result, APR_SUCCESS, 
+            "The certificate for the managed domain has been renewed successfully and can "
+            "be used. A graceful server restart now is recommended.");
+    }
+
+out:
     return rv;
 }
 
-static apr_status_t acme_driver_stage(md_proto_driver_t *d)
+static apr_status_t acme_driver_renew(md_proto_driver_t *d, md_result_t *result)
 {
-    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);
+    rv = acme_renew(d, result);
+    md_result_log(result, MD_LOG_DEBUG);
     return rv;
 }
 
 /**************************************************************************************************/
 /* ACME preload */
 
-static apr_status_t acme_preload(md_store_t *store, md_store_group_t load_group, 
-                                 const char *name, const char *proxy_url, apr_pool_t *p
+static apr_status_t acme_preload(md_proto_driver_t *d, md_store_group_t load_group, 
+                                 const char *name, md_result_t *result
 {
     apr_status_t rv;
     md_pkey_t *privkey, *acct_key;
@@ -931,97 +784,115 @@ static apr_status_t acme_preload(md_store_t *store, md_store_group_t load_group,
     apr_array_header_t *pubcert;
     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.
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: preload start", name);
+    /* Load data from MD_SG_STAGING and save it into "load_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
+     *  4. Once "load_group" is complete an ok, 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_load(d->store, MD_SG_STAGING, name, &md, d->p))) {
+        md_result_set(result, rv, "loading staged md.json");
+        goto leave;
     }
-    if (APR_SUCCESS != (rv = md_pkey_load(store, MD_SG_STAGING, name, &privkey, p))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading staging private key", name);
-        return rv;
+    if (APR_SUCCESS != (rv = md_pkey_load(d->store, MD_SG_STAGING, name, &privkey, d->p))) {
+        md_result_set(result, rv, "loading staged privkey.pem");
+        goto leave;
     }
-    if (APR_SUCCESS != (rv = md_pubcert_load(store, MD_SG_STAGING, name, &pubcert, p))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: loading pubcert", name);
-        return rv;
+    if (APR_SUCCESS != (rv = md_pubcert_load(d->store, MD_SG_STAGING, name, &pubcert, d->p))) {
+        md_result_set(result, rv, "loading staged pubcert.pem");
+        goto leave;
     }
 
     /* See if staging holds a new or modified account data */
-    rv = md_acme_acct_load(&acct, &acct_key, store, MD_SG_STAGING, name, p);
+    rv = md_acme_acct_load(&acct, &acct_key, d->store, MD_SG_STAGING, name, d->p);
     if (APR_STATUS_IS_ENOENT(rv)) {
         acct = NULL;
         acct_key = NULL;
         rv = APR_SUCCESS;
     }
     else if (APR_SUCCESS != rv) {
-        return rv; 
+        md_result_set(result, rv, "loading staged account");
+        goto leave;
     }
 
-    /* Remove any authz information we have here or in MD_SG_CHALLENGES */
-    md_acme_authz_set_purge(store, MD_SG_STAGING, p, name);
+    md_result_activity_setn(result, "purging order information");
+    md_acme_order_purge(d->store, d->p, MD_SG_STAGING, name, d->env);
 
-    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);
+    md_result_activity_setn(result, "purging store tmp space");
+    rv = md_store_purge(d->store, d->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;
+        md_result_set(result, rv, NULL);
+        goto leave;
     }
     
     if (acct) {
         md_acme_t *acme;
+        const char *id = md->ca_account;
+
+        /* We may have STAGED the same account several times. This happens when
+         * several MDs are renewed at once and need a new account. They will all store
+         * the new account in their own STAGING area. By checking for accounts with
+         * the same url, we save them all into a single one.
+         */
+        md_result_activity_setn(result, "saving staged account");
+        if (!id && acct->url) {
+            rv = md_acme_acct_id_for_url(&id, d->store, MD_SG_ACCOUNTS, acct->url, d->p);
+            if (APR_STATUS_IS_ENOENT(rv)) {
+                id = NULL;
+            }
+            else if (APR_SUCCESS != rv) {
+                md_result_set(result, rv, "error searching for existing account by url");
+                goto leave;
+            }
+        }
         
-        if (APR_SUCCESS != (rv = md_acme_create(&acme, p, md->ca_url, proxy_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;
+        if (APR_SUCCESS != (rv = md_acme_create(&acme, d->p, md->ca_url, d->proxy_url))) {
+            md_result_set(result, rv, "error setting up acme");
+            goto leave;
         }
-        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_acme_acct_save(d->store, d->p, acme, &id, acct, acct_key))) {
+            md_result_set(result, rv, "error saving account");
+            goto leave;
+        }
+        md->ca_account = 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;
+    md_result_activity_setn(result, "saving staged md/privkey/pubcert");
+    if (APR_SUCCESS != (rv = md_save(d->store, d->p, load_group, md, 1))) {
+        md_result_set(result, rv, "writing md.json");
+        goto leave;
     }
-    if (APR_SUCCESS != (rv = md_pubcert_save(store, p, load_group, name, pubcert, 1))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving cert chain", name);
-        return rv;
+    if (APR_SUCCESS != (rv = md_pubcert_save(d->store, d->p, load_group, name, pubcert, 1))) {
+        md_result_set(result, rv, "writing pubcert.pem");
+        goto leave;
     }
-    if (APR_SUCCESS != (rv = md_pkey_save(store, p, load_group, name, privkey, 1))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: saving private key", name);
-        return rv;
+    if (APR_SUCCESS != (rv = md_pkey_save(d->store, d->p, load_group, name, privkey, 1))) {
+        md_result_set(result, rv, "writing privkey.pem");
+        goto leave;
     }
+    md_result_set(result, APR_SUCCESS, "saved staged data successfully");
     
+leave:
+    md_result_log(result, MD_LOG_DEBUG);
     return rv;
 }
 
-static apr_status_t acme_driver_preload(md_proto_driver_t *d, md_store_group_t group)
+static apr_status_t acme_driver_preload(md_proto_driver_t *d, 
+                                        md_store_group_t group, md_result_t *result)
 {
-    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->proxy_url, 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);
+    rv = acme_preload(d, group, d->md->name, result);
+    md_result_log(result, MD_LOG_DEBUG);
     return rv;
 }
 
 static md_proto_t ACME_PROTO = {
-    MD_PROTO_ACME, acme_driver_init, acme_driver_stage, acme_driver_preload
+    MD_PROTO_ACME, acme_driver_init, acme_driver_renew, acme_driver_preload
 };
  
 apr_status_t md_acme_protos_add(apr_hash_t *protos, apr_pool_t *p)
diff --git a/modules/md/md_acme_drive.h b/modules/md/md_acme_drive.h
new file mode 100644 (file)
index 0000000..5111408
--- /dev/null
@@ -0,0 +1,53 @@
+/* Copyright 2019 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 md_acme_drive_h
+#define md_acme_drive_h
+
+struct apr_array_header_t;
+struct md_acme_order_t;
+struct md_result_t;
+
+typedef struct md_acme_driver_t {
+    md_proto_driver_t *driver;
+    void *sub_driver;
+    
+    int complete;
+
+    md_pkey_t *privkey;              /* the new private key */
+    apr_array_header_t *certs;       /* the certifiacte chain, starting with the new one */
+    const char *next_up_link;        /* where the next chain cert is */
+    
+    md_acme_t *acme;
+    md_t *md;
+    struct apr_array_header_t *domains;
+    
+    apr_array_header_t *ca_challenges;
+    struct md_acme_order_t *order;
+    apr_interval_time_t authz_monitor_timeout;
+    
+    const char *csr_der_64;
+    apr_interval_time_t cert_poll_timeout;
+    
+} md_acme_driver_t;
+
+apr_status_t md_acme_drive_set_acct(struct md_proto_driver_t *d, 
+                                    struct md_result_t *result);
+apr_status_t md_acme_drive_setup_certificate(struct md_proto_driver_t *d, 
+                                             struct md_result_t *result);
+apr_status_t md_acme_drive_cert_poll(struct md_proto_driver_t *d, int only_once);
+
+#endif /* md_acme_drive_h */
+
diff --git a/modules/md/md_acme_order.c b/modules/md/md_acme_order.c
new file mode 100644 (file)
index 0000000..51956e9
--- /dev/null
@@ -0,0 +1,537 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <stdio.h>
+
+#include <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_result.h"
+#include "md_store.h"
+#include "md_util.h"
+
+#include "md_acme.h"
+#include "md_acme_authz.h"
+#include "md_acme_order.h"
+
+
+md_acme_order_t *md_acme_order_create(apr_pool_t *p)
+{
+    md_acme_order_t *order;
+    
+    order = apr_pcalloc(p, sizeof(*order));
+    order->p = p;
+    order->authz_urls = apr_array_make(p, 5, sizeof(const char *));
+    order->challenge_setups = apr_array_make(p, 5, sizeof(const char *));
+    
+    return order;
+}
+
+/**************************************************************************************************/
+/* order conversion */
+
+#define MD_KEY_CHALLENGE_SETUPS   "challenge-setups"
+
+static md_acme_order_st order_st_from_str(const char *s) 
+{
+    if (s) {
+        if (!strcmp("valid", s)) {
+            return MD_ACME_ORDER_ST_VALID;
+        }
+        else if (!strcmp("invalid", s)) {
+            return MD_ACME_ORDER_ST_INVALID;
+        }
+        else if (!strcmp("ready", s)) {
+            return MD_ACME_ORDER_ST_READY;
+        }
+        else if (!strcmp("pending", s)) {
+            return MD_ACME_ORDER_ST_PENDING;
+        }
+        else if (!strcmp("processing", s)) {
+            return MD_ACME_ORDER_ST_PROCESSING;
+        }
+    }
+    return MD_ACME_ORDER_ST_PENDING;
+}
+
+static const char *order_st_to_str(md_acme_order_st status) 
+{
+    switch (status) {
+        case MD_ACME_ORDER_ST_PENDING:
+            return "pending";
+        case MD_ACME_ORDER_ST_READY:
+            return "ready";
+        case MD_ACME_ORDER_ST_PROCESSING:
+            return "processing";
+        case MD_ACME_ORDER_ST_VALID:
+            return "valid";
+        case MD_ACME_ORDER_ST_INVALID:
+            return "invalid";
+        default:
+            return "invalid";
+    }
+}
+
+md_json_t *md_acme_order_to_json(md_acme_order_t *order, apr_pool_t *p)
+{
+    md_json_t *json = md_json_create(p);
+
+    if (order->url) {
+        md_json_sets(order->url, json, MD_KEY_URL, NULL);
+    }
+    md_json_sets(order_st_to_str(order->status), json, MD_KEY_STATUS, NULL);
+    md_json_setsa(order->authz_urls, json, MD_KEY_AUTHORIZATIONS, NULL);
+    md_json_setsa(order->challenge_setups, json, MD_KEY_CHALLENGE_SETUPS, NULL);
+    if (order->finalize) {
+        md_json_sets(order->finalize, json, MD_KEY_FINALIZE, NULL);
+    }
+    if (order->certificate) {
+        md_json_sets(order->certificate, json, MD_KEY_CERTIFICATE, NULL);
+    }
+    return json;
+}
+
+static void order_update_from_json(md_acme_order_t *order, md_json_t *json, apr_pool_t *p)
+{
+    if (!order->url && md_json_has_key(json, MD_KEY_URL, NULL)) {
+        order->url = md_json_dups(p, json, MD_KEY_URL, NULL);
+    }
+    order->status = order_st_from_str(md_json_gets(json, MD_KEY_STATUS, NULL));
+    if (md_json_has_key(json, MD_KEY_AUTHORIZATIONS, NULL)) {
+        md_json_dupsa(order->authz_urls, p, json, MD_KEY_AUTHORIZATIONS, NULL);
+    }
+    if (md_json_has_key(json, MD_KEY_CHALLENGE_SETUPS, NULL)) {
+        md_json_dupsa(order->challenge_setups, p, json, MD_KEY_CHALLENGE_SETUPS, NULL);
+    }
+    if (md_json_has_key(json, MD_KEY_FINALIZE, NULL)) {
+        order->finalize = md_json_dups(p, json, MD_KEY_FINALIZE, NULL);
+    }
+    if (md_json_has_key(json, MD_KEY_CERTIFICATE, NULL)) {
+        order->certificate = md_json_dups(p, json, MD_KEY_CERTIFICATE, NULL);
+    }
+}
+
+md_acme_order_t *md_acme_order_from_json(md_json_t *json, apr_pool_t *p)
+{
+    md_acme_order_t *order = md_acme_order_create(p);
+
+    order_update_from_json(order, json, p);
+    return order;
+}
+
+apr_status_t md_acme_order_add(md_acme_order_t *order, const char *authz_url)
+{
+    assert(authz_url);
+    if (md_array_str_index(order->authz_urls, authz_url, 0, 1) < 0) {
+        APR_ARRAY_PUSH(order->authz_urls, const char*) = apr_pstrdup(order->p, authz_url);
+    }
+    return APR_SUCCESS;
+}
+
+apr_status_t md_acme_order_remove(md_acme_order_t *order, const char *authz_url)
+{
+    int i;
+    
+    assert(authz_url);
+    i = md_array_str_index(order->authz_urls, authz_url, 0, 1);
+    if (i >= 0) {
+        order->authz_urls = md_array_str_remove(order->p, order->authz_urls, authz_url, 1);
+        return APR_SUCCESS;
+    }
+    return APR_ENOENT;
+}
+
+static apr_status_t add_setup_token(md_acme_order_t *order, const char *token)
+{
+    if (md_array_str_index(order->challenge_setups, token, 0, 1) < 0) {
+        APR_ARRAY_PUSH(order->challenge_setups, const char*) = apr_pstrdup(order->p, token);
+    }
+    return APR_SUCCESS;
+}
+
+/**************************************************************************************************/
+/* persistence */
+
+apr_status_t md_acme_order_load(struct md_store_t *store, md_store_group_t group, 
+                                    const char *md_name, md_acme_order_t **pauthz_set, 
+                                    apr_pool_t *p)
+{
+    apr_status_t rv;
+    md_json_t *json;
+    md_acme_order_t *authz_set;
+    
+    rv = md_store_load_json(store, group, md_name, MD_FN_ORDER, &json, p);
+    if (APR_SUCCESS == rv) {
+        authz_set = md_acme_order_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_order_t *set;
+    const char *md_name;
+    int create;
+    (void)p;   
+    group = (md_store_group_t)va_arg(ap, int);
+    md_name = va_arg(ap, const char *);
+    set = va_arg(ap, md_acme_order_t *);
+    create = va_arg(ap, int);
+
+    json = md_acme_order_to_json(set, ptemp);
+    assert(json);
+    return md_store_save_json(store, ptemp, group, md_name, MD_FN_ORDER, json, create);
+}
+
+apr_status_t md_acme_order_save(struct md_store_t *store, apr_pool_t *p,
+                                    md_store_group_t group, const char *md_name, 
+                                    md_acme_order_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_order_t *order;
+    md_store_group_t group;
+    const char *md_name, *setup_token;
+    apr_table_t *env;
+    int i;
+
+    group = (md_store_group_t)va_arg(ap, int);
+    md_name = va_arg(ap, const char *);
+    env = va_arg(ap, apr_table_t *);
+
+    if (APR_SUCCESS == md_acme_order_load(store, group, md_name, &order, p)) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "order loaded for %s", md_name);
+        for (i = 0; i < order->challenge_setups->nelts; ++i) {
+            setup_token = APR_ARRAY_IDX(order->challenge_setups, i, const char*);
+            if (setup_token) {
+                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                              "order teardown setup %s", setup_token);
+                md_acme_authz_teardown(store, setup_token, env, p);
+            }
+        }
+    }
+    return md_store_remove(store, group, md_name, MD_FN_ORDER, ptemp, 1);
+}
+
+apr_status_t md_acme_order_purge(md_store_t *store, apr_pool_t *p, md_store_group_t group,
+                                 const char *md_name, apr_table_t *env)
+{
+    return md_util_pool_vdo(p_purge, store, p, group, md_name, env, NULL);
+}
+
+/**************************************************************************************************/
+/* ACMEv2 order requests */
+
+typedef struct {
+    apr_pool_t *p;
+    md_acme_order_t *order;
+    md_acme_t *acme;
+    const char *name;
+    apr_array_header_t *domains;
+    md_result_t *result;
+} order_ctx_t;
+
+#define ORDER_CTX_INIT(ctx, p, o, a, n, d, r) \
+    (ctx)->p = (p); (ctx)->order = (o); (ctx)->acme = (a); \
+    (ctx)->name = (n); (ctx)->domains = d; (ctx)->result = r
+
+static apr_status_t identifier_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton)
+{
+    md_json_t *jid;
+    
+    (void)baton;
+    jid = md_json_create(p);
+    md_json_sets("dns", jid, "type", NULL);
+    md_json_sets(value, jid, "value", NULL);
+    return md_json_setj(jid, json, NULL);
+}
+
+static apr_status_t on_init_order_register(md_acme_req_t *req, void *baton)
+{
+    order_ctx_t *ctx = baton;
+    md_json_t *jpayload;
+
+    jpayload = md_json_create(req->p);
+    md_json_seta(ctx->domains, identifier_to_json, NULL, jpayload, "identifiers", NULL);
+
+    return md_acme_req_body_init(req, jpayload);
+} 
+
+static apr_status_t on_order_upd(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs, 
+                                 md_json_t *body, void *baton)
+{
+    order_ctx_t *ctx = baton;
+    const char *location = apr_table_get(hdrs, "location");
+    apr_status_t rv = APR_SUCCESS;
+    
+    (void)acme;
+    (void)p;
+    if (!ctx->order) {
+        if (location) {
+            ctx->order = md_acme_order_create(ctx->p);
+            ctx->order->url = apr_pstrdup(ctx->p, location);
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ctx->p, "new order at %s", location);
+        }
+        else {
+            rv = APR_EINVAL;
+            md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, ctx->p, "new order, no location header");
+            goto out;
+        }
+    }
+    
+    order_update_from_json(ctx->order, body, ctx->p);
+out:
+    return rv;
+}
+
+apr_status_t md_acme_order_register(md_acme_order_t **porder, md_acme_t *acme, apr_pool_t *p, 
+                                    const char *name, apr_array_header_t *domains)
+{
+    order_ctx_t ctx;
+    apr_status_t rv;
+    
+    assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+    ORDER_CTX_INIT(&ctx, p, NULL, acme, name, domains, NULL);
+    rv = md_acme_POST(acme, acme->api.v2.new_order, on_init_order_register, on_order_upd, NULL, NULL, &ctx);
+    *porder = (APR_SUCCESS == rv)? ctx.order : NULL;
+    return rv;
+}
+
+apr_status_t md_acme_order_update(md_acme_order_t *order, md_acme_t *acme, 
+                                  md_result_t *result, apr_pool_t *p)
+{
+    order_ctx_t ctx;
+    apr_status_t rv;
+    
+    assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+    ORDER_CTX_INIT(&ctx, p, order, acme, NULL, NULL, result);
+    rv = md_acme_GET(acme, order->url, NULL, on_order_upd, NULL, NULL, &ctx);
+    if (APR_SUCCESS != rv && APR_SUCCESS != acme->last->status) {
+        md_result_dup(result, acme->last);
+    }
+    return rv;
+}
+
+static apr_status_t await_ready(void *baton, int attempt)
+{
+    order_ctx_t *ctx = baton;
+    apr_status_t rv = APR_SUCCESS;
+    
+    (void)attempt;
+    if (APR_SUCCESS != (rv = md_acme_order_update(ctx->order, ctx->acme,
+                                                  ctx->result, ctx->p))) goto out;
+    switch (ctx->order->status) {
+        case MD_ACME_ORDER_ST_READY:
+        case MD_ACME_ORDER_ST_PROCESSING:
+        case MD_ACME_ORDER_ST_VALID:
+            break;
+        case MD_ACME_ORDER_ST_PENDING:
+            rv = APR_EAGAIN;
+            break;
+        default:
+            rv = APR_EINVAL;
+            break;
+    }
+out:    
+    return rv;
+}
+
+apr_status_t md_acme_order_await_ready(md_acme_order_t *order, md_acme_t *acme, 
+                                       const md_t *md, apr_interval_time_t timeout, 
+                                       md_result_t *result, apr_pool_t *p)
+{
+    order_ctx_t ctx;
+    apr_status_t rv;
+    
+    assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+    ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
+
+    md_result_activity_setn(result, "Waiting for order to become ready");
+    rv = md_util_try(await_ready, &ctx, 0, timeout, 0, 0, 1);
+    md_result_log(result, MD_LOG_DEBUG);
+    return rv;
+}
+
+static apr_status_t await_valid(void *baton, int attempt)
+{
+    order_ctx_t *ctx = baton;
+    apr_status_t rv = APR_SUCCESS;
+
+    (void)attempt;
+    if (APR_SUCCESS != (rv = md_acme_order_update(ctx->order, ctx->acme, 
+                                                  ctx->result, ctx->p))) goto out;
+    switch (ctx->order->status) {
+        case MD_ACME_ORDER_ST_VALID:
+            break;
+        case MD_ACME_ORDER_ST_PROCESSING:
+            rv = APR_EAGAIN;
+            break;
+        default:
+            rv = APR_EINVAL;
+            break;
+    }
+out:    
+    return rv;
+}
+
+apr_status_t md_acme_order_await_valid(md_acme_order_t *order, md_acme_t *acme, 
+                                       const md_t *md, apr_interval_time_t timeout, 
+                                       md_result_t *result, apr_pool_t *p)
+{
+    order_ctx_t ctx;
+    apr_status_t rv;
+    
+    assert(MD_ACME_VERSION_MAJOR(acme->version) > 1);
+    ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
+
+    md_result_activity_setn(result, "Waiting for finalized order to become valid");
+    rv = md_util_try(await_valid, &ctx, 0, timeout, 0, 0, 1);
+    md_result_log(result, MD_LOG_DEBUG);
+    return rv;
+}
+
+/**************************************************************************************************/
+/* processing */
+
+apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *acme, 
+                                            apr_array_header_t *challenge_types,
+                                            md_store_t *store, const md_t *md, 
+                                            apr_table_t *env, md_result_t *result, 
+                                            apr_pool_t *p)
+{
+    apr_status_t rv = APR_SUCCESS;
+    md_acme_authz_t *authz;
+    const char *url, *setup_token;
+    int i;
+    
+    md_result_activity_printf(result, "Starting challenges for domains");
+    for (i = 0; i < order->authz_urls->nelts; ++i) {
+        url = APR_ARRAY_IDX(order->authz_urls, i, const char*);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: check AUTHZ at %s", md->name, url);
+        
+        if (APR_SUCCESS != (rv = md_acme_authz_retrieve(acme, p, url, &authz))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: check authz for %s",
+                          md->name, authz->domain);
+            goto out;
+        }
+
+        switch (authz->state) {
+            case MD_ACME_AUTHZ_S_VALID:
+                break;
+                
+            case MD_ACME_AUTHZ_S_PENDING:
+                rv = md_acme_authz_respond(authz, acme, store, challenge_types, 
+                                           md->pkey_spec, md->acme_tls_1_domains,
+                                           env, p, &setup_token, result);
+                if (APR_SUCCESS != rv) {
+                    goto out;
+                }
+                add_setup_token(order, setup_token);
+                md_acme_order_save(store, p, MD_SG_STAGING, md->name, order, 0);
+                break;
+                
+            default:
+                rv = APR_EINVAL;
+                md_result_printf(result, rv, "unexpected AUTHZ state %d for domain %s", 
+                                 authz->state, authz->domain);
+                md_result_log(result, MD_LOG_ERR);
+             goto out;
+        }
+    }
+out:    
+    return rv;
+}
+
+static apr_status_t check_challenges(void *baton, int attempt)
+{
+    order_ctx_t *ctx = baton;
+    const char *url;
+    md_acme_authz_t *authz;
+    apr_status_t rv = APR_SUCCESS;
+    int i;
+    
+    for (i = 0; i < ctx->order->authz_urls->nelts; ++i) {
+        url = APR_ARRAY_IDX(ctx->order->authz_urls, i, const char*);
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ctx->p, "%s: check AUTHZ at %s (attempt %d)", 
+                      ctx->name, url, attempt);
+        
+        rv = md_acme_authz_retrieve(ctx->acme, ctx->p, url, &authz);
+        if (APR_SUCCESS == rv) {
+            switch (authz->state) {
+                case MD_ACME_AUTHZ_S_VALID:
+                    md_result_printf(ctx->result, rv, 
+                                     "domain authorization for %s is valid", authz->domain);
+                    break;
+                case MD_ACME_AUTHZ_S_PENDING:
+                    rv = APR_EAGAIN;
+                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ctx->p, 
+                                  "%s: status pending at %s", authz->domain, authz->url);
+                    goto leave;
+                default:
+                    rv = APR_EINVAL;
+                    md_result_printf(ctx->result, rv, 
+                                     "domain authorization for %s failed with state %s", 
+                                     authz->domain, authz->state);
+                    md_result_log(ctx->result, MD_LOG_ERR);
+                    goto leave;
+            }
+        }
+        else {
+            md_result_printf(ctx->result, rv, "authorization retrieval failed for domain %s", 
+                             authz->domain);
+        }
+    }
+leave:
+    return rv;
+}
+
+apr_status_t md_acme_order_monitor_authzs(md_acme_order_t *order, md_acme_t *acme, 
+                                          const md_t *md, apr_interval_time_t timeout, 
+                                          md_result_t *result, apr_pool_t *p)
+{
+    order_ctx_t ctx;
+    apr_status_t rv;
+    
+    ORDER_CTX_INIT(&ctx, p, order, acme, md->name, NULL, result);
+    
+    md_result_activity_printf(result, "Monitoring challenge status for %s", md->name);
+    rv = md_util_try(check_challenges, &ctx, 0, timeout, 0, 0, 1);
+    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, "%s: checked authorizations", md->name);
+    return rv;
+}
+
diff --git a/modules/md/md_acme_order.h b/modules/md/md_acme_order.h
new file mode 100644 (file)
index 0000000..ec945c7
--- /dev/null
@@ -0,0 +1,92 @@
+/* Copyright 2019 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 md_acme_order_h
+#define md_acme_order_h
+
+struct md_json_t;
+struct md_result_t;
+
+typedef struct md_acme_order_t md_acme_order_t;
+
+typedef enum {
+    MD_ACME_ORDER_ST_PENDING,
+    MD_ACME_ORDER_ST_READY,
+    MD_ACME_ORDER_ST_PROCESSING,
+    MD_ACME_ORDER_ST_VALID,
+    MD_ACME_ORDER_ST_INVALID,
+} md_acme_order_st;
+
+struct md_acme_order_t {
+    apr_pool_t *p;
+    const char *url;
+    md_acme_order_st status;
+    struct apr_array_header_t *authz_urls;
+    struct apr_array_header_t *challenge_setups;
+    struct md_json_t *json;
+    const char *finalize;
+    const char *certificate;
+};
+
+#define MD_FN_ORDER             "order.json"
+
+/**************************************************************************************************/
+
+md_acme_order_t *md_acme_order_create(apr_pool_t *p);
+
+apr_status_t md_acme_order_add(md_acme_order_t *order, const char *authz_url);
+apr_status_t md_acme_order_remove(md_acme_order_t *order, const char *authz_url);
+
+struct md_json_t *md_acme_order_to_json(md_acme_order_t *set, apr_pool_t *p);
+md_acme_order_t *md_acme_order_from_json(struct md_json_t *json, apr_pool_t *p);
+
+apr_status_t md_acme_order_load(struct md_store_t *store, md_store_group_t group, 
+                                    const char *md_name, md_acme_order_t **pauthz_set, 
+                                    apr_pool_t *p);
+apr_status_t md_acme_order_save(struct md_store_t *store, apr_pool_t *p, 
+                                    md_store_group_t group, const char *md_name, 
+                                    md_acme_order_t *authz_set, int create);
+
+apr_status_t md_acme_order_purge(struct md_store_t *store, apr_pool_t *p, 
+                                 md_store_group_t group, const char *md_name,
+                                 apr_table_t *env);
+
+
+apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *acme, 
+                                            apr_array_header_t *challenge_types,
+                                            md_store_t *store, const md_t *md, 
+                                            apr_table_t *env, struct md_result_t *result,
+                                            apr_pool_t *p);
+
+apr_status_t md_acme_order_monitor_authzs(md_acme_order_t *order, md_acme_t *acme, 
+                                          const md_t *md, apr_interval_time_t timeout,
+                                          struct md_result_t *result, apr_pool_t *p);
+
+/* ACMEv2 only ************************************************************************************/
+
+apr_status_t md_acme_order_register(md_acme_order_t **porder, md_acme_t *acme, apr_pool_t *p, 
+                                    const char *name, struct apr_array_header_t *domains);
+
+apr_status_t md_acme_order_update(md_acme_order_t *order, md_acme_t *acme, 
+                                  struct md_result_t *result, apr_pool_t *p);
+
+apr_status_t md_acme_order_await_ready(md_acme_order_t *order, md_acme_t *acme, 
+                                       const md_t *md, apr_interval_time_t timeout, 
+                                       struct md_result_t *result, apr_pool_t *p);
+apr_status_t md_acme_order_await_valid(md_acme_order_t *order, md_acme_t *acme, 
+                                       const md_t *md, apr_interval_time_t timeout, 
+                                       struct md_result_t *result, apr_pool_t *p);
+
+#endif /* md_acme_order_h */
diff --git a/modules/md/md_acmev1_drive.c b/modules/md/md_acmev1_drive.c
new file mode 100644 (file)
index 0000000..d52e195
--- /dev/null
@@ -0,0 +1,189 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_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_result.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"
+#include "md_acme_order.h"
+
+#include "md_acme_drive.h"
+#include "md_acmev1_drive.h"
+
+/**************************************************************************************************/
+/* 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_order(md_proto_driver_t *d, md_result_t *result)
+{
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv;
+    md_t *md = ad->md;
+    const char *url;
+    md_acme_authz_t *authz;
+    apr_array_header_t *domains_covered;
+    int i;
+    int changed = 0;
+    
+    assert(ad->md);
+    assert(ad->acme);
+
+    md_result_activity_printf(result, "Setup order resource for %s", ad->md->name);
+    
+    /* 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_order_load(d->store, MD_SG_STAGING, md->name, &ad->order, d->p);
+    if (!ad->order || APR_STATUS_IS_ENOENT(rv)) {
+        ad->order = md_acme_order_create(d->p);
+        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_order_purge(d->store, d->p, MD_SG_STAGING, md->name, d->env);
+        return APR_EAGAIN;
+    }
+    
+    /* Retrieve all known authz from ACME server and check status etc. */
+    domains_covered = apr_array_make(d->p, 5, sizeof(const char *));
+    
+    for (i = 0; i < ad->order->authz_urls->nelts;) {
+        url = APR_ARRAY_IDX(ad->order->authz_urls, i, const char*);
+        rv = md_acme_authz_retrieve(ad->acme, d->p, url, &authz);
+        if (APR_SUCCESS == rv) {
+            if (md_array_str_index(ad->domains, authz->domain, 0, 0) < 0) {
+                md_acme_order_remove(ad->order, url);
+                changed = 1;
+                continue;
+            }
+        
+            APR_ARRAY_PUSH(domains_covered, const char *) = authz->domain;
+            ++i;
+        }
+        else if (APR_STATUS_IS_ENOENT(rv)) {
+            md_acme_order_remove(ad->order, url);
+            changed = 1;
+            continue;
+        }
+        else {
+            goto leave;
+        }
+    }
+    
+    /* Do we have authz urls for all domains? If not, register a new one */
+    for (i = 0; i < ad->domains->nelts && APR_SUCCESS == rv; ++i) {
+        const char *domain = APR_ARRAY_IDX(ad->domains, i, const char *);
+    
+        if (md_array_str_index(domains_covered, domain, 0, 0) < 0) {
+            md_result_activity_printf(result, "Creating authz resource for %s", domain);
+            rv = md_acme_authz_register(&authz, ad->acme, domain, d->p);
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: created authz for %s (last problem: %s)", 
+                          md->name, domain, ad->acme->last->problem);
+            if (APR_SUCCESS != rv) goto leave;
+            rv = md_acme_order_add(ad->order, authz->url);
+            changed = 1;
+        }
+    }
+    
+    if (changed) {
+        rv = md_acme_order_save(d->store, d->p, MD_SG_STAGING, md->name, ad->order, 0);
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, "%s: saved", md->name);
+    }
+    
+leave:
+    md_acme_report_result(ad->acme, rv, result);
+    return rv;
+}
+
+apr_status_t md_acmev1_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, md_result_t *result)
+{
+    apr_status_t rv = APR_SUCCESS;
+    const char *required;
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: (ACMEv1) need certificate", d->md->name);
+    
+    /* Chose (or create) and ACME account to use */
+    if (APR_SUCCESS != (rv = md_acme_drive_set_acct(d, result))) goto leave;
+    
+    /* 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 */
+    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, 
+                  "%s: (ACMEv1) check Tems-of-Service agreement", d->md->name);
+    
+    rv = md_acme_check_agreement(ad->acme, d->p, ad->md->ca_agreement, &required);
+    if (APR_STATUS_IS_INCOMPLETE(rv) && required) {
+        /* The CA wants the user to agree to Terms-of-Services. Until the user
+         * has reconfigured and restarted the server, this MD cannot be
+         * driven further */
+        ad->md->state = MD_S_MISSING_INFORMATION;
+        md_save(d->store, d->p, MD_SG_STAGING, ad->md, 0);
+        md_result_printf(result, rv, 
+            "the CA requires you to accept the terms-of-service as specified in <%s>. "
+            "Please read the document that you find at that URL and, if you agree to "
+            "the conditions, configure \"MDCertificateAgreement accepted\" "
+            "in your Apache. Then (graceful) restart the server to activate.", 
+            required);
+        goto leave;
+    }
+    else if (APR_SUCCESS != rv) goto leave;
+    
+    if (!md_array_is_empty(ad->certs)) goto leave;
+    
+    rv = ad_setup_order(d, result);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    rv = md_acme_order_start_challenges(ad->order, ad->acme, ad->ca_challenges,
+                                        d->store, d->md, d->env, result, d->p);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    rv = md_acme_order_monitor_authzs(ad->order, ad->acme, d->md,
+                                      ad->authz_monitor_timeout, result, d->p);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    rv = md_acme_drive_setup_certificate(d, result);
+
+leave:    
+    md_result_log(result, MD_LOG_DEBUG);
+    return result->status;
+}
+
diff --git a/modules/md/md_acmev1_drive.h b/modules/md/md_acmev1_drive.h
new file mode 100644 (file)
index 0000000..9dfa729
--- /dev/null
@@ -0,0 +1,27 @@
+/* Copyright 2019 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 md_acmev1_drive_h
+#define md_acmev1_drive_h
+
+struct md_acme_driver_t;
+struct md_proto_driver_t;
+struct md_result_t;
+
+apr_status_t md_acmev1_drive_renew(struct md_acme_driver_t *ad, 
+                                   struct md_proto_driver_t *d, 
+                                   struct md_result_t *result);
+
+#endif /* md_acmev1_drive_h */
diff --git a/modules/md/md_acmev2_drive.c b/modules/md/md_acmev2_drive.c
new file mode 100644 (file)
index 0000000..cd5214d
--- /dev/null
@@ -0,0 +1,165 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_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_result.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"
+#include "md_acme_order.h"
+
+#include "md_acme_drive.h"
+#include "md_acmev2_drive.h"
+
+
+
+/**************************************************************************************************/
+/* order setup */
+
+/**
+ * Either we have an order stored in the STAGING area, or we need to create a 
+ * new one at the ACME server.
+ */
+static apr_status_t ad_setup_order(md_proto_driver_t *d, md_result_t *result)
+{
+    md_acme_driver_t *ad = d->baton;
+    apr_status_t rv;
+    md_t *md = ad->md;
+    
+    assert(ad->md);
+    assert(ad->acme);
+
+    /* 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_order_load(d->store, MD_SG_STAGING, md->name, &ad->order, d->p);
+    if (APR_SUCCESS == rv) {
+        md_result_activity_setn(result, "Loaded order from staging");
+        goto leave;
+    }
+    else if (!APR_STATUS_IS_ENOENT(rv)) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "%s: loading order", md->name);
+        md_acme_order_purge(d->store, d->p, MD_SG_STAGING, md->name, d->env);
+    }
+    
+    md_result_activity_setn(result, "Creating new order");
+    rv = md_acme_order_register(&ad->order, ad->acme, d->p, d->md->name, ad->domains);
+    if (APR_SUCCESS !=rv) goto leave;
+    rv = md_acme_order_save(d->store, d->p, MD_SG_STAGING, d->md->name, ad->order, 0);
+    if (APR_SUCCESS != rv) {
+        md_result_set(result, rv, "saving order in staging");
+    }
+    
+leave:
+    md_acme_report_result(ad->acme, rv, result);
+    return rv;
+}
+
+/**************************************************************************************************/
+/* ACMEv2 renewal */
+
+apr_status_t md_acmev2_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, md_result_t *result)
+{
+    apr_status_t rv = APR_SUCCESS;
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: (ACMEv2) need certificate", d->md->name);
+    
+    /* Chose (or create) and ACME account to use */
+    rv = md_acme_drive_set_acct(d, result);
+    if (APR_SUCCESS != rv) goto leave;
+
+    if (!md_array_is_empty(ad->certs)) goto leave;
+        
+    /* ACMEv2 strategy:
+     * 1. load an md_acme_order_t from STAGING, if present
+     * 2. if no order found, register a new order at ACME server
+     * 3. update the order from the server
+     * 4. Switch order state:
+     *   * PENDING: process authz challenges
+     *   * READY: finalize the order
+     *   * PROCESSING: wait and re-assses later
+     *   * VALID: retrieve certificate
+     *   * COMPLETE: all done, return success
+     *   * INVALID and otherwise: fail renewal, delete local order
+     */
+    if (APR_SUCCESS != (rv = ad_setup_order(d, result))) {
+        goto leave;
+    }
+    
+    rv = md_acme_order_update(ad->order, ad->acme, result, d->p);
+    if (APR_STATUS_IS_ENOENT(rv)) {
+        /* order is no longer known at the ACME server */
+        ad->order = NULL;
+        md_acme_order_purge(d->store, d->p, MD_SG_STAGING, d->md->name, d->env);
+    }
+    else if (APR_SUCCESS != rv) {
+        goto leave;
+    }
+    
+    if (!ad->order) {
+        rv = ad_setup_order(d, result);
+        if (APR_SUCCESS != rv) goto leave;
+    }
+    
+    rv = md_acme_order_start_challenges(ad->order, ad->acme, ad->ca_challenges,
+                                        d->store, d->md, d->env, result, d->p);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    rv = md_acme_order_monitor_authzs(ad->order, ad->acme, d->md,
+                                      ad->authz_monitor_timeout, result, d->p);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    rv = md_acme_order_await_ready(ad->order, ad->acme, d->md, 
+                                   ad->authz_monitor_timeout, result, d->p);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    rv = md_acme_drive_setup_certificate(d, result);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, d->p, "%s: finalized order", d->md->name);
+    
+    rv = md_acme_order_await_valid(ad->order, ad->acme, d->md, 
+                                   ad->authz_monitor_timeout, result, d->p);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    if (ad->order->certificate) goto leave;
+    md_result_set(result, APR_EINVAL, "Order valid, but certifiate url is missing.");
+
+leave:    
+    md_result_log(result, MD_LOG_DEBUG);
+    return result->status;
+}
+
diff --git a/modules/md/md_acmev2_drive.h b/modules/md/md_acmev2_drive.h
new file mode 100644 (file)
index 0000000..7552c4f
--- /dev/null
@@ -0,0 +1,27 @@
+/* Copyright 2019 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 md_acmev2_drive_h
+#define md_acmev2_drive_h
+
+struct md_json_t;
+struct md_proto_driver_t;
+struct md_result_t;
+
+apr_status_t md_acmev2_drive_renew(struct md_acme_driver_t *ad, 
+                                   struct md_proto_driver_t *d,
+                                   struct md_result_t *result);
+
+#endif /* md_acmev2_drive_h */
index 51ad0057c6d4b5d904d55bda028dc79f72cac025..080e5426961e126100db004423fdefe03b173186 100644 (file)
@@ -79,16 +79,34 @@ apr_size_t md_common_name_count(const md_t *md1, const md_t *md2)
     return hits;
 }
 
+int md_is_covered_by_alt_names(const md_t *md, const struct apr_array_header_t* alt_names)
+{
+    const char *name;
+    int i;
+    
+    if (alt_names) {
+        for (i = 0; i < md->domains->nelts; ++i) {
+            name = APR_ARRAY_IDX(md->domains, i, const char *);
+            if (!md_dns_domains_match(alt_names, name)) {
+                return 0;
+            }
+        }
+        return 1;
+    }
+    return 0;
+}
+
 md_t *md_create_empty(apr_pool_t *p)
 {
     md_t *md = apr_pcalloc(p, sizeof(*md));
     if (md) {
         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->renew_mode = MD_RENEW_DEFAULT;
         md->require_https = MD_REQUIRE_UNSET;
         md->must_staple = -1;
         md->transitive = -1;
+        md->acme_tls_1_domains = apr_array_make(p, 5, sizeof(const char *));
         md->defn_name = "unknown";
         md->defn_line_number = 0;
     }
@@ -204,34 +222,6 @@ md_t *md_create(apr_pool_t *p, apr_array_header_t *domains)
     return md;
 }
 
-int md_should_renew(const md_t *md) 
-{
-    apr_time_t now = apr_time_now();
-
-    if (md->expires <= now) {
-        return 1;
-    }
-    else if (md->expires > 0) {
-        double renew_win,  life;
-        apr_interval_time_t left;
-        
-        renew_win = (double)md->renew_window;
-        if (md->renew_norm > 0 
-            && md->renew_norm > renew_win
-            && md->expires > md->valid_from) {
-            /* Calc renewal days as fraction of cert lifetime - if known */
-            life = (double)(md->expires - md->valid_from); 
-            renew_win = life * renew_win / (double)md->renew_norm;
-        }
-        
-        left = md->expires - now;
-        if (left <= renew_win) {
-            return 1;
-        }                
-    }
-    return 0;
-}
-
 /**************************************************************************************************/
 /* lifetime */
 
@@ -247,6 +237,7 @@ md_t *md_copy(apr_pool_t *p, const md_t *src)
         if (src->ca_challenges) {
             md->ca_challenges = apr_array_copy(p, src->ca_challenges);
         }
+        md->acme_tls_1_domains = apr_array_copy(p, src->acme_tls_1_domains);
     }    
     return md;   
 }
@@ -261,49 +252,28 @@ md_t *md_clone(apr_pool_t *p, const md_t *src)
         md->name = apr_pstrdup(p, src->name);
         md->require_https = src->require_https;
         md->must_staple = src->must_staple;
-        md->drive_mode = src->drive_mode;
+        md->renew_mode = src->renew_mode;
         md->domains = md_array_str_compact(p, src->domains, 0);
         md->pkey_spec = src->pkey_spec;
-        md->renew_norm = src->renew_norm;
         md->renew_window = src->renew_window;
+        md->warn_window = src->warn_window;
         md->contacts = md_array_str_clone(p, src->contacts);
         if (src->ca_url) md->ca_url = apr_pstrdup(p, src->ca_url);
         if (src->ca_proto) md->ca_proto = apr_pstrdup(p, src->ca_proto);
         if (src->ca_account) md->ca_account = apr_pstrdup(p, src->ca_account);
         if (src->ca_agreement) md->ca_agreement = apr_pstrdup(p, src->ca_agreement);
         if (src->defn_name) md->defn_name = apr_pstrdup(p, src->defn_name);
-        if (src->cert_url) md->cert_url = apr_pstrdup(p, src->cert_url);
         md->defn_line_number = src->defn_line_number;
         if (src->ca_challenges) {
             md->ca_challenges = md_array_str_clone(p, src->ca_challenges);
         }
+        md->acme_tls_1_domains = md_array_str_compact(p, src->acme_tls_1_domains, 0);
+        if (src->cert_file) md->cert_file = apr_pstrdup(p, src->cert_file);
+        if (src->pkey_file) md->pkey_file = apr_pstrdup(p, src->pkey_file);
     }    
     return md;   
 }
 
-md_t *md_merge(apr_pool_t *p, const md_t *add, const md_t *base)
-{
-    md_t *n = apr_pcalloc(p, sizeof(*n));
-
-    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->require_https = (add->require_https != MD_REQUIRE_UNSET)? add->require_https : base->require_https;
-    n->must_staple = (add->must_staple >= 0)? add->must_staple : base->must_staple;
-    n->drive_mode = (add->drive_mode != MD_DRIVE_DEFAULT)? add->drive_mode : base->drive_mode;
-    n->pkey_spec = add->pkey_spec? add->pkey_spec : base->pkey_spec;
-    n->renew_norm = (add->renew_norm > 0)? add->renew_norm : base->renew_norm;
-    n->renew_window = (add->renew_window > 0)? add->renew_window : base->renew_window;
-    n->transitive = (add->transitive >= 0)? add->transitive : base->transitive;
-    if (add->ca_challenges) {
-        n->ca_challenges = apr_array_copy(p, add->ca_challenges);
-    }
-    else if (base->ca_challenges) {
-        n->ca_challenges = apr_array_copy(p, base->ca_challenges);
-    }
-    return n;
-}
-
 /**************************************************************************************************/
 /* format conversion */
 
@@ -320,32 +290,15 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
         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);
         md_json_sets(md->ca_agreement, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL);
-        if (md->cert_url) {
-            md_json_sets(md->cert_url, json, MD_KEY_CERT, MD_KEY_URL, NULL);
-        }
         if (md->pkey_spec) {
             md_json_setj(md_pkey_spec_to_json(md->pkey_spec, p), json, MD_KEY_PKEY, NULL);
         }
         md_json_setl(md->state, json, MD_KEY_STATE, NULL);
-        md_json_setl(md->drive_mode, json, MD_KEY_DRIVE_MODE, NULL);
-        if (md->expires > 0) {
-            char *ts = apr_pcalloc(p, APR_RFC822_DATE_LEN);
-            apr_rfc822_date(ts, md->expires);
-            md_json_sets(ts, json, MD_KEY_CERT, MD_KEY_EXPIRES, NULL);
-        }
-        if (md->valid_from > 0) {
-            char *ts = apr_pcalloc(p, APR_RFC822_DATE_LEN);
-            apr_rfc822_date(ts, md->valid_from);
-            md_json_sets(ts, json, MD_KEY_CERT, MD_KEY_VALID_FROM, NULL);
-        }
-        if (md->renew_norm > 0) {
-            md_json_sets(apr_psprintf(p, "%ld%%", (long)(md->renew_window * 100L / md->renew_norm)), 
-                                      json, MD_KEY_RENEW_WINDOW, NULL);
-        }
-        else {
-            md_json_setl((long)apr_time_sec(md->renew_window), json, MD_KEY_RENEW_WINDOW, NULL);
-        }
-        md_json_setb(md_should_renew(md), json, MD_KEY_RENEW, NULL);
+        md_json_setl(md->renew_mode, json, MD_KEY_RENEW_MODE, NULL);
+        if (md->renew_window)
+            md_json_sets(md_timeslice_format(md->renew_window, p), json, MD_KEY_RENEW_WINDOW, NULL);
+        if (md->warn_window)
+            md_json_sets(md_timeslice_format(md->warn_window, p), json, MD_KEY_WARN_WINDOW, NULL);
         if (md->ca_challenges && md->ca_challenges->nelts > 0) {
             apr_array_header_t *na;
             na = md_array_str_compact(p, md->ca_challenges, 0);
@@ -362,6 +315,10 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p)
                 break;
         }
         md_json_setb(md->must_staple > 0, json, MD_KEY_MUST_STAPLE, NULL);
+        if (!apr_is_empty_array(md->acme_tls_1_domains))
+            md_json_setsa(md->acme_tls_1_domains, json, MD_KEY_PROTO, MD_KEY_ACME_TLS_1, NULL);
+        md_json_sets(md->cert_file, json, MD_KEY_CERT_FILE, NULL);
+        md_json_sets(md->pkey_file, json, MD_KEY_PKEY_FILE, NULL);
         return json;
     }
     return NULL;
@@ -379,34 +336,18 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p)
         md->ca_proto = md_json_dups(p, json, MD_KEY_CA, MD_KEY_PROTO, NULL);
         md->ca_url = md_json_dups(p, json, MD_KEY_CA, MD_KEY_URL, NULL);
         md->ca_agreement = md_json_dups(p, json, MD_KEY_CA, MD_KEY_AGREEMENT, NULL);
-        md->cert_url = md_json_dups(p, json, MD_KEY_CERT, MD_KEY_URL, NULL);
         if (md_json_has_key(json, MD_KEY_PKEY, MD_KEY_TYPE, NULL)) {
             md->pkey_spec = md_pkey_spec_from_json(md_json_getj(json, MD_KEY_PKEY, NULL), p);
         }
         md->state = (md_state_t)md_json_getl(json, MD_KEY_STATE, NULL);
-        md->drive_mode = (int)md_json_getl(json, MD_KEY_DRIVE_MODE, NULL);
+        if (MD_S_EXPIRED_DEPRECATED == md->state) md->state = MD_S_COMPLETE;
+        md->renew_mode = (int)md_json_getl(json, MD_KEY_RENEW_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);
-        }
-        s = md_json_dups(p, json, MD_KEY_CERT, MD_KEY_VALID_FROM, NULL);
-        if (s && *s) {
-            md->valid_from = apr_date_parse_rfc(s);
-        }
-        md->renew_norm = 0;
-        md->renew_window = apr_time_from_sec(md_json_getl(json, MD_KEY_RENEW_WINDOW, NULL));
-        if (md->renew_window <= 0) {
-            s = md_json_gets(json, MD_KEY_RENEW_WINDOW, NULL);
-            if (s && strchr(s, '%')) {
-                int percent = atoi(s);
-                if (0 < percent && percent < 100) {
-                    md->renew_norm = apr_time_from_sec(100 * MD_SECS_PER_DAY);
-                    md->renew_window = apr_time_from_sec(percent * MD_SECS_PER_DAY);
-                }
-            }
-        }
+        s = md_json_gets(json, MD_KEY_RENEW_WINDOW, NULL);
+        md_timeslice_parse(&md->renew_window, p, s, MD_TIME_LIFE_NORM);
+        s = md_json_gets(json, MD_KEY_WARN_WINDOW, NULL);
+        md_timeslice_parse(&md->warn_window, p, s, MD_TIME_LIFE_NORM);
         if (md_json_has_key(json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL)) {
             md->ca_challenges = apr_array_make(p, 5, sizeof(const char*));
             md_json_dupsa(md->ca_challenges, p, json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL);
@@ -420,6 +361,10 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p)
             md->require_https = MD_REQUIRE_PERMANENT;
         }
         md->must_staple = (int)md_json_getb(json, MD_KEY_MUST_STAPLE, NULL);
+        md_json_dupsa(md->acme_tls_1_domains, p, json, MD_KEY_PROTO, MD_KEY_ACME_TLS_1, NULL);
+            
+        md->cert_file = md_json_dups(p, json, MD_KEY_CERT_FILE, NULL); 
+        md->pkey_file = md_json_dups(p, json, MD_KEY_PKEY_FILE, NULL); 
         
         return md;
     }
index e0aac3ec99e0f1bc4c57f5a0c165143520d24097..acb62d345683a58c52f0adae518c2c908a91bf6e 100644 (file)
 #define MD_USE_OPENSSL_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L)
 #endif
 
+#if defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x10100000L) 
+/* Missing from LibreSSL and only available since OpenSSL v1.1.x */
+#ifndef OPENSSL_NO_CT
+#define OPENSSL_NO_CT
+#endif
+#endif
+
+#ifndef OPENSSL_NO_CT
+#include <openssl/ct.h>
+#endif
+
 static int initialized;
 
 struct md_pkey_t {
@@ -142,17 +153,13 @@ apr_status_t md_crypt_init(apr_pool_t *pool)
     return APR_SUCCESS;
 }
 
-typedef struct {
-    char *data;
-    apr_size_t len;
-} buffer_rec;
-
 static apr_status_t fwrite_buffer(void *baton, apr_file_t *f, apr_pool_t *p) 
 {
-    buffer_rec *buf = baton;
+    md_data *buf = baton;
+    apr_size_t wlen;
     
     (void)p;
-    return apr_file_write_full(f, buf->data, buf->len, &buf->len);
+    return apr_file_write_full(f, buf->data, buf->len, &wlen);
 }
 
 apr_status_t md_rand_bytes(unsigned char *buf, apr_size_t len, apr_pool_t *p)
@@ -377,7 +384,7 @@ apr_status_t md_pkey_fload(md_pkey_t **ppkey, apr_pool_t *p,
     return rv;
 }
 
-static apr_status_t pkey_to_buffer(buffer_rec *buffer, md_pkey_t *pkey, apr_pool_t *p,
+static apr_status_t pkey_to_buffer(md_data *buf, md_pkey_t *pkey, apr_pool_t *p,
                                    const char *pass, apr_size_t pass_len)
 {
     BIO *bio = BIO_new(BIO_s_mem());
@@ -416,10 +423,9 @@ static apr_status_t pkey_to_buffer(buffer_rec *buffer, md_pkey_t *pkey, apr_pool
 
     i = BIO_pending(bio);
     if (i > 0) {
-        buffer->data = apr_palloc(p, (apr_size_t)i + 1);
-        i = BIO_read(bio, buffer->data, i);
-        buffer->data[i] = '\0';
-        buffer->len = (apr_size_t)i;
+        buf->data = apr_palloc(p, (apr_size_t)i);
+        i = BIO_read(bio, (char*)buf->data, i);
+        buf->len = (apr_size_t)i;
     }
     BIO_free(bio);
     return APR_SUCCESS;
@@ -429,7 +435,7 @@ apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p,
                            const char *pass_phrase, apr_size_t pass_len,
                            const char *fname, apr_fileperms_t perms)
 {
-    buffer_rec buffer;
+    md_data buffer;
     apr_status_t rv;
     
     if (APR_SUCCESS == (rv = pkey_to_buffer(&buffer, pkey, p, pass_phrase, pass_len))) {
@@ -575,56 +581,47 @@ apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *
     return rv;
 }
 
-static apr_status_t sha256_digest(unsigned char **pdigest, size_t *pdigest_len,
-                                  apr_pool_t *p, const char *d, size_t dlen)
+static apr_status_t sha256_digest(md_data **pdigest, apr_pool_t *p, const md_data *buf)
 {
     EVP_MD_CTX *ctx = NULL;
-    unsigned char *buffer;
+    md_data *digest;
     apr_status_t rv = APR_ENOMEM;
-    unsigned int blen;
+    unsigned int dlen;
+
+    digest = apr_palloc(p, sizeof(*digest));
+    if (!digest) goto leave;
+    digest->data = apr_pcalloc(p, EVP_MAX_MD_SIZE);
+    if (!digest->data) goto leave;
     
-    buffer = apr_pcalloc(p, EVP_MAX_MD_SIZE);
-    if (buffer) {
-        ctx = EVP_MD_CTX_create();
-        if (ctx) {
-            rv = APR_ENOTIMPL;
-            if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) {
-                rv = APR_EGENERAL;
-                if (EVP_DigestUpdate(ctx, d, dlen)) {
-                    if (EVP_DigestFinal(ctx, buffer, &blen)) {
-                        rv = APR_SUCCESS;
-                    }
+    ctx = EVP_MD_CTX_create();
+    if (ctx) {
+        rv = APR_ENOTIMPL;
+        if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) {
+            rv = APR_EGENERAL;
+            if (EVP_DigestUpdate(ctx, (unsigned char*)buf->data, buf->len)) {
+                if (EVP_DigestFinal(ctx, (unsigned char*)digest->data, &dlen)) {
+                    digest->len = dlen;
+                    rv = APR_SUCCESS;
                 }
             }
         }
-        
-        if (ctx) {
-            EVP_MD_CTX_destroy(ctx);
-        }
     }
-    
-    if (APR_SUCCESS == rv) {
-        *pdigest = buffer;
-        *pdigest_len = blen;
-    }
-    else {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "digest"); 
-        *pdigest = NULL;
-        *pdigest_len = 0;
+leave:
+    if (ctx) {
+        EVP_MD_CTX_destroy(ctx);
     }
+    *pdigest = (APR_SUCCESS == rv)? digest : NULL;
     return rv;
 }
 
-apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p, 
-                                      const char *d, size_t dlen)
+apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p, const md_data *d)
 {
     const char *digest64 = NULL;
-    unsigned char *buffer;
-    size_t blen;
+    md_data *digest;
     apr_status_t rv;
     
-    if (APR_SUCCESS == (rv = sha256_digest(&buffer, &blen, p, d, dlen))) {
-        if (NULL == (digest64 = md_util_base64url_encode((const char*)buffer, blen, p))) {
+    if (APR_SUCCESS == (rv = sha256_digest(&digest, p, d))) {
+        if (NULL == (digest64 = md_util_base64url_encode(digest->data, digest->len, p))) {
             rv = APR_EGENERAL;
         }
     }
@@ -632,47 +629,16 @@ apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p,
     return rv;
 }
 
-static const char * const hex_const[] = {
-    "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", 
-    "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", 
-    "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", 
-    "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", 
-    "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", 
-    "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", 
-    "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", 
-    "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", 
-    "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", 
-    "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", 
-    "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", 
-    "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", 
-    "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", 
-    "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df", 
-    "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", 
-    "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff", 
-};
-
 apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p, 
-                                        const char *d, size_t dlen)
+                                        const md_data *data)
 {
-    char *dhex = NULL, *cp;
-    const char * x;
-    unsigned char *buffer;
-    size_t blen;
+    md_data *digest;
     apr_status_t rv;
-    unsigned int i;
     
-    if (APR_SUCCESS == (rv = sha256_digest(&buffer, &blen, p, d, dlen))) {
-        cp = dhex = apr_pcalloc(p,  2 * blen + 1);
-        if (!dhex) {
-            rv = APR_EGENERAL;
-        }
-        for (i = 0; i < blen; ++i, cp += 2) {
-            x = hex_const[buffer[i]];
-            cp[0] = x[0];
-            cp[1] = x[1];
-        }
+    if (APR_SUCCESS == (rv = sha256_digest(&digest, p, data))) {
+        return md_data_to_hex(pdigesthex, 0, p, digest);
     }
-    *pdigesthex = dhex;
+    *pdigesthex = NULL;
     return rv;
 }
 
@@ -710,11 +676,28 @@ void md_cert_free(md_cert_t *cert)
     cert_cleanup(cert);
 }
 
-void *md_cert_get_X509(struct md_cert_t *cert)
+void *md_cert_get_X509(const md_cert_t *cert)
 {
     return cert->x509;
 }
 
+const char *md_cert_get_serial_number(const md_cert_t *cert, apr_pool_t *p)
+{
+    const char *s = "";
+    const ASN1_INTEGER *ai = X509_get_serialNumber(cert->x509);
+    if (ai) {
+        BIGNUM *bn; 
+        const char *hex;
+        
+        bn = ASN1_INTEGER_to_BN(ai, NULL);
+        hex = BN_bn2hex(bn);
+        s = apr_pstrdup(p, hex);
+        OPENSSL_free((void*)bn);
+        OPENSSL_free((void*)hex);
+    }
+    return s;
+}
+
 int md_cert_is_valid_now(const md_cert_t *cert)
 {
     return ((X509_cmp_current_time(X509_get_notBefore(cert->x509)) < 0)
@@ -726,23 +709,23 @@ int md_cert_has_expired(const md_cert_t *cert)
     return (X509_cmp_current_time(X509_get_notAfter(cert->x509)) <= 0);
 }
 
-apr_time_t md_cert_get_not_after(md_cert_t *cert)
+apr_time_t md_cert_get_not_after(const md_cert_t *cert)
 {
     return md_asn1_time_get(X509_get_notAfter(cert->x509));
 }
 
-apr_time_t md_cert_get_not_before(md_cert_t *cert)
+apr_time_t md_cert_get_not_before(const md_cert_t *cert)
 {
     return md_asn1_time_get(X509_get_notBefore(cert->x509));
 }
 
 int md_cert_covers_domain(md_cert_t *cert, const char *domain_name)
 {
-    if (!cert->alt_names) {
-        md_cert_get_alt_names(&cert->alt_names, cert, cert->pool);
-    }
-    if (cert->alt_names) {
-        return md_array_str_index(cert->alt_names, domain_name, 0, 0) >= 0;
+    apr_array_header_t *alt_names;
+
+    md_cert_get_alt_names(&alt_names, cert, cert->pool);
+    if (alt_names) {
+        return md_array_str_index(alt_names, domain_name, 0, 0) >= 0;
     }
     return 0;
 }
@@ -760,7 +743,7 @@ int md_cert_covers_md(md_cert_t *cert, const md_t *md)
                       cert->alt_names->nelts); 
         for (i = 0; i < md->domains->nelts; ++i) {
             name = APR_ARRAY_IDX(md->domains, i, const char *);
-            if (md_array_str_index(cert->alt_names, name, 0, 0) < 0) {
+            if (!md_dns_domains_match(cert->alt_names, name)) {
                 md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, cert->pool, 
                               "md domain %s not covered by cert", name);
                 return 0;
@@ -774,7 +757,7 @@ int md_cert_covers_md(md_cert_t *cert, const md_t *md)
     return 0;
 }
 
-apr_status_t md_cert_get_issuers_uri(const char **puri, md_cert_t *cert, apr_pool_t *p)
+apr_status_t md_cert_get_issuers_uri(const char **puri, const md_cert_t *cert, apr_pool_t *p)
 {
     apr_status_t rv = APR_ENOENT;
     STACK_OF(ACCESS_DESCRIPTION) *xinfos;
@@ -801,7 +784,7 @@ apr_status_t md_cert_get_issuers_uri(const char **puri, md_cert_t *cert, apr_poo
     return rv;
 }
 
-apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, md_cert_t *cert, apr_pool_t *p)
+apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t *cert, apr_pool_t *p)
 {
     apr_array_header_t *names;
     apr_status_t rv = APR_ENOENT;
@@ -859,7 +842,7 @@ apr_status_t md_cert_fload(md_cert_t **pcert, apr_pool_t *p, const char *fname)
     return rv;
 }
 
-static apr_status_t cert_to_buffer(buffer_rec *buffer, md_cert_t *cert, apr_pool_t *p)
+static apr_status_t cert_to_buffer(md_data *buffer, const md_cert_t *cert, apr_pool_t *p)
 {
     BIO *bio = BIO_new(BIO_s_mem());
     int i;
@@ -877,9 +860,8 @@ static apr_status_t cert_to_buffer(buffer_rec *buffer, md_cert_t *cert, apr_pool
 
     i = BIO_pending(bio);
     if (i > 0) {
-        buffer->data = apr_palloc(p, (apr_size_t)i + 1);
-        i = BIO_read(bio, buffer->data, i);
-        buffer->data[i] = '\0';
+        buffer->data = apr_palloc(p, (apr_size_t)i);
+        i = BIO_read(bio, (char*)buffer->data, i);
         buffer->len = (apr_size_t)i;
     }
     BIO_free(bio);
@@ -889,7 +871,7 @@ static apr_status_t cert_to_buffer(buffer_rec *buffer, md_cert_t *cert, apr_pool
 apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p, 
                            const char *fname, apr_fileperms_t perms)
 {
-    buffer_rec buffer;
+    md_data buffer;
     apr_status_t rv;
     
     if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) {
@@ -898,9 +880,9 @@ apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p,
     return rv;
 }
 
-apr_status_t md_cert_to_base64url(const char **ps64, md_cert_t *cert, apr_pool_t *p)
+apr_status_t md_cert_to_base64url(const char **ps64, const md_cert_t *cert, apr_pool_t *p)
 {
-    buffer_rec buffer;
+    md_data buffer;
     apr_status_t rv;
     
     if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) {
@@ -911,42 +893,159 @@ apr_status_t md_cert_to_base64url(const char **ps64, md_cert_t *cert, apr_pool_t
     return rv;
 }
 
+apr_status_t md_cert_to_sha256_digest(md_data **pdigest, const md_cert_t *cert, apr_pool_t *p)
+{
+    md_data *digest;
+    unsigned int dlen;
+    apr_status_t rv = APR_ENOMEM;
+    
+    digest = apr_palloc(p, sizeof(*digest));
+    if (!digest) goto leave;
+    digest->data = apr_pcalloc(p, EVP_MAX_MD_SIZE);
+    if (!digest->data) goto leave;
+
+    X509_digest(cert->x509, EVP_sha256(), (unsigned char*)digest->data, &dlen);
+    digest->len = dlen;
+    rv = APR_SUCCESS;
+leave:
+    *pdigest = (APR_SUCCESS == rv)? digest : NULL;
+    return rv;
+}
+
+apr_status_t md_cert_to_sha256_fingerprint(const char **pfinger, const md_cert_t *cert, apr_pool_t *p)
+{
+    md_data *digest;
+    apr_status_t rv;
+    
+    rv = md_cert_to_sha256_digest(&digest, cert, p);
+    if (APR_SUCCESS == rv) {
+        return md_data_to_hex(pfinger, 0, p, digest);
+    }
+    *pfinger = NULL;
+    return rv;
+}
+
+static int md_cert_read_pem(BIO *bf, apr_pool_t *p, md_cert_t **pcert)
+{
+    md_cert_t *cert;
+    X509 *x509;
+    apr_status_t rv;
+    
+    ERR_clear_error();
+    x509 = PEM_read_bio_X509(bf, NULL, NULL, NULL);
+    if (x509 == NULL) {
+        rv = APR_ENOENT;
+        goto out;
+    }
+    cert = make_cert(p, x509);
+    rv = APR_SUCCESS;
+    
+out:
+    *pcert = (APR_SUCCESS == rv)? cert : NULL;
+    return rv;
+}
+
 apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p, 
                                const md_http_response_t *res)
 {
     const char *ct;
     apr_off_t data_len;
+    char *der;
     apr_size_t der_len;
+    md_cert_t *cert = NULL;
     apr_status_t rv;
     
     ct = apr_table_get(res->headers, "Content-Type");
-    if (!res->body || !ct  || strcmp("application/pkix-cert", ct)) {
-        return APR_ENOENT;
+    if (!res->body || !ct || strcmp("application/pkix-cert", ct)) {
+        rv = APR_ENOENT;
+        goto out;
     }
     
     if (APR_SUCCESS == (rv = apr_brigade_length(res->body, 1, &data_len))) {
-        char *der;
         if (data_len > 1024*1024) { /* certs usually are <2k each */
             return APR_EINVAL;
         }
-        if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &der, &der_len, p))) {
+        if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &der, &der_len, res->req->pool))) {
             const unsigned char *bf = (const unsigned char*)der;
             X509 *x509;
             
             if (NULL == (x509 = d2i_X509(NULL, &bf, (long)der_len))) {
                 rv = APR_EINVAL;
+                goto out;
             }
             else {
-                *pcert = make_cert(p, x509);
+                cert = make_cert(p, x509);
+                rv = APR_SUCCESS;
+                md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "cert parsed");
+            }
+        }
+    }
+out:
+    *pcert = (APR_SUCCESS == rv)? cert : NULL;
+    return rv;
+}
+
+apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain,
+                                     apr_pool_t *p, const struct md_http_response_t *res)
+{
+    const char *ct;
+    apr_off_t blen;
+    apr_size_t data_len;
+    char *data;
+    BIO *bf = NULL;
+    apr_status_t rv;
+    
+    if (APR_SUCCESS != (rv = apr_brigade_length(res->body, 1, &blen))) goto out;
+    if (blen > 1024*1024) { /* certs usually are <2k each */
+        rv = APR_EINVAL;
+        goto out;
+    }
+    
+    data_len = (apr_size_t)blen;
+    ct = apr_table_get(res->headers, "Content-Type");
+    if (!res->body || !ct) {
+        rv = APR_ENOENT;
+        goto out;
+    }
+    else if (!strcmp("application/pem-certificate-chain", ct)) {
+        if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool))) {
+            int added = 0;
+            md_cert_t *cert;
+            
+            if (NULL == (bf = BIO_new_mem_buf(data, (int)data_len))) {
+                rv = APR_ENOMEM;
+                goto out;
+            }
+            
+            while (APR_SUCCESS == (rv = md_cert_read_pem(bf, p, &cert))) {
+                APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
+                added = 1;
+            }
+            if (APR_ENOENT == rv && added) {
                 rv = APR_SUCCESS;
             }
         }
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "cert parsed");
     }
+    else if (!strcmp("application/pkix-cert", ct)) {
+        md_cert_t *cert;
+        
+        rv = md_cert_read_http(&cert, p, res);
+        if (APR_SUCCESS == rv) {
+            APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
+        }
+    }
+    else {
+        /* unrecongized content type */
+        rv = APR_ENOENT;
+        goto out;
+    }
+out:
+    if (bf) BIO_free(bf);
     return rv;
 }
 
-md_cert_state_t md_cert_state_get(md_cert_t *cert)
+md_cert_state_t md_cert_state_get(const md_cert_t *cert)
 {
     if (cert->x509) {
         return md_cert_is_valid_now(cert)? MD_CERT_VALID : MD_CERT_EXPIRED;
@@ -1064,18 +1163,22 @@ static apr_status_t add_ext(X509 *x, int nid, const char *value, apr_pool_t *p)
     X509V3_CTX ctx;
     apr_status_t rv;
 
+    ERR_clear_error();
     X509V3_set_ctx_nodb(&ctx);
     X509V3_set_ctx(&ctx, x, x, NULL, NULL, 0);
     if (NULL == (ext = X509V3_EXT_conf_nid(NULL, &ctx, nid, (char*)value))) {
+        unsigned long err =  ERR_get_error();
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "add_ext, create, nid=%d value='%s' "
+                      "(lib=%d, reason=%d)", nid, value, ERR_GET_LIB(err), ERR_GET_REASON(err)); 
         return APR_EGENERAL;
     }
     
     ERR_clear_error();
     rv = X509_add_ext(x, ext, -1)? APR_SUCCESS : APR_EINVAL;
     if (APR_SUCCESS != rv) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "add_ext nid=%dd value='%s'", 
-                      nid, value); 
-        
+        unsigned long err =  ERR_get_error();
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "add_ext, add, nid=%d value='%s' "
+                      "(lib=%d, reason=%d)", nid, value, ERR_GET_LIB(err), ERR_GET_REASON(err)); 
     }
     X509_EXTENSION_free(ext);
     return rv;
@@ -1113,38 +1216,36 @@ static int get_must_staple_nid(void)
     return nid;
 }
 
-int md_cert_must_staple(md_cert_t *cert)
+int md_cert_must_staple(const md_cert_t *cert)
 {
     /* In case we do not get the NID for it, we treat this as not set. */
     int nid = get_must_staple_nid();
     return ((NID_undef != nid)) && X509_get_ext_by_NID(cert->x509, nid, -1) >= 0;
 }
 
-static apr_status_t add_must_staple(STACK_OF(X509_EXTENSION) *exts, const md_t *md, apr_pool_t *p)
+static apr_status_t add_must_staple(STACK_OF(X509_EXTENSION) *exts, const char *name, apr_pool_t *p)
 {
+    X509_EXTENSION *x;
+    int nid;
     
-    if (md->must_staple) {
-        X509_EXTENSION *x;
-        int nid;
-        
-        nid = get_must_staple_nid();
-        if (NID_undef == nid) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, 
-                          "%s: unable to get NID for v3 must-staple TLS feature", md->name);
-            return APR_ENOTIMPL;
-        }
-        x = X509V3_EXT_conf_nid(NULL, NULL, nid, (char*)"DER:30:03:02:01:05");
-        if (NULL == x) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, 
-                          "%s: unable to create x509 extension for must-staple", md->name);
-            return APR_EGENERAL;
-        }
-        sk_X509_EXTENSION_push(exts, x);
+    nid = get_must_staple_nid();
+    if (NID_undef == nid) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, 
+                      "%s: unable to get NID for v3 must-staple TLS feature", name);
+        return APR_ENOTIMPL;
+    }
+    x = X509V3_EXT_conf_nid(NULL, NULL, nid, (char*)"DER:30:03:02:01:05");
+    if (NULL == x) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, 
+                      "%s: unable to create x509 extension for must-staple", name);
+        return APR_EGENERAL;
     }
+    sk_X509_EXTENSION_push(exts, x);
     return APR_SUCCESS;
 }
 
-apr_status_t md_cert_req_create(const char **pcsr_der_64, const md_t *md, 
+apr_status_t md_cert_req_create(const char **pcsr_der_64, const char *name,
+                                apr_array_header_t *domains, int must_staple, 
                                 md_pkey_t *pkey, apr_pool_t *p)
 {
     const char *s, *csr_der, *csr_der_64 = NULL;
@@ -1155,58 +1256,58 @@ apr_status_t md_cert_req_create(const char **pcsr_der_64, const md_t *md,
     apr_status_t rv;
     int csr_der_len;
     
-    assert(md->domains->nelts > 0);
+    assert(domains->nelts > 0);
     
     if (NULL == (csr = X509_REQ_new()) 
         || NULL == (exts = sk_X509_EXTENSION_new_null())
         || NULL == (n = X509_NAME_new())) {
         rv = APR_ENOMEM;
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: openssl alloc X509 things", md->name);
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: openssl alloc X509 things", name);
         goto out; 
     }
 
     /* subject name == first domain */
-    domain = APR_ARRAY_IDX(md->domains, 0, const unsigned char *);
+    domain = APR_ARRAY_IDX(domains, 0, const unsigned char *);
     if (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, domain, -1, -1, 0)
         || !X509_REQ_set_subject_name(csr, n)) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: REQ name add entry", md->name);
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: REQ name add entry", name);
         rv = APR_EGENERAL; goto out;
     }
     /* collect extensions, such as alt names and must staple */
-    if (APR_SUCCESS != (rv = sk_add_alt_names(exts, md->domains, p))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: collecting alt names", md->name);
+    if (APR_SUCCESS != (rv = sk_add_alt_names(exts, domains, p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: collecting alt names", name);
         rv = APR_EGENERAL; goto out;
     }
-    if (APR_SUCCESS != (rv = add_must_staple(exts, md, p))) {
+    if (must_staple && APR_SUCCESS != (rv = add_must_staple(exts, name, p))) {
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: you requested that a certificate "
             "is created with the 'must-staple' extension, however the SSL library was "
             "unable to initialized that extension. Please file a bug report on which platform "
             "and with which library this happens. To continue before this problem is resolved, "
-            "configure 'MDMustStaple off' for your domains", md->name);
+            "configure 'MDMustStaple off' for your domains", name);
         rv = APR_EGENERAL; goto out;
     }
     /* add extensions to csr */
     if (sk_X509_EXTENSION_num(exts) > 0 && !X509_REQ_add_extensions(csr, exts)) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: adding exts", md->name);
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: adding exts", name);
         rv = APR_EGENERAL; goto out;
     }
     /* add our key */
     if (!X509_REQ_set_pubkey(csr, pkey->pkey)) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pkey in csr", md->name);
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pkey in csr", name);
         rv = APR_EGENERAL; goto out;
     }
     /* sign, der encode and base64url encode */
     if (!X509_REQ_sign(csr, pkey->pkey, EVP_sha256())) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign csr", md->name);
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign csr", name);
         rv = APR_EGENERAL; goto out;
     }
     if ((csr_der_len = i2d_X509_REQ(csr, NULL)) < 0) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: der length", md->name);
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: der length", name);
         rv = APR_EGENERAL; goto out;
     }
     s = csr_der = apr_pcalloc(p, (apr_size_t)csr_der_len + 1);
     if (i2d_X509_REQ(csr, (unsigned char**)&s) != csr_der_len) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: csr der enc", md->name);
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: csr der enc", name);
         rv = APR_EGENERAL; goto out;
     }
     csr_der_64 = md_util_base64url_encode(csr_der, (apr_size_t)csr_der_len, p);
@@ -1226,20 +1327,16 @@ out:
     return rv;
 }
 
-apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn, 
-                               apr_array_header_t *domains, md_pkey_t *pkey,
-                               apr_interval_time_t valid_for, apr_pool_t *p)
+static apr_status_t mk_x509(X509 **px, md_pkey_t *pkey, const char *cn,
+                            apr_interval_time_t valid_for, apr_pool_t *p)
 {
-    X509 *x;
+    X509 *x = NULL;
     X509_NAME *n = NULL;
-    md_cert_t *cert = NULL;
-    apr_status_t rv;
-    int days;
     BIGNUM *big_rnd = NULL;
     ASN1_INTEGER *asn1_rnd = NULL;
     unsigned char rnd[20];
-    
-    assert(domains);
+    int days;
+    apr_status_t rv;
     
     if (NULL == (x = X509_new()) 
         || NULL == (n = X509_NAME_new())) {
@@ -1247,24 +1344,22 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: openssl alloc X509 things", cn);
         goto out; 
     }
-    
+
     if (APR_SUCCESS != (rv = md_rand_bytes(rnd, sizeof(rnd), p))
         || !(big_rnd = BN_bin2bn(rnd, sizeof(rnd), NULL))
         || !(asn1_rnd = BN_to_ASN1_INTEGER(big_rnd, NULL))) {
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: setup random serial", cn);
         rv = APR_EGENERAL; goto out;
     }
-     
-    if (1 != X509_set_version(x, 2L)) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: setting x.509v3", cn);
-        rv = APR_EGENERAL; goto out;
-    }
-
     if (!X509_set_serialNumber(x, asn1_rnd)) {
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: set serial number", cn);
         rv = APR_EGENERAL; goto out;
     }
-    /* set common name and issue */
+    if (1 != X509_set_version(x, 2L)) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: setting x.509v3", cn);
+        rv = APR_EGENERAL; goto out;
+    }
+    /* set common name and issuer */
     if (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, (const unsigned char*)cn, -1, -1, 0)
         || !X509_set_subject_name(x, n)
         || !X509_set_issuer_name(x, n)) {
@@ -1276,17 +1371,12 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set basic constraints ext", cn);
         goto out;
     }
-    /* add the domain as alt name */
-    if (APR_SUCCESS != (rv = add_ext(x, NID_subject_alt_name, alt_names(domains, p), p))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set alt_name ext", cn);
-        goto out;
-    }
     /* add our key */
     if (!X509_set_pubkey(x, pkey->pkey)) {
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pkey in x509", cn);
         rv = APR_EGENERAL; goto out;
     }
-    
+    /* validity */
     days = (int)((apr_time_sec(valid_for) + MD_SECS_PER_DAY - 1)/ MD_SECS_PER_DAY);
     if (!X509_set_notBefore(x, ASN1_TIME_set(NULL, time(NULL)))) {
         rv = APR_EGENERAL; goto out;
@@ -1295,6 +1385,33 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
         rv = APR_EGENERAL; goto out;
     }
 
+out:
+    *px = (APR_SUCCESS == rv)? x : NULL;
+    if (APR_SUCCESS != rv && x) X509_free(x);
+    if (big_rnd) BN_free(big_rnd);
+    if (asn1_rnd) ASN1_INTEGER_free(asn1_rnd);
+    if (n) X509_NAME_free(n);
+    return rv;
+}
+
+apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn, 
+                               apr_array_header_t *domains, md_pkey_t *pkey,
+                               apr_interval_time_t valid_for, apr_pool_t *p)
+{
+    X509 *x;
+    md_cert_t *cert = NULL;
+    apr_status_t rv;
+    
+    assert(domains);
+
+    if (APR_SUCCESS != (rv = mk_x509(&x, pkey, cn, valid_for, p))) goto out;
+    
+    /* add the domain as alt name */
+    if (APR_SUCCESS != (rv = add_ext(x, NID_subject_alt_name, alt_names(domains, p), p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set alt_name ext", cn);
+        goto out;
+    }
+
     /* sign with same key */
     if (!X509_sign(x, pkey->pkey, EVP_sha256())) {
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", cn);
@@ -1305,19 +1422,127 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
     rv = APR_SUCCESS;
     
 out:
-    if (!cert && x) {
-        X509_free(x);
+    *pcert = (APR_SUCCESS == rv)? cert : NULL;
+    if (!cert && x) X509_free(x);
+    return rv;
+}
+
+#define MD_OID_ACME_VALIDATION_NUM          "1.3.6.1.5.5.7.1.31"
+#define MD_OID_ACME_VALIDATION_SNAME        "pe-acmeIdentifier"
+#define MD_OID_ACME_VALIDATION_LNAME        "ACME Identifier" 
+
+static int get_acme_validation_nid(void)
+{
+    int nid = OBJ_txt2nid(MD_OID_ACME_VALIDATION_NUM);
+    if (NID_undef == nid) {
+        nid = OBJ_create(MD_OID_ACME_VALIDATION_NUM, 
+                         MD_OID_ACME_VALIDATION_SNAME, MD_OID_ACME_VALIDATION_LNAME);
     }
-    if (n) {
-        X509_NAME_free(n);
+    return nid;
+}
+
+apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain, 
+                                      const char *acme_id, md_pkey_t *pkey, 
+                                      apr_interval_time_t valid_for, apr_pool_t *p)
+{
+    X509 *x;
+    md_cert_t *cert = NULL;
+    const char *alts;
+    apr_status_t rv;
+
+    if (APR_SUCCESS != (rv = mk_x509(&x, pkey, domain, valid_for, p))) goto out;
+    
+    /* add the domain as alt name */
+    alts = apr_psprintf(p, "DNS:%s", domain);
+    if (APR_SUCCESS != (rv = add_ext(x, NID_subject_alt_name, alts, p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set alt_name ext", domain);
+        goto out;
     }
-    if (big_rnd) {
-        BN_free(big_rnd);
+
+    if (APR_SUCCESS != (rv = add_ext(x, get_acme_validation_nid(), acme_id, p))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pe-acmeIdentifier", domain);
+        goto out;
     }
-    if (asn1_rnd) {
-        ASN1_INTEGER_free(asn1_rnd);
+
+    /* sign with same key */
+    if (!X509_sign(x, pkey->pkey, EVP_sha256())) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", domain);
+        rv = APR_EGENERAL; goto out;
+    }
+
+    cert = make_cert(p, x);
+    rv = APR_SUCCESS;
+    
+out:
+    if (!cert && x) {
+        X509_free(x);
     }
     *pcert = (APR_SUCCESS == rv)? cert : NULL;
     return rv;
 }
 
+#define MD_OID_CT_SCTS_NUM          "1.3.6.1.4.1.11129.2.4.2"
+#define MD_OID_CT_SCTS_SNAME        "CT-SCTs"
+#define MD_OID_CT_SCTS_LNAME        "CT Certificate SCTs" 
+
+static int get_ct_scts_nid(void)
+{
+    int nid = OBJ_txt2nid(MD_OID_CT_SCTS_NUM);
+    if (NID_undef == nid) {
+        nid = OBJ_create(MD_OID_CT_SCTS_NUM, 
+                         MD_OID_CT_SCTS_SNAME, MD_OID_CT_SCTS_LNAME);
+    }
+    return nid;
+}
+
+const char *md_nid_get_sname(int nid)
+{
+    return OBJ_nid2sn(nid);
+}
+
+const char *md_nid_get_lname(int nid)
+{
+    return OBJ_nid2ln(nid);
+}
+
+apr_status_t md_cert_get_ct_scts(apr_array_header_t *scts, apr_pool_t *p, const md_cert_t *cert)
+{
+#ifndef OPENSSL_NO_CT
+    int nid, i, idx, critical;
+    STACK_OF(SCT) *sct_list;
+    SCT *sct_handle;
+    md_sct *sct;
+    size_t len;
+    const char *data;
+    
+    nid = get_ct_scts_nid();
+    if (NID_undef == nid) return APR_ENOTIMPL;
+
+    idx = -1;
+    while (1) {
+        sct_list = X509_get_ext_d2i(cert->x509, nid, &critical, &idx);
+        if (sct_list) {
+            for (i = 0; i < sk_SCT_num(sct_list); i++) {
+               sct_handle = sk_SCT_value(sct_list, i);
+                if (sct_handle) {
+                    sct = apr_pcalloc(p, sizeof(*sct));
+                    sct->version = SCT_get_version(sct_handle);
+                    sct->timestamp = apr_time_from_msec(SCT_get_timestamp(sct_handle));
+                    len = SCT_get0_log_id(sct_handle, (unsigned char**)&data);
+                    sct->logid = md_data_create(p, data, len);
+                    sct->signature_type_nid = SCT_get_signature_nid(sct_handle);
+                    len = SCT_get0_signature(sct_handle,  (unsigned char**)&data);
+                    sct->signature = md_data_create(p, data, len);
+                    
+                    APR_ARRAY_PUSH(scts, md_sct*) = sct;
+                }
+            }
+        }
+        if (idx < 0) break;
+    }
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "ct_sct, found %d SCT extensions", scts->nelts);
+    return APR_SUCCESS;
+#else
+    return APR_ENOTIMPL;
+#endif
+}
index e03c29663d5aa49851111a8c2c9dc2bf6ce4eea4..cc024533e8207ba8ac0efa7d0a1cd87439d3abc3 100644 (file)
@@ -24,6 +24,8 @@ struct md_t;
 struct md_http_response_t;
 struct md_cert_t;
 struct md_pkey_t;
+struct md_data;
+
 
 /**************************************************************************************************/
 /* random */
@@ -33,9 +35,11 @@ apr_status_t md_rand_bytes(unsigned char *buf, apr_size_t len, apr_pool_t *p);
 /**************************************************************************************************/
 /* digests */
 apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p, 
-                                      const char *d, size_t dlen);
+                                      const struct md_data *data);
 apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p, 
-                                        const char *d, size_t dlen);
+                                        const struct md_data *data);
+
+#define MD_DATA_SET_STR(d, s)       do { (d)->data = (s); (d)->len = strlen(s); } while(0)
 
 /**************************************************************************************************/
 /* private keys */
@@ -76,7 +80,6 @@ apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p,
 apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *p, 
                              const char *d, size_t dlen);
 
-void *md_cert_get_X509(struct md_cert_t *cert);
 void *md_pkey_get_EVP_PKEY(struct md_pkey_t *pkey);
 
 struct md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p);
@@ -95,29 +98,49 @@ typedef enum {
 } md_cert_state_t;
 
 void md_cert_free(md_cert_t *cert);
+void *md_cert_get_X509(const md_cert_t *cert);
 
 apr_status_t md_cert_fload(md_cert_t **pcert, apr_pool_t *p, const char *fname);
 apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p, 
                            const char *fname, apr_fileperms_t perms);
 
+/**
+ * Read a x509 certificate from a http response.
+ * Will return APR_ENOENT if content-type is not recognized (currently
+ * only "application/pkix-cert" is supported).
+ */
 apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *pool, 
                                const struct md_http_response_t *res);
 
-md_cert_state_t md_cert_state_get(md_cert_t *cert);
+/**
+ * Read one or even a chain of certificates from a http response.
+ * Will return APR_ENOENT if content-type is not recognized (currently
+ * supports only "application/pem-certificate-chain" and "application/pkix-cert").
+ * @param chain    must be non-NULL, retrieved certificates will be added.
+ */
+apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain,
+                                     apr_pool_t *pool, const struct md_http_response_t *res);
+
+md_cert_state_t md_cert_state_get(const md_cert_t *cert);
 int md_cert_is_valid_now(const md_cert_t *cert);
 int md_cert_has_expired(const md_cert_t *cert);
 int md_cert_covers_domain(md_cert_t *cert, const char *domain_name);
 int md_cert_covers_md(md_cert_t *cert, const struct md_t *md);
-int md_cert_must_staple(md_cert_t *cert);
-apr_time_t md_cert_get_not_after(md_cert_t *cert);
-apr_time_t md_cert_get_not_before(md_cert_t *cert);
+int md_cert_must_staple(const md_cert_t *cert);
+apr_time_t md_cert_get_not_after(const md_cert_t *cert);
+apr_time_t md_cert_get_not_before(const md_cert_t *cert);
 
-apr_status_t md_cert_get_issuers_uri(const char **puri, md_cert_t *cert, apr_pool_t *p);
-apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, md_cert_t *cert, apr_pool_t *p);
+apr_status_t md_cert_get_issuers_uri(const char **puri, const md_cert_t *cert, apr_pool_t *p);
+apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t *cert, apr_pool_t *p);
 
-apr_status_t md_cert_to_base64url(const char **ps64, md_cert_t *cert, apr_pool_t *p);
+apr_status_t md_cert_to_base64url(const char **ps64, const md_cert_t *cert, apr_pool_t *p);
 apr_status_t md_cert_from_base64url(md_cert_t **pcert, const char *s64, apr_pool_t *p);
 
+apr_status_t md_cert_to_sha256_digest(struct md_data **pdigest, const md_cert_t *cert, apr_pool_t *p);
+apr_status_t md_cert_to_sha256_fingerprint(const char **pfinger, const md_cert_t *cert, apr_pool_t *p);
+
+const char *md_cert_get_serial_number(const md_cert_t *cert, apr_pool_t *p);
+
 apr_status_t md_chain_fload(struct apr_array_header_t **pcerts, 
                             apr_pool_t *p, const char *fname);
 apr_status_t md_chain_fsave(struct apr_array_header_t *certs, 
@@ -125,11 +148,42 @@ apr_status_t md_chain_fsave(struct apr_array_header_t *certs,
 apr_status_t md_chain_fappend(struct apr_array_header_t *certs, 
                               apr_pool_t *p, const char *fname);
 
-apr_status_t md_cert_req_create(const char **pcsr_der_64, const struct md_t *md, 
+apr_status_t md_cert_req_create(const char **pcsr_der_64, const char *name,
+                                apr_array_header_t *domains, int must_staple, 
                                 md_pkey_t *pkey, apr_pool_t *p);
 
+/**
+ * Create a self-signed cerftificate with the given cn, key and list
+ * of alternate domain names.
+ */
 apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn, 
                                struct apr_array_header_t *domains, md_pkey_t *pkey,
                                apr_interval_time_t valid_for, apr_pool_t *p);
+   
+/**
+ * Create a certificate for answering "tls-alpn-01" ACME challenges 
+ * (see <https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01>).
+ */
+apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain, 
+                                      const char *acme_id, md_pkey_t *pkey, 
+                                      apr_interval_time_t valid_for, apr_pool_t *p);
+
+apr_status_t md_cert_get_ct_scts(apr_array_header_t *scts, apr_pool_t *p, const md_cert_t *cert);
+
+
+/**************************************************************************************************/
+/* X509 certificate transparency */
+
+const char *md_nid_get_sname(int nid);
+const char *md_nid_get_lname(int nid);
+
+typedef struct md_sct md_sct;
+struct md_sct {
+    int version;
+    apr_time_t timestamp;
+    struct md_data *logid;
+    int signature_type_nid;
+    struct md_data *signature;
+};
 
 #endif /* md_crypt_h */
index 02b7c1daaf37ac2828b0d0fac988d13ff46d6fcf..a7aad7d279218868558ad89f49999655dda02c39 100644 (file)
@@ -237,7 +237,7 @@ static apr_status_t curl_perform(md_http_request_t *req)
     }
     
     md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->pool, 
-                  "request %ld --> %s %s", req->id, req->method, req->url);
+                  "request --> %s %s", req->method, req->url);
     
     if (md_log_is_level(req->pool, MD_LOG_TRACE3)) {
         curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
@@ -253,11 +253,11 @@ static apr_status_t curl_perform(md_http_request_t *req)
             res->status = (int)l;
         }
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, res->rv, req->pool, 
-                      "request %ld <-- %d", req->id, res->status);
+                      "request <-- %d", res->status);
     }
     else {
         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, res->rv, req->pool, 
-                      "request %ld failed(%d): %s", req->id, curle, 
+                      "request failed(%d): %s", curle, 
                       curl_easy_strerror(curle));
     }
     
index 310fc5590c7d3f350b76f5602b3606f15bb36477..c9383f88c670c40724a1dc5dfe7a3842971813f5 100644 (file)
@@ -43,8 +43,6 @@ void md_http_use_implementation(md_http_impl_t *impl)
     }
 }
 
-static long next_req_id;
-
 apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_agent,
                             const char *proxy_url)
 {
@@ -97,7 +95,6 @@ static apr_status_t req_create(md_http_request_t **preq, md_http_t *http,
     }
     
     req = apr_pcalloc(pool, sizeof(*req));
-    req->id = next_req_id++;
     req->pool = pool;
     req->bucket_alloc = http->bucket_alloc;
     req->http = http;
@@ -124,8 +121,7 @@ void md_http_req_destroy(md_http_request_t *req)
 }
 
 static apr_status_t schedule(md_http_request_t *req, 
-                             apr_bucket_brigade *body, int detect_clen,
-                             long *preq_id) 
+                             apr_bucket_brigade *body, int detect_clen) 
 {
     apr_status_t rv;
     
@@ -147,19 +143,12 @@ static apr_status_t schedule(md_http_request_t *req,
         apr_table_setn(req->headers, "Content-Length", apr_off_t_toa(req->pool, req->body_len));
     }
     
-    if (preq_id) {
-        *preq_id = req->id;
-    }
-    
-    /* we send right away */
-    rv = req->http->impl->perform(req);
-    
-    return rv;
+    return req->http->impl->perform(req);
 }
 
 apr_status_t md_http_GET(struct md_http_t *http, 
                          const char *url, struct apr_table_t *headers,
-                         md_http_cb *cb, void *baton, long *preq_id)
+                         md_http_cb *cb, void *baton)
 {
     md_http_request_t *req;
     apr_status_t rv;
@@ -169,12 +158,12 @@ apr_status_t md_http_GET(struct md_http_t *http,
         return rv;
     }
     
-    return schedule(req, NULL, 0, preq_id);
+    return schedule(req, NULL, 0);
 }
 
 apr_status_t md_http_HEAD(struct md_http_t *http, 
                           const char *url, struct apr_table_t *headers,
-                          md_http_cb *cb, void *baton, long *preq_id)
+                          md_http_cb *cb, void *baton)
 {
     md_http_request_t *req;
     apr_status_t rv;
@@ -184,13 +173,13 @@ apr_status_t md_http_HEAD(struct md_http_t *http,
         return rv;
     }
     
-    return schedule(req, NULL, 0, preq_id);
+    return schedule(req, NULL, 0);
 }
 
 apr_status_t md_http_POST(struct md_http_t *http, const char *url, 
                           struct apr_table_t *headers, const char *content_type, 
                           apr_bucket_brigade *body,
-                          md_http_cb *cb, void *baton, long *preq_id)
+                          md_http_cb *cb, void *baton)
 {
     md_http_request_t *req;
     apr_status_t rv;
@@ -203,13 +192,13 @@ apr_status_t md_http_POST(struct md_http_t *http, const char *url,
     if (content_type) {
         apr_table_set(req->headers, "Content-Type", content_type); 
     }
-    return schedule(req, body, 1, preq_id);
+    return schedule(req, body, 1);
 }
 
 apr_status_t md_http_POSTd(md_http_t *http, const char *url, 
                            struct apr_table_t *headers, const char *content_type, 
                            const char *data, size_t data_len, 
-                           md_http_cb *cb, void *baton, long *preq_id)
+                           md_http_cb *cb, void *baton)
 {
     md_http_request_t *req;
     apr_status_t rv;
@@ -233,13 +222,5 @@ apr_status_t md_http_POSTd(md_http_t *http, const char *url,
         apr_table_set(req->headers, "Content-Type", content_type); 
     }
      
-    return schedule(req, body, 1, preq_id);
+    return schedule(req, body, 1);
 }
-
-apr_status_t md_http_await(md_http_t *http, long req_id)
-{
-    (void)http;
-    (void)req_id;
-    return APR_SUCCESS;
-}
-
index c6d94bb2d941cf80a9a0628d08c25d7472910d08..47c7cc4b572413680c526328f5c7b166ebd82da6 100644 (file)
@@ -29,7 +29,6 @@ typedef struct md_http_response_t md_http_response_t;
 typedef apr_status_t md_http_cb(const md_http_response_t *res);
 
 struct md_http_request_t {
-    long id;
     md_http_t *http;
     apr_pool_t *pool;
     struct apr_bucket_alloc_t *bucket_alloc;
@@ -61,23 +60,21 @@ void md_http_set_response_limit(md_http_t *http, apr_off_t resp_limit);
 
 apr_status_t md_http_GET(md_http_t *http, 
                          const char *url, struct apr_table_t *headers,
-                         md_http_cb *cb, void *baton, long *preq_id);
+                         md_http_cb *cb, void *baton);
 
 apr_status_t md_http_HEAD(md_http_t *http, 
                           const char *url, struct apr_table_t *headers,
-                          md_http_cb *cb, void *baton, long *preq_id);
+                          md_http_cb *cb, void *baton);
 
 apr_status_t md_http_POST(md_http_t *http, const char *url, 
                           struct apr_table_t *headers, const char *content_type, 
                           struct apr_bucket_brigade *body,
-                          md_http_cb *cb, void *baton, long *preq_id);
+                          md_http_cb *cb, void *baton);
 
 apr_status_t md_http_POSTd(md_http_t *http, const char *url, 
                            struct apr_table_t *headers, const char *content_type, 
                            const char *data, size_t data_len, 
-                           md_http_cb *cb, void *baton, long *preq_id);
-
-apr_status_t md_http_await(md_http_t *http, long req_id);
+                           md_http_cb *cb, void *baton);
 
 void md_http_req_destroy(md_http_request_t *req);
 
index f73ab148ed8e14d277af2af2023cfcfe3ca74cd3..56654795659d70acf7e8fc79f490673ea1281dbd 100644 (file)
@@ -120,7 +120,7 @@ md_json_t *md_json_clone(apr_pool_t *pool, md_json_t *json)
 /* selectors */
 
 
-static json_t *jselect(md_json_t *json, va_list ap)
+static json_t *jselect(const md_json_t *json, va_list ap)
 {
     json_t *j;
     const char *key;
@@ -187,6 +187,38 @@ static apr_status_t jselect_add(json_t *val, md_json_t *json, va_list ap)
     return APR_SUCCESS;
 }
 
+static apr_status_t jselect_insert(json_t *val, size_t index, md_json_t *json, va_list ap)
+{
+    const char *key;
+    json_t *j, *aj;
+    
+    j = jselect_parent(&key, 1, json, ap);
+    
+    if (!j || !json_is_object(j)) {
+        json_decref(val);
+        return APR_EINVAL;
+    }
+    
+    aj = json_object_get(j, key);
+    if (!aj) {
+        aj = json_array();
+        json_object_set_new(j, key, aj);
+    }
+    
+    if (!json_is_array(aj)) {
+        json_decref(val);
+        return APR_EINVAL;
+    }
+
+    if (json_array_size(aj) <= index) {
+        json_array_append(aj, val);
+    }
+    else {
+        json_array_insert(aj, index, val);
+    }
+    return APR_SUCCESS;
+}
+
 static apr_status_t jselect_set(json_t *val, md_json_t *json, va_list ap)
 {
     const char *key;
@@ -246,7 +278,7 @@ static apr_status_t jselect_set_new(json_t *val, md_json_t *json, va_list ap)
     return APR_SUCCESS;
 }
 
-int md_json_has_key(md_json_t *json, ...)
+int md_json_has_key(const md_json_t *json, ...)
 {
     json_t *j;
     va_list ap;
@@ -258,10 +290,33 @@ int md_json_has_key(md_json_t *json, ...)
     return j != NULL;
 }
 
+/**************************************************************************************************/
+/* type things */
+
+int md_json_is(const md_json_type_t jtype, md_json_t *json, ...)
+{
+    json_t *j;
+    va_list ap;
+    
+    va_start(ap, json);
+    j = jselect(json, ap);
+    va_end(ap);
+    switch (jtype) {
+        case MD_JSON_TYPE_OBJECT: return (j && json_is_object(j));
+        case MD_JSON_TYPE_ARRAY: return (j && json_is_array(j));
+        case MD_JSON_TYPE_STRING: return (j && json_is_string(j));
+        case MD_JSON_TYPE_REAL: return (j && json_is_real(j));
+        case MD_JSON_TYPE_INT: return (j && json_is_integer(j));
+        case MD_JSON_TYPE_BOOL: return (j && (json_is_true(j) || json_is_false(j)));
+        case MD_JSON_TYPE_NULL: return (j == NULL);
+    }
+    return 0;
+}
+
 /**************************************************************************************************/
 /* booleans */
 
-int md_json_getb(md_json_t *json, ...)
+int md_json_getb(const md_json_t *json, ...)
 {
     json_t *j;
     va_list ap;
@@ -287,7 +342,7 @@ apr_status_t md_json_setb(int value, md_json_t *json, ...)
 /**************************************************************************************************/
 /* numbers */
 
-double md_json_getn(md_json_t *json, ...)
+double md_json_getn(const md_json_t *json, ...)
 {
     json_t *j;
     va_list ap;
@@ -312,7 +367,7 @@ apr_status_t md_json_setn(double value, md_json_t *json, ...)
 /**************************************************************************************************/
 /* longs */
 
-long md_json_getl(md_json_t *json, ...)
+long md_json_getl(const md_json_t *json, ...)
 {
     json_t *j;
     va_list ap;
@@ -337,7 +392,7 @@ apr_status_t md_json_setl(long value, md_json_t *json, ...)
 /**************************************************************************************************/
 /* strings */
 
-const char *md_json_gets(md_json_t *json, ...)
+const char *md_json_gets(const md_json_t *json, ...)
 {
     json_t *j;
     va_list ap;
@@ -349,7 +404,7 @@ const char *md_json_gets(md_json_t *json, ...)
     return (j && json_is_string(j))? json_string_value(j) : NULL;
 }
 
-const char *md_json_dups(apr_pool_t *p, md_json_t *json, ...)
+const char *md_json_dups(apr_pool_t *p, const md_json_t *json, ...)
 {
     json_t *j;
     va_list ap;
@@ -394,6 +449,25 @@ md_json_t *md_json_getj(md_json_t *json, ...)
     return NULL;
 }
 
+const md_json_t *md_json_getcj(const md_json_t *json, ...)
+{
+    json_t *j;
+    va_list ap;
+    
+    va_start(ap, json);
+    j = jselect(json, ap);
+    va_end(ap);
+    
+    if (j) {
+        if (j == json->j) {
+            return json;
+        }
+        json_incref(j);
+        return json_create(json->p, j);
+    }
+    return NULL;
+}
+
 apr_status_t md_json_setj(md_json_t *value, md_json_t *json, ...)
 {
     va_list ap;
@@ -433,6 +507,17 @@ apr_status_t md_json_addj(md_json_t *value, md_json_t *json, ...)
     return rv;
 }
 
+apr_status_t md_json_insertj(md_json_t *value, size_t index, md_json_t *json, ...)
+{
+    va_list ap;
+    apr_status_t rv;
+    
+    va_start(ap, json);
+    rv = jselect_insert(value->j, index, json, ap);
+    va_end(ap);
+    return rv;
+}
+
 
 /**************************************************************************************************/
 /* arrays / objects */
@@ -474,7 +559,7 @@ apr_status_t md_json_del(md_json_t *json, ...)
 /**************************************************************************************************/
 /* object strings */
 
-apr_status_t md_json_gets_dict(apr_table_t *dict, md_json_t *json, ...)
+apr_status_t md_json_gets_dict(apr_table_t *dict, const md_json_t *json, ...)
 {
     json_t *j;
     va_list ap;
@@ -568,7 +653,7 @@ apr_status_t md_json_clone_from(void **pvalue, md_json_t *json, apr_pool_t *p, v
 /* array generic */
 
 apr_status_t md_json_geta(apr_array_header_t *a, md_json_from_cb *cb, void *baton,
-                          md_json_t *json, ...)
+                          const md_json_t *json, ...)
 {
     json_t *j;
     va_list ap;
@@ -675,7 +760,7 @@ int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...)
 /**************************************************************************************************/
 /* array strings */
 
-apr_status_t md_json_getsa(apr_array_header_t *a, md_json_t *json, ...)
+apr_status_t md_json_getsa(apr_array_header_t *a, const md_json_t *json, ...)
 {
     json_t *j;
     va_list ap;
@@ -711,6 +796,7 @@ apr_status_t md_json_dupsa(apr_array_header_t *a, apr_pool_t *p, md_json_t *json
         size_t index;
         json_t *val;
         
+        apr_array_clear(a);
         json_array_foreach(j, index, val) {
             if (json_is_string(val)) {
                 APR_ARRAY_PUSH(a, const char *) = apr_pstrdup(p, json_string_value(val));
@@ -805,8 +891,7 @@ const char *md_json_writep(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt)
 
     chunks = apr_array_make(p, 10, sizeof(char *));
     rv = json_dump_callback(json->j, chunk_cb, chunks, fmt_to_flags(fmt));
-
-    if (rv) {
+    if (APR_SUCCESS != rv) {
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p,
                       "md_json_writep failed to dump JSON");
         return NULL;
@@ -827,17 +912,15 @@ apr_status_t md_json_writef(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, a
     apr_status_t rv;
     const char *s;
     
-    s = md_json_writep(json, p, fmt);
-
-    if (s) {
+    if ((s = md_json_writep(json, p, fmt))) {
         rv = apr_file_write_full(f, s, strlen(s), NULL);
+        if (APR_SUCCESS != rv) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p, "md_json_writef: error writing file");
+        }
     }
     else {
         rv = APR_EINVAL;
-    }
-
-    if (APR_SUCCESS != rv) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p, "md_json_writef");
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p, "md_json_writef: error dumping json");
     }
     return rv;
 }
@@ -1017,17 +1100,15 @@ static apr_status_t json_resp_cb(const md_http_response_t *res)
 apr_status_t md_json_http_get(md_json_t **pjson, apr_pool_t *pool,
                               struct md_http_t *http, const char *url)
 {
-    long req_id;
     apr_status_t rv;
     resp_data resp;
     
     memset(&resp, 0, sizeof(resp));
     resp.pool = pool;
     
-    rv = md_http_GET(http, url, NULL, json_resp_cb, &resp, &req_id);
+    rv = md_http_GET(http, url, NULL, json_resp_cb, &resp);
     
     if (rv == APR_SUCCESS) {
-        md_http_await(http, req_id);
         *pjson = resp.json;
         return resp.rv;
     }
@@ -1035,3 +1116,21 @@ apr_status_t md_json_http_get(md_json_t **pjson, apr_pool_t *pool,
     return rv;
 }
 
+
+apr_status_t md_json_copy_to(md_json_t *dest, const md_json_t *src, ...)
+{
+    json_t *j;
+    va_list ap;
+    apr_status_t rv = APR_SUCCESS;
+    
+    va_start(ap, src);
+    j = jselect(src, ap);
+    va_end(ap);
+
+    if (j) {
+        va_start(ap, src);
+        rv = jselect_set(j, dest, ap);
+        va_end(ap);
+    }
+    return rv;
+}
index 7f2e4f331bec09bbb91fbb0c24571424dd7b8159..480ab7ca568d48678efa6146526896bf31a15060 100644 (file)
@@ -28,6 +28,17 @@ struct md_http_response_t;
 
 typedef struct md_json_t md_json_t;
 
+typedef enum {
+    MD_JSON_TYPE_OBJECT,
+    MD_JSON_TYPE_ARRAY,
+    MD_JSON_TYPE_STRING,
+    MD_JSON_TYPE_REAL,
+    MD_JSON_TYPE_INT,
+    MD_JSON_TYPE_BOOL,
+    MD_JSON_TYPE_NULL,
+} md_json_type_t;
+
+
 typedef enum {
     MD_JSON_FMT_COMPACT,
     MD_JSON_FMT_INDENT,
@@ -39,30 +50,34 @@ void md_json_destroy(md_json_t *json);
 md_json_t *md_json_copy(apr_pool_t *pool, md_json_t *json);
 md_json_t *md_json_clone(apr_pool_t *pool, md_json_t *json);
 
-int md_json_has_key(md_json_t *json, ...);
+
+int md_json_has_key(const md_json_t *json, ...);
+int md_json_is(const md_json_type_t type, md_json_t *json, ...);
 
 /* boolean manipulation */
-int md_json_getb(md_json_t *json, ...);
+int md_json_getb(const md_json_t *json, ...);
 apr_status_t md_json_setb(int value, md_json_t *json, ...);
 
 /* number manipulation */
-double md_json_getn(md_json_t *json, ...);
+double md_json_getn(const md_json_t *json, ...);
 apr_status_t md_json_setn(double value, md_json_t *json, ...);
 
 /* long manipulation */
-long md_json_getl(md_json_t *json, ...);
+long md_json_getl(const md_json_t *json, ...);
 apr_status_t md_json_setl(long value, md_json_t *json, ...);
 
 /* string manipulation */
 md_json_t *md_json_create_s(apr_pool_t *pool, const char *s);
-const char *md_json_gets(md_json_t *json, ...);
-const char *md_json_dups(apr_pool_t *p, md_json_t *json, ...);
+const char *md_json_gets(const md_json_t *json, ...);
+const char *md_json_dups(apr_pool_t *p, const md_json_t *json, ...);
 apr_status_t md_json_sets(const char *s, md_json_t *json, ...);
 
 /* json manipulation */
 md_json_t *md_json_getj(md_json_t *json, ...);
+const md_json_t *md_json_getcj(const md_json_t *json, ...);
 apr_status_t md_json_setj(md_json_t *value, md_json_t *json, ...);
 apr_status_t md_json_addj(md_json_t *value, md_json_t *json, ...);
+apr_status_t md_json_insertj(md_json_t *value, size_t index, md_json_t *json, ...);
 
 /* Array/Object manipulation */
 apr_status_t md_json_clr(md_json_t *json, ...);
@@ -82,19 +97,20 @@ apr_status_t md_json_clone_from(void **pvalue, md_json_t *json, apr_pool_t *p, v
 
 /* Manipulating/Iteration on generic Arrays */
 apr_status_t md_json_geta(apr_array_header_t *a, md_json_from_cb *cb, 
-                          void *baton, md_json_t *json, ...);
+                          void *baton, const md_json_t *json, ...);
 apr_status_t md_json_seta(apr_array_header_t *a, md_json_to_cb *cb, 
                           void *baton, md_json_t *json, ...);
 
+/* Called on each array element, aborts iteration when returning 0 */
 typedef int md_json_itera_cb(void *baton, size_t index, md_json_t *json);
 int md_json_itera(md_json_itera_cb *cb, void *baton, md_json_t *json, ...);
 
 /* Manipulating Object String values */
-apr_status_t md_json_gets_dict(apr_table_t *dict, md_json_t *json, ...);
+apr_status_t md_json_gets_dict(apr_table_t *dict, const md_json_t *json, ...);
 apr_status_t md_json_sets_dict(apr_table_t *dict, md_json_t *json, ...);
 
 /* Manipulating String Arrays */
-apr_status_t md_json_getsa(apr_array_header_t *a, md_json_t *json, ...);
+apr_status_t md_json_getsa(apr_array_header_t *a, const md_json_t *json, ...);
 apr_status_t md_json_dupsa(apr_array_header_t *a, apr_pool_t *p, md_json_t *json, ...);
 apr_status_t md_json_setsa(apr_array_header_t *a, md_json_t *json, ...);
 
@@ -119,4 +135,6 @@ apr_status_t md_json_http_get(md_json_t **pjson, apr_pool_t *pool,
 apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool, 
                                const struct md_http_response_t *res);
 
+apr_status_t md_json_copy_to(md_json_t *dest, const md_json_t *src, ...);
+
 #endif /* md_json_h */
index 37c1b0e3526fa1e274d736a028e589083e9ae79d..810c151c2f41056cbc2b88fd54c07d84ce255bb7 100644 (file)
@@ -91,6 +91,7 @@ apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
 apr_status_t md_jws_pkey_thumb(const char **pthumb, apr_pool_t *p, struct md_pkey_t *pkey)
 {
     const char *e64, *n64, *s;
+    md_data data;
     apr_status_t rv;
     
     e64 = md_pkey_get_rsa_e64(pkey, p);
@@ -101,6 +102,7 @@ apr_status_t md_jws_pkey_thumb(const char **pthumb, apr_pool_t *p, struct md_pke
 
     /* whitespace and order is relevant, since we hand out a digest of this */
     s = apr_psprintf(p, "{\"e\":\"%s\",\"kty\":\"RSA\",\"n\":\"%s\"}", e64, n64);
-    rv = md_crypt_sha256_digest64(pthumb, p, s, strlen(s));
+    MD_DATA_SET_STR(&data, s);
+    rv = md_crypt_sha256_digest64(pthumb, p, &data);
     return rv;
 }
index 233fea79d726b228457f2a55245a31ec10565d94..0c4c9244a86a47b945187072a813e7854d242279 100644 (file)
 #include "md_crypt.h"
 #include "md_log.h"
 #include "md_json.h"
+#include "md_result.h"
 #include "md_reg.h"
 #include "md_store.h"
+#include "md_status.h"
 #include "md_util.h"
 
 #include "md_acme.h"
 #include "md_acme_acct.h"
 
 struct md_reg_t {
+    apr_pool_t *p;
     struct md_store_t *store;
     struct apr_hash_t *protos;
+    struct apr_hash_t *certs;
     int can_http;
     int can_https;
     const char *proxy_url;
+    int domains_frozen;
+    const md_timeslice_t *renew_window;
+    const md_timeslice_t *warn_window;
 };
 
 /**************************************************************************************************/
@@ -67,19 +74,24 @@ static apr_status_t load_props(md_reg_t *reg, apr_pool_t *p)
     return rv;
 }
 
-apr_status_t md_reg_init(md_reg_t **preg, apr_pool_t *p, struct md_store_t *store,
-                         const char *proxy_url)
+apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *p, struct md_store_t *store,
+                           const char *proxy_url)
 {
     md_reg_t *reg;
     apr_status_t rv;
     
     reg = apr_pcalloc(p, sizeof(*reg));
+    reg->p = p;
     reg->store = store;
     reg->protos = apr_hash_make(p);
+    reg->certs = apr_hash_make(p);
     reg->can_http = 1;
     reg->can_https = 1;
     reg->proxy_url = proxy_url? apr_pstrdup(p, proxy_url) : NULL;
     
+    md_timeslice_create(&reg->renew_window, p, MD_TIME_LIFE_NORM, MD_TIME_RENEW_WINDOW_DEF); 
+    md_timeslice_create(&reg->warn_window, p, MD_TIME_LIFE_NORM, MD_TIME_WARN_WINDOW_DEF); 
+    
     if (APR_SUCCESS == (rv = md_acme_protos_add(reg->protos, p))) {
         rv = load_props(reg, p);
     }
@@ -114,7 +126,7 @@ static apr_status_t check_values(md_reg_t *reg, apr_pool_t *p, const md_t *md, i
         
         for (i = 0; i < md->domains->nelts; ++i) {
             domain = APR_ARRAY_IDX(md->domains, i, const char *);
-            if (!md_util_is_dns_name(p, domain, 1)) {
+            if (!md_dns_is_name(p, domain, 1) && !md_dns_is_wildcard(p, domain)) {
                 md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p, 
                               "md %s with invalid domain name: %s", md->name, domain);
                 return APR_EINVAL;
@@ -162,7 +174,8 @@ static apr_status_t check_values(md_reg_t *reg, apr_pool_t *p, const md_t *md, i
         /* hmm, in case we know the protocol, some checks could be done */
     }
 
-    if ((MD_UPD_AGREEMENT & fields) && md->ca_agreement) { /* setting to empty is ok */
+    if ((MD_UPD_AGREEMENT & fields) && md->ca_agreement
+        && strcmp("accepted", md->ca_agreement)) { /* setting to empty is ok */
         rv = md_util_abs_uri_check(p, md->ca_agreement, &err);
         if (err) {
             md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, p, 
@@ -177,73 +190,43 @@ static apr_status_t check_values(md_reg_t *reg, apr_pool_t *p, const md_t *md, i
 /**************************************************************************************************/
 /* state assessment */
 
-static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md, int save_changes)
+static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md)
 {
     md_state_t state = MD_S_UNKNOWN;
-    const md_creds_t *creds;
+    const md_pubcert_t *pub;
     const md_cert_t *cert;
-    apr_time_t expires = 0, valid_from = 0;
     apr_status_t rv;
-    int i;
 
-    if (APR_SUCCESS == (rv = md_reg_creds_get(&creds, reg, MD_SG_DOMAINS, md, p))) {
-        state = MD_S_INCOMPLETE;
-        if (!creds->privkey) {
+    if (md->renew_window == NULL) md->renew_window = reg->renew_window;
+    if (md->warn_window == NULL) md->warn_window = reg->warn_window;
+
+    if (APR_SUCCESS == (rv = md_reg_get_pubcert(&pub, reg, md, p))) {
+        cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+        if (!md_is_covered_by_alt_names(md, pub->alt_names)) {
+            state = MD_S_INCOMPLETE;
             md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                          "md{%s}: incomplete, without private key", md->name);
+                          "md{%s}: incomplete, cert no longer covers all domains, "
+                          "needs sign up for a new certificate", md->name);
+            goto out;
         }
-        else if (!creds->cert) {
+        if (!md->must_staple != !md_cert_must_staple(cert)) {
+            state = MD_S_INCOMPLETE;
             md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                          "md{%s}: incomplete, has key but no certificate", md->name);
-        }
-        else {
-            valid_from = md_cert_get_not_before(creds->cert);
-            expires = md_cert_get_not_after(creds->cert);
-            if (md_cert_has_expired(creds->cert)) {
-                state = MD_S_EXPIRED;
-                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                              "md{%s}: expired, certificate has expired", md->name);
-                goto out;
-            }
-            if (!md_cert_is_valid_now(creds->cert)) {
-                state = MD_S_ERROR;
-                md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, 
-                              "md{%s}: error, certificate valid in future (clock wrong?)", 
-                              md->name);
-                goto out;
-            }
-            if (!md_cert_covers_md(creds->cert, md)) {
-                state = MD_S_INCOMPLETE;
-                md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, 
-                              "md{%s}: incomplete, cert no longer covers all domains, "
-                              "needs sign up for a new certificate", md->name);
-                goto out;
-            }
-            if (!md->must_staple != !md_cert_must_staple(creds->cert)) {
-                state = MD_S_INCOMPLETE;
-                md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, 
-                              "md{%s}: OCSP Stapling is%s requested, but certificate "
-                              "has it%s enabled. Need to get a new certificate.", md->name,
-                              md->must_staple? "" : " not", 
-                              !md->must_staple? "" : " not");
-                goto out;
-            }
-
-            for (i = 1; i < creds->pubcert->nelts; ++i) {
-                cert = APR_ARRAY_IDX(creds->pubcert, i, const md_cert_t *);
-                if (!md_cert_is_valid_now(cert)) {
-                    state = MD_S_ERROR;
-                    md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, 
-                                  "md{%s}: error, the certificate itself is valid, however the %d. "
-                                  "certificate in the chain is not valid now (clock wrong?).", 
-                                  md->name, i);
-                    goto out;
-                }
-            } 
-
-            state = MD_S_COMPLETE;
-            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: is complete", md->name);
+                          "md{%s}: OCSP Stapling is%s requested, but certificate "
+                          "has it%s enabled. Need to get a new certificate.", md->name,
+                          md->must_staple? "" : " not", 
+                          !md->must_staple? "" : " not");
+            goto out;
         }
+        
+        state = MD_S_COMPLETE;
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md{%s}: is complete", md->name);
+    }
+    else if (APR_STATUS_IS_ENOENT(rv)) {
+        state = MD_S_INCOMPLETE;
+        rv = APR_SUCCESS;
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
+                      "md{%s}: incomplete, credentials not all there", md->name);
     }
 
 out:    
@@ -251,64 +234,10 @@ out:
         state = MD_S_ERROR;
         md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "md{%s}: error", md->name);
     }
-    
-    if (save_changes && md->state == state
-        && md->valid_from == valid_from && md->expires == expires) {
-        save_changes = 0;
-    }
     md->state = state;
-    md->valid_from = valid_from;
-    md->expires = expires;
-    if (save_changes && APR_SUCCESS == rv) {
-        return md_save(reg->store, p, MD_SG_DOMAINS, md, 0);
-    }
     return rv;
 }
 
-apr_status_t md_reg_assess(md_reg_t *reg, md_t *md, int *perrored, int *prenew, apr_pool_t *p)
-{
-    int renew = 0;
-    int errored = 0;
-    
-    (void)reg;
-    switch (md->state) {
-        case MD_S_UNKNOWN:
-            md_log_perror( MD_LOG_MARK, MD_LOG_ERR, 0, p, "md(%s): in unknown state.", md->name);
-            break;
-        case MD_S_ERROR:
-            md_log_perror( MD_LOG_MARK, MD_LOG_ERR, 0, p,  
-                         "md(%s): in error state, unable to drive forward. If unable to "
-                         " detect the cause, you may remove the staging or even domain "
-                         " sub-directory for this MD and start all over.", md->name);
-            errored = 1;
-            break;
-        case MD_S_COMPLETE:
-            if (!md->expires) {
-                md_log_perror( MD_LOG_MARK, MD_LOG_WARNING, 0, p,  
-                             "md(%s): looks complete, but has unknown expiration date.", md->name);
-                errored = 1;
-            }
-            else if (md->expires <= apr_time_now()) {
-                /* Maybe we hibernated in the meantime? */
-                md->state = MD_S_EXPIRED;
-                renew = 1;
-            }
-            else {
-                renew = md_should_renew(md);
-            }
-            break;
-        case MD_S_INCOMPLETE:
-        case MD_S_EXPIRED:
-            renew = 1;
-            break;
-        case MD_S_MISSING:
-            break;
-    }
-    *prenew = renew;
-    *perrored = errored;
-    return APR_SUCCESS;
-}
-
 /**************************************************************************************************/
 /* iteration */
 
@@ -326,7 +255,7 @@ static int reg_md_iter(void *baton, md_store_t *store, md_t *md, apr_pool_t *pte
     
     (void)store;
     if (!ctx->exclude || strcmp(ctx->exclude, md->name)) {
-        state_init(ctx->reg, ptemp, (md_t*)md, 1);
+        state_init(ctx->reg, ptemp, (md_t*)md);
         return ctx->cb(ctx->baton, ctx->reg, md);
     }
     return 1;
@@ -357,12 +286,17 @@ md_t *md_reg_get(md_reg_t *reg, const char *name, apr_pool_t *p)
     md_t *md;
     
     if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, name, &md, p)) {
-        state_init(reg, p, md, 1);
+        state_init(reg, p, md);
         return md;
     }
     return NULL;
 }
 
+apr_status_t md_reg_reinit_state(md_reg_t *reg, md_t *md, apr_pool_t *p)
+{
+    return state_init(reg, p, md);
+}
+
 typedef struct {
     const char *domain;
     md_t *md;
@@ -389,7 +323,7 @@ md_t *md_reg_find(md_reg_t *reg, const char *domain, apr_pool_t *p)
     
     md_reg_do(find_domain, &ctx, reg, p);
     if (ctx.md) {
-        state_init(reg, p, ctx.md, 1);
+        state_init(reg, p, ctx.md);
     }
     return ctx.md;
 }
@@ -427,23 +361,11 @@ md_t *md_reg_find_overlap(md_reg_t *reg, const md_t *md, const char **pdomain, a
         *pdomain = ctx.s;
     }
     if (ctx.md) {
-        state_init(reg, p, ctx.md, 1);
+        state_init(reg, p, ctx.md);
     }
     return ctx.md;
 }
 
-apr_status_t md_reg_get_cred_files(md_reg_t *reg, const md_t *md, apr_pool_t *p,
-                                   const char **pkeyfile, const char **pcertfile)
-{
-    apr_status_t rv;
-    
-    rv = md_store_get_fname(pkeyfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PRIVKEY, p);
-    if (APR_SUCCESS == rv) {
-        rv = md_store_get_fname(pcertfile, reg->store, MD_SG_DOMAINS, md->name, MD_FN_PUBCERT, p);
-    }
-    return rv;
-}
-
 /**************************************************************************************************/
 /* manipulation */
 
@@ -452,19 +374,28 @@ static apr_status_t p_md_add(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
     md_reg_t *reg = baton;
     apr_status_t rv = APR_SUCCESS;
     md_t *md, *mine;
+    int do_check;
     
     md = va_arg(ap, md_t *);
+    do_check = va_arg(ap, int);
+
+    if (reg->domains_frozen) return APR_EACCES; 
     mine = md_clone(ptemp, md);
-    if (APR_SUCCESS == (rv = check_values(reg, ptemp, md, MD_UPD_ALL))
-        && APR_SUCCESS == (rv = state_init(reg, ptemp, mine, 0))
-        && APR_SUCCESS == (rv = md_save(reg->store, p, MD_SG_DOMAINS, mine, 1))) {
-    }
+    if (do_check && APR_SUCCESS != (rv = check_values(reg, ptemp, md, MD_UPD_ALL))) goto leave;
+    if (APR_SUCCESS != (rv = state_init(reg, ptemp, mine))) goto leave;
+    rv = md_save(reg->store, p, MD_SG_DOMAINS, mine, 1);
+leave:
     return rv;
 }
 
+static apr_status_t add_md(md_reg_t *reg, md_t *md, apr_pool_t *p, int do_checks)
+{
+    return md_util_pool_vdo(p_md_add, reg, p, md, do_checks, NULL);
+}
+
 apr_status_t md_reg_add(md_reg_t *reg, md_t *md, apr_pool_t *p)
 {
-    return md_util_pool_vdo(p_md_add, reg, p, md, NULL);
+    return add_md(reg, md, p, 1);
 }
 
 static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
@@ -473,12 +404,13 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v
     apr_status_t rv = APR_SUCCESS;
     const char *name;
     const md_t *md, *updates;
-    int fields;
+    int fields, do_checks;
     md_t *nmd;
     
     name = va_arg(ap, const char *);
     updates = va_arg(ap, const md_t *);
     fields = va_arg(ap, int);
+    do_checks = va_arg(ap, int);
     
     if (NULL == (md = md_reg_get(reg, name, ptemp))) {
         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, APR_ENOENT, ptemp, "md %s", name);
@@ -487,10 +419,11 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v
     
     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "update md %s", name);
     
-    if (APR_SUCCESS != (rv = check_values(reg, ptemp, updates, fields))) {
+    if (do_checks && APR_SUCCESS != (rv = check_values(reg, ptemp, updates, fields))) {
         return rv;
     }
     
+    if (reg->domains_frozen) return APR_EACCES; 
     nmd = md_copy(ptemp, md);
     if (MD_UPD_DOMAINS & fields) {
         nmd->domains = updates->domains;
@@ -516,19 +449,18 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update agreement: %s", name);
         nmd->ca_agreement = updates->ca_agreement;
     }
-    if (MD_UPD_CERT_URL & fields) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update cert url: %s", name);
-        nmd->cert_url = updates->cert_url;
-    }
     if (MD_UPD_DRIVE_MODE & fields) {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update drive-mode: %s", name);
-        nmd->drive_mode = updates->drive_mode;
+        nmd->renew_mode = updates->renew_mode;
     }
     if (MD_UPD_RENEW_WINDOW & fields) {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update renew-window: %s", name);
-        nmd->renew_norm = updates->renew_norm;
         nmd->renew_window = updates->renew_window;
     }
+    if (MD_UPD_WARN_WINDOW & fields) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update warn-window: %s", name);
+        nmd->warn_window = updates->warn_window;
+    }
     if (MD_UPD_CA_CHALLENGES & fields) {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update ca challenges: %s", name);
         nmd->ca_challenges = (updates->ca_challenges? 
@@ -553,83 +485,187 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update must-staple: %s", name);
         nmd->must_staple = updates->must_staple;
     }
+    if (MD_UPD_PROTO & fields) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update proto: %s", name);
+        nmd->acme_tls_1_domains = updates->acme_tls_1_domains;
+    }
     
     if (fields && APR_SUCCESS == (rv = md_save(reg->store, p, MD_SG_DOMAINS, nmd, 0))) {
-        rv = state_init(reg, ptemp, nmd, 0);
+        rv = state_init(reg, ptemp, nmd);
     }
     return rv;
 }
 
+static apr_status_t update_md(md_reg_t *reg, apr_pool_t *p, 
+                              const char *name, const md_t *md, 
+                              int fields, int do_checks)
+{
+    return md_util_pool_vdo(p_md_update, reg, p, name, md, fields, do_checks, NULL);
+}
+
 apr_status_t md_reg_update(md_reg_t *reg, apr_pool_t *p, 
                            const char *name, const md_t *md, int fields)
 {
-    return md_util_pool_vdo(p_md_update, reg, p, name, md, fields, NULL);
+    return update_md(reg, p, name, md, fields, 1);
 }
 
-/**************************************************************************************************/
-/* certificate related */
-
-static int ok_or_noent(apr_status_t rv) 
+apr_status_t md_reg_delete_acct(md_reg_t *reg, apr_pool_t *p, const char *acct_id) 
 {
-    return (APR_SUCCESS == rv || APR_ENOENT == rv);
+    apr_status_t rv = APR_SUCCESS;
+    
+    rv = md_store_remove(reg->store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCOUNT, p, 1);
+    if (APR_SUCCESS == rv) {
+        md_store_remove(reg->store, MD_SG_ACCOUNTS, acct_id, MD_FN_ACCT_KEY, p, 1);
+    }
+    return rv;
 }
 
-static apr_status_t creds_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+/**************************************************************************************************/
+/* certificate related */
+
+static apr_status_t pubcert_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
 {
     md_reg_t *reg = baton;
-    md_pkey_t *privkey;
-    apr_array_header_t *pubcert;
-    md_creds_t *creds, **pcreds;
+    apr_array_header_t *certs;
+    md_pubcert_t *pubcert, **ppubcert;
     const md_t *md;
+    const md_cert_t *cert;
     md_cert_state_t cert_state;
     md_store_group_t group;
     apr_status_t rv;
     
-    pcreds = va_arg(ap, md_creds_t **);
+    ppubcert = va_arg(ap, md_pubcert_t **);
     group = (md_store_group_t)va_arg(ap, int);
     md = va_arg(ap, const md_t *);
     
-    if (ok_or_noent(rv = md_pkey_load(reg->store, group, md->name, &privkey, p))
-        && ok_or_noent(rv = md_pubcert_load(reg->store, group, md->name, &pubcert, p))) {
-        rv = APR_SUCCESS;
+    if (md->cert_file) {
+        rv = md_chain_fload(&certs, p, md->cert_file);
+    }
+    else {
+        rv = md_pubcert_load(reg->store, group, md->name, &certs, p);
+    }
+    if (APR_SUCCESS != rv) goto leave;
             
-        creds = apr_pcalloc(p, sizeof(*creds));
-        creds->privkey = privkey;
-        if (pubcert && pubcert->nelts > 0) {
-            creds->pubcert = pubcert;
-            creds->cert = APR_ARRAY_IDX(pubcert, 0, md_cert_t *);
-        }
-        if (creds->cert) {
-            switch ((cert_state = md_cert_state_get(creds->cert))) {
-                case MD_CERT_VALID:
-                    creds->expired = 0;
-                    break;
-                case MD_CERT_EXPIRED:
-                    creds->expired = 1;
-                    break;
-                default:
-                    md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, ptemp, 
-                                  "md %s has unexpected cert state: %d", md->name, cert_state);
-                    rv = APR_ENOTIMPL;
-                    break;
-            }
-        }
+    pubcert = apr_pcalloc(p, sizeof(*pubcert));
+    pubcert->certs = certs;
+    cert = APR_ARRAY_IDX(certs, 0, const md_cert_t *);
+    if (APR_SUCCESS != (rv = md_cert_get_alt_names(&pubcert->alt_names, cert, p))) goto leave;
+    switch ((cert_state = md_cert_state_get(cert))) {
+        case MD_CERT_VALID:
+        case MD_CERT_EXPIRED:
+            break;
+        default:
+            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, ptemp, 
+                          "md %s has unexpected cert state: %d", md->name, cert_state);
+            rv = APR_ENOTIMPL;
+            break;
     }
-    *pcreds = (APR_SUCCESS == rv)? creds : NULL;
+leave:
+    *ppubcert = (APR_SUCCESS == rv)? pubcert : NULL;
     return rv;
 }
 
-apr_status_t md_reg_creds_get(const md_creds_t **pcreds, md_reg_t *reg, 
-                              md_store_group_t group, const md_t *md, apr_pool_t *p)
+apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg, 
+                                const md_t *md, apr_pool_t *p)
 {
     apr_status_t rv = APR_SUCCESS;
-    md_creds_t *creds;
-    
-    rv = md_util_pool_vdo(creds_load, reg, p, &creds, group, md, NULL);
-    *pcreds = (APR_SUCCESS == rv)? creds : NULL;
+    const md_pubcert_t *pubcert;
+    const char *name;
+
+    pubcert = apr_hash_get(reg->certs, md->name, (apr_ssize_t)strlen(md->name));
+    if (!pubcert && !reg->domains_frozen) {
+        rv = md_util_pool_vdo(pubcert_load, reg, reg->p, &pubcert, MD_SG_DOMAINS, md, NULL);
+        if (APR_STATUS_IS_ENOENT(rv)) {
+            /* We cache it missing with an empty record */
+            pubcert = apr_pcalloc(reg->p, sizeof(*pubcert));
+        }
+        else if (APR_SUCCESS != rv) goto leave;
+        name = (p != reg->p)? apr_pstrdup(reg->p, md->name) : md->name;
+        apr_hash_set(reg->certs, name, (apr_ssize_t)strlen(name), pubcert);
+    }
+leave:
+    if (APR_SUCCESS == rv && (!pubcert || !pubcert->certs)) {
+        rv = APR_ENOENT;
+    }
+    *ppubcert = (APR_SUCCESS == rv)? pubcert : NULL;
     return rv;
 }
 
+apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile,
+                                   md_reg_t *reg, md_store_group_t group, 
+                                   const md_t *md, apr_pool_t *p)
+{
+    apr_status_t rv;
+    
+    if (md->cert_file) {
+        /* With fixed files configured, we use those without further checking them ourself */
+        *pcertfile = md->cert_file;
+        *pkeyfile = md->pkey_file;
+        return APR_SUCCESS;
+    }
+    rv = md_store_get_fname(pkeyfile, reg->store, group, md->name, MD_FN_PRIVKEY, p);
+    if (APR_SUCCESS != rv) return rv;
+    if (!md_file_exists(*pkeyfile, p)) return APR_ENOENT;
+    rv = md_store_get_fname(pcertfile, reg->store, group, md->name, MD_FN_PUBCERT, p);
+    if (APR_SUCCESS != rv) return rv;
+    if (!md_file_exists(*pcertfile, p)) return APR_ENOENT;
+    return APR_SUCCESS;
+}
+
+int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p) 
+{
+    const md_pubcert_t *pub;
+    const md_cert_t *cert;
+    md_timeperiod_t certlife, renewal;
+    apr_status_t rv;
+    
+    if (md->state == MD_S_INCOMPLETE) return 1;
+    rv = md_reg_get_pubcert(&pub, reg, md, p);
+    if (APR_STATUS_IS_ENOENT(rv)) return 1;
+    if (APR_SUCCESS == rv) {
+        cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+        certlife.start = md_cert_get_not_before(cert);
+        certlife.end = md_cert_get_not_after(cert);
+
+        renewal = md_timeperiod_slice_before_end(&certlife, md->renew_window);
+        if (md_log_is_level(p, MD_LOG_TRACE1)) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, 
+                          "md[%s]: cert-life[%s] renewal[%s]", md->name, 
+                          md_timeperiod_print(p, &certlife),
+                          md_timeperiod_print(p, &renewal));
+        }
+        return md_timeperiod_has_started(&renewal, apr_time_now());
+    }
+    return 0;
+}
+
+int md_reg_should_warn(md_reg_t *reg, const md_t *md, apr_pool_t *p)
+{
+    const md_pubcert_t *pub;
+    const md_cert_t *cert;
+    md_timeperiod_t certlife, warn;
+    apr_status_t rv;
+    
+    if (md->state == MD_S_INCOMPLETE) return 0;
+    rv = md_reg_get_pubcert(&pub, reg, md, p);
+    if (APR_STATUS_IS_ENOENT(rv)) return 0;
+    if (APR_SUCCESS == rv) {
+        cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
+        certlife.start = md_cert_get_not_before(cert);
+        certlife.end = md_cert_get_not_after(cert);
+
+        warn = md_timeperiod_slice_before_end(&certlife, md->warn_window);
+        if (md_log_is_level(p, MD_LOG_TRACE1)) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, 
+                          "md[%s]: cert-life[%s] warn[%s]", md->name, 
+                          md_timeperiod_print(p, &certlife),
+                          md_timeperiod_print(p, &warn));
+        }
+        return md_timeperiod_has_started(&warn, apr_time_now());
+    }
+    return 0;
+}
+
 /**************************************************************************************************/
 /* synching */
 
@@ -654,7 +690,7 @@ static apr_status_t read_store_mds(md_reg_t *reg, sync_ctx *ctx)
     
     apr_array_clear(ctx->store_mds);
     rv = md_store_md_iter(do_add_md, ctx, reg->store, ctx->p, MD_SG_DOMAINS, "*");
-    if (APR_STATUS_IS_ENOENT(rv)) {
+    if (APR_STATUS_IS_ENOENT(rv) || APR_STATUS_IS_EINVAL(rv)) {
         rv = APR_SUCCESS;
     }
     return rv;
@@ -665,6 +701,7 @@ apr_status_t md_reg_set_props(md_reg_t *reg, apr_pool_t *p, int can_http, int ca
     if (reg->can_http != can_http || reg->can_https != can_https) {
         md_json_t *json;
         
+        if (reg->domains_frozen) return APR_EACCES; 
         reg->can_http = can_http;
         reg->can_https = can_https;
         
@@ -676,6 +713,10 @@ apr_status_t md_reg_set_props(md_reg_t *reg, apr_pool_t *p, int can_http, int ca
     }
     return APR_SUCCESS;
 }
+
+static apr_status_t update_md(md_reg_t *reg, apr_pool_t *p, 
+                              const char *name, const md_t *md, 
+                              int fields, int do_checks);
  
 /**
  * Procedure:
@@ -700,11 +741,12 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
     apr_status_t rv;
 
     ctx.p = ptemp;
-    ctx.store_mds = apr_array_make(ptemp,100, sizeof(md_t *));
+    ctx.store_mds = apr_array_make(ptemp, 100, sizeof(md_t *));
     rv = read_store_mds(reg, &ctx);
     
     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
                   "sync: found %d mds in store", ctx.store_mds->nelts);
+    if (reg->domains_frozen) return APR_EACCES; 
     if (APR_SUCCESS == rv) {
         int i, fields;
         md_t *md, *config_md, *smd, *omd;
@@ -718,8 +760,11 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
             if (smd) {
                 fields = 0;
                 
-                /* Once stored, we keep the name */
+                /* Did the name change? This happens when the order of names in configuration
+                 * changes or when the first name is removed. Use the name from the store, but
+                 * remember the original one. We try to align this later on. */
                 if (strcmp(md->name, smd->name)) {
+                    md->configured_name = md->name;
                     md->name = apr_pstrdup(p, smd->name);
                 }
                 
@@ -758,11 +803,17 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
                             md_reg_remove(reg, ptemp, omd->name, 1); /* best effort */
                         }
                         else {
-                            rv = md_reg_update(reg, ptemp, omd->name, omd, MD_UPD_DOMAINS);
+                            rv = update_md(reg, ptemp, omd->name, omd, MD_UPD_DOMAINS, 0);
                         }
                     }
                 }
 
+                /* If no CA url/proto is configured for the MD, take the default */
+                if (!md->ca_url) {
+                    md->ca_url = MD_ACME_DEF_URL;
+                    md->ca_proto = MD_PROTO_ACME; 
+                }
+                
                 if (MD_SVAL_UPDATE(md, smd, ca_url)) {
                     smd->ca_url = md->ca_url;
                     fields |= MD_UPD_CA_URL;
@@ -779,8 +830,8 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
                     smd->transitive = md->transitive;
                     fields |= MD_UPD_TRANSITIVE;
                 }
-                if (MD_VAL_UPDATE(md, smd, drive_mode)) {
-                    smd->drive_mode = md->drive_mode;
+                if (MD_VAL_UPDATE(md, smd, renew_mode)) {
+                    smd->renew_mode = md->renew_mode;
                     fields |= MD_UPD_DRIVE_MODE;
                 }
                 if (!apr_is_empty_array(md->contacts) 
@@ -788,15 +839,14 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
                     smd->contacts = md->contacts;
                     fields |= MD_UPD_CONTACTS;
                 }
-                if (MD_VAL_UPDATE(md, smd, renew_window) 
-                    || MD_VAL_UPDATE(md, smd, renew_norm)) {
-                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                                  "%s: update renew norm=%ld, window=%ld", 
-                                  smd->name, (long)md->renew_norm, (long)md->renew_window);
-                    smd->renew_norm = md->renew_norm;
+                if (!md_timeslice_eq(md->renew_window, smd->renew_window)) {
                     smd->renew_window = md->renew_window;
                     fields |= MD_UPD_RENEW_WINDOW;
                 }
+                if (!md_timeslice_eq(md->warn_window, smd->warn_window)) {
+                    smd->warn_window = md->warn_window;
+                    fields |= MD_UPD_WARN_WINDOW;
+                }
                 if (md->ca_challenges) {
                     md->ca_challenges = md_array_str_compact(p, md->ca_challenges, 0);
                     if (!smd->ca_challenges 
@@ -824,15 +874,24 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
                     smd->must_staple = md->must_staple;
                     fields |= MD_UPD_MUST_STAPLE;
                 }
+                if (!md_array_str_eq(md->acme_tls_1_domains, smd->acme_tls_1_domains, 0)) {
+                    smd->acme_tls_1_domains = md->acme_tls_1_domains;
+                    fields |= MD_UPD_PROTO;
+                }
                 
                 if (fields) {
-                    rv = md_reg_update(reg, ptemp, smd->name, smd, fields);
+                    rv = update_md(reg, ptemp, smd->name, smd, fields, 0);
                     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "md %s updated", smd->name);
                 }
             }
             else {
                 /* new managed domain */
-                rv = md_reg_add(reg, md, ptemp);
+                /* If no CA url/proto is configured for the MD, take the default */
+                if (!md->ca_url) {
+                    md->ca_url = MD_ACME_DEF_URL;
+                    md->ca_proto = MD_PROTO_ACME; 
+                }
+                rv = add_md(reg, md, ptemp, 0);
                 md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "new md %s added", md->name);
             }
         }
@@ -846,159 +905,257 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
 
 apr_status_t md_reg_remove(md_reg_t *reg, apr_pool_t *p, const char *name, int archive)
 {
+    if (reg->domains_frozen) return APR_EACCES; 
     return md_store_move(reg->store, p, MD_SG_DOMAINS, MD_SG_ARCHIVE, name, archive);
 }
 
+typedef struct {
+    md_reg_t *reg;
+    apr_pool_t *p;
+    apr_array_header_t *mds;
+} cleanup_challenge_ctx;
+static apr_status_t cleanup_challenge_inspector(void *baton, const char *dir, const char *name, 
+                                                md_store_vtype_t vtype, void *value, 
+                                                apr_pool_t *ptemp)
+{
+    cleanup_challenge_ctx *ctx = baton;
+    const md_t *md;
+    int i, used;
+    apr_status_t rv;
+    
+    (void)value;
+    (void)vtype;
+    (void)dir;
+    for (used = 0, i = 0; i < ctx->mds->nelts && !used; ++i) {
+        md = APR_ARRAY_IDX(ctx->mds, i, const md_t *);
+        used = !strcmp(name, md->name);
+    }
+    if (!used) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, 
+                      "challenges/%s: not in use, purging", name);
+        rv = md_store_purge(ctx->reg->store, ctx->p, MD_SG_CHALLENGES, name);
+        if (APR_SUCCESS != rv) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, ptemp, 
+                          "challenges/%s: unable to purge", name);
+        }
+    }
+    return APR_SUCCESS;
+}
+
+apr_status_t md_reg_cleanup_challenges(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, 
+                                       apr_array_header_t *mds)
+{
+    apr_status_t rv;
+    cleanup_challenge_ctx ctx;
+
+    (void)p;
+    ctx.reg = reg;
+    ctx.p = ptemp;
+    ctx.mds = mds;
+    rv = md_store_iter_names(cleanup_challenge_inspector, &ctx, reg->store, ptemp, 
+                             MD_SG_CHALLENGES, "*");
+    return rv;
+}
+
 
 /**************************************************************************************************/
 /* driving */
 
-static apr_status_t init_proto_driver(md_proto_driver_t *driver, const md_proto_t *proto, 
-                                      md_reg_t *reg, const md_t *md, 
-                                      const char *challenge, int reset, apr_pool_t *p) 
+static apr_status_t run_init(void *baton, apr_pool_t *p, ...)
 {
-    apr_status_t rv = APR_SUCCESS;
+    va_list ap;
+    md_reg_t *reg = baton;
+    const md_t *md;
+    md_proto_driver_t *driver, **pdriver;
+    md_result_t *result;
+    apr_table_t *env;
+    
+    (void)p;
+    va_start(ap, p);
+    pdriver = va_arg(ap, md_proto_driver_t **);
+    md = va_arg(ap, const md_t *);
+    env = va_arg(ap, apr_table_t *);
+    result = va_arg(ap, md_result_t *); 
+    va_end(ap);
+    
+    *pdriver = driver = apr_pcalloc(p, sizeof(*driver));
 
-    /* If this registry instance was not synched before (and obtained server
-     * properties that way), read them from the store.
-     */
-    driver->proto = proto;
     driver->p = p;
-    driver->challenge = challenge;
-    driver->can_http = reg->can_http;
-    driver->can_https = reg->can_https;
+    driver->env = env? apr_table_copy(p, env) : apr_table_make(p, 10);
     driver->reg = reg;
     driver->store = md_reg_store_get(reg);
     driver->proxy_url = reg->proxy_url;
     driver->md = md;
-    driver->reset = reset;
+    driver->can_http = reg->can_http;
+    driver->can_https = reg->can_https;
 
-    return rv;
+    if (!md->ca_proto) {
+        md_result_printf(result, APR_EGENERAL, "CA protocol is not defined"); 
+        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "md[%s]: %s", md->name, result->detail);
+        goto leave;
+    }
+    
+    driver->proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto));
+    if (!driver->proto) {
+        md_result_printf(result, APR_EGENERAL, "Unknown CA protocol '%s'", md->ca_proto); 
+        goto leave;
+    }
+    
+    result->status = driver->proto->init(driver, result);
+
+leave:
+    if (APR_SUCCESS != result->status) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, result->status, p, "md[%s]: %s", md->name, 
+                      result->detail? result->detail : "<see error log for details>");
+    }
+    else {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: init done", md->name);
+    }
+    return result->status;
 }
 
-static apr_status_t run_stage(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+static apr_status_t run_test_init(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+    const md_t *md;
+    apr_table_t *env;
+    md_result_t *result;
+    md_proto_driver_t *driver;
+    
+    (void)p;
+    md = va_arg(ap, const md_t *);
+    env = va_arg(ap, apr_table_t *);
+    result = va_arg(ap, md_result_t *); 
+
+    return run_init(baton, ptemp, &driver, md, env, result, NULL);
+}
+
+apr_status_t md_reg_test_init(md_reg_t *reg, const md_t *md, struct apr_table_t *env, 
+                              md_result_t *result, apr_pool_t *p)
+{
+    return md_util_pool_vdo(run_test_init, reg, p, md, env, result, NULL);
+}
+
+static apr_status_t run_renew(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
 {
-    md_reg_t *reg = baton;
-    const md_proto_t *proto;
     const md_t *md;
     int reset;
     md_proto_driver_t *driver;
-    const char *challenge;
-    apr_time_t *pvalid_from;
+    apr_table_t *env;
     apr_status_t rv;
+    md_result_t *result;
     
     (void)p;
-    proto = va_arg(ap, const md_proto_t *);
     md = va_arg(ap, const md_t *);
-    challenge = va_arg(ap, const char *);
+    env = va_arg(ap, apr_table_t *);
     reset = va_arg(ap, int); 
-    pvalid_from = va_arg(ap, apr_time_t*);
-    
-    driver = apr_pcalloc(ptemp, sizeof(*driver));
-    rv = init_proto_driver(driver, proto, reg, md, challenge, reset, ptemp);
-    if (APR_SUCCESS == rv && 
-        APR_SUCCESS == (rv = proto->init(driver))) {
-        
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run staging", md->name);
-        rv = proto->stage(driver);
+    result = va_arg(ap, md_result_t *); 
 
-        if (APR_SUCCESS == rv && pvalid_from) {
-            *pvalid_from = driver->stage_valid_from;
-        }
+    rv = run_init(baton, ptemp, &driver, md, env, result, NULL);
+    if (APR_SUCCESS == rv) { 
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run staging", md->name);
+        driver->reset = reset;
+        rv = driver->proto->renew(driver, result);
     }
     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: staging done", md->name);
     return rv;
 }
 
-apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md, const char *challenge
-                          int reset, apr_time_t *pvalid_from, apr_pool_t *p)
+apr_status_t md_reg_renew(md_reg_t *reg, const md_t *md, apr_table_t *env
+                          int reset, md_result_t *result, apr_pool_t *p)
 {
-    const md_proto_t *proto;
-    
-    if (!md->ca_proto) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "md %s has no CA protocol", md->name);
-        ((md_t *)md)->state = MD_S_ERROR;
-        return APR_SUCCESS;
-    }
-    
-    proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto));
-    if (!proto) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, 
-                      "md %s has unknown CA protocol: %s", md->name, md->ca_proto);
-        ((md_t *)md)->state = MD_S_ERROR;
-        return APR_EINVAL;
-    }
-    
-    return md_util_pool_vdo(run_stage, reg, p, proto, md, challenge, reset, pvalid_from, NULL);
+    return md_util_pool_vdo(run_renew, reg, p, md, env, reset, result, NULL);
 }
 
-static apr_status_t run_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+static apr_status_t run_load_staging(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
 {
     md_reg_t *reg = baton;
-    const char *name;
-    const md_proto_t *proto;
-    const md_t *md, *nmd;
+    const md_t *md;
     md_proto_driver_t *driver;
+    md_result_t *result;
+    apr_table_t *env;
+    md_job_t *job;
     apr_status_t rv;
     
-    name = va_arg(ap, const char *);
+    /* For the MD,  check if something is in the STAGING area. If none is there, 
+     * return that status. Otherwise ask the protocol driver to preload it into
+     * a new, temporary area. 
+     * If that succeeds, we move the TEMP area over the DOMAINS (causing the 
+     * existing one go to ARCHIVE).
+     * Finally, we clean up the data from CHALLENGES and STAGING.
+     */
+    md = va_arg(ap, const md_t*);
+    env =  va_arg(ap, apr_table_t*);
+    result =  va_arg(ap, md_result_t*);
     
-    if (APR_STATUS_IS_ENOENT(rv = md_load(reg->store, MD_SG_STAGING, name, NULL, ptemp))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, ptemp, "%s: nothing staged", name);
-        return APR_ENOENT;
+    if (APR_STATUS_IS_ENOENT(rv = md_load(reg->store, MD_SG_STAGING, md->name, NULL, ptemp))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, ptemp, "%s: nothing staged", md->name);
+        goto out;
     }
     
-    md = md_reg_get(reg, name, p);
-    if (!md) {
-        return APR_ENOENT;
-    }
+    rv = run_init(baton, ptemp, &driver, md, env, result, NULL);
+    if (APR_SUCCESS != rv) goto out;
     
-    if (!md->ca_proto) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "md %s has no CA protocol", name);
-        ((md_t *)md)->state = MD_S_ERROR;
-        return APR_EINVAL;
+    apr_hash_set(reg->certs, md->name, (apr_ssize_t)strlen(md->name), NULL);
+    md_result_activity_setn(result, "preloading staged to tmp");
+    rv = driver->proto->preload(driver, MD_SG_TMP, result);
+    if (APR_SUCCESS != rv) goto out;
+
+    /* If we had a job saved in STAGING, copy it over too */
+    job = md_job_make(ptemp, md->name);
+    if (APR_SUCCESS == md_job_load(job, reg, MD_SG_STAGING, ptemp)) {
+        md_job_save(job, reg, MD_SG_TMP, NULL, ptemp);
     }
     
-    proto = apr_hash_get(reg->protos, md->ca_proto, (apr_ssize_t)strlen(md->ca_proto));
-    if (!proto) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, 
-                      "md %s has unknown CA protocol: %s", md->name, md->ca_proto);
-        ((md_t *)md)->state = MD_S_ERROR;
-        return APR_EINVAL;
+    /* swap */
+    md_result_activity_setn(result, "moving tmp to become new domains");
+    rv = md_store_move(reg->store, p, MD_SG_TMP, MD_SG_DOMAINS, md->name, 1);
+    if (APR_SUCCESS != rv) {
+        md_result_set(result, rv, NULL);
+        goto out;
     }
     
-    driver = apr_pcalloc(ptemp, sizeof(*driver));
-    init_proto_driver(driver, proto, reg, md, NULL, 0, ptemp);
+    md_store_purge(reg->store, p, MD_SG_STAGING, md->name);
+    md_store_purge(reg->store, p, MD_SG_CHALLENGES, md->name);
+    md_result_set(result, APR_SUCCESS, "new certificate successfully saved in domains");
 
-    if (APR_SUCCESS == (rv = proto->init(driver))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run load", md->name);
-        
-        if (APR_SUCCESS == (rv = proto->preload(driver, MD_SG_TMP))) {
-            /* swap */
-            rv = md_store_move(reg->store, p, MD_SG_TMP, MD_SG_DOMAINS, md->name, 1);
-            if (APR_SUCCESS == rv) {
-                /* load again */
-                nmd = md_reg_get(reg, md->name, p);
-                if (!nmd) {
-                    rv = APR_ENOENT;
-                    md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "loading md after staging");
-                }
-                else if (nmd->state != MD_S_COMPLETE) {
-                    md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, 
-                                  "md has state %d after load", nmd->state);
-                }
-                
-                md_store_purge(reg->store, p, MD_SG_STAGING, md->name);
-                md_store_purge(reg->store, p, MD_SG_CHALLENGES, md->name);
-            }
-        }
-    }
+out:
     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: load done", md->name);
     return rv;
 }
 
-apr_status_t md_reg_load(md_reg_t *reg, const char *name, apr_pool_t *p)
+apr_status_t md_reg_load_staging(md_reg_t *reg, const md_t *md, apr_table_t *env, 
+                                 md_result_t *result, apr_pool_t *p)
 {
-    return md_util_pool_vdo(run_load, reg, p, name, NULL);
+    if (reg->domains_frozen) return APR_EACCES;
+    return md_util_pool_vdo(run_load_staging, reg, p, md, env, result, NULL);
 }
 
+apr_status_t md_reg_freeze_domains(md_reg_t *reg, apr_array_header_t *mds)
+{
+    apr_status_t rv = APR_SUCCESS;
+    md_t *md;
+    const md_pubcert_t *pubcert;
+    int i;
+    
+    assert(!reg->domains_frozen);
+    /* prefill the certs cache for all mds */
+    for (i = 0; i < mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mds, i, md_t*);
+        rv = md_reg_get_pubcert(&pubcert, reg, md, reg->p);
+        if (APR_SUCCESS != rv && !APR_STATUS_IS_ENOENT(rv)) goto leave;
+    }
+    reg->domains_frozen = 1;
+leave:
+    return rv;
+}
+
+void md_reg_set_renew_window_default(md_reg_t *reg, const md_timeslice_t *renew_window)
+{
+    reg->renew_window = renew_window;
+}
+
+void md_reg_set_warn_window_default(md_reg_t *reg, const md_timeslice_t *warn_window)
+{
+    reg->warn_window = warn_window;
+}
index d976b7fe8038a9a9b092f6848be9bf1fe4d736eb..c026af1db1c9392871bace34c94fe049c6d706d3 100644 (file)
@@ -22,6 +22,7 @@ struct apr_array_header_t;
 struct md_store_t;
 struct md_pkey_t;
 struct md_cert_t;
+struct md_result_t;
 
 /**
  * A registry for managed domains with a md_store_t as persistence.
@@ -30,11 +31,10 @@ struct md_cert_t;
 typedef struct md_reg_t md_reg_t;
 
 /**
- * Initialize the registry, using the pool and loading any existing information
- * from the store.
+ * Create the MD registry, using the pool and store.
  */
-apr_status_t md_reg_init(md_reg_t **preg, apr_pool_t *pm, struct md_store_t *store,
-                         const char *proxy_url);
+apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *pm, struct md_store_t *store,
+                           const char *proxy_url);
 
 struct md_store_t *md_reg_store_get(md_reg_t *reg);
 
@@ -66,9 +66,9 @@ md_t *md_reg_find_overlap(md_reg_t *reg, const md_t *md, const char **pdomain, a
 md_t *md_reg_get(md_reg_t *reg, const char *name, apr_pool_t *p);
 
 /**
- * Assess the capability and need to driving this managed domain.
+ * Re-compute the state of the MD, given current store contents.
  */
-apr_status_t md_reg_assess(md_reg_t *reg, md_t *md, int *perrored, int *prenew, apr_pool_t *p);
+apr_status_t md_reg_reinit_state(md_reg_t *reg, md_t *md, apr_pool_t *p);
 
 /**
  * Callback invoked for every md in the registry. If 0 is returned, iteration stops.
@@ -91,7 +91,6 @@ int md_reg_do(md_reg_do_cb *cb, void *baton, md_reg_t *reg, apr_pool_t *p);
 #define MD_UPD_CA_ACCOUNT    0x0008
 #define MD_UPD_CONTACTS      0x0010
 #define MD_UPD_AGREEMENT     0x0020
-#define MD_UPD_CERT_URL      0x0040
 #define MD_UPD_DRIVE_MODE    0x0080
 #define MD_UPD_RENEW_WINDOW  0x0100
 #define MD_UPD_CA_CHALLENGES 0x0200
@@ -99,6 +98,8 @@ int md_reg_do(md_reg_do_cb *cb, void *baton, md_reg_t *reg, apr_pool_t *p);
 #define MD_UPD_REQUIRE_HTTPS 0x0800
 #define MD_UPD_TRANSITIVE    0x1000
 #define MD_UPD_MUST_STAPLE   0x2000
+#define MD_UPD_PROTO         0x4000
+#define MD_UPD_WARN_WINDOW   0x8000
 #define MD_UPD_ALL           0x7FFFFFFF
 
 /**
@@ -109,14 +110,19 @@ apr_status_t md_reg_update(md_reg_t *reg, apr_pool_t *p,
                            const char *name, const md_t *md, int fields);
 
 /**
- * Get the credentials available for the managed domain md. Returns APR_ENOENT
- * when none is available. The returned values are immutable. 
+ * Get the chain of public certificates of the managed domain md, starting with the cert
+ * of the domain and going up the issuers. Returns APR_ENOENT when not available. 
  */
-apr_status_t md_reg_creds_get(const md_creds_t **pcreds, md_reg_t *reg, 
-                              md_store_group_t group, const md_t *md, apr_pool_t *p);
+apr_status_t md_reg_get_pubcert(const md_pubcert_t **ppubcert, md_reg_t *reg, 
+                                const md_t *md, apr_pool_t *p);
 
-apr_status_t md_reg_get_cred_files(md_reg_t *reg, const md_t *md, apr_pool_t *p,
-                                   const char **pkeyfile, const char **pcertfile);
+/**
+ * Get the filenames of private key and pubcert of the MD - if they exist.
+ * @return APR_ENOENT if one or both do not exist.
+ */
+apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile,
+                                   md_reg_t *reg, md_store_group_t group, 
+                                   const md_t *md, apr_pool_t *p);
 
 /**
  * Synchronise the give master mds with the store.
@@ -126,6 +132,44 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp,
 
 apr_status_t md_reg_remove(md_reg_t *reg, apr_pool_t *p, const char *name, int archive);
 
+/**
+ * Delete the account from the local store.
+ */
+apr_status_t md_reg_delete_acct(md_reg_t *reg, apr_pool_t *p, const char *acct_id);
+
+
+/**
+ * Cleanup any challenges that are no longer in use.
+ * 
+ * @param reg   the registry
+ * @param p     pool for permament storage
+ * @param ptemp pool for temporary storage
+ * @param mds   the list of configured MDs
+ */
+apr_status_t md_reg_cleanup_challenges(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, 
+                                       apr_array_header_t *mds);
+
+/**
+ * Mark all information from group MD_SG_DOMAINS as readonly, deny future modifications 
+ * (MD_SG_STAGING and MD_SG_CHALLENGES remain writeable). For the given MDs, cache
+ * the public information (MDs themselves and their pubcerts or lack of).
+ */
+apr_status_t md_reg_freeze_domains(md_reg_t *reg, apr_array_header_t *mds);
+
+/**
+ * Return if the certificate of the MD shoud be renewed. This includes reaching
+ * the renewal window of an otherwise valid certificate. It return also !0 iff
+ * no certificate has been obtained yet.
+ */
+int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p);
+
+/**
+ * Return if a warning should be issued about the certificate expiration. 
+ * This applies the configured warn window to the remaining lifetime of the 
+ * current certiciate. If no certificate is present, this returns 0.
+ */
+int md_reg_should_warn(md_reg_t *reg, const md_t *md, apr_pool_t *p);
+
 /**************************************************************************************************/
 /* protocol drivers */
 
@@ -133,47 +177,69 @@ typedef struct md_proto_t md_proto_t;
 
 typedef struct md_proto_driver_t md_proto_driver_t;
 
+/** 
+ * Operating environment for a protocol driver. This is valid only for the
+ * duration of one run (init + renew, init + preload).
+ */
 struct md_proto_driver_t {
     const md_proto_t *proto;
     apr_pool_t *p;
-    const char *challenge;
-    int can_http;
-    int can_https;
-    struct md_store_t *store;
+    void *baton;
+    struct apr_table_t *env;
+
     md_reg_t *reg;
+    struct md_store_t *store;
+    const char *proxy_url;
     const md_t *md;
-    void *baton;
+
+    int can_http;
+    int can_https;
     int reset;
-    apr_time_t stage_valid_from;
-    const char *proxy_url;
 };
 
-typedef apr_status_t md_proto_init_cb(md_proto_driver_t *driver);
-typedef apr_status_t md_proto_stage_cb(md_proto_driver_t *driver);
-typedef apr_status_t md_proto_preload_cb(md_proto_driver_t *driver, md_store_group_t group);
+typedef apr_status_t md_proto_init_cb(md_proto_driver_t *driver, struct md_result_t *result);
+typedef apr_status_t md_proto_renew_cb(md_proto_driver_t *driver, struct md_result_t *result);
+typedef apr_status_t md_proto_preload_cb(md_proto_driver_t *driver, 
+                                         md_store_group_t group, struct md_result_t *result);
 
 struct md_proto_t {
     const char *protocol;
     md_proto_init_cb *init;
-    md_proto_stage_cb *stage;
+    md_proto_renew_cb *renew;
     md_proto_preload_cb *preload;
 };
 
+/**
+ * Run a test intialization of the renew protocol for the given MD. This verifies
+ * basic parameter settings and is expected to return a description of encountered
+ * problems in <pmessage> when != APR_SUCCESS.
+ * A message return is allocated fromt the given pool.
+ */
+apr_status_t md_reg_test_init(md_reg_t *reg, const md_t *md, struct apr_table_t *env, 
+                              struct md_result_t *result, apr_pool_t *p);
 
 /**
- * Stage a new credentials set for the given managed domain in a separate location
- * without interfering with any existing credentials.
+ * Obtain new credentials for the given managed domain in STAGING.
+ *
+ * @return APR_SUCCESS if new credentials have been staged successfully
  */
-apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md, 
-                          const char *challenge, int reset, 
-                          apr_time_t *pvalid_from, apr_pool_t *p);
+apr_status_t md_reg_renew(md_reg_t *reg, const md_t *md, 
+                          struct apr_table_t *env, int reset, 
+                          struct md_result_t *result, apr_pool_t *p);
 
 /**
- * Load a staged set of new credentials for the managed domain. This will archive
- * any existing credential data and make the staged set the new live one.
+ * Load a new set of credentials for the managed domain from STAGING - if it exists. 
+ * This will archive any existing credential data and make the staged set the new one
+ * in DOMAINS.
  * If staging is incomplete or missing, the load will fail and all credentials remain
  * as they are.
+ *
+ * @return APR_SUCCESS on loading new data, APR_ENOENT when nothing is staged, error otherwise.
  */
-apr_status_t md_reg_load(md_reg_t *reg, const char *name, apr_pool_t *p);
+apr_status_t md_reg_load_staging(md_reg_t *reg, const md_t *md, struct apr_table_t *env, 
+                                 struct md_result_t *result, apr_pool_t *p);
+
+void md_reg_set_renew_window_default(md_reg_t *reg, const md_timeslice_t *renew_window);
+void md_reg_set_warn_window_default(md_reg_t *reg, const md_timeslice_t *warn_window);
 
 #endif /* mod_md_md_reg_h */
diff --git a/modules/md/md_result.c b/modules/md/md_result.c
new file mode 100644 (file)
index 0000000..4076d5b
--- /dev/null
@@ -0,0 +1,245 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_date.h>
+#include <apr_time.h>
+#include <apr_strings.h>
+
+#include "md.h"
+#include "md_json.h"
+#include "md_log.h"
+#include "md_result.h"
+
+static const char *dup_trim(apr_pool_t *p, const char *s)
+{
+    char *d = apr_pstrdup(p, s);
+    apr_collapse_spaces(d, d);
+    return d;
+}
+
+md_result_t *md_result_make(apr_pool_t *p, apr_status_t status)
+{
+    md_result_t *result;
+    
+    result = apr_pcalloc(p, sizeof(*result));
+    result->p = p;
+    result->status = status;
+    return result;
+}
+
+md_result_t *md_result_md_make(apr_pool_t *p, const struct md_t *md)
+{
+    md_result_t *result = md_result_make(p, APR_SUCCESS);
+    result->md = md;
+    return result;
+}
+
+void md_result_reset(md_result_t *result)
+{
+    apr_pool_t *p = result->p;
+    memset(result, 0, sizeof(*result));
+    result->p = p;
+}
+
+static void on_change(md_result_t *result)
+{
+    if (result->on_change) result->on_change(result, result->on_change_data);
+}
+
+void md_result_activity_set(md_result_t *result, const char *activity)
+{
+    md_result_activity_setn(result, activity? apr_pstrdup(result->p, activity) : NULL);
+}
+
+void md_result_activity_setn(md_result_t *result, const char *activity)
+{
+    result->activity = activity;
+    result->problem = result->detail = NULL;
+    on_change(result);
+}
+
+void md_result_activity_printf(md_result_t *result, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    md_result_activity_setn(result, apr_pvsprintf(result->p, fmt, ap));
+    va_end(ap);
+}
+
+void md_result_set(md_result_t *result, apr_status_t status, const char *detail)
+{
+    result->status = status;
+    result->problem = NULL;
+    result->detail = detail? apr_pstrdup(result->p, detail) : NULL;
+    on_change(result);
+}
+
+void md_result_problem_set(md_result_t *result, apr_status_t status,
+                           const char *problem, const char *detail)
+{
+    result->status = status;
+    result->problem = dup_trim(result->p, problem);
+    result->detail = apr_pstrdup(result->p, detail);
+    on_change(result);
+}
+
+void md_result_problem_printf(md_result_t *result, apr_status_t status,
+                              const char *problem, const char *fmt, ...)
+{
+    va_list ap;
+
+    result->status = status;
+    result->problem = dup_trim(result->p, problem);
+
+    va_start(ap, fmt);
+    result->detail = apr_pvsprintf(result->p, fmt, ap);
+    va_end(ap);
+    on_change(result);
+}
+
+void md_result_printf(md_result_t *result, apr_status_t status, const char *fmt, ...)
+{
+    va_list ap;
+
+    result->status = status;
+    va_start(ap, fmt);
+    result->detail = apr_pvsprintf(result->p, fmt, ap);
+    va_end(ap);
+    on_change(result);
+}
+
+void md_result_delay_set(md_result_t *result, apr_time_t ready_at)
+{
+    result->ready_at = ready_at;
+    on_change(result);
+}
+
+md_result_t*md_result_from_json(const struct md_json_t *json, apr_pool_t *p)
+{
+    md_result_t *result;
+    const char *s;
+    
+    result = md_result_make(p, APR_SUCCESS);
+    result->status = (int)md_json_getl(json, MD_KEY_STATUS, NULL);
+    result->problem = md_json_dups(p, json, MD_KEY_PROBLEM, NULL);
+    result->detail = md_json_dups(p, json, MD_KEY_DETAIL, NULL);
+    result->activity = md_json_dups(p, json, MD_KEY_ACTIVITY, NULL);
+    s = md_json_dups(p, json, MD_KEY_VALID_FROM, NULL);
+    if (s && *s) result->ready_at = apr_date_parse_rfc(s);
+
+    return result;
+}
+
+struct md_json_t *md_result_to_json(const md_result_t *result, apr_pool_t *p)
+{
+    md_json_t *json;
+    char ts[APR_RFC822_DATE_LEN];
+   
+    json = md_json_create(p);
+    md_json_setl(result->status, json, MD_KEY_STATUS, NULL);
+    if (result->status > 0) {
+        char buffer[HUGE_STRING_LEN];
+        apr_strerror(result->status, buffer, sizeof(buffer));
+        md_json_sets(buffer, json, "status-description", NULL);
+    }
+    if (result->problem) md_json_sets(result->problem, json, MD_KEY_PROBLEM, NULL);
+    if (result->detail) md_json_sets(result->detail, json, MD_KEY_DETAIL, NULL);
+    if (result->activity) md_json_sets(result->activity, json, MD_KEY_ACTIVITY, NULL);
+    if (result->ready_at > 0) {
+        apr_rfc822_date(ts, result->ready_at);
+        md_json_sets(ts, json, MD_KEY_VALID_FROM, NULL);
+    }
+    return json;
+}
+
+static int str_cmp(const char *s1, const char *s2)
+{
+    if (s1 == s2) return 0;
+    if (!s1) return -1;
+    if (!s2) return 1;
+    return strcmp(s1, s2);
+}
+
+int md_result_cmp(const md_result_t *r1, const md_result_t *r2)
+{
+    int n;
+    if (r1 == r2) return 0;
+    if (!r1) return -1;
+    if (!r2) return 1;
+    if ((n = r1->status - r2->status)) return n;
+    if ((n = str_cmp(r1->problem, r2->problem))) return n;
+    if ((n = str_cmp(r1->detail, r2->detail))) return n;
+    if ((n = str_cmp(r1->activity, r2->activity))) return n;
+    return (int)(r1->ready_at - r2->ready_at);
+}
+
+void md_result_assign(md_result_t *dest, const md_result_t *src)
+{
+   dest->status = src->status;
+   dest->problem = src->problem;
+   dest->detail = src->detail;
+   dest->activity = src->activity;
+   dest->ready_at = src->ready_at;
+}
+
+void md_result_dup(md_result_t *dest, const md_result_t *src)
+{
+   dest->status = src->status;
+   dest->problem = src->problem? dup_trim(dest->p, src->problem) : NULL; 
+   dest->detail = src->detail? apr_pstrdup(dest->p, src->detail) : NULL; 
+   dest->activity = src->activity? apr_pstrdup(dest->p, src->activity) : NULL; 
+   dest->ready_at = src->ready_at;
+   on_change(dest);
+}
+
+void md_result_log(md_result_t *result, int level)
+{
+    if (md_log_is_level(result->p, (md_log_level_t)level)) {
+        const char *sep = "";
+        const char *msg = "";
+        
+        if (result->md) {
+            msg = apr_psprintf(result->p, "md[%s]", result->md->name);
+            sep = " ";
+        }
+        if (result->activity) {
+            msg = apr_psprintf(result->p, "%s%swhile[%s]", msg, sep, result->activity);
+            sep = " ";
+        }
+        if (result->problem) {
+            msg = apr_psprintf(result->p, "%s%sproblem[%s]", msg, sep, result->problem);
+            sep = " ";
+        }
+        if (result->detail) {
+            msg = apr_psprintf(result->p, "%s%sdetail[%s]", msg, sep, result->detail);
+            sep = " ";
+        }
+        md_log_perror(MD_LOG_MARK, (md_log_level_t)level, result->status, result->p, "%s", msg);
+    }
+}
+
+void md_result_on_change(md_result_t *result, md_result_change_cb *cb, void *data)
+{
+    result->on_change = cb;
+    result->on_change_data = data;
+}
diff --git a/modules/md/md_result.h b/modules/md/md_result.h
new file mode 100644 (file)
index 0000000..13c5cd2
--- /dev/null
@@ -0,0 +1,71 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_result_h
+#define mod_md_md_result_h
+
+struct md_json_t;
+struct md_t;
+
+typedef struct md_result_t md_result_t;
+
+typedef void md_result_change_cb(md_result_t *result, void *data);
+
+struct md_result_t {
+    apr_pool_t *p;
+    const struct md_t *md;
+    apr_status_t status;
+    const char *problem;
+    const char *detail;
+    const char *activity;
+    apr_time_t ready_at;
+    md_result_change_cb *on_change;
+    void *on_change_data;
+};
+
+md_result_t *md_result_make(apr_pool_t *p, apr_status_t status);
+md_result_t *md_result_md_make(apr_pool_t *p, const struct md_t *md);
+void md_result_reset(md_result_t *result);
+
+void md_result_activity_set(md_result_t *result, const char *activity);
+void md_result_activity_setn(md_result_t *result, const char *activity);
+void md_result_activity_printf(md_result_t *result, const char *fmt, ...);
+
+void md_result_set(md_result_t *result, apr_status_t status, const char *detail);
+void md_result_problem_set(md_result_t *result, apr_status_t status, 
+                           const char *problem, const char *detail);
+void md_result_problem_printf(md_result_t *result, apr_status_t status,
+                              const char *problem, const char *fmt, ...);
+
+#define MD_RESULT_LOG_ID(logno)       "urn:org:apache:httpd:log:"logno
+
+void md_result_printf(md_result_t *result, apr_status_t status, const char *fmt, ...);
+
+void md_result_delay_set(md_result_t *result, apr_time_t ready_at);
+
+md_result_t*md_result_from_json(const struct md_json_t *json, apr_pool_t *p);
+struct md_json_t *md_result_to_json(const md_result_t *result, apr_pool_t *p);
+
+int md_result_cmp(const md_result_t *r1, const md_result_t *r2);
+
+void md_result_assign(md_result_t *dest, const md_result_t *src);
+void md_result_dup(md_result_t *dest, const md_result_t *src);
+
+void md_result_log(md_result_t *result, int level);
+
+void md_result_on_change(md_result_t *result, md_result_change_cb *cb, void *data);
+
+#endif /* mod_md_md_result_h */
diff --git a/modules/md/md_status.c b/modules/md/md_status.c
new file mode 100644 (file)
index 0000000..8aa1de2
--- /dev/null
@@ -0,0 +1,364 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+#include <apr_time.h>
+#include <apr_date.h>
+
+#include "md_json.h"
+#include "md.h"
+#include "md_crypt.h"
+#include "md_log.h"
+#include "md_store.h"
+#include "md_result.h"
+#include "md_reg.h"
+#include "md_util.h"
+#include "md_status.h"
+
+#define MD_STATUS_WITH_SCTS     0
+
+/**************************************************************************************************/
+/* certificate status information */
+
+static apr_status_t status_get_cert_json(md_json_t **pjson, const md_cert_t *cert, apr_pool_t *p)
+{
+    char ts[APR_RFC822_DATE_LEN];
+    const char *finger;
+    apr_status_t rv = APR_SUCCESS;
+    md_json_t *json;
+    
+    json = md_json_create(p);
+    apr_rfc822_date(ts, md_cert_get_not_before(cert));
+    md_json_sets(ts, json, MD_KEY_VALID_FROM, NULL);
+    apr_rfc822_date(ts, md_cert_get_not_after(cert));
+    md_json_sets(ts, json, MD_KEY_VALID_UNTIL, NULL);
+    md_json_sets(md_cert_get_serial_number(cert, p), json, MD_KEY_SERIAL, NULL);
+    if (APR_SUCCESS != (rv = md_cert_to_sha256_fingerprint(&finger, cert, p))) goto leave;
+    md_json_sets(finger, json, MD_KEY_SHA256_FINGERPRINT, NULL);
+
+#if MD_STATUS_WITH_SCTS
+    do {
+        apr_array_header_t *scts;
+        const char *hex;
+        const md_sct *sct;
+        md_json_t *sctj;
+        int i;
+        
+        scts = apr_array_make(p, 5, sizeof(const md_sct*));
+        if (APR_SUCCESS == md_cert_get_ct_scts(scts, p, cert)) {
+            for (i = 0; i < scts->nelts; ++i) {
+                sct = APR_ARRAY_IDX(scts, i, const md_sct*);
+                sctj = md_json_create(p);
+                
+                apr_rfc822_date(ts, sct->timestamp);
+                md_json_sets(ts, sctj, "signed", NULL);
+                md_json_setl(sct->version, sctj, MD_KEY_VERSION, NULL);
+                md_data_to_hex(&hex, 0, p, sct->logid);
+                md_json_sets(hex, sctj, "logid", NULL);
+                md_data_to_hex(&hex, 0, p, sct->signature);
+                md_json_sets(hex, sctj, "signature", NULL);
+                md_json_sets(md_nid_get_sname(sct->signature_type_nid), sctj, "signature-type", NULL);
+                md_json_addj(sctj, json, "scts", NULL);
+            }
+        }
+    while (0);
+#endif
+leave:
+    *pjson = (APR_SUCCESS == rv)? json : NULL;
+    return rv;
+}
+
+/**************************************************************************************************/
+/* md status information */
+
+static apr_status_t get_staging_cert_json(md_json_t **pjson, apr_pool_t *p, 
+                                          md_reg_t *reg, const md_t *md)
+{ 
+    md_json_t *json = NULL;
+    apr_array_header_t *certs;
+    md_cert_t *cert;
+    apr_status_t rv = APR_SUCCESS;
+    
+    rv = md_pubcert_load(md_reg_store_get(reg), MD_SG_STAGING, md->name, &certs, p);
+    if (APR_STATUS_IS_ENOENT(rv) || certs->nelts == 0) {
+        rv = APR_SUCCESS;
+        goto leave;
+    }
+    else if (APR_SUCCESS != rv) {
+        goto leave;
+    }
+    cert = APR_ARRAY_IDX(certs, 0, md_cert_t *);
+    rv = status_get_cert_json(&json, cert, p);
+leave:
+    *pjson = (APR_SUCCESS == rv)? json : NULL;
+    return rv;
+}
+
+static apr_status_t job_loadj(md_json_t **pjson, const char *name, 
+                              struct md_reg_t *reg, apr_pool_t *p)
+{
+    md_store_t *store = md_reg_store_get(reg);
+    return md_store_load_json(store, MD_SG_STAGING, name, MD_FN_JOB, pjson, p);
+}
+
+apr_status_t md_status_get_md_json(md_json_t **pjson, const md_t *md, 
+                                   md_reg_t *reg, apr_pool_t *p)
+{
+    md_json_t *mdj, *jobj, *certj;
+    int renew;
+    const md_pubcert_t *pubcert;
+    const md_cert_t *cert;
+    apr_status_t rv = APR_SUCCESS;
+
+    mdj = md_to_json(md, p);
+    if (APR_SUCCESS == md_reg_get_pubcert(&pubcert, reg, md, p)) {
+        cert = APR_ARRAY_IDX(pubcert->certs, 0, const md_cert_t*);
+        if (APR_SUCCESS != (rv = status_get_cert_json(&certj, cert, p))) goto leave;
+        md_json_setj(certj, mdj, MD_KEY_CERT, NULL);
+    }
+    
+    renew = md_reg_should_renew(reg, md, p);
+    md_json_setb(renew, mdj, MD_KEY_RENEW, NULL);
+    if (renew) {
+        rv = job_loadj(&jobj, md->name, reg, p);
+        if (APR_SUCCESS == rv) {
+            rv = get_staging_cert_json(&certj, p, reg, md);
+            if (APR_SUCCESS != rv) goto leave;
+            if (certj) md_json_setj(certj, jobj, MD_KEY_CERT, NULL);
+            md_json_setj(jobj, mdj, MD_KEY_RENEWAL, NULL);
+        }
+        else if (APR_STATUS_IS_ENOENT(rv)) rv = APR_SUCCESS;
+        else goto leave;
+    }
+leave:
+    *pjson = (APR_SUCCESS == rv)? mdj : NULL;
+    return rv;
+}
+
+apr_status_t md_status_get_json(md_json_t **pjson, apr_array_header_t *mds, 
+                                md_reg_t *reg, apr_pool_t *p) 
+{
+    md_json_t *json, *mdj;
+    apr_status_t rv = APR_SUCCESS;
+    const md_t *md;
+    int i;
+    
+    json = md_json_create(p);
+    md_json_sets(MOD_MD_VERSION, json, MD_KEY_VERSION, NULL);
+    for (i = 0; i < mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mds, i, const md_t *);
+        rv = md_status_get_md_json(&mdj, md, reg, p);
+        if (APR_SUCCESS != rv) goto leave;
+        md_json_addj(mdj, json, MD_KEY_MDS, NULL);
+    }
+leave:
+    *pjson = (APR_SUCCESS == rv)? json : NULL;
+    return rv;
+}
+
+/**************************************************************************************************/
+/* drive job persistence */
+
+md_job_t *md_job_make(apr_pool_t *p, const char *name)
+{
+    md_job_t *job = apr_pcalloc(p, sizeof(*job));
+    job->name = apr_pstrdup(p, name);
+    job->p = p;
+    return job;
+}
+
+static void md_job_from_json(md_job_t *job, md_json_t *json, apr_pool_t *p)
+{
+    const char *s;
+    
+    /* not good, this is malloced from a temp pool */
+    /*job->name = md_json_gets(json, MD_KEY_NAME, NULL);*/
+    job->finished = md_json_getb(json, MD_KEY_FINISHED, NULL);
+    s = md_json_dups(p, json, MD_KEY_NEXT_RUN, NULL);
+    if (s && *s) job->next_run = apr_date_parse_rfc(s);
+    s = md_json_dups(p, json, MD_KEY_LAST_RUN, NULL);
+    if (s && *s) job->last_run = apr_date_parse_rfc(s);
+    s = md_json_dups(p, json, MD_KEY_VALID_FROM, NULL);
+    if (s && *s) job->valid_from = apr_date_parse_rfc(s);
+    job->error_runs = (int)md_json_getl(json, MD_KEY_ERRORS, NULL);
+    if (md_json_has_key(json, MD_KEY_LAST, NULL)) {
+        job->last_result = md_result_from_json(md_json_getcj(json, MD_KEY_LAST, NULL), p);
+    }
+    job->log = md_json_getj(json, MD_KEY_LOG, NULL);
+}
+
+static void job_to_json(md_json_t *json, const md_job_t *job, 
+                        md_result_t *result, apr_pool_t *p)
+{
+    char ts[APR_RFC822_DATE_LEN];
+
+    md_json_sets(job->name, json, MD_KEY_NAME, NULL);
+    md_json_setb(job->finished, json, MD_KEY_FINISHED, NULL);
+    if (job->next_run > 0) {
+        apr_rfc822_date(ts, job->next_run);
+        md_json_sets(ts, json, MD_KEY_NEXT_RUN, NULL);
+    }
+    if (job->last_run > 0) {
+        apr_rfc822_date(ts, job->last_run);
+        md_json_sets(ts, json, MD_KEY_LAST_RUN, NULL);
+    }
+    if (job->valid_from > 0) {
+        apr_rfc822_date(ts, job->valid_from);
+        md_json_sets(ts, json, MD_KEY_VALID_FROM, NULL);
+    }
+    md_json_setl(job->error_runs, json, MD_KEY_ERRORS, NULL);
+    if (!result) result = job->last_result;
+    if (result) {
+        md_json_setj(md_result_to_json(result, p), json, MD_KEY_LAST, NULL);
+    }
+    if (job->log) md_json_setj(job->log, json, MD_KEY_LOG, NULL);
+}
+
+apr_status_t md_job_load(md_job_t *job, md_reg_t *reg, 
+                         md_store_group_t group, apr_pool_t *p)
+{
+    md_store_t *store = md_reg_store_get(reg);
+    md_json_t *jprops;
+    apr_status_t rv;
+    
+    rv = md_store_load_json(store, group, job->name, MD_FN_JOB, &jprops, p);
+    if (APR_SUCCESS == rv) {
+        md_job_from_json(job, jprops, p);
+    }
+    return rv;
+}
+
+apr_status_t md_job_save(md_job_t *job, struct md_reg_t *reg, 
+                         md_store_group_t group, md_result_t *result, 
+                         apr_pool_t *p)
+{
+    md_store_t *store = md_reg_store_get(reg);
+    md_json_t *jprops;
+    apr_status_t rv;
+    
+    jprops = md_json_create(p);
+    job_to_json(jprops, job, result, p);
+    rv = md_store_save_json(store, p, group, job->name, MD_FN_JOB, jprops, 0);
+    return rv;
+}
+
+void md_job_log_append(md_job_t *job, const char *type, 
+                       const char *status, const char *detail)
+{
+    md_json_t *entry;
+    char ts[APR_RFC822_DATE_LEN];
+    
+    entry = md_json_create(job->p);
+    apr_rfc822_date(ts, apr_time_now());
+    md_json_sets(ts, entry, MD_KEY_WHEN, NULL);
+    md_json_sets(type, entry, MD_KEY_TYPE, NULL);
+    if (status) md_json_sets(status, entry, MD_KEY_STATUS, NULL);
+    if (detail) md_json_sets(detail, entry, MD_KEY_DETAIL, NULL);
+    if (!job->log) job->log = md_json_create(job->p);
+    md_json_insertj(entry, 0, job->log, MD_KEY_ENTRIES, NULL);
+}
+
+typedef struct {
+    md_job_t *job;
+    const char *type;
+    md_json_t *entry;
+    size_t index;
+} log_find_ctx;
+
+static int find_first_log_entry(void *baton, size_t index, md_json_t *entry)
+{
+    log_find_ctx *ctx = baton;
+    const char *etype;
+    
+    etype = md_json_gets(entry, MD_KEY_TYPE, NULL);
+    if (etype == ctx->type || (etype && ctx->type && !strcmp(etype, ctx->type))) {
+        ctx->entry = entry;
+        ctx->index = index;
+        return 0;
+    }
+    return 1;
+}
+
+md_json_t *md_job_log_get_latest(md_job_t *job, const char *type)
+
+{
+    log_find_ctx ctx;
+    ctx.job = job;
+    ctx.type = type;
+    memset(&ctx, 0, sizeof(ctx));
+    if (job->log) md_json_itera(find_first_log_entry, &ctx, job->log, MD_KEY_ENTRIES, NULL);
+    return ctx.entry;
+}
+
+apr_time_t md_job_log_get_time_of_latest(md_job_t *job, const char *type)
+{
+    md_json_t *entry;
+    const char *s;
+    
+    entry = md_job_log_get_latest(job, type);
+    if (entry) {
+        s = md_json_gets(entry, MD_KEY_WHEN, NULL);
+        if (s) return apr_date_parse_rfc(s);
+    }
+    return 0;
+}
+
+void  md_status_take_stock(md_json_t **pjson, apr_array_header_t *mds, 
+                           md_reg_t *reg, apr_pool_t *p)
+{
+    const md_t *md;
+    md_job_t job;
+    int i, complete, renewing, errored, ready, total;
+    md_json_t *json;
+
+    json = md_json_create(p);
+    complete = renewing = errored = ready = total = 0;
+    for (i = 0; i < mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mds, i, const md_t *);
+        ++total;
+        switch (md->state) {
+            case MD_S_COMPLETE: ++complete; /* fall through */
+            case MD_S_INCOMPLETE:
+                if (md_reg_should_renew(reg, md, p)) {
+                    ++renewing;
+                    memset(&job, 0, sizeof(job));
+                    job.name = md->name;
+                    if (APR_SUCCESS == md_job_load(&job, reg, MD_SG_STAGING, p)) {
+                        if (job.error_runs > 0 
+                            || (job.last_result && job.last_result->status != APR_SUCCESS)) {
+                            ++errored;
+                        }
+                        else if (job.finished) {
+                            ++ready;
+                        }
+                    }
+                }
+                break;
+            default: ++errored; break;
+        }
+    }
+    md_json_setl(total, json, MD_KEY_TOTAL, NULL);
+    md_json_setl(complete, json, MD_KEY_COMPLETE, NULL);
+    md_json_setl(renewing, json, MD_KEY_RENEWING, NULL);
+    md_json_setl(errored, json, MD_KEY_ERRORED, NULL);
+    md_json_setl(ready, json, MD_KEY_READY, NULL);
+    *pjson = json;
+}
diff --git a/modules/md/md_status.h b/modules/md/md_status.h
new file mode 100644 (file)
index 0000000..c74d680
--- /dev/null
@@ -0,0 +1,97 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef md_status_h
+#define md_status_h
+
+struct md_json_t;
+struct md_reg_t;
+struct md_result_t;
+
+/** 
+ * Get a JSON summary of the MD and its status (certificates, jobs, etc.).
+ */
+apr_status_t md_status_get_md_json(struct md_json_t **pjson, const md_t *md, 
+                                   struct md_reg_t *reg, apr_pool_t *p);
+
+/** 
+ * Get a JSON summary of all MDs and their status.
+ */
+apr_status_t md_status_get_json(struct md_json_t **pjson, apr_array_header_t *mds, 
+                                struct md_reg_t *reg, apr_pool_t *p);
+
+/**
+ * Take stock of all MDs given for a short overview. The JSON returned
+ * will carry intergers for MD_KEY_COMPLETE, MD_KEY_RENEWING, 
+ * MD_KEY_ERRORED, MD_KEY_READY and MD_KEY_TOTAL.
+ */
+void  md_status_take_stock(struct md_json_t **pjson, apr_array_header_t *mds, 
+                           struct md_reg_t *reg, apr_pool_t *p);
+
+typedef struct md_job_t md_job_t;
+struct md_job_t {
+    const char *name;      /* Name of the MD this job is about */
+    apr_pool_t *p;     
+    apr_time_t next_run;   /* Time this job wants to be processed next */
+    apr_time_t last_run;   /* Time this job ran last (or 0) */
+    struct md_result_t *last_result; /* Result from last run */
+    int finished;          /* true iff the job finished successfully */
+    apr_time_t valid_from; /* at which time the finished job results become valid, 0 if immediate */
+    int error_runs;        /* Number of errored runs of an unfinished job */
+    md_json_t *log;        /* array of log objects with minimum fields
+                              MD_KEY_WHEN (timestamp) and MD_KEY_TYPE (string) */   
+};
+
+/**
+ * Create a new job instance for the given MD name. Job load/save will work
+ * on the MD_SG_STAGING for the name.
+ */
+md_job_t *md_job_make(apr_pool_t *p, const char *name);
+
+/**
+ * Update the job from storage in <group>/job->name.
+ */
+apr_status_t md_job_load(md_job_t *job, struct md_reg_t *reg, 
+                         md_store_group_t group, apr_pool_t *p);
+
+/**
+ * Update storage from job in <group>/job->name.
+ */
+apr_status_t md_job_save(md_job_t *job, struct md_reg_t *reg, 
+                         md_store_group_t group, struct md_result_t *result, 
+                         apr_pool_t *p);
+
+/**
+ * Append to the job's log. Timestamp is automatically added.
+ * @param type          type of log entry
+ * @param status        status of entry (maybe NULL)
+ * @param detail        description of what happened
+ */
+void md_job_log_append(md_job_t *job, const char *type, 
+                       const char *status, const char *detail);
+
+/**
+ * Retrieve the lastest log entry of a certain type.
+ */
+md_json_t *md_job_log_get_latest(md_job_t *job, const char *type);
+
+/**
+ * Get the time the latest log entry of the given type happened, or 0 if
+ * none is found.
+ */
+apr_time_t md_job_log_get_time_of_latest(md_job_t *job, const char *type);
+
+#endif /* md_status_h */
index a047ff3749f380319a64819495f569b696aa441d..c66890eec0694b05880aaa0dcb038a9a6a0bd5a3 100644 (file)
@@ -145,6 +145,12 @@ int md_store_is_newer(md_store_t *store, md_store_group_t group1, md_store_group
     return store->is_newer(store, group1, group2, name, aspect, p);
 }
 
+apr_status_t md_store_iter_names(md_store_inspect *inspect, void *baton, md_store_t *store, 
+                                 apr_pool_t *p, md_store_group_t group, const char *pattern)
+{
+    return store->iterate_names(inspect, baton, store, p, group, pattern);
+}
+
 /**************************************************************************************************/
 /* convenience */
 
@@ -243,32 +249,6 @@ apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t gro
     return md_store_save(store, p, group, name, MD_FN_PRIVKEY, MD_SV_PKEY, pkey, create);
 }
 
-apr_status_t md_cert_load(md_store_t *store, md_store_group_t group, const char *name, 
-                          struct md_cert_t **pcert, apr_pool_t *p)
-{
-    return md_store_load(store, group, name, MD_FN_CERT, MD_SV_CERT, (void**)pcert, p);
-}
-
-apr_status_t md_cert_save(md_store_t *store, apr_pool_t *p, 
-                          md_store_group_t group, const char *name, 
-                          struct md_cert_t *cert, int create)
-{
-    return md_store_save(store, p, group, name, MD_FN_CERT, MD_SV_CERT, cert, create);
-}
-
-apr_status_t md_chain_load(md_store_t *store, md_store_group_t group, const char *name, 
-                           struct apr_array_header_t **pchain, apr_pool_t *p)
-{
-    return md_store_load(store, group, name, MD_FN_CHAIN, MD_SV_CHAIN, (void**)pchain, p);
-}
-
-apr_status_t md_chain_save(md_store_t *store, apr_pool_t *p, 
-                           md_store_group_t group, const char *name, 
-                           struct apr_array_header_t *chain, int create)
-{
-    return md_store_save(store, p, group, name, MD_FN_CHAIN, MD_SV_CHAIN, chain, create);
-}
-
 apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name, 
                              struct apr_array_header_t **ppubcert, apr_pool_t *p)
 {
index 5825189a3b69384307ad756b0edb4c5dc2757b3e..7dec9f39fe8c01a631dfcc2689265c70df0c8b51 100644 (file)
@@ -49,6 +49,9 @@ typedef apr_status_t md_store_iter_cb(md_store_inspect *inspect, void *baton, md
                                       apr_pool_t *p, md_store_group_t group, const char *pattern,
                                       const char *aspect, md_store_vtype_t vtype);
 
+typedef apr_status_t md_store_names_iter_cb(md_store_inspect *inspect, void *baton, md_store_t *store, 
+                                            apr_pool_t *p, md_store_group_t group, const char *pattern);
+
 typedef apr_status_t md_store_move_cb(md_store_t *store, apr_pool_t *p, md_store_group_t from, 
                                       md_store_group_t to, const char *name, int archive);
 
@@ -69,6 +72,7 @@ struct md_store_t {
     md_store_remove_cb *remove;
     md_store_move_cb *move;
     md_store_iter_cb *iterate;
+    md_store_names_iter_cb *iterate_names;
     md_store_purge_cb *purge;
     md_store_get_fname_cb *get_fname;
     md_store_is_newer_cb *is_newer;
@@ -115,6 +119,10 @@ apr_status_t md_store_get_fname(const char **pfname,
 int md_store_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,  
                       const char *name, const char *aspect, apr_pool_t *p);
 
+apr_status_t md_store_iter_names(md_store_inspect *inspect, void *baton, md_store_t *store, 
+                                 apr_pool_t *p, md_store_group_t group, const char *pattern);
+
+
 /**************************************************************************************************/
 /* Storage handling utils */
 
@@ -138,14 +146,6 @@ apr_status_t md_pkey_load(md_store_t *store, md_store_group_t group,
                           const char *name, struct md_pkey_t **ppkey, apr_pool_t *p);
 apr_status_t md_pkey_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                           const char *name, struct md_pkey_t *pkey, int create);
-apr_status_t md_cert_load(md_store_t *store, md_store_group_t group, 
-                          const char *name, struct md_cert_t **pcert, apr_pool_t *p);
-apr_status_t md_cert_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
-                          const char *name, struct md_cert_t *cert, int create);
-apr_status_t md_chain_load(md_store_t *store, md_store_group_t group, 
-                           const char *name, struct apr_array_header_t **pchain, apr_pool_t *p);
-apr_status_t md_chain_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
-                           const char *name, struct apr_array_header_t *chain, int create);
 
 apr_status_t md_pubcert_load(md_store_t *store, md_store_group_t group, const char *name, 
                              struct apr_array_header_t **ppubcert, apr_pool_t *p);
index e76093e08b0df19674d3d8af90ecbbf6f837842d..04c7e6055793123f2b019b6d4aeb909dffd79e07 100644 (file)
@@ -84,6 +84,8 @@ static apr_status_t fs_move(md_store_t *store, apr_pool_t *p,
 static apr_status_t fs_iterate(md_store_inspect *inspect, void *baton, md_store_t *store, 
                                apr_pool_t *p, md_store_group_t group,  const char *pattern,
                                const char *aspect, md_store_vtype_t vtype);
+static apr_status_t fs_iterate_names(md_store_inspect *inspect, void *baton, md_store_t *store, 
+                                     apr_pool_t *p, md_store_group_t group, const char *pattern);
 
 static apr_status_t fs_get_fname(const char **pfname, 
                                  md_store_t *store, md_store_group_t group, 
@@ -122,13 +124,12 @@ static apr_status_t rename_pkey(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
 {
     const char *from, *to;
     apr_status_t rv = APR_SUCCESS;
-    MD_CHK_VARS;
     
     (void)baton;
     (void)ftype;
     if (   MD_OK(md_util_path_merge(&from, ptemp, dir, name, NULL))
         && MD_OK(md_util_path_merge(&to, ptemp, dir, MD_FN_PRIVKEY, NULL))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "renaming %s/%s to %s", 
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, p, "renaming %s/%s to %s", 
                       dir, name, MD_FN_PRIVKEY);
         return apr_file_rename(from, to, ptemp);
     }
@@ -143,16 +144,15 @@ static apr_status_t mk_pubcert(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
     apr_array_header_t *chain, *pubcert;
     const char *fname, *fpubcert;
     apr_status_t rv = APR_SUCCESS;
-    MD_CHK_VARS;
     
     (void)baton;
     (void)ftype;
     (void)p;
     if (   MD_OK(md_util_path_merge(&fpubcert, ptemp, dir, MD_FN_PUBCERT, NULL))
-        && MD_IS_ERR(md_chain_fload(&pubcert, ptemp, fpubcert), ENOENT)
+        && APR_STATUS_IS_ENOENT(rv = md_chain_fload(&pubcert, ptemp, fpubcert))
         && MD_OK(md_util_path_merge(&fname, ptemp, dir, name, NULL))
         && MD_OK(md_cert_fload(&cert, ptemp, fname))
-        && MD_OK(md_util_path_merge(&fname, ptemp, dir, MD_FN_CHAIN, NULL))) {
+        && MD_OK(md_util_path_merge(&fname, ptemp, dir, "chain.pem", NULL))) {
         
         rv = md_chain_fload(&chain, ptemp, fname);
         if (APR_STATUS_IS_ENOENT(rv)) {
@@ -196,7 +196,6 @@ static apr_status_t read_store_file(md_store_fs_t *s_fs, const char *fname,
     const char *key64, *key;
     apr_status_t rv;
     double store_version;
-    MD_CHK_VARS;
     
     if (MD_OK(md_json_readf(&json, p, fname))) {
         store_version = md_json_getn(json, MD_KEY_STORE, MD_KEY_VERSION, NULL);
@@ -249,7 +248,6 @@ static apr_status_t setup_store_file(void *baton, apr_pool_t *p, apr_pool_t *pte
     md_store_fs_t *s_fs = baton;
     const char *fname;
     apr_status_t rv;
-    MD_CHK_VARS;
 
     (void)ap;
     s_fs->plain_pkey[MD_SG_DOMAINS] = 1;
@@ -264,7 +262,7 @@ read:
         rv = read_store_file(s_fs, fname, p, ptemp);
     }
     else if (APR_STATUS_IS_ENOENT(rv)
-        && MD_IS_ERR(init_store_file(s_fs, fname, p, ptemp), EEXIST)) {
+        && APR_STATUS_IS_EEXIST(rv = init_store_file(s_fs, fname, p, ptemp))) {
         goto read;
     }
     return rv;
@@ -274,7 +272,6 @@ apr_status_t md_store_fs_init(md_store_t **pstore, apr_pool_t *p, const char *pa
 {
     md_store_fs_t *s_fs;
     apr_status_t rv = APR_SUCCESS;
-    MD_CHK_VARS;
     
     s_fs = apr_pcalloc(p, sizeof(*s_fs));
 
@@ -284,6 +281,7 @@ apr_status_t md_store_fs_init(md_store_t **pstore, apr_pool_t *p, const char *pa
     s_fs->s.move = fs_move;
     s_fs->s.purge = fs_purge;
     s_fs->s.iterate = fs_iterate;
+    s_fs->s.iterate_names = fs_iterate_names;
     s_fs->s.get_fname = fs_get_fname;
     s_fs->s.is_newer = fs_is_newer;
     
@@ -303,7 +301,7 @@ apr_status_t md_store_fs_init(md_store_t **pstore, apr_pool_t *p, const char *pa
 
     s_fs->base = apr_pstrdup(p, path);
     
-    if (MD_IS_ERR(md_util_is_dir(s_fs->base, p), ENOENT)
+    if (APR_STATUS_IS_ENOENT(rv = md_util_is_dir(s_fs->base, p))
         && MD_OK(apr_dir_make_recursive(s_fs->base, s_fs->def_perms.dir, p))) {
         rv = apr_file_perms_set(s_fs->base, MD_FPROT_D_UALL_WREAD);
         if (APR_STATUS_IS_ENOTIMPL(rv)) {
@@ -446,7 +444,6 @@ static apr_status_t pfs_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
     md_store_group_t group;
     void **pvalue;
     apr_status_t rv;
-    MD_CHK_VARS;
     
     group = (md_store_group_t)va_arg(ap, int);
     name = va_arg(ap, const char *);
@@ -477,7 +474,6 @@ static apr_status_t mk_group_dir(const char **pdir, md_store_fs_t *s_fs,
 {
     const perms_t *perms;
     apr_status_t rv;
-    MD_CHK_VARS;
     
     perms = gperms(s_fs, group);
 
@@ -507,7 +503,6 @@ static apr_status_t pfs_is_newer(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
     apr_finfo_t inf1, inf2;
     int *pnewer;
     apr_status_t rv;
-    MD_CHK_VARS;
     
     (void)p;
     group1 = (md_store_group_t)va_arg(ap, int);
@@ -554,7 +549,6 @@ static apr_status_t pfs_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
     const perms_t *perms;
     const char *pass;
     apr_size_t pass_len;
-    MD_CHK_VARS;
     
     group = (md_store_group_t)va_arg(ap, int);
     name = va_arg(ap, const char*);
@@ -612,7 +606,6 @@ static apr_status_t pfs_remove(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va
     int force;
     apr_finfo_t info;
     md_store_group_t group;
-    MD_CHK_VARS;
     
     (void)p;
     group = (md_store_group_t)va_arg(ap, int);
@@ -624,7 +617,7 @@ static apr_status_t pfs_remove(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va
     
     if (   MD_OK(md_util_path_merge(&dir, ptemp, s_fs->base, groupname, name, NULL))
         && MD_OK(md_util_path_merge(&fpath, ptemp, dir, aspect, NULL))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "start remove of md %s/%s/%s", 
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "start remove of md %s/%s/%s", 
                       groupname, name, aspect);
 
         if (!MD_OK(apr_stat(&info, dir, APR_FINFO_TYPE, ptemp))) {
@@ -673,7 +666,6 @@ static apr_status_t pfs_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_
     const char *dir, *name, *groupname;
     md_store_group_t group;
     apr_status_t rv;
-    MD_CHK_VARS;
     
     (void)p;
     group = (md_store_group_t)va_arg(ap, int);
@@ -685,7 +677,7 @@ static apr_status_t pfs_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_
         /* Remove all files in dir, there should be no sub-dirs */
         rv = md_util_rm_recursive(dir, ptemp, 1);
     }
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "purge %s/%s (%s)", groupname, name, dir);
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, ptemp, "purge %s/%s (%s)", groupname, name, dir);
     return APR_SUCCESS;
 }
 
@@ -706,6 +698,7 @@ typedef struct {
     const char *aspect;
     md_store_vtype_t vtype;
     md_store_inspect *inspect;
+    const char *dirname;
     void *baton;
 } inspect_ctx;
 
@@ -716,15 +709,38 @@ static apr_status_t insp(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
     apr_status_t rv;
     void *value;
     const char *fpath;
-    MD_CHK_VARS;
  
     (void)ftype;   
     md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "inspecting value at: %s/%s", dir, name);
-    if (   MD_OK(md_util_path_merge(&fpath, ptemp, dir, name, NULL)) 
-        && MD_OK(fs_fload(&value, ctx->s_fs, fpath, ctx->group, ctx->vtype, p, ptemp))
-        && !ctx->inspect(ctx->baton, name, ctx->aspect, ctx->vtype, value, ptemp)) {
-        return APR_EOF;
-    }
+    if (APR_SUCCESS == (rv = md_util_path_merge(&fpath, ptemp, dir, name, NULL))) {
+        rv = fs_fload(&value, ctx->s_fs, fpath, ctx->group, ctx->vtype, p, ptemp);
+        if (APR_SUCCESS == rv 
+            && !ctx->inspect(ctx->baton, ctx->dirname, name, ctx->vtype, value, p)) {
+            return APR_EOF;
+        }
+        else if (APR_STATUS_IS_ENOENT(rv)) {
+            rv = APR_SUCCESS;
+        }
+    } 
+    return rv;
+}
+
+static apr_status_t insp_dir(void *baton, apr_pool_t *p, apr_pool_t *ptemp, 
+                             const char *dir, const char *name, apr_filetype_e ftype)
+{
+    inspect_ctx *ctx = baton;
+    apr_status_t rv;
+    const char *fpath;
+    (void)ftype;
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "inspecting dir at: %s/%s", dir, name);
+    if (MD_OK(md_util_path_merge(&fpath, p, dir, name, NULL))) {
+        ctx->dirname = name;
+        rv = md_util_files_do(insp, ctx, p, fpath, ctx->aspect, NULL);
+        if (APR_STATUS_IS_ENOENT(rv)) {
+            rv = APR_SUCCESS;
+        }
+    } 
     return rv;
 }
 
@@ -745,7 +761,37 @@ static apr_status_t fs_iterate(md_store_inspect *inspect, void *baton, md_store_
     ctx.baton = baton;
     groupname = md_store_group_name(group);
 
-    rv = md_util_files_do(insp, &ctx, p, ctx.s_fs->base, groupname, ctx.pattern, aspect, NULL);
+    rv = md_util_files_do(insp_dir, &ctx, p, ctx.s_fs->base, groupname, pattern, NULL);
+    
+    return rv;
+}
+
+static apr_status_t insp_name(void *baton, apr_pool_t *p, apr_pool_t *ptemp, 
+                              const char *dir, const char *name, apr_filetype_e ftype)
+{
+    inspect_ctx *ctx = baton;
+    
+    (void)ftype;
+    (void)p;
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "inspecting name at: %s/%s", dir, name);
+    return ctx->inspect(ctx->baton, dir, name, 0, NULL, ptemp);
+}
+
+static apr_status_t fs_iterate_names(md_store_inspect *inspect, void *baton, md_store_t *store, 
+                                     apr_pool_t *p, md_store_group_t group, const char *pattern)
+{
+    const char *groupname;
+    apr_status_t rv;
+    inspect_ctx ctx;
+    
+    ctx.s_fs = FS_STORE(store);
+    ctx.group = group;
+    ctx.pattern = pattern;
+    ctx.inspect = inspect;
+    ctx.baton = baton;
+    groupname = md_store_group_name(group);
+
+    rv = md_util_files_do(insp_name, &ctx, p, ctx.s_fs->base, groupname, pattern, NULL);
     
     return rv;
 }
@@ -760,7 +806,6 @@ static apr_status_t pfs_move(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
     md_store_group_t from, to;
     int archive;
     apr_status_t rv;
-    MD_CHK_VARS;
     
     (void)p;
     from = (md_store_group_t)va_arg(ap, int);
@@ -802,7 +847,7 @@ static apr_status_t pfs_move(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
             narch_dir = apr_psprintf(ptemp, "%s.%d", arch_dir, n);
             rv = md_util_is_dir(narch_dir, ptemp);
             if (APR_STATUS_IS_ENOENT(rv)) {
-                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "using archive dir: %s", 
+                md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ptemp, "using archive dir: %s", 
                               narch_dir);
                 break;
             }
@@ -817,7 +862,7 @@ static apr_status_t pfs_move(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_l
         while (n < 1000) {
             narch_dir = apr_psprintf(ptemp, "%s.%d", arch_dir, n);
             if (MD_OK(apr_dir_make(narch_dir, MD_FPROT_D_UONLY, ptemp))) {
-                md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "using archive dir: %s", 
+                md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ptemp, "using archive dir: %s", 
                               narch_dir);
                 break;
             }
diff --git a/modules/md/md_time.c b/modules/md/md_time.c
new file mode 100644 (file)
index 0000000..f99b886
--- /dev/null
@@ -0,0 +1,284 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <stdio.h>
+
+#include <apr_lib.h>
+#include <apr_strings.h>
+#include <apr_time.h>
+
+#include "md.h"
+#include "md_time.h"
+
+apr_time_t md_timeperiod_length(const md_timeperiod_t *period)
+{
+    return (period->start < period->end)? (period->end - period->start) : 0;
+}
+
+int md_timeperiod_contains(const md_timeperiod_t *period, apr_time_t time)
+{
+    return md_timeperiod_has_started(period, time) 
+        && !md_timeperiod_has_ended(period, time);
+}
+
+int md_timeperiod_has_started(const md_timeperiod_t *period, apr_time_t time)
+{
+    return (time >= period->start);
+}
+
+int md_timeperiod_has_ended(const md_timeperiod_t *period, apr_time_t time)
+{
+    return (time >= period->start) && (time <= period->end);
+}
+
+char *md_timeperiod_print(apr_pool_t *p, const md_timeperiod_t *period)
+{
+    char tstart[APR_RFC822_DATE_LEN];
+    char tend[APR_RFC822_DATE_LEN];
+
+    apr_rfc822_date(tstart, period->start);
+    apr_rfc822_date(tend, period->end);
+    return apr_pstrcat(p, tstart, " - ", tend, NULL);
+}
+
+const char *md_duration_print(apr_pool_t *p, apr_interval_time_t duration)
+{
+    const char *s = "", *sep = "";
+    long days = (long)(apr_time_sec(duration) / MD_SECS_PER_DAY);
+    int rem = (int)(apr_time_sec(duration) % MD_SECS_PER_DAY);
+    
+    if (days > 0) {
+        s = apr_psprintf(p, "%ld days", days);
+        sep = " "; 
+    }
+    if (rem > 0) {
+        int hours = (rem / MD_SECS_PER_HOUR);
+        rem = (rem % MD_SECS_PER_HOUR);
+        if (hours > 0) {
+            s = apr_psprintf(p, "%s%s%02d hours", s, sep, hours); 
+            sep = " "; 
+        }
+        if (rem > 0) {
+            int minutes = (rem / 60);
+            rem = (rem % 60);
+            if (minutes > 0) {
+                s = apr_psprintf(p, "%s%s%02d minutes", s, sep, minutes); 
+            }
+            if (rem > 0) {
+                s = apr_psprintf(p, "%s%s%02d seconds", s, sep, rem); 
+            }
+        }
+    }
+    else if (days == 0) {
+        s = "0 seconds";
+        if (duration != 0) {
+            s = apr_psprintf(p, "%d ms", (int)apr_time_msec(duration));
+        }
+    }
+    return s;
+}
+
+static const char *duration_format(apr_pool_t *p, apr_interval_time_t duration)
+{
+    const char *s = "0";
+    int units = (int)(apr_time_sec(duration) / MD_SECS_PER_DAY);
+    int rem = (int)(apr_time_sec(duration) % MD_SECS_PER_DAY);
+    
+    if (rem == 0) {
+        s = apr_psprintf(p, "%dd", units); 
+    }
+    else {
+        units = (int)(apr_time_sec(duration) / MD_SECS_PER_HOUR);
+        rem = (int)(apr_time_sec(duration) % MD_SECS_PER_HOUR);
+        if (rem == 0) {
+            s = apr_psprintf(p, "%dh", units); 
+        }
+        else {
+            units = (int)(apr_time_sec(duration) / 60);
+            rem = (int)(apr_time_sec(duration) % 60);
+            if (rem == 0) {
+                s = apr_psprintf(p, "%dmi", units); 
+            }
+            else {
+                units = (int)(apr_time_sec(duration));
+                rem = (int)(apr_time_msec(duration) % 1000);
+                if (rem == 0) {
+                    s = apr_psprintf(p, "%ds", units); 
+                }
+                else {
+                    s = apr_psprintf(p, "%dms", (int)(apr_time_msec(duration))); 
+                }
+            }
+        }
+    }
+    return s;
+}
+
+apr_status_t md_duration_parse(apr_interval_time_t *ptimeout, const char *value, 
+                               const char *def_unit)
+{
+    char *endp;
+    apr_int64_t n;
+    
+    n = apr_strtoi64(value, &endp, 10);
+    if (errno) {
+        return errno;
+    }
+    if (!endp || !*endp) {
+        if (!def_unit) def_unit = "s";
+    }
+    else if (endp == value) {
+        return APR_EINVAL;
+    }
+    else {
+        def_unit = endp;
+    }
+    
+    switch (*def_unit) {
+    case 'D':
+    case 'd':
+        *ptimeout = apr_time_from_sec(n * MD_SECS_PER_DAY);
+        break;
+    case 's':
+    case 'S':
+        *ptimeout = (apr_interval_time_t) apr_time_from_sec(n);
+        break;
+    case 'h':
+    case 'H':
+        /* Time is in hours */
+        *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * MD_SECS_PER_HOUR);
+        break;
+    case 'm':
+    case 'M':
+        switch (*(++def_unit)) {
+        /* Time is in milliseconds */
+        case 's':
+        case 'S':
+            *ptimeout = (apr_interval_time_t) n * 1000;
+            break;
+        /* Time is in minutes */
+        case 'i':
+        case 'I':
+            *ptimeout = (apr_interval_time_t) apr_time_from_sec(n * 60);
+            break;
+        default:
+            return APR_EGENERAL;
+        }
+        break;
+    default:
+        return APR_EGENERAL;
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t percentage_parse(const char *value, int *ppercent)
+{
+    char *endp;
+    apr_int64_t n;
+    
+    n = apr_strtoi64(value, &endp, 10);
+    if (errno) {
+        return errno;
+    }
+    if (*endp == '%') {
+        if (n < 0) {
+            return APR_BADARG;
+        }
+        *ppercent = (int)n;
+        return APR_SUCCESS;
+    }
+    return APR_EINVAL;
+}
+
+apr_status_t md_timeslice_create(const md_timeslice_t **pts, apr_pool_t *p,
+                                 apr_interval_time_t norm, apr_interval_time_t len)
+{
+    md_timeslice_t *ts;
+
+    ts = apr_pcalloc(p, sizeof(*ts));
+    ts->norm = norm;
+    ts->len = len;
+    *pts = ts;
+    return APR_SUCCESS;
+}
+
+const char *md_timeslice_parse(const md_timeslice_t **pts, apr_pool_t *p, 
+                               const char *val, apr_interval_time_t norm)
+{
+    md_timeslice_t *ts;
+    int percent;
+
+    *pts = NULL;
+    if (!val) {
+        return "cannot parse NULL value";
+    }
+
+    ts = apr_pcalloc(p, sizeof(*ts));
+    if (md_duration_parse(&ts->len, val, "d") == APR_SUCCESS) {
+        *pts = ts;
+        return NULL;
+    }
+    else {
+        switch (percentage_parse(val, &percent)) {
+            case APR_SUCCESS:
+                ts->norm = norm;
+                ts->len = apr_time_from_sec((apr_time_sec(norm) * percent / 100L));
+                *pts = ts;
+                return NULL;
+            case APR_BADARG:
+                return "percent must be less than 100";
+        }
+    }
+    return "has unrecognized format";
+}
+
+const char *md_timeslice_format(const md_timeslice_t *ts, apr_pool_t *p) {
+    if (ts->norm > 0) {
+        int percent = (int)(((long)apr_time_sec(ts->len)) * 100L 
+                            / ((long)apr_time_sec(ts->norm))); 
+        return apr_psprintf(p, "%d%%", percent);
+    }
+    return duration_format(p, ts->len);
+}
+
+md_timeperiod_t md_timeperiod_slice_before_end(const md_timeperiod_t *period, 
+                                               const md_timeslice_t *ts)
+{
+    md_timeperiod_t r;
+    apr_time_t duration = ts->len;
+    
+    if (ts->norm > 0) {
+        int percent = (int)(((long)apr_time_sec(ts->len)) * 100L 
+                            / ((long)apr_time_sec(ts->norm))); 
+        apr_time_t plen = md_timeperiod_length(period);
+        if (apr_time_sec(plen) > 100) {
+            duration = apr_time_from_sec(apr_time_sec(plen) * percent / 100);
+        }
+        else {
+            duration = plen * percent / 100;
+        }
+    }
+    r.start = period->end - duration;
+    r.end = period->end;
+    return r;
+}
+
+int md_timeslice_eq(const md_timeslice_t *ts1, const md_timeslice_t *ts2)
+{
+    if (ts1 == ts2) return 1;
+    if (!ts1 || !ts2) return 0;
+    return (ts1->norm == ts2->norm) && (ts1->len == ts2->len);
+}
diff --git a/modules/md/md_time.h b/modules/md/md_time.h
new file mode 100644 (file)
index 0000000..80ba9d9
--- /dev/null
@@ -0,0 +1,67 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_time_h
+#define mod_md_md_time_h
+
+#include <stdio.h>
+
+#define MD_SECS_PER_HOUR      (60*60)
+#define MD_SECS_PER_DAY       (24*MD_SECS_PER_HOUR)
+
+typedef struct {
+    apr_time_t start;
+    apr_time_t end;
+} md_timeperiod_t;
+
+apr_time_t md_timeperiod_length(const md_timeperiod_t *period);
+
+int md_timeperiod_contains(const md_timeperiod_t *period, apr_time_t time);
+int md_timeperiod_has_started(const md_timeperiod_t *period, apr_time_t time);
+int md_timeperiod_has_ended(const md_timeperiod_t *period, apr_time_t time);
+
+char *md_timeperiod_print(apr_pool_t *p, const md_timeperiod_t *period);
+
+/**
+ * Print a human readable form of the give duration in days/hours/min/sec 
+ */
+const char *md_duration_print(apr_pool_t *p, apr_interval_time_t duration);
+
+/**
+ * Parse a machine readable string duration in the form of NN[unit], where
+ * unit is d/h/mi/s/ms with the default given should the unit not be specified.
+ */
+apr_status_t md_duration_parse(apr_interval_time_t *ptimeout, const char *value, 
+                               const char *def_unit);
+
+typedef struct {
+    apr_interval_time_t norm; /* if > 0, normalized base length */
+    apr_interval_time_t len;  /* length of the timespan */
+} md_timeslice_t;
+
+apr_status_t md_timeslice_create(const md_timeslice_t **pts, apr_pool_t *p,
+                                 apr_interval_time_t norm, apr_interval_time_t len); 
+
+int md_timeslice_eq(const md_timeslice_t *ts1, const md_timeslice_t *ts2);
+
+const char *md_timeslice_parse(const md_timeslice_t **pts, apr_pool_t *p, 
+                              const char *val, apr_interval_time_t defnorm);
+const char *md_timeslice_format(const md_timeslice_t *ts, apr_pool_t *p);
+
+md_timeperiod_t md_timeperiod_slice_before_end(const md_timeperiod_t *period, 
+                                               const md_timeslice_t *ts);
+
+#endif /* md_util_h */
index 83c6a4b5231641f02e1ea9d5f1d17cce8582397a..0b5e1920332510e7ee7d57966ff24f6a46a710a5 100644 (file)
@@ -24,6 +24,7 @@
 #include <apr_tables.h>
 #include <apr_uri.h>
 
+#include "md.h"
 #include "md_log.h"
 #include "md_util.h"
 
@@ -66,9 +67,68 @@ apr_status_t md_util_pool_vdo(md_util_vaction *cb, void *baton, apr_pool_t *p, .
     return rv;
 }
  
+/**************************************************************************************************/
+/* data chunks */
+
+md_data *md_data_create(apr_pool_t *p, const char *data, apr_size_t len)
+{
+    md_data *d;
+    
+    d = apr_palloc(p, sizeof(*d));
+    d->len = len;
+    d->data = len? apr_pstrndup(p, data, len) : NULL;
+    return d;
+}
+
+static const char * const hex_const[] = {
+    "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", 
+    "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", 
+    "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", 
+    "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", 
+    "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", 
+    "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", 
+    "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", 
+    "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", 
+    "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", 
+    "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", 
+    "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", 
+    "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", 
+    "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", 
+    "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df", 
+    "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", 
+    "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff", 
+};
+
+apr_status_t md_data_to_hex(const char **phex, char separator,
+                            apr_pool_t *p, const md_data *data)
+{
+    char *hex, *cp;
+    const char * x;
+    unsigned int i;
+    
+    cp = hex = apr_pcalloc(p, ((separator? 3 : 2) * data->len) + 1);
+    if (!hex) {
+        *phex = NULL;
+        return APR_ENOMEM;
+    }
+    for (i = 0; i < data->len; ++i) {
+        x = hex_const[(unsigned char)data->data[i]];
+        if (i && separator) *cp++ = separator;
+        *cp++ = x[0];
+        *cp++ = x[1];
+    }
+    *phex = hex;
+    return APR_SUCCESS;
+}
+
 /**************************************************************************************************/
 /* string related */
 
+int md_array_is_empty(const struct apr_array_header_t *array)
+{
+    return (array == NULL) || (array->nelts == 0);
+}
+
 char *md_util_str_tolower(char *s)
 {
     char *orig = s;
@@ -230,6 +290,11 @@ apr_status_t md_util_is_file(const char *path, apr_pool_t *pool)
     return rv;
 }
 
+int md_file_exists(const char *fname, apr_pool_t *p)
+{
+    return (fname && *fname && APR_SUCCESS == md_util_is_file(fname, p));
+}
+
 apr_status_t md_util_path_merge(const char **ppath, apr_pool_t *p, ...)
 {
     const char *segment, *path;
@@ -324,6 +389,13 @@ apr_status_t md_text_fcreatex(const char *fpath, apr_fileperms_t perms,
     if (APR_SUCCESS == rv) {
         rv = write_text((void*)text, f, p);
         apr_file_close(f);
+        /* See <https://github.com/icing/mod_md/issues/117>: when a umask
+         * is set, files need to be assigned permissions explicitly.
+         * Otherwise, as in the issues reported, it will break our access model. */
+        rv = apr_file_perms_set(fpath, perms);
+        if (APR_STATUS_IS_ENOTIMPL(rv)) {
+            rv = APR_SUCCESS;
+        }
     }
     return rv;
 }
@@ -413,17 +485,25 @@ static apr_status_t match_and_do(md_util_fwalk_t *ctx, const char *path, int dep
     }
     pattern = APR_ARRAY_IDX(ctx->patterns, depth, const char *);
     
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
+                  "path=%s depth=%d pattern=%s", path, depth, pattern);
     rv = apr_dir_open(&d, path, ptemp);
     if (APR_SUCCESS != rv) {
         return rv;
     }
     
     while (APR_SUCCESS == (rv = apr_dir_read(&finfo, wanted, d))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
+                      "candidate=%s", finfo.name);
         if (!strcmp(".", finfo.name) || !strcmp("..", finfo.name)) {
             continue;
         } 
         if (APR_SUCCESS == apr_fnmatch(pattern, finfo.name, 0)) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
+                          "candidate=%s matches pattern", finfo.name);
             if (ndepth < ctx->patterns->nelts) {
+                md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
+                              "need to go deepter");
                 if (APR_DIR == finfo.filetype) { 
                     /* deeper and deeper, irgendwo in der tiefe leuchtet ein licht */
                     rv = md_util_path_merge(&npath, ptemp, path, finfo.name, NULL);
@@ -433,6 +513,8 @@ static apr_status_t match_and_do(md_util_fwalk_t *ctx, const char *path, int dep
                 }
             }
             else {
+                md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, ptemp, "match_and_do "
+                              "invoking inspector on name=%s", finfo.name);
                 rv = ctx->cb(ctx->baton, p, ptemp, path, finfo.name, finfo.filetype);
             }
         }
@@ -608,7 +690,7 @@ apr_status_t md_util_ftree_remove(const char *path, apr_pool_t *p)
 
 /* DNS name checks ********************************************************************************/
 
-int md_util_is_dns_name(apr_pool_t *p, const char *hostname, int need_fqdn)
+int md_dns_is_name(apr_pool_t *p, const char *hostname, int need_fqdn)
 {
     char c, last = 0;
     const char *cp = hostname;
@@ -649,6 +731,73 @@ int md_util_is_dns_name(apr_pool_t *p, const char *hostname, int need_fqdn)
     return 1; /* empty string not allowed */
 }
 
+int md_dns_is_wildcard(apr_pool_t *p, const char *domain)
+{
+    if (domain[0] != '*' || domain[1] != '.') return 0;
+    return md_dns_is_name(p, domain+2, 1);
+}
+
+int md_dns_matches(const char *pattern, const char *domain)
+{
+    const char *s;
+    
+    if (!apr_strnatcasecmp(pattern, domain)) return 1;
+    if (pattern[0] == '*' && pattern[1] == '.') {
+        s = strchr(domain, '.');
+        if (s && !apr_strnatcasecmp(pattern+1, s)) return 1;
+    }
+    return 0;
+}
+
+apr_array_header_t *md_dns_make_minimal(apr_pool_t *p, apr_array_header_t *domains)
+{
+    apr_array_header_t *minimal;
+    const char *domain, *pattern;
+    int i, j, duplicate;
+    
+    minimal = apr_array_make(p, domains->nelts, sizeof(const char *));
+    for (i = 0; i < domains->nelts; ++i) {
+        domain = APR_ARRAY_IDX(domains, i, const char*);
+        duplicate = 0;
+        /* is it matched in minimal already? */
+        for (j = 0; j < minimal->nelts; ++j) {
+            pattern = APR_ARRAY_IDX(minimal, j, const char*);
+            if (md_dns_matches(pattern, domain)) {
+                duplicate = 1;
+                break;
+            }
+        }
+        if (!duplicate) {
+            if (!md_dns_is_wildcard(p, domain)) {
+                /* plain name, will we see a wildcard that replaces it? */
+                for (j = i+1; j < domains->nelts; ++j) {
+                    pattern = APR_ARRAY_IDX(domains, j, const char*);
+                    if (md_dns_is_wildcard(p, pattern) && md_dns_matches(pattern, domain)) {
+                        duplicate = 1;
+                        break;
+                    }
+                }
+            }
+            if (!duplicate) {
+                APR_ARRAY_PUSH(minimal, const char *) = domain; 
+            }
+        }
+    }
+    return minimal;
+}
+
+int md_dns_domains_match(const apr_array_header_t *domains, const char *name)
+{
+    const char *domain;
+    int i;
+    
+    for (i = 0; i < domains->nelts; ++i) {
+        domain = APR_ARRAY_IDX(domains, i, const char*);
+        if (md_dns_matches(domain, name)) return 1;
+    }
+    return 0;
+}
+
 const char *md_util_schemify(apr_pool_t *p, const char *s, const char *def_scheme)
 {
     const char *cp = s;
@@ -682,7 +831,7 @@ static apr_status_t uri_check(apr_uri_t *uri_parsed, apr_pool_t *p,
             if (!uri_parsed->hostname) {
                 err = "missing hostname";
             }
-            else if (!md_util_is_dns_name(p, uri_parsed->hostname, 0)) {
+            else if (!md_dns_is_name(p, uri_parsed->hostname, 0)) {
                 err = "invalid hostname";
             }
             if (uri_parsed->port_str 
@@ -808,38 +957,37 @@ apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *ar
     apr_procattr_t *procattr;
     apr_proc_t *proc;
     apr_exit_why_e ewhy;
-
+    char buffer[1024];
+    
     *exit_code = 0;
     if (!(proc = apr_pcalloc(p, sizeof(*proc)))) {
         return APR_ENOMEM;
     }
     if (   APR_SUCCESS == (rv = apr_procattr_create(&procattr, p))
         && APR_SUCCESS == (rv = apr_procattr_io_set(procattr, APR_NO_FILE, 
-                                                    APR_NO_PIPE, APR_NO_PIPE))
+                                                    APR_NO_PIPE, APR_FULL_BLOCK))
         && APR_SUCCESS == (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM))
-        && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, NULL, procattr, p))
-        && APR_CHILD_DONE == (rv = apr_proc_wait(proc, exit_code, &ewhy, APR_WAIT))) {
-        /* let's not dwell on exit stati, but core should signal something's bad */
-        if (*exit_code > 127 || APR_PROC_SIGNAL_CORE == ewhy) {
-            return APR_EINCOMPLETE;
+        && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, NULL, procattr, p))) {
+        
+        /* read stderr and log on INFO for possible fault analysis. */
+        while(APR_SUCCESS == (rv = apr_file_gets(buffer, sizeof(buffer)-1, proc->err))) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, p, "cmd(%s) stderr: %s", cmd, buffer);
+        }
+        if (!APR_STATUS_IS_EOF(rv)) goto out;
+        apr_file_close(proc->err);
+        
+        if (APR_CHILD_DONE == (rv = apr_proc_wait(proc, exit_code, &ewhy, APR_WAIT))) {
+            /* let's not dwell on exit stati, but core should signal something's bad */
+            if (*exit_code > 127 || APR_PROC_SIGNAL_CORE == ewhy) {
+                return APR_EINCOMPLETE;
+            }
+            return APR_SUCCESS;
         }
-        return APR_SUCCESS;
     }
+out:
     return rv;
 }
 
-
-/* date/time encoding *****************************************************************************/
-
-const char *md_print_duration(apr_pool_t *p, apr_interval_time_t duration)
-{
-    int secs = (int)(apr_time_sec(duration) % MD_SECS_PER_DAY);
-    return apr_psprintf(p, "%2d:%02d:%02d hours", 
-                        (int)secs/MD_SECS_PER_HOUR, (int)(secs%(MD_SECS_PER_HOUR))/60,
-                        (int)(secs%60));
-}
-
-
 /* base64 url encoding ****************************************************************************/
 
 #define N6 (unsigned int)-1
index 5b3a2eab5f7498cd4488f36c91249fab7a17febe..bb2667f2d0ceabfaf68855773b8dc10398fb74da 100644 (file)
@@ -32,10 +32,29 @@ typedef apr_status_t md_util_vaction(void *baton, apr_pool_t *p, apr_pool_t *pte
 apr_status_t md_util_pool_do(md_util_action *cb, void *baton, apr_pool_t *p); 
 apr_status_t md_util_pool_vdo(md_util_vaction *cb, void *baton, apr_pool_t *p, ...); 
 
+/**************************************************************************************************/
+/* data chunks */
+
+typedef struct md_data md_data;
+struct md_data {
+    const char *data;
+    apr_size_t len;
+};
+
+md_data *md_data_create(apr_pool_t *p, const char *data, apr_size_t len);
+
+apr_status_t md_data_to_hex(const char **phex, char separator,
+                            apr_pool_t *p, const md_data *data);
+
 /**************************************************************************************************/
 /* string related */
 char *md_util_str_tolower(char *s);
 
+/**
+ * Return != 0 iff array is either NULL or empty 
+ */ 
+int md_array_is_empty(const struct apr_array_header_t *array);
+
 int md_array_str_index(const struct apr_array_header_t *array, const char *s, 
                        int start, int case_sensitive);
 
@@ -44,9 +63,15 @@ int md_array_str_eq(const struct apr_array_header_t *a1,
 
 struct apr_array_header_t *md_array_str_clone(apr_pool_t *p, struct apr_array_header_t *array);
 
+/**
+ * Create a new array with duplicates removed.
+ */
 struct apr_array_header_t *md_array_str_compact(apr_pool_t *p, struct apr_array_header_t *src,
                                                 int case_sensitive);
 
+/**
+ * Create a new array with all occurances of <exclude> removed.
+ */
 struct apr_array_header_t *md_array_str_remove(apr_pool_t *p, struct apr_array_header_t *src, 
                                                const char *exclude, int case_sensitive);
 
@@ -61,7 +86,41 @@ apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *ar
 /**************************************************************************************************/
 /* dns name check */
 
-int md_util_is_dns_name(apr_pool_t *p, const char *hostname, int need_fqdn);
+/**
+ * Is a host/domain name using allowed characters. Not a wildcard.
+ * @param domain     name to check
+ * @param need_fqdn  iff != 0, check that domain contains '.'
+ * @return != 0 iff domain looks like  a non-wildcard, legal DNS domain name.
+ */
+int md_dns_is_name(apr_pool_t *p, const char *domain, int need_fqdn);
+
+/**
+ * Check if the given domain is a valid wildcard DNS name, e.g. *.example.org
+ * @param domain    name to check
+ * @return != 0 iff domain is a DNS wildcard.
+ */
+int md_dns_is_wildcard(apr_pool_t *p, const char *domain);
+
+/**
+ * Determine iff pattern matches domain, including case-ignore and wildcard domains.
+ * It is assumed that both names follow dns syntax.
+ * @return != 0 iff pattern matches domain
+ */ 
+int md_dns_matches(const char *pattern, const char *domain);
+
+/**
+ * Create a new array with the minimal set out of the given domain names that match all
+ * of them. If none of the domains is a wildcard, only duplicates are removed.
+ * If domains contain a wildcard, any name matching the wildcard will be removed.
+ */
+struct apr_array_header_t *md_dns_make_minimal(apr_pool_t *p, 
+                                               struct apr_array_header_t *domains);
+
+/**
+ * Determine if the given domains cover the name, including wildcard matching.
+ * @return != 0 iff name is matched by list of domains
+ */
+int md_dns_domains_match(const apr_array_header_t *domains, const char *name);
 
 /**************************************************************************************************/
 /* file system related */
@@ -78,6 +137,7 @@ apr_status_t md_util_path_merge(const char **ppath, apr_pool_t *p, ...);
 
 apr_status_t md_util_is_dir(const char *path, apr_pool_t *pool);
 apr_status_t md_util_is_file(const char *path, apr_pool_t *pool);
+int md_file_exists(const char *fname, apr_pool_t *p);
 
 typedef apr_status_t md_util_file_cb(void *baton, struct apr_file_t *f, apr_pool_t *p);
 
@@ -137,12 +197,4 @@ apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs,
                          apr_interval_time_t timeout, apr_interval_time_t start_delay, 
                          apr_interval_time_t max_delay, int backoff);
 
-/**************************************************************************************************/
-/* date/time related */
-
-#define MD_SECS_PER_HOUR      (60*60)
-#define MD_SECS_PER_DAY       (24*MD_SECS_PER_HOUR)
-
-const char *md_print_duration(apr_pool_t *p, apr_interval_time_t duration);
-
 #endif /* md_util_h */
index 45c1d2e0e24a54d3c14b0378dcc85461b49566db..4a668bc38184f480e6fe606548fdb1bdd80f7465 100644 (file)
@@ -27,7 +27,7 @@
  * @macro
  * Version number of the md module as c string
  */
-#define MOD_MD_VERSION "1.1.19-DEV"
+#define MOD_MD_VERSION "2.0.6"
 
 /**
  * @macro
@@ -35,8 +35,8 @@
  * 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 0x010113
+#define MOD_MD_VERSION_NUM 0x020006
 
-#define MD_ACME_DEF_URL    "https://acme-v01.api.letsencrypt.org/directory"
+#define MD_ACME_DEF_URL    "https://acme-v02.api.letsencrypt.org/directory"
 
 #endif /* mod_md_md_version_h */
index 4ba9508ca55bdc7200b52e373b08371134d9dad1..02ab599203dfa8e1822e286304216fe2d43ad2c1 100644 (file)
@@ -31,6 +31,8 @@
 #include <http_vhost.h>
 #include <ap_listen.h>
 
+#include "mod_status.h"
+
 #include "md.h"
 #include "md_curl.h"
 #include "md_crypt.h"
@@ -39,6 +41,7 @@
 #include "md_store.h"
 #include "md_store_fs.h"
 #include "md_log.h"
+#include "md_result.h"
 #include "md_reg.h"
 #include "md_util.h"
 #include "md_version.h"
 
 #include "mod_md.h"
 #include "mod_md_config.h"
+#include "mod_md_drive.h"
 #include "mod_md_os.h"
+#include "mod_md_status.h"
 #include "mod_ssl.h"
-#include "mod_watchdog.h"
 
 static void md_hooks(apr_pool_t *pool);
 
@@ -66,7 +70,144 @@ AP_DECLARE_MODULE(md) = {
 #endif
 };
 
-static void md_merge_srv(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
+/**************************************************************************************************/
+/* logging setup */
+
+static server_rec *log_server;
+
+static int log_is_level(void *baton, apr_pool_t *p, md_log_level_t level)
+{
+    (void)baton;
+    (void)p;
+    if (log_server) {
+        return APLOG_IS_LEVEL(log_server, (int)level);
+    }
+    return level <= MD_LOG_INFO;
+}
+
+#define LOG_BUF_LEN 16*1024
+
+static void log_print(const char *file, int line, md_log_level_t level, 
+                      apr_status_t rv, void *baton, apr_pool_t *p, const char *fmt, va_list ap)
+{
+    if (log_is_level(baton, p, level)) {
+        char buffer[LOG_BUF_LEN];
+        
+        memset(buffer, 0, sizeof(buffer));
+        apr_vsnprintf(buffer, LOG_BUF_LEN-1, fmt, ap);
+        buffer[LOG_BUF_LEN-1] = '\0';
+
+        if (log_server) {
+            ap_log_error(file, line, APLOG_MODULE_INDEX, (int)level, rv, log_server, "%s",buffer);
+        }
+        else {
+            ap_log_perror(file, line, APLOG_MODULE_INDEX, (int)level, rv, p, "%s", buffer);
+        }
+    }
+}
+
+/**************************************************************************************************/
+/* mod_ssl interface */
+
+static APR_OPTIONAL_FN_TYPE(ssl_is_https) *opt_ssl_is_https;
+
+static void init_ssl(void)
+{
+    opt_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
+}
+
+/**************************************************************************************************/
+/* lifecycle */
+
+static apr_status_t cleanup_setups(void *dummy)
+{
+    (void)dummy;
+    log_server = NULL;
+    return APR_SUCCESS;
+}
+
+static void init_setups(apr_pool_t *p, server_rec *base_server) 
+{
+    log_server = base_server;
+    apr_pool_cleanup_register(p, NULL, cleanup_setups, apr_pool_cleanup_null);
+}
+
+/**************************************************************************************************/
+/* store & registry setup */
+
+static apr_status_t store_file_ev(void *baton, struct md_store_t *store,
+                                    md_store_fs_ev_t ev, unsigned int group, 
+                                    const char *fname, apr_filetype_e ftype,  
+                                    apr_pool_t *p)
+{
+    server_rec *s = baton;
+    apr_status_t rv;
+    
+    (void)store;
+    ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "store event=%d on %s %s (group %d)", 
+                 ev, (ftype == APR_DIR)? "dir" : "file", fname, group);
+                 
+    /* Directories in group CHALLENGES and STAGING are written to under a different user. 
+     * Give him ownership. 
+     */
+    if (ftype == APR_DIR) {
+        switch (group) {
+            case MD_SG_CHALLENGES:
+            case MD_SG_STAGING:
+                rv = md_make_worker_accessible(fname, p);
+                if (APR_ENOTIMPL != rv) {
+                    return rv;
+                }
+                break;
+            default: 
+                break;
+        }
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group, 
+                                    apr_pool_t *p, server_rec *s)
+{
+    const char *dir;
+    apr_status_t rv;
+    
+    if (APR_SUCCESS == (rv = md_store_get_fname(&dir, store, group, NULL, NULL, p))
+        && APR_SUCCESS == (rv = apr_dir_make_recursive(dir, MD_FPROT_D_UALL_GREAD, p))) {
+        rv = store_file_ev(s, store, MD_S_FS_EV_CREATED, group, dir, APR_DIR, p);
+    }
+    return rv;
+}
+
+static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc, 
+                                apr_pool_t *p, server_rec *s)
+{
+    const char *base_dir;
+    apr_status_t rv;
+    
+    base_dir = ap_server_root_relative(p, mc->base_dir);
+    
+    if (APR_SUCCESS != (rv = md_store_fs_init(pstore, p, base_dir))) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10046)"setup store for %s", base_dir);
+        goto out;
+    }
+
+    md_store_fs_set_event_cb(*pstore, store_file_ev, s);
+    if (APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_CHALLENGES, p, s))
+        || APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_STAGING, p, s))
+        || APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_ACCOUNTS, p, s))) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047) 
+                     "setup challenges directory");
+    }
+    
+out:
+    return rv;
+}
+
+/**************************************************************************************************/
+/* post config handling */
+
+static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
 {
     if (!md->sc) {
         md->sc = base_sc;
@@ -86,13 +227,11 @@ static void md_merge_srv(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
         APR_ARRAY_PUSH(md->contacts, const char *) = 
         md_util_schemify(p, md->sc->s->server_admin, "mailto");
     }
-    if (md->drive_mode == MD_DRIVE_DEFAULT) {
-        md->drive_mode = md_config_geti(md->sc, MD_CONFIG_DRIVE_MODE);
-    }
-    if (md->renew_norm <= 0 && md->renew_window <= 0) {
-        md->renew_norm = md_config_get_interval(md->sc, MD_CONFIG_RENEW_NORM);
-        md->renew_window = md_config_get_interval(md->sc, MD_CONFIG_RENEW_WINDOW);
+    if (md->renew_mode == MD_RENEW_DEFAULT) {
+        md->renew_mode = md_config_geti(md->sc, MD_CONFIG_DRIVE_MODE);
     }
+    if (!md->renew_window) md_config_get_timespan(&md->renew_window, md->sc, MD_CONFIG_RENEW_WINDOW);
+    if (!md->warn_window) md_config_get_timespan(&md->warn_window, md->sc, MD_CONFIG_WARN_WINDOW);
     if (md->transitive < 0) {
         md->transitive = md_config_geti(md->sc, MD_CONFIG_TRANSITIVE);
     }
@@ -164,7 +303,7 @@ static int matches_port_somewhere(server_rec *s, int port)
     return 0;
 }
 
-static int uses_port_only(server_rec *s, int port)
+static int uses_port(server_rec *s, int port)
 {
     server_addr_rec *sa;
     int match = 0;
@@ -181,20 +320,98 @@ static int uses_port_only(server_rec *s, int port)
     return match;
 }
 
-static apr_status_t assign_to_servers(md_t *md, server_rec *base_server, 
-                                     apr_pool_t *p, apr_pool_t *ptemp)
+static apr_status_t detect_supported_ports(md_mod_conf_t *mc, server_rec *s, 
+                                           apr_pool_t *p, int log_level)
+{
+    ap_listen_rec *lr;
+    apr_sockaddr_t *sa;
+
+    mc->can_http = 0;
+    mc->can_https = 0;
+    for (lr = ap_listeners; lr; lr = lr->next) {
+        for (sa = lr->bind_addr; sa; sa = sa->next) {
+            if  (sa->port == mc->local_80 
+                 && (!lr->protocol || !strncmp("http", lr->protocol, 4))) {
+                mc->can_http = 1;
+            }
+            else if (sa->port == mc->local_443
+                     && (!lr->protocol || !strncmp("http", lr->protocol, 4))) {
+                mc->can_https = 1;
+            }
+        }
+    }
+
+    ap_log_error(APLOG_MARK, log_level, 0, s, APLOGNO(10037)
+                 "server seems%s reachable via http: (port 80->%d) "
+                 "and%s reachable via https: (port 443->%d) ",
+                 mc->can_http? "" : " not", mc->local_80,
+                 mc->can_https? "" : " not", mc->local_443);
+    return md_reg_set_props(mc->reg, p, mc->can_http, mc->can_https); 
+}
+
+static server_rec *get_https_server(const char *domain, server_rec *base_server)
+{
+    md_srv_conf_t *sc;
+    md_mod_conf_t *mc;
+    server_rec *s;
+    request_rec r;
+
+    sc = md_config_get(base_server);
+    mc = sc->mc;
+    memset(&r, 0, sizeof(r));
+    
+    for (s = base_server; s && (mc->local_443 > 0); s = s->next) {
+        if (!mc->manage_base_server && s == base_server) {
+            /* we shall not assign ourselves to the base server */
+            continue;
+        }
+        r.server = s;
+        if (ap_matches_request_vhost(&r, domain, s->port) && uses_port(s, mc->local_443)) {
+            return s;
+        }
+    }
+    return NULL;
+}
+
+static void init_acme_tls_1_domains(md_t *md, server_rec *base_server)
+{
+    server_rec *s;
+    int i;
+    const char *domain;
+    
+    /* Collect those domains that support the "acme-tls/1" protocol. This
+     * is part of the MD (and not tested dynamically), since challenge selection
+     * may be done outside the server, e.g. in the a2md command. */
+     apr_array_clear(md->acme_tls_1_domains);
+    for (i = 0; i < md->domains->nelts; ++i) {
+        domain = APR_ARRAY_IDX(md->domains, i, const char*);
+        if (NULL == (s = get_https_server(domain, base_server))) {
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO()
+                         "%s: no https server_rec found for %s", md->name, domain);
+            continue;
+        }
+        if (!ap_is_allowed_protocol(NULL, NULL, s, PROTO_ACME_TLS_1)) {
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO()
+                         "%s: https server_rec for %s does not have protocol %s enabled", 
+                         md->name, domain, PROTO_ACME_TLS_1);
+            continue;
+        }
+        APR_ARRAY_PUSH(md->acme_tls_1_domains, const char*) = domain;
+    }
+}
+
+static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *base_server, 
+                                       apr_pool_t *p, apr_pool_t *ptemp)
 {
     server_rec *s, *s_https;
     request_rec r;
     md_srv_conf_t *sc;
-    md_mod_conf_t *mc;
     apr_status_t rv = APR_SUCCESS;
     int i;
     const char *domain;
     apr_array_header_t *servers;
     
     sc = md_config_get(base_server);
-    mc = sc->mc;
 
     /* Assign the MD to all server_rec configs that it matches. If there already
      * is an assigned MD not equal this one, the configuration is in error.
@@ -232,15 +449,16 @@ static apr_status_t assign_to_servers(md_t *md, server_rec *base_server,
                 }
                 
                 /* If this server_rec is only for http: requests. Defined
-                 * alias names to not matter for this MD.
+                 * alias names do not matter for this MD.
                  * (see gh issue https://github.com/icing/mod_md/issues/57)
                  * Otherwise, if server has name or an alias not covered,
                  * it is by default auto-added (config transitive).
                  * If mode is "manual", a generated certificate will not match
                  * all necessary names. */
-                if ((!mc->local_80 || !uses_port_only(s, mc->local_80))
-                    && APR_SUCCESS != (rv = md_covers_server(md, s, p))) {
-                    return rv;
+                if (!mc->local_80 || !uses_port(s, mc->local_80)) {
+                    if (APR_SUCCESS != (rv = md_covers_server(md, s, p))) {
+                        return rv;
+                    }
                 }
 
                 sc->assigned = md;
@@ -259,7 +477,7 @@ static apr_status_t assign_to_servers(md_t *md, server_rec *base_server,
 
     if (APR_SUCCESS == rv) {
         if (apr_is_empty_array(servers)) {
-            if (md->drive_mode != MD_DRIVE_ALWAYS) {
+            if (md->renew_mode != MD_RENEW_ALWAYS) {
                 /* Not an error, but looks suspicious */
                 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(10045)
                              "No VirtualHost matches Managed Domain %s", md->name);
@@ -294,692 +512,204 @@ static apr_status_t assign_to_servers(md_t *md, server_rec *base_server,
                     return APR_EINVAL;
                     
                 }
-                
-                /* Ok, we know which local port represents 443, do we have a server_rec
-                 * for MD that has addresses with port 443? */
-                s_https = NULL;
-                for (i = 0; i < servers->nelts; ++i) {
-                    s = APR_ARRAY_IDX(servers, i, server_rec*);
-                    if (matches_port_somewhere(s, mc->local_443)) {
-                        s_https = s;
-                        break;
-                    }
-                }
-                
-                if (!s_https) {
-                    /* Did not find any server_rec that matches this MD *and* has an
-                     * s->addrs match for the https port. Suspicious. */
-                    ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(10106)
-                                 "MD %s is configured to require https, but there seems to be "
-                                 "no VirtualHost for it that has port %d in its address list. "
-                                 "This looks as if it will not work.", 
-                                 md->name, mc->local_443);
-                }
-            }
-        }
-        
-    }
-    return rv;
-}
-
-static apr_status_t md_calc_md_list(apr_pool_t *p, apr_pool_t *plog,
-                                    apr_pool_t *ptemp, server_rec *base_server)
-{
-    md_srv_conf_t *sc;
-    md_mod_conf_t *mc;
-    md_t *md, *omd;
-    const char *domain;
-    apr_status_t rv = APR_SUCCESS;
-    ap_listen_rec *lr;
-    apr_sockaddr_t *sa;
-    int i, j;
-
-    (void)plog;
-    sc = md_config_get(base_server);
-    mc = sc->mc;
-    
-    mc->can_http = 0;
-    mc->can_https = 0;
-
-    for (lr = ap_listeners; lr; lr = lr->next) {
-        for (sa = lr->bind_addr; sa; sa = sa->next) {
-            if  (sa->port == mc->local_80 
-                 && (!lr->protocol || !strncmp("http", lr->protocol, 4))) {
-                mc->can_http = 1;
-            }
-            else if (sa->port == mc->local_443
-                     && (!lr->protocol || !strncmp("http", lr->protocol, 4))) {
-                mc->can_https = 1;
-            }
-        }
-    }
-    
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10037)
-                 "server seems%s reachable via http: (port 80->%d) "
-                 "and%s reachable via https: (port 443->%d) ",
-                 mc->can_http? "" : " not", mc->local_80,
-                 mc->can_https? "" : " not", mc->local_443);
-    
-    /* Complete the properties of the MDs, now that we have the complete, merged
-     * server configurations. 
-     */
-    for (i = 0; i < mc->mds->nelts; ++i) {
-        md = APR_ARRAY_IDX(mc->mds, i, md_t*);
-        md_merge_srv(md, sc, p);
-
-        /* Check that we have no overlap with the MDs already completed */
-        for (j = 0; j < i; ++j) {
-            omd = APR_ARRAY_IDX(mc->mds, j, md_t*);
-            if ((domain = md_common_name(md, omd)) != NULL) {
-                ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10038)
-                             "two Managed Domains have an overlap in domain '%s'"
-                             ", first definition in %s(line %d), second in %s(line %d)",
-                             domain, md->defn_name, md->defn_line_number,
-                             omd->defn_name, omd->defn_line_number);
-                return APR_EINVAL;
-            }
-        }
-
-        /* Assign MD to the server_rec configs that it matches. Perform some
-         * last finishing touches on the MD. */
-        if (APR_SUCCESS != (rv = assign_to_servers(md, base_server, p, ptemp))) {
-            return rv;
-        }
-
-        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10039)
-                     "Completed MD[%s, CA=%s, Proto=%s, Agreement=%s, Drive=%d, renew=%ld]",
-                     md->name, md->ca_url, md->ca_proto, md->ca_agreement,
-                     md->drive_mode, (long)md->renew_window);
-    }
-    
-    return rv;
-}
-
-/**************************************************************************************************/
-/* store & registry setup */
-
-static apr_status_t store_file_ev(void *baton, struct md_store_t *store,
-                                    md_store_fs_ev_t ev, unsigned int group, 
-                                    const char *fname, apr_filetype_e ftype,  
-                                    apr_pool_t *p)
-{
-    server_rec *s = baton;
-    apr_status_t rv;
-    
-    (void)store;
-    ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "store event=%d on %s %s (group %d)", 
-                 ev, (ftype == APR_DIR)? "dir" : "file", fname, group);
-                 
-    /* Directories in group CHALLENGES and STAGING are written to by our watchdog,
-     * running on certain mpms in a child process under a different user. Give them
-     * ownership. 
-     */
-    if (ftype == APR_DIR) {
-        switch (group) {
-            case MD_SG_CHALLENGES:
-            case MD_SG_STAGING:
-                rv = md_make_worker_accessible(fname, p);
-                if (APR_ENOTIMPL != rv) {
-                    return rv;
-                }
-                break;
-            default: 
-                break;
-        }
-    }
-    return APR_SUCCESS;
-}
-
-static apr_status_t check_group_dir(md_store_t *store, md_store_group_t group, 
-                                    apr_pool_t *p, server_rec *s)
-{
-    const char *dir;
-    apr_status_t rv;
-    
-    if (APR_SUCCESS == (rv = md_store_get_fname(&dir, store, group, NULL, NULL, p))
-        && APR_SUCCESS == (rv = apr_dir_make_recursive(dir, MD_FPROT_D_UALL_GREAD, p))) {
-        rv = store_file_ev(s, store, MD_S_FS_EV_CREATED, group, dir, APR_DIR, p);
-    }
-    return rv;
-}
-
-static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc, 
-                                apr_pool_t *p, server_rec *s)
-{
-    const char *base_dir;
-    apr_status_t rv;
-    MD_CHK_VARS;
-    
-    base_dir = ap_server_root_relative(p, mc->base_dir);
-    
-    if (!MD_OK(md_store_fs_init(pstore, p, base_dir))) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10046)"setup store for %s", base_dir);
-        goto out;
-    }
-
-    md_store_fs_set_event_cb(*pstore, store_file_ev, s);
-    if (   !MD_OK(check_group_dir(*pstore, MD_SG_CHALLENGES, p, s))
-        || !MD_OK(check_group_dir(*pstore, MD_SG_STAGING, p, s))
-        || !MD_OK(check_group_dir(*pstore, MD_SG_ACCOUNTS, p, s))) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047) 
-                     "setup challenges directory, call %s", MD_LAST_CHK);
-    }
-    
-out:
-    return rv;
-}
-
-static apr_status_t setup_reg(md_reg_t **preg, apr_pool_t *p, server_rec *s, 
-                              int can_http, int can_https)
-{
-    md_srv_conf_t *sc;
-    md_mod_conf_t *mc;
-    md_store_t *store;
-    apr_status_t rv;
-    MD_CHK_VARS;
-    
-    sc = md_config_get(s);
-    mc = sc->mc;
-    
-    if (   MD_OK(setup_store(&store, mc, p, s))
-        && MD_OK(md_reg_init(preg, p, store, mc->proxy_url))) {
-        mc->reg = *preg;
-        return md_reg_set_props(*preg, p, can_http, can_https); 
-    }
-    return rv;
-}
-
-/**************************************************************************************************/
-/* logging setup */
-
-static server_rec *log_server;
-
-static int log_is_level(void *baton, apr_pool_t *p, md_log_level_t level)
-{
-    (void)baton;
-    (void)p;
-    if (log_server) {
-        return APLOG_IS_LEVEL(log_server, (int)level);
-    }
-    return level <= MD_LOG_INFO;
-}
-
-#define LOG_BUF_LEN 16*1024
-
-static void log_print(const char *file, int line, md_log_level_t level, 
-                      apr_status_t rv, void *baton, apr_pool_t *p, const char *fmt, va_list ap)
-{
-    if (log_is_level(baton, p, level)) {
-        char buffer[LOG_BUF_LEN];
-        
-        memset(buffer, 0, sizeof(buffer));
-        apr_vsnprintf(buffer, LOG_BUF_LEN-1, fmt, ap);
-        buffer[LOG_BUF_LEN-1] = '\0';
-
-        if (log_server) {
-            ap_log_error(file, line, APLOG_MODULE_INDEX, (int)level, rv, log_server, "%s",buffer);
-        }
-        else {
-            ap_log_perror(file, line, APLOG_MODULE_INDEX, (int)level, rv, p, "%s", buffer);
-        }
-    }
-}
-
-/**************************************************************************************************/
-/* lifecycle */
-
-static apr_status_t cleanup_setups(void *dummy)
-{
-    (void)dummy;
-    log_server = NULL;
-    return APR_SUCCESS;
-}
-
-static void init_setups(apr_pool_t *p, server_rec *base_server) 
-{
-    log_server = base_server;
-    apr_pool_cleanup_register(p, NULL, cleanup_setups, apr_pool_cleanup_null);
-}
-
-/**************************************************************************************************/
-/* mod_ssl interface */
-
-static APR_OPTIONAL_FN_TYPE(ssl_is_https) *opt_ssl_is_https;
-
-static void init_ssl(void)
-{
-    opt_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
-}
-
-/**************************************************************************************************/
-/* watchdog based impl. */
-
-#define MD_WATCHDOG_NAME   "_md_"
-
-static APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *wd_get_instance;
-static APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *wd_register_callback;
-static APR_OPTIONAL_FN_TYPE(ap_watchdog_set_callback_interval) *wd_set_interval;
-
-typedef struct {
-    md_t *md;
-
-    int stalled;
-    int renewed;
-    int renewal_notified;
-    apr_time_t restart_at;
-    int need_restart;
-    int restart_processed;
-
-    apr_status_t last_rv;
-    apr_time_t next_check;
-    int error_runs;
-} md_job_t;
-
-typedef struct {
-    apr_pool_t *p;
-    server_rec *s;
-    md_mod_conf_t *mc;
-    ap_watchdog_t *watchdog;
-    
-    apr_time_t next_change;
-    
-    apr_array_header_t *jobs;
-    md_reg_t *reg;
-} md_watchdog;
-
-static void assess_renewal(md_watchdog *wd, md_job_t *job, apr_pool_t *ptemp) 
-{
-    apr_time_t now = apr_time_now();
-    if (now >= job->restart_at) {
-        job->need_restart = 1;
-        ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, wd->s, 
-                     "md(%s): has been renewed, needs restart now", job->md->name);
-    }
-    else {
-        job->next_check = job->restart_at;
-        
-        if (job->renewal_notified) {
-            ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, 
-                         "%s: renewed cert valid in %s", 
-                         job->md->name, md_print_duration(ptemp, job->restart_at - now));
-        }
-        else {
-            char ts[APR_RFC822_DATE_LEN];
-
-            apr_rfc822_date(ts, job->restart_at);
-            ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, wd->s, APLOGNO(10051) 
-                         "%s: has been renewed successfully and should be activated at %s"
-                         " (this requires a server restart latest in %s)", 
-                         job->md->name, ts, md_print_duration(ptemp, job->restart_at - now));
-            job->renewal_notified = 1;
+                
+                /* Ok, we know which local port represents 443, do we have a server_rec
+                 * for MD that has addresses with port 443? */
+                s_https = NULL;
+                for (i = 0; i < servers->nelts; ++i) {
+                    s = APR_ARRAY_IDX(servers, i, server_rec*);
+                    if (matches_port_somewhere(s, mc->local_443)) {
+                        s_https = s;
+                        break;
+                    }
+                }
+                
+                if (!s_https) {
+                    /* Did not find any server_rec that matches this MD *and* has an
+                     * s->addrs match for the https port. Suspicious. */
+                    ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(10106)
+                                 "MD %s is configured to require https, but there seems to be "
+                                 "no VirtualHost for it that has port %d in its address list. "
+                                 "This looks as if it will not work.", 
+                                 md->name, mc->local_443);
+                }
+            }
+            
         }
-    }
-}
-
-static apr_status_t load_job_props(md_reg_t *reg, md_job_t *job, apr_pool_t *p)
-{
-    md_store_t *store = md_reg_store_get(reg);
-    md_json_t *jprops;
-    apr_status_t rv;
-    
-    rv = md_store_load_json(store, MD_SG_STAGING, job->md->name,
-                            MD_FN_JOB, &jprops, p);
-    if (APR_SUCCESS == rv) {
-        job->restart_processed = md_json_getb(jprops, MD_KEY_PROCESSED, NULL);
-        job->error_runs = (int)md_json_getl(jprops, MD_KEY_ERRORS, NULL);
-    }
-    return rv;
-}
-
-static apr_status_t save_job_props(md_reg_t *reg, md_job_t *job, apr_pool_t *p)
-{
-    md_store_t *store = md_reg_store_get(reg);
-    md_json_t *jprops;
-    apr_status_t rv;
-    
-    rv = md_store_load_json(store, MD_SG_STAGING, job->md->name, MD_FN_JOB, &jprops, p);
-    if (APR_STATUS_IS_ENOENT(rv)) {
-        jprops = md_json_create(p);
-        rv = APR_SUCCESS;
-    }
-    if (APR_SUCCESS == rv) {
-        md_json_setb(job->restart_processed, jprops, MD_KEY_PROCESSED, NULL);
-        md_json_setl(job->error_runs, jprops, MD_KEY_ERRORS, NULL);
-        rv = md_store_save_json(store, p, MD_SG_STAGING, job->md->name,
-                                MD_FN_JOB, jprops, 0);
+        
     }
     return rv;
 }
 
-static apr_status_t check_job(md_watchdog *wd, md_job_t *job, apr_pool_t *ptemp)
+static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s, 
+                                            apr_pool_t *p, apr_pool_t *ptemp)
 {
+    int i;
+    md_t *md;
     apr_status_t rv = APR_SUCCESS;
-    apr_time_t valid_from, delay;
-    int errored, renew, error_runs;
-    char ts[APR_RFC822_DATE_LEN];
-    
-    if (apr_time_now() < job->next_check) {
-        /* Job needs to wait */
-        return APR_EAGAIN;
-    }
-    
-    job->next_check = 0;
-    error_runs = job->error_runs;
-
-    if (job->md->state == MD_S_MISSING) {
-        job->stalled = 1;
-    }
-    
-    if (job->stalled) {
-        /* Missing information, this will not change until configuration
-         * is changed and server restarted */
-        rv = APR_INCOMPLETE;
-        ++job->error_runs;
-        goto out;
-    }
-    else if (job->renewed) {
-        assess_renewal(wd, job, ptemp);
-    }
-    else if (APR_SUCCESS == (rv = md_reg_assess(wd->reg, job->md, &errored, &renew, wd->p))) {
-        if (errored) {
-            ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10050) 
-                         "md(%s): in error state", job->md->name);
-        }
-        else if (renew) {
-            ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10052) 
-                         "md(%s): state=%d, driving", job->md->name, job->md->state);
-                         
-            rv = md_reg_stage(wd->reg, job->md, NULL, 0, &valid_from, ptemp);
-            
-            if (APR_SUCCESS == rv) {
-                job->renewed = 1;
-                job->restart_at = valid_from;
-                assess_renewal(wd, job, ptemp);
-            }
-        }
-        else {
-            /* Renew is not necessary yet, leave job->next_check as 0 since 
-             * that keeps the default schedule of running twice a day. */
-            apr_rfc822_date(ts, job->md->expires);
-            ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10053) 
-                         "md(%s): no need to renew yet, cert expires %s", job->md->name, ts);
-        }
-    }
     
-    if (APR_SUCCESS == rv) {
-        job->error_runs = 0;
-    }
-    else {
-        ap_log_error( APLOG_MARK, APLOG_ERR, rv, wd->s, APLOGNO(10056) 
-                     "processing %s", job->md->name);
-        ++job->error_runs;
-        /* back off duration, depending on the errors we encounter in a row */
-        delay = apr_time_from_sec(5 << (job->error_runs - 1));
-        if (delay > apr_time_from_sec(60*60)) {
-            delay = apr_time_from_sec(60*60);
+    apr_array_clear(mc->unused_names);
+    for (i = 0; i < mc->mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mc->mds, i, md_t*);
+        if (APR_SUCCESS != (rv = link_md_to_servers(mc, md, s, p, ptemp))) {
+            goto leave;
         }
-        job->next_check = apr_time_now() + delay;
-        ap_log_error(APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO(10057) 
-                     "%s: encountered error for the %d. time, next run in %s",
-                     job->md->name, job->error_runs, md_print_duration(ptemp, delay));
-    }
-    
-out:
-    if (error_runs != job->error_runs) {
-        apr_status_t rv2 = save_job_props(wd->reg, job, ptemp);
-        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv2, wd->s, "%s: saving job props", job->md->name);
     }
-
-    job->last_rv = rv;
+leave:
     return rv;
 }
 
-static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
+static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p, 
+                                        server_rec *base_server, int log_level)
 {
-    md_watchdog *wd = baton;
+    md_srv_conf_t *base_conf;
+    md_t *md, *omd;
+    const char *domain;
+    const md_timeslice_t *ts;
     apr_status_t rv = APR_SUCCESS;
-    md_job_t *job;
-    apr_time_t next_run, now;
-    int restart = 0;
-    int i;
-    
-    switch (state) {
-        case AP_WATCHDOG_STATE_STARTING:
-            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10054)
-                         "md watchdog start, auto drive %d mds", wd->jobs->nelts);
-            assert(wd->reg);
-        
-            for (i = 0; i < wd->jobs->nelts; ++i) {
-                job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *);
-                load_job_props(wd->reg, job, ptemp);
-            }
-            break;
-        case AP_WATCHDOG_STATE_RUNNING:
-        
-            wd->next_change = 0;
-            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10055)
-                         "md watchdog run, auto drive %d mds", wd->jobs->nelts);
-                         
-            /* normally, we'd like to run at least twice a day */
-            next_run = apr_time_now() + apr_time_from_sec(MD_SECS_PER_DAY / 2);
-
-            /* Check on all the jobs we have */
-            for (i = 0; i < wd->jobs->nelts; ++i) {
-                job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *);
-                
-                rv = check_job(wd, job, ptemp);
+    int i, j;
 
-                if (job->need_restart && !job->restart_processed) {
-                    restart = 1;
-                }
-                if (job->next_check && job->next_check < next_run) {
-                    next_run = job->next_check;
-                }
-            }
+    /* The global module configuration 'mc' keeps a list of all configured MDomains
+     * in the server. This list is collected during configuration processing and,
+     * in the post config phase, get updated from all merged server configurations
+     * before the server starts processing.
+     */ 
+    base_conf = md_config_get(base_server);
+    md_config_get_timespan(&ts, base_conf, MD_CONFIG_RENEW_WINDOW);
+    if (ts) md_reg_set_renew_window_default(mc->reg, ts);
+    md_config_get_timespan(&ts, base_conf, MD_CONFIG_WARN_WINDOW);
+    if (ts) md_reg_set_warn_window_default(mc->reg, ts);
+    /* Complete the properties of the MDs, now that we have the complete, merged
+     * server configurations.
+     */
+    for (i = 0; i < mc->mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mc->mds, i, md_t*);
+        merge_srv_config(md, base_conf, p);
 
-            now = apr_time_now();
-            if (APLOGdebug(wd->s)) {
-                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10107)
-                             "next run in %s", md_print_duration(ptemp, next_run - now));
+        /* Check that we have no overlap with the MDs already completed */
+        for (j = 0; j < i; ++j) {
+            omd = APR_ARRAY_IDX(mc->mds, j, md_t*);
+            if ((domain = md_common_name(md, omd)) != NULL) {
+                ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10038)
+                             "two Managed Domains have an overlap in domain '%s'"
+                             ", first definition in %s(line %d), second in %s(line %d)",
+                             domain, md->defn_name, md->defn_line_number,
+                             omd->defn_name, omd->defn_line_number);
+                return APR_EINVAL;
             }
-            wd_set_interval(wd->watchdog, next_run - now, wd, run_watchdog);
-            break;
-            
-        case AP_WATCHDOG_STATE_STOPPING:
-            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10058)
-                         "md watchdog stopping");
-            break;
-    }
-
-    if (restart) {
-        const char *action, *names = "";
-        int n;
+        }
         
-        for (i = 0, n = 0; i < wd->jobs->nelts; ++i) {
-            job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *);
-            if (job->need_restart && !job->restart_processed) {
-                names = apr_psprintf(ptemp, "%s%s%s", names, n? " " : "", job->md->name);
-                ++n;
-            }
+        if (md->cert_file && !md->pkey_file) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO()
+                         "The Managed Domain '%s', defined in %s(line %d), "
+                         "has a MDCertificateFile but no MDCertificateKeyFile.",
+                         md->name, md->defn_name, md->defn_line_number);
+            return APR_EINVAL;
+        }
+        if (!md->cert_file && md->pkey_file) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO()
+                         "The Managed Domain '%s', defined in %s(line %d), "
+                         "has a MDCertificateKeyFile but no MDCertificateFile.",
+                         md->name, md->defn_name, md->defn_line_number);
+            return APR_EINVAL;
         }
 
-        if (n > 0) {
-            int notified = 1;
-
-            /* Run notify command for ready MDs (if configured) and persist that
-             * we have done so. This process might be reaped after n requests or die
-             * of another cause. The one taking over the watchdog need to notify again.
-             */
-            if (wd->mc->notify_cmd) {
-                const char * const *argv;
-                const char *cmdline;
-                int exit_code;
-                
-                cmdline = apr_psprintf(ptemp, "%s %s", wd->mc->notify_cmd, names); 
-                apr_tokenize_to_argv(cmdline, (char***)&argv, ptemp);
-                if (APR_SUCCESS == (rv = md_util_exec(ptemp, argv[0], argv, &exit_code))) {
-                    ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, wd->s, APLOGNO(10108) 
-                                 "notify command '%s' returned %d", 
-                                 wd->mc->notify_cmd, exit_code);
-                }
-                else {
-                    if (APR_EINCOMPLETE == rv && exit_code) {
-                        rv = 0;
-                    }
-                    ap_log_error(APLOG_MARK, APLOG_ERR, rv, wd->s, APLOGNO(10109) 
-                                 "executing MDNotifyCmd %s returned %d", 
-                                  wd->mc->notify_cmd, exit_code);
-                    notified = 0;
-                } 
-            }
-            
-            if (notified) {
-                /* persist the jobs that were notified */
-                for (i = 0, n = 0; i < wd->jobs->nelts; ++i) {
-                    job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *);
-                    if (job->need_restart && !job->restart_processed) {
-                        job->restart_processed = 1;
-                        save_job_props(wd->reg, job, ptemp);
-                    }
-                }
-            }
-            
-            /* FIXME: the server needs to start gracefully to take the new certificate in.
-             * This poses a variety of problems to solve satisfactory for everyone:
-             * - I myself, have no implementation for Windows 
-             * - on *NIX, child processes run with less privileges, preventing
-             *   the signal based restart trigger to work
-             * - admins want better control of timing windows for restarts, e.g.
-             *   during less busy hours/days.
-             */
-            rv = md_server_graceful(ptemp, wd->s);
-            if (APR_ENOTIMPL == rv) {
-                /* self-graceful restart not supported in this setup */
-                action = " and changes will be activated on next (graceful) server restart.";
-            }
-            else {
-                action = " and server has been asked to restart now.";
-            }
-            ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, wd->s, APLOGNO(10059) 
-                         "The Managed Domain%s %s %s been setup%s",
-                         (n > 1)? "s" : "", names, (n > 1)? "have" : "has", action);
+        init_acme_tls_1_domains(md, base_server);
+
+        if (APLOG_IS_LEVEL(base_server, log_level)) {
+            ap_log_error(APLOG_MARK, log_level, 0, base_server, APLOGNO(10039)
+                         "Completed MD[%s, CA=%s, Proto=%s, Agreement=%s, renew-mode=%d "
+                         "renew_window=%s, warn_window=%s",
+                         md->name, md->ca_url, md->ca_proto, md->ca_agreement, md->renew_mode,
+                         md->renew_window? md_timeslice_format(md->renew_window, p) : "unset",
+                         md->warn_window? md_timeslice_format(md->warn_window, p) : "unset");
         }
     }
-    
-    return APR_SUCCESS;
+    return rv;
 }
 
-static apr_status_t start_watchdog(apr_array_header_t *names, apr_pool_t *p, 
-                                   md_reg_t *reg, server_rec *s, md_mod_conf_t *mc)
+static void load_staged_data(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
 {
-    apr_allocator_t *allocator;
-    md_watchdog *wd;
-    apr_pool_t *wdp;
     apr_status_t rv;
-    const char *name;
     md_t *md;
-    md_job_t *job;
-    int i, errored, renew;
-    
-    wd_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance);
-    wd_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback);
-    wd_set_interval = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_set_callback_interval);
-    
-    if (!wd_get_instance || !wd_register_callback || !wd_set_interval) {
-        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(10061) "mod_watchdog is required");
-        return !OK;
-    }
-    
-    /* We want our own pool with own allocator to keep data across watchdog invocations */
-    apr_allocator_create(&allocator);
-    apr_allocator_max_free_set(allocator, ap_max_mem_free);
-    rv = apr_pool_create_ex(&wdp, p, NULL, allocator);
-    if (rv != APR_SUCCESS) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10062) "md_watchdog: create pool");
-        return rv;
-    }
-    apr_allocator_owner_set(allocator, wdp);
-    apr_pool_tag(wdp, "md_watchdog");
-
-    wd = apr_pcalloc(wdp, sizeof(*wd));
-    wd->p = wdp;
-    wd->reg = reg;
-    wd->s = s;
-    wd->mc = mc;
+    md_result_t *result;
+    int i;
     
-    wd->jobs = apr_array_make(wd->p, 10, sizeof(md_job_t *));
-    for (i = 0; i < names->nelts; ++i) {
-        name = APR_ARRAY_IDX(names, i, const char *);
-        md = md_reg_get(wd->reg, name, wd->p);
-        if (md) {
-            md_reg_assess(wd->reg, md, &errored, &renew, wd->p);
-            if (errored) {
-                ap_log_error( APLOG_MARK, APLOG_WARNING, 0, wd->s, APLOGNO(10063) 
-                             "md(%s): seems errored. Will not process this any further.", name);
-            }
-            else {
-                job = apr_pcalloc(wd->p, sizeof(*job));
-                
-                job->md = md;
-                APR_ARRAY_PUSH(wd->jobs, md_job_t*) = job;
-
-                ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10064) 
-                             "md(%s): state=%d, driving", name, md->state);
-                
-                load_job_props(reg, job, wd->p);
-                if (job->error_runs) {
-                    /* We are just restarting. If we encounter jobs that had errors
-                     * running the protocol on previous staging runs, we reset
-                     * the staging area for it, in case we persisted something that
-                     * causes a loop. */
-                    md_store_t *store = md_reg_store_get(wd->reg);
-                    
-                    md_store_purge(store, p, MD_SG_STAGING, job->md->name);
-                    md_store_purge(store, p, MD_SG_CHALLENGES, job->md->name);
-                }
-            }
+    for (i = 0; i < mc->mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mc->mds, i, md_t *);
+        result = md_result_md_make(p, md);
+        if (APR_SUCCESS == (rv = md_reg_load_staging(mc->reg, md, mc->env, result, p))) {
+            ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10068) 
+                         "%s: staged set activated", md->name);
+        }
+        else if (!APR_STATUS_IS_ENOENT(rv)) {
+            ap_log_error( APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10069)
+                         "%s: error loading staged set", md->name);
         }
     }
+}
 
-    if (!wd->jobs->nelts) {
-        ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10065)
-                     "no managed domain in state to drive, no watchdog needed, "
-                     "will check again on next server (graceful) restart");
-        apr_pool_destroy(wd->p);
-        return APR_SUCCESS;
-    }
+static apr_status_t reinit_mds(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+{
+    md_t *md; 
+    apr_status_t rv = APR_SUCCESS;
+    int i;
     
-    if (APR_SUCCESS != (rv = wd_get_instance(&wd->watchdog, MD_WATCHDOG_NAME, 0, 1, wd->p))) {
-        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10066) 
-                     "create md watchdog(%s)", MD_WATCHDOG_NAME);
-        return rv;
+    for (i = 0; i < mc->mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mc->mds, i, md_t *);
+        if (APR_SUCCESS != (rv = md_reg_reinit_state(mc->reg, (md_t*)md, p))) {
+            ap_log_error( APLOG_MARK, APLOG_ERR, rv, s, APLOGNO()
+                         "%s: error reinitiazing from store", md->name);
+            break;
+        }
     }
-    rv = wd_register_callback(wd->watchdog, 0, wd, run_watchdog);
-    ap_log_error(APLOG_MARK, rv? APLOG_CRIT : APLOG_DEBUG, rv, s, APLOGNO(10067) 
-                 "register md watchdog(%s)", MD_WATCHDOG_NAME);
     return rv;
 }
-static void load_stage_sets(apr_array_header_t *names, apr_pool_t *p, 
-                            md_reg_t *reg, server_rec *s)
+
+static void init_watched_names(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *ptemp, server_rec *s)
 {
-    const char *name; 
-    apr_status_t rv;
+    const md_t *md;
+    md_result_t *result;
     int i;
     
-    for (i = 0; i < names->nelts; ++i) {
-        name = APR_ARRAY_IDX(names, i, const char*);
-        if (APR_SUCCESS == (rv = md_reg_load(reg, name, p))) {
-            ap_log_error( APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10068) 
-                         "%s: staged set activated", name);
+    /* Calculate the list of MD names which we need to watch:
+     * - all MDs that are used somewhere
+     * - all MDs in drive mode 'AUTO' that are not in 'unused_names'
+     */
+    result = md_result_make(ptemp, APR_SUCCESS);
+    apr_array_clear(mc->watched_names);
+    for (i = 0; i < mc->mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mc->mds, i, const md_t *);
+        md_result_set(result, APR_SUCCESS, NULL);
+
+        if (md->state == MD_S_ERROR) {
+            md_result_set(result, APR_EGENERAL, 
+                          "in error state, unable to drive forward. This "
+                          "indicates an incomplete or inconsistent configuration. "
+                          "Please check the log for warnings in this regard.");
+            continue;
         }
-        else if (!APR_STATUS_IS_ENOENT(rv)) {
-            ap_log_error( APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10069)
-                         "%s: error loading staged set", name);
+
+        if (md->renew_mode == MD_RENEW_AUTO
+            && md_array_str_index(mc->unused_names, md->name, 0, 0) >= 0) {
+            /* This MD is not used in any virtualhost, do not watch */
+            continue;
+        }
+        
+        if (md_will_renew_cert(md)) {
+            /* make a test init to detect early errors. */
+            md_reg_test_init(mc->reg, md, mc->env, result, p);
+            if (APR_SUCCESS != result->status && result->detail) {
+                apr_hash_set(mc->init_errors, md->name, APR_HASH_KEY_STRING, apr_pstrdup(p, result->detail));
+                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO() 
+                             "md[%s]: %s", md->name, result->detail);
+            }
         }
+        
+        APR_ARRAY_PUSH(mc->watched_names, const char *) = md->name; 
     }
-    return;
-}
+}   
 
 static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog,
                                    apr_pool_t *ptemp, server_rec *s)
@@ -988,11 +718,9 @@ static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog,
     const char *mod_md_init_key = "mod_md_init_counter";
     md_srv_conf_t *sc;
     md_mod_conf_t *mc;
-    md_reg_t *reg;
-    const md_t *md;
-    apr_array_header_t *drive_names;
     apr_status_t rv = APR_SUCCESS;
-    int i, dry_run = 0;
+    int dry_run = 0, log_level = APLOG_DEBUG;
+    md_store_t *store;
 
     apr_pool_userdata_get(&data, mod_md_init_key, s->process->pool);
     if (data == NULL) {
@@ -1000,20 +728,16 @@ static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog,
          * runs all config hooks to check if it can. If so, it does
          * this all again and starts serving requests.
          * 
-         * This is known.
-         *
          * On a dry run, we therefore do all the cheap config things we
-         * need to do. Because otherwise mod_ssl fails because it calls
-         * us unprepared.
-         * But synching our configuration with the md store
-         * and determining which domains to drive and start a watchdog
-         * and all that, we do not.
+         * need to do to find out if the settings are ok. More expensive
+         * things we delay to the real run.
          */
-        ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10070)
+        dry_run = 1;
+        log_level = APLOG_TRACE1;
+        ap_log_error( APLOG_MARK, log_level, 0, s, APLOGNO(10070)
                      "initializing post config dry run");
         apr_pool_userdata_set((const void *)1, mod_md_init_key,
                               apr_pool_cleanup_null, s->process->pool);
-        dry_run = 1;
     }
     else {
         ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(10071)
@@ -1024,80 +748,136 @@ static apr_status_t md_post_config(apr_pool_t *p, apr_pool_t *plog,
     init_setups(p, s);
     md_log_set(log_is_level, log_print, NULL);
 
-    /* Check uniqueness of MDs, calculate global, configured MD list.
-     * If successful, we have a list of MD definitions that do not overlap. */
-    /* We also need to find out if we can be reached on 80/443 from the outside (e.g. the CA) */
-    if (APR_SUCCESS != (rv =  md_calc_md_list(p, plog, ptemp, s))) {
-        return rv;
-    }
-
     md_config_post_config(s, p);
     sc = md_config_get(s);
     mc = sc->mc;
+    mc->dry_run = dry_run;
 
-    /* Synchronize the definitions we now have with the store via a registry (reg). */
-    if (APR_SUCCESS != (rv = setup_reg(&reg, p, s, mc->can_http, mc->can_https))) {
+    if (APR_SUCCESS != (rv = setup_store(&store, mc, p, s))
+        || APR_SUCCESS != (rv = md_reg_create(&mc->reg, p, store, mc->proxy_url))) {
         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10072)
                      "setup md registry");
-        goto out;
+        goto leave;
     }
     
-    if (APR_SUCCESS != (rv = md_reg_sync(reg, p, ptemp, mc->mds))) {
+    init_ssl();
+
+    /* How to bootstrap this module:
+     * 1. find out if we know where http: and https: requests will arrive
+     * 2. apply the now complete configuration setttings to the MDs
+     * 3. Link MDs to the server_recs they are used in. Detect unused MDs.
+     * 4. Update the store with the MDs. Change domain names, create new MDs, etc.
+     *    Basically all MD properties that are configured directly.
+     *    WARNING: this may change the name of an MD. If an MD loses the first
+     *    of its domain names, it first gets the new first one as name. The 
+     *    store will find the old settings and "recover" the previous name.
+     * 5. Load any staged data from previous driving.
+     * 6. on a dry run, this is all we do
+     * 7. Read back the MD properties that reflect the existance and aspect of
+     *    credentials that are in the store (or missing there). 
+     *    Expiry times, MD state, etc.
+     * 8. Determine the list of MDs that need driving/supervision.
+     * 9. Cleanup any left-overs in registry/store that are no longer needed for
+     *    the list of MDs as we know it now.
+     * 10. If this list is non-empty, setup a watchdog to run. 
+     */
+    /*1*/
+    if (APR_SUCCESS != (rv = detect_supported_ports(mc, s, p, log_level))) goto leave;
+    /*2*/
+    if (APR_SUCCESS != (rv = merge_mds_with_conf(mc, p, s, log_level))) goto leave;
+    /*3*/
+    if (APR_SUCCESS != (rv = link_mds_to_servers(mc, s, p, ptemp))) goto leave;
+    /*4*/
+    if (APR_SUCCESS != (rv = md_reg_sync(mc->reg, p, ptemp, mc->mds))) {
         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10073)
                      "synching %d mds to registry", mc->mds->nelts);
-    }
-    
-    /* Determine the managed domains that are in auto drive_mode. For those,
-     * determine in which state they are:
-     *  - UNKNOWN:            should not happen, report, don't drive
-     *  - ERROR:              something we do not know how to fix, report, don't drive
-     *  - INCOMPLETE/EXPIRED: need to drive them right away
-     *  - COMPLETE:           determine when cert expires, drive when the time comes
-     *
-     * Start the watchdog if we have anything, now or in the future.
-     */
-    drive_names = apr_array_make(ptemp, mc->mds->nelts+1, sizeof(const char *));
-    for (i = 0; i < mc->mds->nelts; ++i) {
-        md = APR_ARRAY_IDX(mc->mds, i, const md_t *);
-        switch (md->drive_mode) {
-            case MD_DRIVE_AUTO:
-                if (md_array_str_index(mc->unused_names, md->name, 0, 0) >= 0) {
-                    break;
-                }
-                /* fall through */
-            case MD_DRIVE_ALWAYS:
-                APR_ARRAY_PUSH(drive_names, const char *) = md->name; 
-                break;
-            default:
-                /* leave out */
-                break;
-        }
-    }
-    
-    init_ssl();
+        goto leave;
+    }
+    /*5*/
+    load_staged_data(mc, s, p);
+    /*6*/
+    if (dry_run) goto leave;
+    /*7*/
+    if (APR_SUCCESS != (rv = reinit_mds(mc, s, p))) goto leave;
+    /*8*/
+    init_watched_names(mc, p, ptemp, s);
+    /*9*/
+    md_reg_cleanup_challenges(mc->reg, p, ptemp, mc->mds);
     
-    if (dry_run) {
-        goto out;
-    }
+    /* From here on, the domains in the registry are readonly 
+     * and only staging/challenges may be manipulated */
+    md_reg_freeze_domains(mc->reg, mc->mds);
     
-    /* If there are MDs to drive, start a watchdog to check on them regularly */
-    if (drive_names->nelts > 0) {
+    if (mc->watched_names->nelts > 0) {
+        /*10*/
         ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10074)
-                     "%d out of %d mds are configured for auto-drive", 
-                     drive_names->nelts, mc->mds->nelts);
+                     "%d out of %d mds need watching", 
+                     mc->watched_names->nelts, mc->mds->nelts);
     
-        load_stage_sets(drive_names, p, reg, s);
         md_http_use_implementation(md_curl_get_impl(p));
-        rv = start_watchdog(drive_names, p, reg, s, mc);
+        rv = md_start_watching(mc, s, p);
     }
     else {
-        ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075)
-                     "no mds to auto drive, no watchdog needed");
+        ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) "no mds to drive");
     }
-out:
+leave:
     return rv;
 }
 
+/**************************************************************************************************/
+/* connection context */
+
+typedef struct {
+    const char *protocol;
+} md_conn_ctx;
+
+static const char *md_protocol_get(const conn_rec *c)
+{
+    md_conn_ctx *ctx;
+
+    ctx = (md_conn_ctx*)ap_get_module_config(c->conn_config, &md_module);
+    return ctx? ctx->protocol : NULL;
+}
+
+/**************************************************************************************************/
+/* ALPN handling */
+
+static int md_protocol_propose(conn_rec *c, request_rec *r,
+                               server_rec *s,
+                               const apr_array_header_t *offers,
+                               apr_array_header_t *proposals)
+{
+    (void)s;
+    if (!r && offers && opt_ssl_is_https && opt_ssl_is_https(c) 
+        && ap_array_str_contains(offers, PROTO_ACME_TLS_1)) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                      "proposing protocol '%s'", PROTO_ACME_TLS_1);
+        APR_ARRAY_PUSH(proposals, const char*) = PROTO_ACME_TLS_1;
+        return OK;
+    }
+    return DECLINED;
+}
+
+static int md_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
+                              const char *protocol)
+{
+    md_conn_ctx *ctx;
+    
+    (void)s;
+    if (!r && opt_ssl_is_https && opt_ssl_is_https(c) && !strcmp(PROTO_ACME_TLS_1, protocol)) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                      "switching protocol '%s'", PROTO_ACME_TLS_1);
+        ctx = apr_pcalloc(c->pool, sizeof(*ctx));
+        ctx->protocol = PROTO_ACME_TLS_1;
+        ap_set_module_config(c->conn_config, &md_module, ctx);
+
+        c->keepalive = AP_CONN_CLOSE;
+        return OK;
+    }
+    return DECLINED;
+}
+
 /**************************************************************************************************/
 /* Access API to other httpd components */
 
@@ -1122,29 +902,23 @@ static apr_status_t setup_fallback_cert(md_store_t *store, const md_t *md,
     md_cert_t *cert;
     md_pkey_spec_t spec;
     apr_status_t rv;
-    MD_CHK_VARS;
     
     spec.type = MD_PKEY_TYPE_RSA;
     spec.params.rsa.bits = MD_PKEY_RSA_BITS_DEF;
     
-    if (   !MD_OK(md_pkey_gen(&pkey, p, &spec))
-        || !MD_OK(md_store_save(store, p, MD_SG_DOMAINS, md->name, 
+    if (APR_SUCCESS != (rv = md_pkey_gen(&pkey, p, &spec))
+        || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, 
                                 MD_FN_FALLBACK_PKEY, MD_SV_PKEY, (void*)pkey, 0))
-        || !MD_OK(md_cert_self_sign(&cert, "Apache Managed Domain Fallback", 
+        || APR_SUCCESS != (rv = md_cert_self_sign(&cert, "Apache Managed Domain Fallback", 
                                     md->domains, pkey, apr_time_from_sec(14 * MD_SECS_PER_DAY), p))
-        || !MD_OK(md_store_save(store, p, MD_SG_DOMAINS, md->name, 
+        || APR_SUCCESS != (rv = md_store_save(store, p, MD_SG_DOMAINS, md->name, 
                                 MD_FN_FALLBACK_CERT, MD_SV_CERT, (void*)cert, 0))) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,  
-                     "%s: setup fallback certificate, call %s", md->name, MD_LAST_CHK);
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO()
+                     "%s: setup fallback certificate", md->name);
     }
     return rv;
 }
 
-static int fexists(const char *fname, apr_pool_t *p)
-{
-    return (*fname && APR_SUCCESS == md_util_is_file(fname, p));
-}
-
 static apr_status_t md_get_certificate(server_rec *s, apr_pool_t *p,
                                        const char **pkeyfile, const char **pcertfile)
 {
@@ -1153,7 +927,6 @@ static apr_status_t md_get_certificate(server_rec *s, apr_pool_t *p,
     md_reg_t *reg;
     md_store_t *store;
     const md_t *md;
-    MD_CHK_VARS;
     
     *pkeyfile = NULL;
     *pcertfile = NULL;
@@ -1188,7 +961,7 @@ static apr_status_t md_get_certificate(server_rec *s, apr_pool_t *p,
     reg = sc->mc->reg;
     assert(reg);
     
-    md = md_reg_get(reg, sc->assigned->name, p);
+    md = sc->assigned;
     if (!md) {
         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10115) 
                      "unable to hand out certificates, as registry can no longer "
@@ -1196,106 +969,100 @@ static apr_status_t md_get_certificate(server_rec *s, apr_pool_t *p,
         return APR_ENOENT;
     }
     
-    if (!MD_OK(md_reg_get_cred_files(reg, md, p, pkeyfile, pcertfile))) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10110) 
-                     "retrieving credentials for MD %s", md->name);
-        return rv;
-    }
-    
-    if (!fexists(*pkeyfile, p) || !fexists(*pcertfile, p)) { 
+    rv = md_reg_get_cred_files(pkeyfile, pcertfile, reg, MD_SG_DOMAINS, md, p);
+    if (APR_STATUS_IS_ENOENT(rv)) {
         /* Provide temporary, self-signed certificate as fallback, so that
          * clients do not get obscure TLS handshake errors or will see a fallback
          * virtual host that is not intended to be served here. */
         store = md_reg_store_get(reg);
         assert(store);    
         
-        md_store_get_fname(pkeyfile, store, MD_SG_DOMAINS, 
-                           md->name, MD_FN_FALLBACK_PKEY, p);
-        md_store_get_fname(pcertfile, store, MD_SG_DOMAINS, 
-                           md->name, MD_FN_FALLBACK_CERT, p);
-        if (!fexists(*pkeyfile, p) || !fexists(*pcertfile, p)) { 
-            if (!MD_OK(setup_fallback_cert(store, md, s, p))) {
+        md_store_get_fname(pkeyfile, store, MD_SG_DOMAINS, md->name, MD_FN_FALLBACK_PKEY, p);
+        md_store_get_fname(pcertfile, store, MD_SG_DOMAINS, md->name, MD_FN_FALLBACK_CERT, p);
+        if (!md_file_exists(*pkeyfile, p) || !md_file_exists(*pcertfile, p)) { 
+            if (APR_SUCCESS != (rv = setup_fallback_cert(store, md, s, p))) {
                 return rv;
             }
         }
-        
         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10116)  
                      "%s: providing fallback certificate for server %s", 
                      md->name, s->server_hostname);
         return APR_EAGAIN;
     }
-    
-    /* We have key and cert files, but they might no longer be valid or not
-     * match all domain names. Still use these files for now, but indicate that 
-     * resources should no longer be served until we have a new certificate again. */
-    if (md->state != MD_S_COMPLETE) {
-        rv = APR_EAGAIN;
+    else if (APR_SUCCESS != rv) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10110) 
+                     "retrieving credentials for MD %s", md->name);
+        return rv;
     }
+    
     ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10077) 
-                 "%s: providing certificate for server %s", md->name, s->server_hostname);
+                 "%s[state=%d]: providing certificate for server %s", 
+                 md->name, md->state, s->server_hostname);
     return rv;
 }
 
-static int compat_warned;
-static apr_status_t md_get_credentials(server_rec *s, apr_pool_t *p,
-                                       const char **pkeyfile, 
-                                       const char **pcertfile, 
-                                       const char **pchainfile)
-{
-    *pchainfile = NULL;
-    if (!compat_warned) {
-        compat_warned = 1;
-        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, /* no APLOGNO */
-                     "You are using mod_md with an old patch to mod_ssl. This will "
-                     " work for now, but support will be dropped in a future release.");
-    }
-    return md_get_certificate(s, p, pkeyfile, pcertfile);
-}
-
 static int md_is_challenge(conn_rec *c, const char *servername,
                            X509 **pcert, EVP_PKEY **pkey)
 {
     md_srv_conf_t *sc;
     apr_size_t slen, sufflen = sizeof(MD_TLSSNI01_DNS_SUFFIX) - 1;
+    const char *protocol, *challenge, *cert_name, *pkey_name;
     apr_status_t rv;
 
+    if (!servername) goto out;
+                  
+    challenge = NULL;
     slen = strlen(servername);
-    if (slen <= sufflen 
-        || apr_strnatcasecmp(MD_TLSSNI01_DNS_SUFFIX, servername + slen - sufflen)) {
-        return 0;
+    if (slen > sufflen 
+        && !apr_strnatcasecmp(MD_TLSSNI01_DNS_SUFFIX, servername + slen - sufflen)) {
+        /* server name ends with the tls-sni-01 challenge suffix, answer if
+         * we have prepared a certificate in store under this name */
+        challenge = "tls-sni-01";
+        cert_name = MD_FN_TLSSNI01_CERT;
+        pkey_name = MD_FN_TLSSNI01_PKEY;
+    }
+    else if ((protocol = md_protocol_get(c)) && !strcmp(PROTO_ACME_TLS_1, protocol)) {
+        challenge = "tls-alpn-01";
+        cert_name = MD_FN_TLSALPN01_CERT;
+        pkey_name = MD_FN_TLSALPN01_PKEY;
     }
     
-    sc = md_config_get(c->base_server);
-    if (sc && sc->mc->reg) {
-        md_store_t *store = md_reg_store_get(sc->mc->reg);
-        md_cert_t *mdcert;
-        md_pkey_t *mdpkey;
-        
-        rv = md_store_load(store, MD_SG_CHALLENGES, servername, 
-                           MD_FN_TLSSNI01_CERT, MD_SV_CERT, (void**)&mdcert, c->pool);
-        if (APR_SUCCESS == rv && (*pcert = md_cert_get_X509(mdcert))) {
-            rv = md_store_load(store, MD_SG_CHALLENGES, servername, 
-                               MD_FN_TLSSNI01_PKEY, MD_SV_PKEY, (void**)&mdpkey, c->pool);
-            if (APR_SUCCESS == rv && (*pkey = md_pkey_get_EVP_PKEY(mdpkey))) {
-                ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10078)
-                              "%s: is a tls-sni-01 challenge host", servername);
-                return 1;
+    if (challenge) {
+        sc = md_config_get(c->base_server);
+        if (sc && sc->mc->reg) {
+            md_store_t *store = md_reg_store_get(sc->mc->reg);
+            md_cert_t *mdcert;
+            md_pkey_t *mdpkey;
+            
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "%s: load certs/keys %s/%s",
+                          servername, cert_name, pkey_name);
+            rv = md_store_load(store, MD_SG_CHALLENGES, servername, cert_name, 
+                               MD_SV_CERT, (void**)&mdcert, c->pool);
+            if (APR_SUCCESS == rv && (*pcert = md_cert_get_X509(mdcert))) {
+                rv = md_store_load(store, MD_SG_CHALLENGES, servername, pkey_name, 
+                                   MD_SV_PKEY, (void**)&mdpkey, c->pool);
+                if (APR_SUCCESS == rv && (*pkey = md_pkey_get_EVP_PKEY(mdpkey))) {
+                    ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10078)
+                                  "%s: is a %s challenge host", servername, challenge);
+                    return 1;
+                }
+                ap_log_cerror(APLOG_MARK, APLOG_WARNING, rv, c, APLOGNO(10079)
+                              "%s: challenge data not complete, key unavailable", servername);
+            }
+            else {
+                ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, APLOGNO(10080)
+                              "%s: unknown %s challenge host", servername, challenge);
             }
-            ap_log_cerror(APLOG_MARK, APLOG_WARNING, rv, c, APLOGNO(10079)
-                          "%s: challenge data not complete, key unavailable", servername);
-        }
-        else {
-            ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, APLOGNO(10080)
-                          "%s: unknown TLS SNI challenge host", servername);
         }
     }
+out:
     *pcert = NULL;
     *pkey = NULL;
     return 0;
 }
 
 /**************************************************************************************************/
-/* ACME challenge responses */
+/* ACME 'http-01' challenge responses */
 
 #define WELL_KNOWN_PREFIX           "/.well-known/"
 #define ACME_CHALLENGE_PREFIX       WELL_KNOWN_PREFIX"acme-challenge/"
@@ -1306,7 +1073,7 @@ static int md_http_challenge_pr(request_rec *r)
     const md_srv_conf_t *sc;
     const char *name, *data;
     md_reg_t *reg;
-    int configured;
+    const md_t *md;
     apr_status_t rv;
     
     if (r->parsed_uri.path 
@@ -1316,7 +1083,7 @@ static int md_http_challenge_pr(request_rec *r)
             ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, 
                           "access inside /.well-known/acme-challenge for %s%s", 
                           r->hostname, r->parsed_uri.path);
-            configured = (NULL != md_get_by_domain(sc->mc->mds, r->hostname));
+            md = md_get_by_domain(sc->mc->mds, r->hostname);
             name = r->parsed_uri.path + sizeof(ACME_CHALLENGE_PREFIX)-1;
             reg = sc && sc->mc? sc->mc->reg : NULL;
             
@@ -1345,21 +1112,22 @@ static int md_http_challenge_pr(request_rec *r)
                     
                     return DONE;
                 }
-                else if (!configured) {
-                    /* The request hostname is not for a configured domain. We are not
+                else if (!md || md->renew_mode == MD_RENEW_MANUAL
+                    || (md->cert_file && md->renew_mode == MD_RENEW_AUTO)) {
+                    /* The request hostname is not for a domain - or at least not for
+                     * a domain that we renew ourselves. We are not
                      * the sole authority here for /.well-known/acme-challenge (see PR62189).
-                     * So, we decline to handle this and let others step in.
+                     * So, we decline to handle this and give others a chance to provide
+                     * the answer.
                      */
                     return DECLINED;
                 }
                 else if (APR_STATUS_IS_ENOENT(rv)) {
                     return HTTP_NOT_FOUND;
                 }
-                else {
-                    ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10081)
-                                  "loading challenge %s from store", name);
-                    return HTTP_INTERNAL_SERVER_ERROR;
-                }
+                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10081)
+                              "loading challenge %s from store", name);
+                return HTTP_INTERNAL_SERVER_ERROR;
             }
         }
     }
@@ -1436,7 +1204,8 @@ static void md_hooks(apr_pool_t *pool)
 {
     static const char *const mod_ssl[] = { "mod_ssl.c", NULL};
 
-    md_acme_init(pool, AP_SERVER_BASEVERSION);
+    /* Leave the ssl initialization to mod_ssl or friends. */
+    md_acme_init(pool, AP_SERVER_BASEVERSION, 0);
         
     ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks");
     
@@ -1449,12 +1218,20 @@ static void md_hooks(apr_pool_t *pool)
     ap_hook_child_init(md_child_init, NULL, mod_ssl, APR_HOOK_MIDDLE);
 
     /* answer challenges *very* early, before any configured authentication may strike */
-    ap_hook_post_read_request(md_require_https_maybe, NULL, NULL, APR_HOOK_FIRST);
+    ap_hook_post_read_request(md_require_https_maybe, mod_ssl, NULL, APR_HOOK_MIDDLE);
     ap_hook_post_read_request(md_http_challenge_pr, NULL, NULL, APR_HOOK_MIDDLE);
 
+    ap_hook_protocol_propose(md_protocol_propose, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_protocol_switch(md_protocol_switch, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_protocol_get(md_protocol_get, NULL, NULL, APR_HOOK_MIDDLE);
+
+    /* Status request handlers and contributors */
+    ap_hook_post_read_request(md_http_cert_status, NULL, mod_ssl, APR_HOOK_MIDDLE);
+    APR_OPTIONAL_HOOK(ap, status_hook, md_status_hook, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_handler(md_status_handler, NULL, NULL, APR_HOOK_MIDDLE);
+
     APR_REGISTER_OPTIONAL_FN(md_is_managed);
     APR_REGISTER_OPTIONAL_FN(md_get_certificate);
     APR_REGISTER_OPTIONAL_FN(md_is_challenge);
-    APR_REGISTER_OPTIONAL_FN(md_get_credentials);
 }
 
index 5ff8f5221c398da057197a9852f1cd8f8f4e07ba..cf043c45306fdee1a70752dd4c0ad974a9b3fb49 100644 (file)
@@ -39,12 +39,4 @@ APR_DECLARE_OPTIONAL_FN(int,
                         md_is_challenge, (struct conn_rec *, const char *,
                                           X509 **pcert, EVP_PKEY **pkey));
 
-/* Backward compatibility to older mod_ssl patches, will generate
- * a WARNING in the logs, use 'md_get_certificate' instead */
-APR_DECLARE_OPTIONAL_FN(apr_status_t, 
-                        md_get_credentials, (struct server_rec *, apr_pool_t *,
-                                             const char **pkeyfile, 
-                                             const char **pcertfile, 
-                                             const char **pchainfile));
-
 #endif /* mod_md_mod_md_h */
index d48c10937259f9a9c5e86ea34e350d66a1fd3901..9123a22d326bb070593695f79d53b18394984b76 100644 (file)
 
 #include "md.h"
 #include "md_crypt.h"
+#include "md_log.h"
 #include "md_util.h"
 #include "mod_md_private.h"
 #include "mod_md_config.h"
 
-#define MD_CMD_MD             "MDomain"
-#define MD_CMD_OLD_MD         "ManagedDomain"
 #define MD_CMD_MD_SECTION     "<MDomainSet"
-#define MD_CMD_MD_OLD_SECTION "<ManagedDomain"
-#define MD_CMD_BASE_SERVER    "MDBaseServer"
-#define MD_CMD_CA             "MDCertificateAuthority"
-#define MD_CMD_CAAGREEMENT    "MDCertificateAgreement"
-#define MD_CMD_CACHALLENGES   "MDCAChallenges"
-#define MD_CMD_CAPROTO        "MDCertificateProtocol"
-#define MD_CMD_DRIVEMODE      "MDDriveMode"
-#define MD_CMD_MEMBER         "MDMember"
-#define MD_CMD_MEMBERS        "MDMembers"
-#define MD_CMD_MUSTSTAPLE     "MDMustStaple"
-#define MD_CMD_NOTIFYCMD      "MDNotifyCmd"
-#define MD_CMD_PORTMAP        "MDPortMap"
-#define MD_CMD_PKEYS          "MDPrivateKeys"
-#define MD_CMD_PROXY          "MDHttpProxy"
-#define MD_CMD_RENEWWINDOW    "MDRenewWindow"
-#define MD_CMD_REQUIREHTTPS   "MDRequireHttps"
-#define MD_CMD_STOREDIR       "MDStoreDir"
+#define MD_CMD_MD2_SECTION    "<MDomain"
 
 #define DEF_VAL     (-1)
 
 
 /* Default settings for the global conf */
 static md_mod_conf_t defmc = {
-    NULL,
+    NULL,                      /* list of mds */
 #if AP_MODULE_MAGIC_AT_LEAST(20180906, 2)
-    NULL, /* apply default state-dir-relative */
+    NULL,                      /* base dirm by default state-dir-relative */
 #else
     MD_DEFAULT_BASE_DIR,
 #endif
-    NULL,
-    NULL,
-    80,
-    443,
-    0,
-    0,
-    0,
-    MD_HSTS_MAX_AGE_DEFAULT,
-    NULL,
-    NULL,
-    NULL,
+    NULL,                      /* proxy url for outgoing http */
+    NULL,                      /* md_reg */
+    80,                        /* local http: port */
+    443,                       /* local https: port */
+    0,                         /* can http: */
+    0,                         /* can https: */
+    0,                         /* manage base server */
+    MD_HSTS_MAX_AGE_DEFAULT,   /* hsts max-age */
+    NULL,                      /* hsts headers */
+    NULL,                      /* unused names */
+    NULL,                      /* watched names */
+    NULL,                      /* init errors hash */
+    NULL,                      /* notify cmd */
+    NULL,                      /* message cmd */
+    NULL,                      /* env table */
+    0,                         /* dry_run flag */
+    1,                         /* server_status_enabled */
+    1,                         /* certificate_status_enabled */
+};
+
+static md_timeslice_t def_renew_window = {
+    MD_TIME_LIFE_NORM,
+    MD_TIME_RENEW_WINDOW_DEF,
+};
+static md_timeslice_t def_warn_window = {
+    MD_TIME_LIFE_NORM,
+    MD_TIME_WARN_WINDOW_DEF,
 };
 
 /* Default server specific setting */
 static md_srv_conf_t defconf = {
-    "default",
-    NULL,
-    &defmc,
-
-    1,
-    MD_REQUIRE_OFF,
-    MD_DRIVE_AUTO,
-    0,
-    NULL, 
-    apr_time_from_sec(90 * MD_SECS_PER_DAY), /* If the cert lifetime were 90 days, renew */
-    apr_time_from_sec(30 * MD_SECS_PER_DAY), /* 30 days before. Adjust to actual lifetime */
-    MD_ACME_DEF_URL,
-    "ACME",
-    NULL,
-    NULL,
-    NULL,
-    NULL,
+    "default",                 /* name */
+    NULL,                      /* server_rec */
+    &defmc,                    /* mc */
+    1,                         /* transitive */
+    MD_REQUIRE_OFF,            /* require https */
+    MD_RENEW_AUTO,             /* renew mode */
+    0,                         /* must staple */
+    NULL,                      /* pkey spec */
+    &def_renew_window,         /* renew window */
+    &def_warn_window,          /* warn window */
+    NULL,                      /* ca url */
+    "ACME",                    /* ca protocol */
+    NULL,                      /* ca agreemnent */
+    NULL,                      /* ca challenges array */
+    NULL,                      /* currently defined md */
+    NULL,                      /* assigned md, post config */
 };
 
 static md_mod_conf_t *mod_md_config;
@@ -120,7 +118,10 @@ static md_mod_conf_t *md_mod_conf_get(apr_pool_t *pool, int create)
         memcpy(mod_md_config, &defmc, sizeof(*mod_md_config));
         mod_md_config->mds = apr_array_make(pool, 5, sizeof(const md_t *));
         mod_md_config->unused_names = apr_array_make(pool, 5, sizeof(const md_t *));
-        
+        mod_md_config->watched_names = apr_array_make(pool, 5, sizeof(const md_t *));
+        mod_md_config->env = apr_table_make(pool, 10);
+        mod_md_config->init_errors = apr_hash_make(pool);
+         
         apr_pool_cleanup_register(pool, NULL, cleanup_mod_config, apr_pool_cleanup_null);
     }
     
@@ -133,11 +134,11 @@ static void srv_conf_props_clear(md_srv_conf_t *sc)
 {
     sc->transitive = DEF_VAL;
     sc->require_https = MD_REQUIRE_UNSET;
-    sc->drive_mode = DEF_VAL;
+    sc->renew_mode = DEF_VAL;
     sc->must_staple = DEF_VAL;
     sc->pkey_spec = NULL;
-    sc->renew_norm = DEF_VAL;
-    sc->renew_window = DEF_VAL;
+    sc->renew_window = NULL;
+    sc->warn_window = NULL;
     sc->ca_url = NULL;
     sc->ca_proto = NULL;
     sc->ca_agreement = NULL;
@@ -148,10 +149,10 @@ static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
 {
     to->transitive = from->transitive;
     to->require_https = from->require_https;
-    to->drive_mode = from->drive_mode;
+    to->renew_mode = from->renew_mode;
     to->must_staple = from->must_staple;
     to->pkey_spec = from->pkey_spec;
-    to->renew_norm = from->renew_norm;
+    to->warn_window = from->warn_window;
     to->renew_window = from->renew_window;
     to->ca_url = from->ca_url;
     to->ca_proto = from->ca_proto;
@@ -163,12 +164,11 @@ static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t
 {
     if (from->require_https != MD_REQUIRE_UNSET) md->require_https = from->require_https;
     if (from->transitive != DEF_VAL) md->transitive = from->transitive;
-    if (from->drive_mode != DEF_VAL) md->drive_mode = from->drive_mode;
+    if (from->renew_mode != DEF_VAL) md->renew_mode = from->renew_mode;
     if (from->must_staple != DEF_VAL) md->must_staple = from->must_staple;
     if (from->pkey_spec) md->pkey_spec = from->pkey_spec;
-    if (from->renew_norm != DEF_VAL) md->renew_norm = from->renew_norm;
-    if (from->renew_window != DEF_VAL) md->renew_window = from->renew_window;
-
+    if (from->renew_window) md->renew_window = from->renew_window;
+    if (from->warn_window) md->warn_window = from->warn_window;
     if (from->ca_url) md->ca_url = from->ca_url;
     if (from->ca_proto) md->ca_proto = from->ca_proto;
     if (from->ca_agreement) md->ca_agreement = from->ca_agreement;
@@ -202,11 +202,11 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv)
 
     nsc->transitive = (add->transitive != DEF_VAL)? add->transitive : base->transitive;
     nsc->require_https = (add->require_https != MD_REQUIRE_UNSET)? add->require_https : base->require_https;
-    nsc->drive_mode = (add->drive_mode != DEF_VAL)? add->drive_mode : base->drive_mode;
+    nsc->renew_mode = (add->renew_mode != DEF_VAL)? add->renew_mode : base->renew_mode;
     nsc->must_staple = (add->must_staple != DEF_VAL)? add->must_staple : base->must_staple;
     nsc->pkey_spec = add->pkey_spec? add->pkey_spec : base->pkey_spec;
-    nsc->renew_window = (add->renew_norm != DEF_VAL)? add->renew_norm : base->renew_norm;
-    nsc->renew_window = (add->renew_window != DEF_VAL)? add->renew_window : base->renew_window;
+    nsc->renew_window = add->renew_window? add->renew_window : base->renew_window;
+    nsc->warn_window = add->warn_window? add->warn_window : base->warn_window;
 
     nsc->ca_url = add->ca_url? add->ca_url : base->ca_url;
     nsc->ca_proto = add->ca_proto? add->ca_proto : base->ca_proto;
@@ -235,7 +235,7 @@ static int inside_section(cmd_parms *cmd, const char *section) {
 }
 
 static int inside_md_section(cmd_parms *cmd) {
-    return (inside_section(cmd, MD_CMD_MD_SECTION) || inside_section(cmd, MD_CMD_MD_OLD_SECTION));
+    return (inside_section(cmd, MD_CMD_MD_SECTION) || inside_section(cmd, MD_CMD_MD2_SECTION));
 }
 
 static const char *md_section_check(cmd_parms *cmd) {
@@ -246,6 +246,22 @@ static const char *md_section_check(cmd_parms *cmd) {
     return NULL;
 }
 
+static const char *set_on_off(int *pvalue, const char *s, apr_pool_t *p)
+{
+    if (!apr_strnatcasecmp("off", s)) {
+        *pvalue = 0;
+    }
+    else if (!apr_strnatcasecmp("on", s)) {
+        *pvalue = 1;
+    }
+    else {
+        return apr_pstrcat(p, "unknown '", s, 
+                           "', supported parameter values are 'on' and 'off'", 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) {
@@ -432,21 +448,21 @@ static const char *md_config_set_agreement(cmd_parms *cmd, void *dc, const char
     return NULL;
 }
 
-static const char *md_config_set_drive_mode(cmd_parms *cmd, void *dc, const char *value)
+static const char *md_config_set_renew_mode(cmd_parms *cmd, void *dc, const char *value)
 {
     md_srv_conf_t *config = md_config_get(cmd->server);
     const char *err;
-    md_drive_mode_t drive_mode;
+    md_renew_mode_t renew_mode;
 
     (void)dc;
     if (!apr_strnatcasecmp("auto", value) || !apr_strnatcasecmp("automatic", value)) {
-        drive_mode = MD_DRIVE_AUTO;
+        renew_mode = MD_RENEW_AUTO;
     }
     else if (!apr_strnatcasecmp("always", value)) {
-        drive_mode = MD_DRIVE_ALWAYS;
+        renew_mode = MD_RENEW_ALWAYS;
     }
     else if (!apr_strnatcasecmp("manual", value) || !apr_strnatcasecmp("stick", value)) {
-        drive_mode = MD_DRIVE_MANUAL;
+        renew_mode = MD_RENEW_MANUAL;
     }
     else {
         return apr_pstrcat(cmd->pool, "unknown MDDriveMode ", value, NULL);
@@ -455,7 +471,7 @@ static const char *md_config_set_drive_mode(cmd_parms *cmd, void *dc, const char
     if (!inside_md_section(cmd) && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
         return err;
     }
-    config->drive_mode = drive_mode;
+    config->renew_mode = renew_mode;
     return NULL;
 }
 
@@ -468,18 +484,7 @@ static const char *md_config_set_must_staple(cmd_parms *cmd, void *dc, const cha
     if (!inside_md_section(cmd) && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
         return err;
     }
-
-    if (!apr_strnatcasecmp("off", value)) {
-        config->must_staple = 0;
-    }
-    else if (!apr_strnatcasecmp("on", value)) {
-        config->must_staple = 1;
-    }
-    else {
-        return apr_pstrcat(cmd->pool, "unknown '", value, 
-                           "', supported parameter values are 'on' and 'off'", NULL);
-    }
-    return NULL;
+    return set_on_off(&config->must_staple, value, cmd->pool);
 }
 
 static const char *md_config_set_base_server(cmd_parms *cmd, void *dc, const char *value)
@@ -488,19 +493,8 @@ static const char *md_config_set_base_server(cmd_parms *cmd, void *dc, const cha
     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
 
     (void)dc;
-    if (!err) {
-        if (!apr_strnatcasecmp("off", value)) {
-            config->mc->manage_base_server = 0;
-        }
-        else if (!apr_strnatcasecmp("on", value)) {
-            config->mc->manage_base_server = 1;
-        }
-        else {
-            err = apr_pstrcat(cmd->pool, "unknown '", value, 
-                              "', supported parameter values are 'on' and 'off'", NULL);
-        }
-    }
-    return err;
+    if (err) return err;
+    return set_on_off(&config->mc->manage_base_server, value, cmd->pool);
 }
 
 static const char *md_config_set_require_https(cmd_parms *cmd, void *dc, const char *value)
@@ -529,90 +523,42 @@ static const char *md_config_set_require_https(cmd_parms *cmd, void *dc, const c
     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 == value) {
-        return APR_EINVAL;
-    }
-    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 apr_status_t percentage_parse(const char *value, int *ppercent)
+static const char *md_config_set_renew_window(cmd_parms *cmd, void *dc, const char *value)
 {
-    char *endp;
-    apr_int64_t n;
+    md_srv_conf_t *config = md_config_get(cmd->server);
+    const char *err;
     
-    n = apr_strtoi64(value, &endp, 10);
-    if (errno) {
-        return errno;
+    (void)dc;
+    if (!inside_md_section(cmd)
+        && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+        return err;
     }
-    if (*endp == '%') {
-        if (n < 0 || n >= 100) {
-            return APR_BADARG;
-        }
-        *ppercent = (int)n;
-        return APR_SUCCESS;
+    err = md_timeslice_parse(&config->renew_window, cmd->pool, value, MD_TIME_LIFE_NORM);
+    if (!err && config->renew_window->norm 
+        && (config->renew_window->len >= config->renew_window->norm)) {
+        err = "a length of 100% or more is not allowed.";
     }
-    return APR_EINVAL;
+    if (err) return apr_psprintf(cmd->pool, "MDRenewWindow %s", err);
+    return NULL;
 }
 
-static const char *md_config_set_renew_window(cmd_parms *cmd, void *dc, const char *value)
+static const char *md_config_set_warn_window(cmd_parms *cmd, void *dc, const char *value)
 {
     md_srv_conf_t *config = md_config_get(cmd->server);
     const char *err;
-    apr_interval_time_t timeout;
-    int percent = 0;
     
     (void)dc;
     if (!inside_md_section(cmd)
         && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
         return err;
     }
-
-    /* Inspired by http_core.c */
-    if (duration_parse(value, &timeout, "d") == APR_SUCCESS) {
-        config->renew_norm = 0;
-        config->renew_window = timeout;
-        return NULL;
+    err = md_timeslice_parse(&config->warn_window, cmd->pool, value, MD_TIME_LIFE_NORM);
+    if (!err && config->warn_window->norm 
+        && (config->warn_window->len >= config->warn_window->norm)) {
+        err = "a length of 100% or more is not allowed.";
     }
-    else {
-        switch (percentage_parse(value, &percent)) {
-            case APR_SUCCESS:
-                config->renew_norm = apr_time_from_sec(100 * MD_SECS_PER_DAY);
-                config->renew_window = apr_time_from_sec(percent * MD_SECS_PER_DAY);
-                return NULL;
-            case APR_BADARG:
-                return "MDRenewWindow as percent must be less than 100";
-        }
-    }
-    return "MDRenewWindow has unrecognized format";
+    if (err) return apr_psprintf(cmd->pool, "MDWarnWindow %s", err);
+    return NULL;
 }
 
 static const char *md_config_set_proxy(cmd_parms *cmd, void *arg, const char *value)
@@ -648,11 +594,19 @@ static const char *md_config_set_store_dir(cmd_parms *cmd, void *arg, const char
 static const char *set_port_map(md_mod_conf_t *mc, const char *value)
 {
     int net_port, local_port;
-    char *endp;
+    const char *endp;
 
-    net_port = (int)apr_strtoi64(value, &endp, 10);
-    if (errno) {
-        return "unable to parse first port number";
+    if (!strncmp("http:", value, sizeof("http:") - 1)) {
+        net_port = 80; endp = value + sizeof("http") - 1; 
+    }
+    else if (!strncmp("https:", value, sizeof("https:") - 1)) {
+        net_port = 443; endp = value + sizeof("https") - 1; 
+    }
+    else {
+        net_port = (int)apr_strtoi64(value, (char**)&endp, 10);
+        if (errno) {
+            return "unable to parse first port number";
+        }
     }
     if (!endp || *endp != ':') {
         return "no ':' after first port number";
@@ -662,7 +616,7 @@ static const char *set_port_map(md_mod_conf_t *mc, const char *value)
         local_port = 0;
     }
     else {
-        local_port = (int)apr_strtoi64(endp, &endp, 10);
+        local_port = (int)apr_strtoi64(endp, (char**)&endp, 10);
         if (errno) {
             return "unable to parse second port number";
         }
@@ -792,70 +746,141 @@ static const char *md_config_set_notify_cmd(cmd_parms *cmd, void *mconfig, const
     return NULL;
 }
 
-static const char *md_config_set_names_old(cmd_parms *cmd, void *dc, 
-                                           int argc, char *const argv[])
+static const char *md_config_set_msg_cmd(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+    md_srv_conf_t *sc = md_config_get(cmd->server);
+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+    if (err) {
+        return err;
+    }
+    sc->mc->message_cmd = arg;
+    (void)mconfig;
+    return NULL;
+}
+
+static const char *md_config_set_dns01_cmd(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+    md_srv_conf_t *sc = md_config_get(cmd->server);
+    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+    if (err) {
+        return err;
+    }
+    apr_table_set(sc->mc->env, MD_KEY_CMD_DNS01, arg);
+    (void)mconfig;
+    return NULL;
+}
+
+static const char *md_config_set_cert_file(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+    md_srv_conf_t *sc = md_config_get(cmd->server);
+    const char *err;
+    
+    (void)mconfig;
+    if (NULL != (err = md_section_check(cmd))) return err;
+    assert(sc->current);
+    sc->current->cert_file = arg;
+    return NULL;
+}
+
+static const char *md_config_set_key_file(cmd_parms *cmd, void *mconfig, const char *arg)
 {
-    ap_log_error( APLOG_MARK, APLOG_WARNING, 0, cmd->server,  
-                 "mod_md: directive 'ManagedDomain' is deprecated, replace with 'MDomain'.");
-    return md_config_set_names(cmd, dc, argc, argv);
+    md_srv_conf_t *sc = md_config_get(cmd->server);
+    const char *err;
+    
+    (void)mconfig;
+    if (NULL != (err = md_section_check(cmd))) return err;
+    assert(sc->current);
+    sc->current->pkey_file = arg;
+    return NULL;
 }
 
-static const char *md_config_sec_start_old(cmd_parms *cmd, void *mconfig, const char *arg)
+static const char *md_config_set_server_status(cmd_parms *cmd, void *dc, const char *value)
 {
-    ap_log_error( APLOG_MARK, APLOG_WARNING, 0, cmd->server,  
-                 "mod_md: directive '<ManagedDomain' is deprecated, replace with '<MDomainSet'.");
-    return md_config_sec_start(cmd, mconfig, arg);
+    md_srv_conf_t *sc = md_config_get(cmd->server);
+    const char *err;
+
+    (void)dc;
+    if (!inside_md_section(cmd) && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+        return err;
+    }
+    return set_on_off(&sc->mc->server_status_enabled, value, cmd->pool);
 }
 
+static const char *md_config_set_certificate_status(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_srv_conf_t *sc = md_config_get(cmd->server);
+    const char *err;
+
+    (void)dc;
+    if (!inside_md_section(cmd) && (err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+        return err;
+    }
+    return set_on_off(&sc->mc->certificate_status_enabled, value, cmd->pool);
+}
+
+
 const command_rec md_cmds[] = {
-    AP_INIT_TAKE1(     MD_CMD_CA, md_config_set_ca, NULL, RSRC_CONF, 
+    AP_INIT_TAKE1("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF, 
                   "URL of CA issuing the certificates"),
-    AP_INIT_TAKE1(     MD_CMD_CAAGREEMENT, md_config_set_agreement, NULL, RSRC_CONF, 
-                  "URL of CA Terms-of-Service agreement you accept"),
-    AP_INIT_TAKE_ARGV( MD_CMD_CACHALLENGES, md_config_set_cha_tyes, NULL, RSRC_CONF, 
+    AP_INIT_TAKE1("MDCertificateAgreement", md_config_set_agreement, NULL, RSRC_CONF, 
+                  "either 'accepted' or the URL of CA Terms-of-Service agreement you accept"),
+    AP_INIT_TAKE_ARGV("MDCAChallenges", md_config_set_cha_tyes, NULL, RSRC_CONF, 
                       "A list of challenge types to be used."),
-    AP_INIT_TAKE1(     MD_CMD_CAPROTO, md_config_set_ca_proto, NULL, RSRC_CONF, 
+    AP_INIT_TAKE1("MDCertificateProtocol", md_config_set_ca_proto, NULL, RSRC_CONF, 
                   "Protocol used to obtain/renew certificates"),
-    AP_INIT_TAKE1(     MD_CMD_DRIVEMODE, md_config_set_drive_mode, NULL, RSRC_CONF, 
-                  "method of obtaining certificates for the managed domain"),
-    AP_INIT_TAKE_ARGV( MD_CMD_MD, md_config_set_names, NULL, RSRC_CONF, 
+    AP_INIT_TAKE1("MDDriveMode", md_config_set_renew_mode, NULL, RSRC_CONF, 
+                  "deprecated, older name for MDRenewMode"),
+    AP_INIT_TAKE1("MDRenewMode", md_config_set_renew_mode, NULL, RSRC_CONF, 
+                  "Controls how renewal of Managed Domain certificates shall be handled."),
+    AP_INIT_TAKE_ARGV("MDomain", md_config_set_names, NULL, RSRC_CONF, 
                       "A group of server names with one certificate"),
-    AP_INIT_RAW_ARGS(  MD_CMD_MD_SECTION, md_config_sec_start, NULL, RSRC_CONF, 
+    AP_INIT_RAW_ARGS(MD_CMD_MD_SECTION, md_config_sec_start, NULL, RSRC_CONF, 
                      "Container for a managed domain with common settings and certificate."),
-    AP_INIT_TAKE_ARGV( MD_CMD_MEMBER, md_config_sec_add_members, NULL, RSRC_CONF, 
+    AP_INIT_RAW_ARGS(MD_CMD_MD2_SECTION, md_config_sec_start, NULL, RSRC_CONF, 
+                     "Short form for <MDomainSet> container."),
+    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( MD_CMD_MEMBERS, md_config_sec_add_members, NULL, RSRC_CONF, 
+    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_TAKE1(     MD_CMD_MUSTSTAPLE, md_config_set_must_staple, NULL, RSRC_CONF, 
+    AP_INIT_TAKE1("MDMustStaple", md_config_set_must_staple, NULL, RSRC_CONF, 
                   "Enable/Disable the Must-Staple flag for new certificates."),
-    AP_INIT_TAKE12(    MD_CMD_PORTMAP, md_config_set_port_map, NULL, RSRC_CONF, 
+    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( MD_CMD_PKEYS, md_config_set_pkeys, NULL, RSRC_CONF, 
+    AP_INIT_TAKE_ARGV("MDPrivateKeys", md_config_set_pkeys, NULL, RSRC_CONF, 
                   "set the type and parameters for private key generation"),
-    AP_INIT_TAKE1(     MD_CMD_PROXY, md_config_set_proxy, NULL, RSRC_CONF, 
+    AP_INIT_TAKE1("MDHttpProxy", md_config_set_proxy, NULL, RSRC_CONF, 
                   "URL of a HTTP(S) proxy to use for outgoing connections"),
-    AP_INIT_TAKE1(     MD_CMD_STOREDIR, md_config_set_store_dir, NULL, RSRC_CONF, 
+    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(     MD_CMD_RENEWWINDOW, md_config_set_renew_window, NULL, RSRC_CONF, 
+    AP_INIT_TAKE1("MDRenewWindow", md_config_set_renew_window, NULL, RSRC_CONF, 
                   "Time length for renewal before certificate expires (defaults to days)"),
-    AP_INIT_TAKE1(     MD_CMD_REQUIREHTTPS, md_config_set_require_https, NULL, RSRC_CONF, 
+    AP_INIT_TAKE1("MDRequireHttps", md_config_set_require_https, NULL, RSRC_CONF, 
                   "Redirect non-secure requests to the https: equivalent."),
-    AP_INIT_RAW_ARGS(MD_CMD_NOTIFYCMD, md_config_set_notify_cmd, NULL, RSRC_CONF, 
-                  "set the command and optional arguments to run when signup/renew of domain is complete."),
-    AP_INIT_TAKE1(     MD_CMD_BASE_SERVER, md_config_set_base_server, NULL, RSRC_CONF, 
-                  "allow managing of base server outside virtual hosts."),
-
-/* This will disappear soon */
-    AP_INIT_TAKE_ARGV( MD_CMD_OLD_MD, md_config_set_names_old, NULL, RSRC_CONF, 
-                      "Deprecated, replace with 'MDomain'."),
-    AP_INIT_RAW_ARGS(  MD_CMD_MD_OLD_SECTION, md_config_sec_start_old, NULL, RSRC_CONF, 
-                     "Deprecated, replace with '<MDomainSet'."),
-/* */
+    AP_INIT_RAW_ARGS("MDNotifyCmd", md_config_set_notify_cmd, NULL, RSRC_CONF, 
+                  "Set the command to run when signup/renew of domain is complete."),
+    AP_INIT_TAKE1("MDBaseServer", md_config_set_base_server, NULL, RSRC_CONF, 
+                  "Allow managing of base server outside virtual hosts."),
+    AP_INIT_RAW_ARGS("MDChallengeDns01", md_config_set_dns01_cmd, NULL, RSRC_CONF, 
+                  "Set the command for setup/teardown of dns-01 challenges"),
+    AP_INIT_TAKE1("MDCertificateFile", md_config_set_cert_file, NULL, RSRC_CONF, 
+                  "set the static certificate (chain) file to use for this domain."),
+    AP_INIT_TAKE1("MDCertificateKeyFile", md_config_set_key_file, NULL, RSRC_CONF, 
+                  "set the static private key file to use for this domain."),
+    AP_INIT_TAKE1("MDServerStatus", md_config_set_server_status, NULL, RSRC_CONF, 
+                  "On to see Managed Domains in server-status."),
+    AP_INIT_TAKE1("MDCertificateStatus", md_config_set_certificate_status, NULL, RSRC_CONF, 
+                  "On to see Managed Domain expose /.httpd/certificate-status."),
+    AP_INIT_TAKE1("MDWarnWindow", md_config_set_warn_window, NULL, RSRC_CONF, 
+                  "When less time remains for a certificate, send our/log a warning (defaults to days)"),
+    AP_INIT_RAW_ARGS("MDMessageCmd", md_config_set_msg_cmd, NULL, RSRC_CONF, 
+                  "Set the command run when a message about a domain is issued."),
 
     AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
 };
@@ -935,7 +960,7 @@ int md_config_geti(const md_srv_conf_t *sc, md_config_var_t var)
 {
     switch (var) {
         case MD_CONFIG_DRIVE_MODE:
-            return (sc->drive_mode != DEF_VAL)? sc->drive_mode : defconf.drive_mode;
+            return (sc->renew_mode != DEF_VAL)? sc->renew_mode : defconf.renew_mode;
         case MD_CONFIG_LOCAL_80:
             return sc->mc->local_80;
         case MD_CONFIG_LOCAL_443:
@@ -951,14 +976,17 @@ int md_config_geti(const md_srv_conf_t *sc, md_config_var_t var)
     }
 }
 
-apr_interval_time_t md_config_get_interval(const md_srv_conf_t *sc, md_config_var_t var)
+void md_config_get_timespan(const md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var)
 {
     switch (var) {
-        case MD_CONFIG_RENEW_NORM:
-            return (sc->renew_norm != DEF_VAL)? sc->renew_norm : defconf.renew_norm;
         case MD_CONFIG_RENEW_WINDOW:
-            return (sc->renew_window != DEF_VAL)? sc->renew_window : defconf.renew_window;
+            *pspan = sc->renew_window? sc->renew_window : defconf.renew_window;
+            break;
+        case MD_CONFIG_WARN_WINDOW:
+            *pspan = sc->warn_window? sc->warn_window : defconf.warn_window;
+            break;
         default:
-            return 0;
+            break;
     }
 }
+
index 7c7df51676721944e029b07ff4662fdf1ef5fa4f..fde919b2ff7d35d9e43a0a10c400b33a41f38888 100644 (file)
@@ -17,6 +17,7 @@
 #ifndef mod_md_md_config_h
 #define mod_md_md_config_h
 
+struct apr_hash_t;
 struct md_store_t;
 struct md_reg_t;
 struct md_pkey_spec_t;
@@ -29,16 +30,18 @@ typedef enum {
     MD_CONFIG_DRIVE_MODE,
     MD_CONFIG_LOCAL_80,
     MD_CONFIG_LOCAL_443,
-    MD_CONFIG_RENEW_NORM,
     MD_CONFIG_RENEW_WINDOW,
+    MD_CONFIG_WARN_WINDOW,
     MD_CONFIG_TRANSITIVE,
     MD_CONFIG_PROXY,
     MD_CONFIG_REQUIRE_HTTPS,
     MD_CONFIG_MUST_STAPLE,
     MD_CONFIG_NOTIFY_CMD,
+    MD_CONFIG_MESSGE_CMD,
 } md_config_var_t;
 
-typedef struct {
+typedef struct md_mod_conf_t md_mod_conf_t;
+struct md_mod_conf_t {
     apr_array_header_t *mds;           /* all md_t* defined in the config, shared */
     const char *base_dir;              /* base dir for store */
     const char *proxy_url;             /* proxy url to use (or NULL) */
@@ -52,9 +55,16 @@ typedef struct {
     int hsts_max_age;                  /* max-age of HSTS (rfc6797) header */
     const char *hsts_header;           /* computed HTST header to use or NULL */
     apr_array_header_t *unused_names;  /* post config, names of all MDs not assigned to a vhost */
+    apr_array_header_t *watched_names; /* post config, names of all MDs that we need to watch */
+    struct apr_hash_t *init_errors;    /* init errors reported with MD name as key */
 
     const char *notify_cmd;            /* notification command to execute on signup/renew */
-} md_mod_conf_t;
+    const char *message_cmd;           /* message command to execute on signup/renew/warnings */
+    struct apr_table_t *env;           /* environment for operation */
+    int dry_run;                       /* != 0 iff config dry run */
+    int server_status_enabled;         /* if module should add to server-status handler */
+    int certificate_status_enabled;    /* if module should expose /.httpd/certificate-status */
+};
 
 typedef struct md_srv_conf_t {
     const char *name;
@@ -63,13 +73,11 @@ typedef struct md_srv_conf_t {
     
     int transitive;                    /* != 0 iff VirtualHost names/aliases are auto-added */
     md_require_t require_https;        /* If MDs require https: access */
-    int drive_mode;                    /* mode of obtaining credentials */
+    int renew_mode;                    /* mode of obtaining credentials */
     int must_staple;                   /* certificates should set the OCSP Must Staple extension */
     struct md_pkey_spec_t *pkey_spec;  /* specification for generating private keys */
-    apr_interval_time_t renew_norm;    /* If > 0, use as normalizing value for cert lifetime
-                                        * Example: renew_norm=90d renew_win=30d, cert lives
-                                        * for 12 days => renewal 4 days before */
-    apr_interval_time_t renew_window;  /* time before expiration that starts renewal */
+    const md_timeslice_t *renew_window; /* time before expiration that starts renewal */
+    const md_timeslice_t *warn_window;  /* time before expiration that warning are sent out */
     
     const char *ca_url;                /* url of CA certificate service */
     const char *ca_proto;              /* protocol used vs CA (e.g. ACME) */
@@ -97,6 +105,8 @@ md_srv_conf_t *md_config_get_unique(server_rec *s, apr_pool_t *p);
 
 const char *md_config_gets(const md_srv_conf_t *config, md_config_var_t var);
 int md_config_geti(const md_srv_conf_t *config, md_config_var_t var);
-apr_interval_time_t md_config_get_interval(const md_srv_conf_t *config, md_config_var_t var);
+
+void md_config_get_timespan(const md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var);
+
 
 #endif /* md_config_h */
diff --git a/modules/md/mod_md_drive.c b/modules/md/mod_md_drive.c
new file mode 100644 (file)
index 0000000..670c7e7
--- /dev/null
@@ -0,0 +1,496 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_optional.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+#include <apr_date.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+
+#include "mod_watchdog.h"
+
+#include "md.h"
+#include "md_curl.h"
+#include "md_crypt.h"
+#include "md_http.h"
+#include "md_json.h"
+#include "md_status.h"
+#include "md_store.h"
+#include "md_store_fs.h"
+#include "md_log.h"
+#include "md_result.h"
+#include "md_reg.h"
+#include "md_util.h"
+#include "md_version.h"
+#include "md_acme.h"
+#include "md_acme_authz.h"
+
+#include "mod_md.h"
+#include "mod_md_private.h"
+#include "mod_md_config.h"
+#include "mod_md_status.h"
+#include "mod_md_drive.h"
+
+/**************************************************************************************************/
+/* watchdog based impl. */
+
+#define MD_WATCHDOG_NAME   "_md_"
+
+static APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *wd_get_instance;
+static APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *wd_register_callback;
+static APR_OPTIONAL_FN_TYPE(ap_watchdog_set_callback_interval) *wd_set_interval;
+
+struct md_drive_ctx {
+    apr_pool_t *p;
+    server_rec *s;
+    md_mod_conf_t *mc;
+    ap_watchdog_t *watchdog;
+    
+    apr_array_header_t *jobs;
+};
+
+typedef struct {
+    apr_pool_t *p;
+    md_job_t *job;
+    md_reg_t *reg;
+    md_result_t *last;
+    apr_time_t last_save;
+} md_job_result_ctx;
+
+static void job_result_update(md_result_t *result, void *data)
+{
+    md_job_result_ctx *ctx = data;
+    apr_time_t now;
+    const char *msg, *sep;
+    
+    if (md_result_cmp(ctx->last, result)) {
+        now = apr_time_now();
+        md_result_assign(ctx->last, result);
+        if (result->activity || result->problem || result->detail) {
+            msg = sep = "";
+            if (result->activity) {
+                msg = apr_psprintf(result->p, "%s", result->activity);
+                sep = ": ";
+            }
+            if (result->detail) {
+                msg = apr_psprintf(result->p, "%s%s%s", msg, sep, result->detail);
+                sep = ", ";
+            }
+            if (result->problem) {
+                msg = apr_psprintf(result->p, "%s%sproblem: %s", msg, sep, result->problem);
+                sep = " ";
+            }
+            md_job_log_append(ctx->job, "progress", NULL, msg);
+
+            if (apr_time_msec(now - ctx->last_save) > 500) {
+                md_job_save(ctx->job, ctx->reg, MD_SG_STAGING, result, ctx->p);
+                ctx->last_save = now;
+            }
+        }
+    }
+}
+
+static void job_result_observation_start(md_job_t *job, md_result_t *result, 
+                                         md_reg_t *reg, apr_pool_t *p)
+{
+    md_job_result_ctx *ctx;
+
+    ctx = apr_pcalloc(p, sizeof(*ctx));
+    ctx->p = p;
+    ctx->job = job;
+    ctx->reg = reg;
+    ctx->last = md_result_md_make(p, APR_SUCCESS);
+    md_result_assign(ctx->last, result);
+    md_result_on_change(result, job_result_update, ctx);
+}
+
+static void job_result_observation_end(md_job_t *job, md_result_t *result)
+{
+    (void)job;
+    md_result_on_change(result, NULL, NULL);
+} 
+
+static apr_time_t calc_err_delay(int err_count)
+{
+    apr_time_t delay = 0;
+    
+    if (err_count > 0) {
+        /* back off duration, depending on the errors we encounter in a row */
+        delay = apr_time_from_sec(5 << (err_count - 1));
+        if (delay > apr_time_from_sec(60*60)) {
+            delay = apr_time_from_sec(60*60);
+        }
+    }
+    return delay;
+}
+
+static apr_status_t send_notification(md_drive_ctx *dctx, md_job_t *job, const md_t *md, 
+                                      const char *reason, md_result_t *result, apr_pool_t *ptemp)
+{
+    const char * const *argv;
+    const char *cmdline;
+    int exit_code;
+    apr_status_t rv;            
+    
+    if (!strcmp("renewed", reason)) {
+        if (dctx->mc->notify_cmd) {
+            cmdline = apr_psprintf(ptemp, "%s %s", dctx->mc->notify_cmd, md->name); 
+            apr_tokenize_to_argv(cmdline, (char***)&argv, ptemp);
+            rv = md_util_exec(ptemp, argv[0], argv, &exit_code);
+            
+            if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL;
+            if (APR_SUCCESS != rv) {
+                if (!result) result = md_result_make(ptemp, rv);
+                md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10108)), 
+                                         "MDNotifyCmd %s failed with exit code %d.", 
+                                         dctx->mc->notify_cmd, exit_code);
+                md_result_log(result, MD_LOG_ERR);
+                md_job_log_append(job, "notify-error", result->problem, result->detail);
+                goto leave;
+            }
+        }
+        ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, dctx->s, APLOGNO(10059) 
+                     "The Managed Domain %s has been setup and changes "
+                     "will be activated on next (graceful) server restart.", md->name);
+    }
+    if (dctx->mc->message_cmd) {
+        cmdline = apr_psprintf(ptemp, "%s %s %s", dctx->mc->message_cmd, reason, md->name); 
+        ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, dctx->s, "Message command: %s", cmdline);
+        apr_tokenize_to_argv(cmdline, (char***)&argv, ptemp);
+        rv = md_util_exec(ptemp, argv[0], argv, &exit_code);
+        
+        if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL;
+        if (APR_SUCCESS != rv) {
+            if (!result) result = md_result_make(ptemp, rv);
+            md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10109)), 
+                                     "MDMessageCmd %s failed with exit code %d.", 
+                                     dctx->mc->notify_cmd, exit_code);
+            md_result_log(result, MD_LOG_ERR);
+            md_job_log_append(job, "message-error", reason, result->detail);
+            goto leave;
+        }
+    }
+leave:
+    return rv;
+}
+
+static void check_expiration(md_drive_ctx *dctx, md_job_t *job, const md_t *md, apr_pool_t *ptemp)
+{
+    md_timeperiod_t since_last;
+    
+    ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, dctx->s, "md(%s): check expiration", md->name);
+    if (!md_reg_should_warn(dctx->mc->reg, md, dctx->p)) return;
+    
+    /* Sends these out at most once per day */
+    since_last.start = md_job_log_get_time_of_latest(job, "message-expiring");
+    since_last.end = apr_time_now();
+
+    if (md_timeperiod_length(&since_last) >= apr_time_from_sec(MD_SECS_PER_DAY)) {
+        ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, dctx->s, 
+                     "md(%s): message expiration warning", md->name);
+        send_notification(dctx, job, md, "expiring", NULL, ptemp);
+    }
+}
+
+static void process_drive_job(md_drive_ctx *dctx, md_job_t *job, apr_pool_t *ptemp)
+{
+    const md_t *md;
+    md_result_t *result;
+    int error_run = 0, fatal_run = 0, save = 0;
+    apr_status_t rv;
+    
+    md_job_load(job, dctx->mc->reg, MD_SG_STAGING, ptemp);
+    /* Evaluate again on loaded value. Values will change when watchdog switches child process */
+    if (apr_time_now() < job->next_run) return;
+    
+    md = md_get_by_name(dctx->mc->mds, job->name);
+    AP_DEBUG_ASSERT(md);
+
+    result = md_result_md_make(ptemp, md);
+    if (job->last_result) md_result_assign(result, job->last_result); 
+    
+    if (md->state == MD_S_MISSING_INFORMATION) {
+        /* Missing information, this will not change until configuration
+         * is changed and server reloaded. */
+        fatal_run = 1;
+        goto leave;
+    }
+    
+    while (md_will_renew_cert(md)) {
+        if (job->finished) {
+            job->next_run = 0;
+            /* Finished jobs might take a while before the results become valid.
+             * If that is in the future, request to run then */
+            if (apr_time_now() < job->valid_from) {
+                job->next_run = job->valid_from;
+            }
+            else if (md_job_log_get_time_of_latest(job, "notified") == 0) {
+                rv = send_notification(dctx, job, md, "renewed", result, ptemp);
+                if (APR_SUCCESS == rv) {
+                    md_job_log_append(job, "notified", NULL, NULL);
+                    save = 1;
+                }
+                else { 
+                    /* we treat this as an error that triggers retries */
+                    error_run = 1;
+                }
+            }
+            goto leave;
+        }
+        
+        if (!md_reg_should_renew(dctx->mc->reg, md, dctx->p)) {
+            ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10053) 
+                         "md(%s): no need to renew yet", job->name);
+            job->next_run = 0;
+            goto leave;
+        }
+
+        /* Renew the MDs credentials in a STAGING area. Might be invoked repeatedly 
+         * without discarding previous/intermediate results.
+         * Only returns SUCCESS when the renewal is complete, e.g. STAGING as a
+         * complete set of new credentials.
+         */
+        ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10052) 
+                     "md(%s): state=%d, driving", job->name, md->state);
+        md_job_log_append(job, "renewal-start", NULL, NULL);
+        /* observe result changes and persist them with limited frequency */
+        job_result_observation_start(job, result, dctx->mc->reg, ptemp);
+        
+        md_reg_renew(dctx->mc->reg, md, dctx->mc->env, 0, result, ptemp);
+        
+        job_result_observation_end(job, result);
+        if (APR_SUCCESS != result->status) {
+            ap_log_error( APLOG_MARK, APLOG_ERR, result->status, dctx->s, APLOGNO(10056) 
+                         "processing %s: %s", job->name, result->detail);
+            error_run = 1;
+            md_job_log_append(job, "renewal-error", result->problem, result->detail);
+            send_notification(dctx, job, md, "errored", result, ptemp);
+            goto leave;
+        }
+        
+        job->finished = 1;
+        job->valid_from = result->ready_at;
+        job->error_runs = 0;
+        md_job_log_append(job, "renewal-finish", NULL, NULL);
+        save = 1;
+    }
+    
+leave:
+    if (!job->finished) {
+        check_expiration(dctx, job, md, ptemp);
+    }
+    
+    if (fatal_run) {
+        save = 1;
+        job->next_run = 0;
+    }
+    if (error_run) {
+        ++job->error_runs;
+        save = 1;
+        job->next_run = apr_time_now() + calc_err_delay(job->error_runs);
+        ap_log_error(APLOG_MARK, APLOG_INFO, 0, dctx->s, APLOGNO(10057) 
+                     "%s: encountered error for the %d. time, next run in %s",
+                     job->name, job->error_runs, 
+                     md_duration_print(ptemp, job->next_run - apr_time_now()));
+    }
+    if (save) {
+        apr_status_t rv2 = md_job_save(job, dctx->mc->reg, MD_SG_STAGING, result, ptemp);
+        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv2, dctx->s, "%s: saving job props", job->name);
+    }
+}
+
+int md_will_renew_cert(const md_t *md)
+{
+    if (md->renew_mode == MD_RENEW_MANUAL) {
+        return 0;
+    }
+    else if (md->renew_mode == MD_RENEW_AUTO && md->cert_file) {
+        return 0;
+    } 
+    return 1;
+}
+
+static apr_time_t next_run_default(void)
+{
+    /* we'd like to run at least twice a day by default */
+    return apr_time_now() + apr_time_from_sec(MD_SECS_PER_DAY / 2);
+}
+
+static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
+{
+    md_drive_ctx *dctx = baton;
+    md_job_t *job;
+    apr_time_t next_run, wait_time;
+    int i;
+    
+    /* mod_watchdog invoked us as a single thread inside the whole server (on this machine).
+     * This might be a repeated run inside the same child (mod_watchdog keeps affinity as
+     * long as the child lives) or another/new child.
+     */
+    switch (state) {
+        case AP_WATCHDOG_STATE_STARTING:
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10054)
+                         "md watchdog start, auto drive %d mds", dctx->jobs->nelts);
+            break;
+            
+        case AP_WATCHDOG_STATE_RUNNING:
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10055)
+                         "md watchdog run, auto drive %d mds", dctx->jobs->nelts);
+                         
+            /* Process all drive jobs. They will update their next_run property
+             * and we schedule ourself at the earliest of all. A job may specify 0
+             * as next_run to indicate that it wants to participate in the normal
+             * regular runs. */
+            next_run = next_run_default();
+            for (i = 0; i < dctx->jobs->nelts; ++i) {
+                job = APR_ARRAY_IDX(dctx->jobs, i, md_job_t *);
+                
+                if (apr_time_now() >= job->next_run) {
+                    process_drive_job(dctx, job, ptemp);
+                }
+                
+                if (job->next_run && job->next_run < next_run) {
+                    next_run = job->next_run;
+                }
+            }
+
+            wait_time = next_run - apr_time_now();
+            if (APLOGdebug(dctx->s)) {
+                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10107)
+                             "next run in %s", md_duration_print(ptemp, wait_time));
+            }
+            wd_set_interval(dctx->watchdog, wait_time, dctx, run_watchdog);
+            break;
+            
+        case AP_WATCHDOG_STATE_STOPPING:
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, dctx->s, APLOGNO(10058)
+                         "md watchdog stopping");
+            break;
+    }
+    
+    return APR_SUCCESS;
+}
+
+apr_status_t md_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+{
+    apr_allocator_t *allocator;
+    md_drive_ctx *dctx;
+    apr_pool_t *dctxp;
+    apr_status_t rv;
+    const char *name;
+    md_t *md;
+    md_job_t *job;
+    int i;
+    
+    /* We use mod_watchdog to run a single thread in one of the child processes
+     * to monitor the MDs in mc->watched_names, using the const data in the list
+     * mc->mds of our MD structures.
+     *
+     * The data in mc cannot be changed, as we may spawn copies in new child processes
+     * of the original data at any time. The child which hosts the watchdog thread
+     * may also die or be recycled, which causes a new watchdog thread to run
+     * in another process with the original data.
+     * 
+     * Instead, we use our store to persist changes in group STAGING. This is
+     * kept writable to child processes, but the data stored there is not live.
+     * However, mod_watchdog makes sure that we only ever have a single thread in
+     * our server (on this machine) that writes there. Other processes, e.g. informing
+     * the user about progress, only read from there.
+     *
+     * All changes during driving an MD are stored as files in MG_SG_STAGING/<MD.name>.
+     * All will have "md.json" and "job.json". There may be a range of other files used
+     * by the protocol obtaining the certificate/keys.
+     * 
+     * 
+     */
+    wd_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance);
+    wd_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback);
+    wd_set_interval = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_set_callback_interval);
+    
+    if (!wd_get_instance || !wd_register_callback || !wd_set_interval) {
+        ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(10061) "mod_watchdog is required");
+        return !OK;
+    }
+    
+    /* We want our own pool with own allocator to keep data across watchdog invocations.
+     * Since we'll run in a single watchdog thread, using our own allocator will prevent 
+     * any confusion in the parent pool. */
+    apr_allocator_create(&allocator);
+    apr_allocator_max_free_set(allocator, 1);
+    rv = apr_pool_create_ex(&dctxp, p, NULL, allocator);
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10062) "md_drive_ctx: create pool");
+        return rv;
+    }
+    apr_allocator_owner_set(allocator, dctxp);
+    apr_pool_tag(dctxp, "md_drive_ctx");
+
+    dctx = apr_pcalloc(dctxp, sizeof(*dctx));
+    dctx->p = dctxp;
+    dctx->s = s;
+    dctx->mc = mc;
+    
+    dctx->jobs = apr_array_make(dctx->p, mc->watched_names->nelts, sizeof(md_job_t *));
+    for (i = 0; i < mc->watched_names->nelts; ++i) {
+        name = APR_ARRAY_IDX(mc->watched_names, i, const char *);
+        md = md_get_by_name(mc->mds, name);
+        if (!md) continue;
+        
+        job = md_job_make(p, md->name);
+        APR_ARRAY_PUSH(dctx->jobs, md_job_t*) = job;
+        ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, dctx->s,  
+                     "md(%s): state=%d, created drive job", name, md->state);
+        
+        md_job_load(job, mc->reg, MD_SG_STAGING, dctx->p);
+        if (job->error_runs) {
+            /* Server has just restarted. If we encounter an MD job with errors
+             * on a previous driving, we purge its STAGING area.
+             * This will reset the driving for the MD. It may run into the same
+             * error again, or in case of race/confusion/our error/CA error, it
+             * might allow the MD to succeed by a fresh start.
+             */
+            ap_log_error( APLOG_MARK, APLOG_NOTICE, 0, dctx->s, APLOGNO(10064) 
+                         "md(%s): previous drive job showed %d errors, purging STAGING "
+                         "area to reset.", name, job->error_runs);
+            md_store_purge(md_reg_store_get(dctx->mc->reg), p, MD_SG_STAGING, md->name);
+            md_store_purge(md_reg_store_get(dctx->mc->reg), p, MD_SG_CHALLENGES, md->name);
+            job->error_runs = 0;
+        }
+    }
+
+    if (!dctx->jobs->nelts) {
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10065)
+                     "no managed domain to drive, no watchdog needed.");
+        apr_pool_destroy(dctx->p);
+        return APR_SUCCESS;
+    }
+    
+    if (APR_SUCCESS != (rv = wd_get_instance(&dctx->watchdog, MD_WATCHDOG_NAME, 0, 1, dctx->p))) {
+        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10066) 
+                     "create md watchdog(%s)", MD_WATCHDOG_NAME);
+        return rv;
+    }
+    rv = wd_register_callback(dctx->watchdog, 0, dctx, run_watchdog);
+    ap_log_error(APLOG_MARK, rv? APLOG_CRIT : APLOG_DEBUG, rv, s, APLOGNO(10067) 
+                 "register md watchdog(%s)", MD_WATCHDOG_NAME);
+    return rv;
+}
diff --git a/modules/md/mod_md_drive.h b/modules/md/mod_md_drive.h
new file mode 100644 (file)
index 0000000..be15867
--- /dev/null
@@ -0,0 +1,35 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_drive_h
+#define mod_md_md_drive_h
+
+struct md_mod_conf_t;
+struct md_reg_t;
+
+typedef struct md_drive_ctx md_drive_ctx;
+
+int md_will_renew_cert(const md_t *md);
+
+/**
+ * Start driving the certificate procotol for the domains mentioned in mc->watched_names.
+ */
+apr_status_t md_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr_pool_t *p);
+
+
+
+
+#endif /* mod_md_md_drive_h */
index 6f0b121e1ee1bfd3ce53cffdb35da224d9c799ea..6544e9ae127c1e566d4756037c207e7a26b99ccb 100644 (file)
 
 apr_status_t md_try_chown(const char *fname, unsigned int uid, int gid, apr_pool_t *p)
 {
-#if AP_NEED_SET_MUTEX_PERMS
-    if (-1 == chown(fname, (uid_t)uid, (gid_t)gid)) {
-        apr_status_t rv = APR_FROM_OS_ERROR(errno);
-        if (!APR_STATUS_IS_ENOENT(rv)) {
-            ap_log_perror(APLOG_MARK, APLOG_ERR, rv, p, APLOGNO(10082)
-                         "Can't change owner of %s", fname);
+#if AP_NEED_SET_MUTEX_PERMS && HAVE_UNISTD_H
+    /* Since we only switch user when running as root, we only need to chown directories
+     * in that case. Otherwise, the server will ignore any "user/group" directives and
+     * child processes have the same privileges as the parent.
+     */
+    if (!geteuid()) {
+        if (-1 == chown(fname, (uid_t)uid, (gid_t)gid)) {
+            apr_status_t rv = APR_FROM_OS_ERROR(errno);
+            if (!APR_STATUS_IS_ENOENT(rv)) {
+                ap_log_perror(APLOG_MARK, APLOG_ERR, rv, p, APLOGNO(10082)
+                              "Can't change owner of %s", fname);
+            }
+            return rv;
         }
-        return rv;
     }
     return APR_SUCCESS;
 #else 
@@ -61,10 +67,10 @@ apr_status_t md_try_chown(const char *fname, unsigned int uid, int gid, apr_pool
 
 apr_status_t md_make_worker_accessible(const char *fname, apr_pool_t *p)
 {
-#if AP_NEED_SET_MUTEX_PERMS
-    return md_try_chown(fname, ap_unixd_config.user_id, -1, p);
-#else 
+#ifdef WIN32
     return APR_ENOTIMPL;
+#else 
+    return md_try_chown(fname, ap_unixd_config.user_id, -1, p);
 #endif
 }
 
diff --git a/modules/md/mod_md_status.c b/modules/md/mod_md_status.c
new file mode 100644 (file)
index 0000000..779ceaa
--- /dev/null
@@ -0,0 +1,544 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <assert.h>
+#include <apr_optional.h>
+#include <apr_time.h>
+#include <apr_date.h>
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_protocol.h>
+#include <http_request.h>
+#include <http_log.h>
+
+#include "mod_status.h"
+
+#include "md.h"
+#include "md_curl.h"
+#include "md_crypt.h"
+#include "md_http.h"
+#include "md_json.h"
+#include "md_status.h"
+#include "md_store.h"
+#include "md_store_fs.h"
+#include "md_log.h"
+#include "md_reg.h"
+#include "md_util.h"
+#include "md_version.h"
+#include "md_acme.h"
+#include "md_acme_authz.h"
+
+#include "mod_md.h"
+#include "mod_md_private.h"
+#include "mod_md_config.h"
+#include "mod_md_drive.h"
+#include "mod_md_status.h"
+
+/**************************************************************************************************/
+/* Certificate status */
+
+#define APACHE_PREFIX               "/.httpd/"
+#define MD_STATUS_RESOURCE          APACHE_PREFIX"certificate-status"
+
+int md_http_cert_status(request_rec *r)
+{
+    md_json_t *resp, *j, *mdj, *certj;
+    const md_srv_conf_t *sc;
+    const md_t *md;
+    apr_bucket_brigade *bb;
+    apr_status_t rv;
+    
+    if (!r->parsed_uri.path || strcmp(MD_STATUS_RESOURCE, r->parsed_uri.path))
+        return DECLINED;
+        
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                  "requesting status for: %s", r->hostname);
+    
+    /* We are looking for information about a staged certificate */
+    sc = ap_get_module_config(r->server->module_config, &md_module);
+    if (!sc || !sc->mc || !sc->mc->reg || !sc->mc->certificate_status_enabled) return DECLINED;
+    md = md_get_by_domain(sc->mc->mds, r->hostname);
+    if (!md) return DECLINED;
+
+    if (r->method_number != M_GET) {
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                      "md(%s): status supports only GET", md->name);
+        return HTTP_NOT_IMPLEMENTED;
+    }
+    
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                  "requesting status for MD: %s", md->name);
+
+    if (APR_SUCCESS != (rv = md_status_get_md_json(&mdj, md, sc->mc->reg, r->pool))) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO()
+                      "loading md status for %s", md->name);
+        return HTTP_INTERNAL_SERVER_ERROR;
+    }
+    
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                  "status for MD: %s is %s", md->name, md_json_writep(mdj, r->pool, MD_JSON_FMT_INDENT));
+
+    resp = md_json_create(r->pool);
+    
+    if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_VALID_UNTIL, NULL)) {
+        md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_VALID_UNTIL, NULL), 
+                     resp, MD_KEY_VALID_UNTIL, NULL);
+    }
+    if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_VALID_FROM, NULL)) {
+        md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_VALID_FROM, NULL), 
+                     resp, MD_KEY_VALID_FROM, NULL);
+    }
+    if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_SERIAL, NULL)) {
+        md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_SERIAL, NULL), 
+                     resp, MD_KEY_SERIAL, NULL);
+    }
+    if (md_json_has_key(mdj, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL)) {
+        md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL), 
+                     resp, MD_KEY_SHA256_FINGERPRINT, NULL);
+    }
+    
+    if (md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) {
+        /* copy over the information we want to make public about this:
+         *  - when not finished, add an empty object to indicate something is going on
+         *  - when a certificate is staged, add the information from that */
+        certj = md_json_getj(mdj, MD_KEY_RENEWAL, MD_KEY_CERT, NULL);
+        j = certj? certj : md_json_create(r->pool);; 
+        md_json_setj(j, resp, MD_KEY_RENEWAL, NULL);
+    }
+    
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "md[%s]: sending status", md->name);
+    apr_table_set(r->headers_out, "Content-Type", "application/json"); 
+    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+    md_json_writeb(resp, MD_JSON_FMT_INDENT, bb);
+    ap_pass_brigade(r->output_filters, bb);
+    apr_brigade_cleanup(bb);
+    
+    return DONE;
+}
+
+/**************************************************************************************************/
+/* Status hook */
+
+typedef struct {
+    apr_pool_t *p;
+    const md_mod_conf_t *mc;
+    apr_bucket_brigade *bb;
+    const char *separator;
+} status_ctx;
+
+typedef struct status_info status_info; 
+
+static void add_json_val(status_ctx *ctx, md_json_t *j);
+
+typedef void add_status_fn(status_ctx *ctx, md_json_t *mdj, const status_info *info);
+
+struct status_info {
+    const char *label;
+    const char *key;
+    add_status_fn *fn;
+};
+
+static void si_val_status(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    const char *s = "unknown";
+    (void)info;
+    switch (md_json_getl(mdj, MD_KEY_STATE, NULL)) {
+        case MD_S_INCOMPLETE: s = "incomplete"; break;
+        case MD_S_EXPIRED_DEPRECATED:
+        case MD_S_COMPLETE: s = "ok"; break;
+        case MD_S_ERROR: s = "error"; break;
+        case MD_S_MISSING_INFORMATION: s = "missing information"; break;
+        default: break;
+    }
+    apr_brigade_puts(ctx->bb, NULL, NULL, s);
+}
+
+static void si_val_renew_mode(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    const char *s;
+    switch (md_json_getl(mdj, info->key, NULL)) {
+        case MD_RENEW_MANUAL: s = "manual"; break;
+        case MD_RENEW_ALWAYS: s = "always"; break;
+        default: s = "auto"; break;
+    }
+    apr_brigade_puts(ctx->bb, NULL, NULL, s);
+}
+
+
+static void si_val_date(status_ctx *ctx, apr_time_t timestamp)
+{
+    if (timestamp > 0) {
+        char ts[128];
+        char ts2[128];
+        apr_time_exp_t texp;
+        apr_size_t len;
+        
+        apr_time_exp_gmt(&texp, timestamp);
+        apr_strftime(ts, &len, sizeof(ts)-1, "%Y-%m-%dT%H:%M:%SZ", &texp);
+        ts[len] = '\0';
+        apr_strftime(ts2, &len, sizeof(ts2)-1, "%Y-%m-%d", &texp);
+        ts2[len] = '\0';
+        apr_brigade_printf(ctx->bb, NULL, NULL, 
+                           "<span title='%s' style='white-space: nowrap;'>%s</span>", 
+                           ts, ts2);
+    }
+    else {
+        apr_brigade_puts(ctx->bb, NULL, NULL, "-");
+    }
+}
+
+static void si_val_time(status_ctx *ctx, apr_time_t timestamp)
+{
+    if (timestamp > 0) {
+        char ts[128];
+        char ts2[128];
+        apr_time_exp_t texp;
+        apr_size_t len;
+        
+        apr_time_exp_gmt(&texp, timestamp);
+        apr_strftime(ts, &len, sizeof(ts)-1, "%Y-%m-%dT%H:%M:%SZ", &texp);
+        ts[len] = '\0';
+        apr_strftime(ts2, &len, sizeof(ts2)-1, "%H:%M:%SZ", &texp);
+        ts2[len] = '\0';
+        apr_brigade_printf(ctx->bb, NULL, NULL, 
+                           "<span title='%s' style='white-space: nowrap;'>%s</span>", 
+                           ts, ts2);
+    }
+    else {
+        apr_brigade_puts(ctx->bb, NULL, NULL, "-");
+    }
+}
+
+static void si_val_expires(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    const char *s;
+    apr_time_t t;
+    
+    (void)info;
+    s = md_json_dups(ctx->p, mdj, MD_KEY_CERT, MD_KEY_VALID_UNTIL, NULL);
+    if (s) {
+        t = apr_date_parse_rfc(s);
+        si_val_date(ctx, t);
+    }
+}
+
+static void si_val_valid_from(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    const char *s;
+    apr_time_t t;
+    
+    (void)info;
+    s = md_json_dups(ctx->p, mdj, MD_KEY_CERT, MD_KEY_VALID_FROM, NULL);
+    if (s) {
+        t = apr_date_parse_rfc(s);
+        si_val_date(ctx, t);
+    }
+}
+    
+static void si_val_props(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    const char *s, *url;
+    md_pkey_type_t ptype;
+    int i = 0;
+    (void)info;
+
+    if (md_json_getb(mdj, MD_KEY_MUST_STAPLE, NULL)) {
+        ++i;
+        apr_brigade_puts(ctx->bb, NULL, NULL, "must-staple");
+    }
+    s = md_json_gets(mdj, MD_KEY_RENEW_WINDOW, NULL);
+    if (s) {
+        if (i++) apr_brigade_puts(ctx->bb, NULL, NULL, " \n"); 
+        apr_brigade_printf(ctx->bb, NULL, NULL, "renew-at[%s]", s);
+    }
+    url = s = md_json_gets(mdj, MD_KEY_CA, MD_KEY_URL, NULL);
+    if (s) {
+        if (i++) apr_brigade_puts(ctx->bb, NULL, NULL, " \n"); 
+        if (!strcmp(LE_ACMEv2_PROD, s)) s = "letsencrypt(v2)";
+        else if (!strcmp(LE_ACMEv1_PROD, s)) s = "letsencrypt(v1)";
+        else if (!strcmp(LE_ACMEv2_STAGING, s)) s = "letsencrypt(Testv2)";
+        else if (!strcmp(LE_ACMEv1_STAGING, s)) s = "letsencrypt(Testv1)";
+        
+        apr_brigade_printf(ctx->bb, NULL, NULL, "ca=[<a href=\"%s\">%s</a>]", url, s);
+    }
+    if (md_json_has_key(mdj, MD_KEY_CONTACTS, NULL)) {
+        if (i++) apr_brigade_puts(ctx->bb, NULL, NULL, " \n"); 
+        apr_brigade_puts(ctx->bb, NULL, NULL, "contacts=[");
+        add_json_val(ctx, md_json_getj(mdj, MD_KEY_CONTACTS, NULL));
+        apr_brigade_puts(ctx->bb, NULL, NULL, "]");
+    }
+    ptype = md_json_has_key(mdj, MD_KEY_PKEY, MD_KEY_TYPE, NULL)?
+            (unsigned)md_json_getl(mdj, MD_KEY_PKEY, MD_KEY_TYPE, NULL) : MD_PKEY_TYPE_DEFAULT; 
+    switch (ptype) {
+        case MD_PKEY_TYPE_RSA:
+            if (i++) apr_brigade_puts(ctx->bb, NULL, NULL, " \n"); 
+            apr_brigade_printf(ctx->bb, NULL, NULL, "key[RSA(%u)]", 
+                (unsigned)md_json_getl(mdj, MD_KEY_PKEY, MD_PKEY_RSA_BITS_MIN, NULL));
+        default:
+            break;
+    }
+}
+
+static void si_val_renewal(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    char buffer[HUGE_STRING_LEN];
+    apr_status_t rv;
+    int finished, errors;
+    apr_time_t t;
+    const char *s;
+    
+    (void)info;
+    if (!md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) {
+        return;
+    }
+    
+    finished = (int)md_json_getl(mdj, MD_KEY_RENEWAL, MD_KEY_FINISHED, NULL);
+    errors = (int)md_json_getl(mdj, MD_KEY_RENEWAL, MD_KEY_ERRORS, NULL);
+    rv = (apr_status_t)md_json_getl(mdj, MD_KEY_RENEWAL, MD_KEY_LAST, MD_KEY_STATUS, NULL);
+    
+    if (rv != APR_SUCCESS) {
+        s = md_json_gets(mdj, MD_KEY_RENEWAL, MD_KEY_LAST, MD_KEY_PROBLEM, NULL);
+        apr_brigade_printf(ctx->bb, NULL, NULL, "Error[%s]: %s", 
+                           apr_strerror(rv, buffer, sizeof(buffer)), s? s : "");
+    }
+    
+    if (finished) {
+        apr_brigade_puts(ctx->bb, NULL, NULL, "Finished");
+        if (md_json_has_key(mdj, MD_KEY_RENEWAL, MD_KEY_VALID_FROM, NULL)) {
+            s = md_json_gets(mdj,  MD_KEY_RENEWAL, MD_KEY_VALID_FROM, NULL);
+            t = apr_date_parse_rfc(s);
+            apr_brigade_puts(ctx->bb, NULL, NULL, (apr_time_now() >= t)?
+                             ", valid since: " : ", activate at: ");
+            si_val_time(ctx, t);
+        }
+        apr_brigade_puts(ctx->bb, NULL, NULL, ".");
+    } 
+    
+    s = md_json_gets(mdj, MD_KEY_RENEWAL, MD_KEY_LAST, MD_KEY_DETAIL, NULL);
+    if (s) apr_brigade_puts(ctx->bb, NULL, NULL, s);
+    
+    errors = (int)md_json_getl(mdj, MD_KEY_ERRORS, NULL);
+    if (errors > 0) {
+        apr_brigade_printf(ctx->bb, NULL, NULL, ", Had %d errors.", errors);
+    } 
+    
+    s = md_json_gets(mdj,  MD_KEY_RENEWAL, MD_KEY_NEXT_RUN, NULL);
+    if (s) {
+        t = apr_date_parse_rfc(s);
+        apr_brigade_puts(ctx->bb, NULL, NULL, "Next attempt: ");
+        si_val_time(ctx, t);
+        apr_brigade_puts(ctx->bb, NULL, NULL, ".");
+    }
+}
+
+static void si_val_remote_check(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    const char *fingerprint;
+    
+    (void)info;
+    fingerprint = md_json_gets(mdj, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL);
+    if (fingerprint) {
+        apr_brigade_printf(ctx->bb, NULL, NULL, 
+                           "<a href=\"https://censys.io/certificates/%s\">censys.io</a> ", 
+                           fingerprint);
+        apr_brigade_printf(ctx->bb, NULL, NULL, 
+                           "<a href=\"https://crt.sh?q=%s\">crt.sh</a> ", fingerprint);
+    }
+}
+
+const status_info status_infos[] = {
+    { "Name", MD_KEY_NAME, NULL },
+    { "Domains", MD_KEY_DOMAINS, NULL },
+    { "Status", MD_KEY_STATUS, si_val_status },
+    { "Valid", MD_KEY_VALID_FROM, si_val_valid_from },
+    { "Expires", MD_KEY_VALID_UNTIL, si_val_expires },
+    { "Renew", MD_KEY_RENEW_MODE, si_val_renew_mode },
+    { "Check@", MD_KEY_SHA256_FINGERPRINT, si_val_remote_check },
+    { "Configuration", MD_KEY_MUST_STAPLE, si_val_props },
+    { "Renewal",  MD_KEY_NOTIFIED, si_val_renewal },
+};
+
+static int json_iter_val(void *data, size_t index, md_json_t *json)
+{
+    status_ctx *ctx = data;
+    if (index) apr_brigade_puts(ctx->bb, NULL, NULL, ctx->separator);
+    add_json_val(ctx, json);
+    return 1;
+}
+
+static void add_json_val(status_ctx *ctx, md_json_t *j)
+{
+    if (!j) return;
+    else if (md_json_is(MD_JSON_TYPE_ARRAY, j, NULL)) {
+        md_json_itera(json_iter_val, ctx, j, NULL);
+    }
+    else if (md_json_is(MD_JSON_TYPE_INT, j, NULL)) {
+        md_json_writeb(j, MD_JSON_FMT_COMPACT, ctx->bb);
+    }
+    else if (md_json_is(MD_JSON_TYPE_STRING, j, NULL)) {
+        apr_brigade_puts(ctx->bb, NULL, NULL, md_json_gets(j, NULL));
+    }
+    else if (md_json_is(MD_JSON_TYPE_OBJECT, j, NULL)) {
+        md_json_writeb(j, MD_JSON_FMT_COMPACT, ctx->bb);
+    }
+}
+
+static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    if (info->fn) {
+        info->fn(ctx, mdj, info);
+    }
+    else {
+        add_json_val(ctx, md_json_getj(mdj, info->key, NULL));
+    }
+}
+
+static int add_md_row(void *baton, apr_size_t index, md_json_t *mdj)
+{
+    status_ctx *ctx = baton;
+    int i;
+    
+    apr_brigade_printf(ctx->bb, NULL, NULL, "<tr class=\"%s\">", (index % 2)? "odd" : "even");
+    for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) {
+        apr_brigade_puts(ctx->bb, NULL, NULL, "<td>");
+        add_status_cell(ctx, mdj, &status_infos[i]);
+        apr_brigade_puts(ctx->bb, NULL, NULL, "</td>");
+    }
+    apr_brigade_puts(ctx->bb, NULL, NULL, "</tr>");
+    return 1;
+}
+
+static int md_name_cmp(const void *v1, const void *v2)
+{
+    return strcmp((*(const md_t**)v1)->name, (*(const md_t**)v2)->name);
+}
+
+int md_status_hook(request_rec *r, int flags)
+{
+    const md_srv_conf_t *sc;
+    const md_mod_conf_t *mc;
+    int i, html;
+    status_ctx ctx;
+    apr_array_header_t *mds;
+    md_json_t *jstatus, *jstock;
+    
+    sc = ap_get_module_config(r->server->module_config, &md_module);
+    if (!sc) return DECLINED;
+    mc = sc->mc;
+    if (!mc || !mc->server_status_enabled) return DECLINED;
+
+    html = !(flags & AP_STATUS_SHORT);
+    ctx.p = r->pool;
+    ctx.mc = mc;
+    ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+    ctx.separator = " ";
+
+    mds = apr_array_copy(r->pool, mc->mds);
+    qsort(mds->elts, (size_t)mds->nelts, sizeof(md_t *), md_name_cmp);
+
+    if (!html) {
+        apr_brigade_puts(ctx.bb, NULL, NULL, "ManagedDomains: ");
+        if (mc->mds->nelts > 0) {
+            md_status_take_stock(&jstock, mds, mc->reg, r->pool);
+            apr_brigade_printf(ctx.bb, NULL, NULL, "total=%d, ok=%d renew=%d errored=%d ready=%d",
+                                (int)md_json_getl(jstock, MD_KEY_TOTAL, NULL), 
+                                (int)md_json_getl(jstock, MD_KEY_COMPLETE, NULL), 
+                                (int)md_json_getl(jstock, MD_KEY_RENEWING, NULL), 
+                                (int)md_json_getl(jstock, MD_KEY_ERRORED, NULL), 
+                                (int)md_json_getl(jstock, MD_KEY_READY, NULL));
+        } 
+        else {
+            apr_brigade_puts(ctx.bb, NULL, NULL, "[]"); 
+        }
+        apr_brigade_puts(ctx.bb, NULL, NULL, "\n"); 
+    }
+    else if (mc->mds->nelts > 0) {
+        md_status_get_json(&jstatus, mds, mc->reg, r->pool);
+        apr_brigade_puts(ctx.bb, NULL, NULL, 
+                         "<hr>\n<h2>Managed Domains</h2>\n<table class='md_status'><thead><tr>\n");
+        for (i = 0; i < (int)(sizeof(status_infos)/sizeof(status_infos[0])); ++i) {
+            apr_brigade_puts(ctx.bb, NULL, NULL, "<th>");
+            apr_brigade_puts(ctx.bb, NULL, NULL, status_infos[i].label);
+            apr_brigade_puts(ctx.bb, NULL, NULL, "</th>");
+        }
+        apr_brigade_puts(ctx.bb, NULL, NULL, "</tr>\n</thead><tbody>");
+        md_json_itera(add_md_row, &ctx, jstatus, MD_KEY_MDS, NULL);
+        apr_brigade_puts(ctx.bb, NULL, NULL, "</td></tr>\n</tbody>\n</table>\n");
+    }
+
+    ap_pass_brigade(r->output_filters, ctx.bb);
+    apr_brigade_cleanup(ctx.bb);
+    
+    return OK;
+}
+
+/**************************************************************************************************/
+/* Status handler */
+
+int md_status_handler(request_rec *r)
+{
+    const md_srv_conf_t *sc;
+    const md_mod_conf_t *mc;
+    apr_array_header_t *mds;
+    md_json_t *jstatus;
+    apr_bucket_brigade *bb;
+    const md_t *md;
+    const char *name;
+
+    if (strcmp(r->handler, "md-status")) {
+        return DECLINED;
+    }
+
+    sc = ap_get_module_config(r->server->module_config, &md_module);
+    if (!sc) return DECLINED;
+    mc = sc->mc;
+    if (!mc) return DECLINED;
+
+    if (r->method_number != M_GET) {
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "md-status supports only GET");
+        return HTTP_NOT_IMPLEMENTED;
+    }
+    
+    jstatus = NULL;
+    md = NULL;
+    if (r->path_info && r->path_info[0] == '/' && r->path_info[1] != '\0') {
+        name = strrchr(r->path_info, '/') + 1;
+        md = md_get_by_name(mc->mds, name);
+        if (!md) md = md_get_by_domain(mc->mds, name);
+    }
+    
+    if (md) {
+        md_status_get_md_json(&jstatus, md, mc->reg, r->pool);
+    }
+    else {
+        mds = apr_array_copy(r->pool, mc->mds);
+        qsort(mds->elts, (size_t)mds->nelts, sizeof(md_t *), md_name_cmp);
+        md_status_get_json(&jstatus, mds, mc->reg, r->pool);
+    }
+
+    if (jstatus) {
+        apr_table_set(r->headers_out, "Content-Type", "application/json"); 
+        bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+        md_json_writeb(jstatus, MD_JSON_FMT_INDENT, bb);
+        ap_pass_brigade(r->output_filters, bb);
+        apr_brigade_cleanup(bb);
+        
+        return DONE;
+    }
+    return DECLINED;
+}
diff --git a/modules/md/mod_md_status.h b/modules/md/mod_md_status.h
new file mode 100644 (file)
index 0000000..39db4c2
--- /dev/null
@@ -0,0 +1,26 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mod_md_md_status_h
+#define mod_md_md_status_h
+
+int md_http_cert_status(request_rec *r);
+
+int md_status_hook(request_rec *r, int flags);
+
+int md_status_handler(request_rec *r);
+
+#endif /* mod_md_md_status_h */