]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
Merged /httpd/httpd/trunk:r1861448,1862013,1862041,1862052,1862785
authorStefan Eissing <icing@apache.org>
Tue, 9 Jul 2019 08:40:17 +0000 (08:40 +0000)
committerStefan Eissing <icing@apache.org>
Tue, 9 Jul 2019 08:40:17 +0000 (08:40 +0000)
  *) mod_md: new features
     - supports the ACMEv2 protocol
     - new challenge method 'tls-alpn-01' implemented, needs mod_ssl patch to become available
     - 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/branches/2.4.x@1862791 13f79535-47bb-0310-9956-ffa450edef68

53 files changed:
CHANGES
CMakeLists.txt
docs/manual/mod/mod_md.xml
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_store_fs.h
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.dsp
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 21707c50039ee421ba4c873015e234b444925782..42c93a26513ccd7257daa29c3d255062c8bc8866 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,27 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.4.40
 
+  *) mod_md: new features
+     - supports the ACMEv2 protocol
+     - new challenge method 'tls-alpn-01' implemented, needs mod_ssl patch to become available 
+     - 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_mime_magic: Fix possible corruption of returned strings.
      [Christophe Jaillet]
 
index 8e6d564d6483ed033dbe63b043e13835c91f40c0..5e27162b0c146d033e39b137f8001b696ecb63a7 100644 (file)
@@ -64,6 +64,18 @@ ELSE()
   SET(default_brotli_libraries)
 ENDIF()
 
+IF(EXISTS "${CMAKE_INSTALL_PREFIX}/lib/curl.lib")
+  SET(default_curl_libraries "${CMAKE_INSTALL_PREFIX}/lib/curl.lib")
+ELSE()
+  SET(default_curl_libraries)
+ENDIF()
+
+IF(EXISTS "${CMAKE_INSTALL_PREFIX}/lib/jansson.lib")
+  SET(default_jansson_libraries "${CMAKE_INSTALL_PREFIX}/lib/jansson.lib")
+ELSE()
+  SET(default_jansson_libraries)
+ENDIF()
+
 SET(APR_INCLUDE_DIR       "${CMAKE_INSTALL_PREFIX}/include" CACHE STRING "Directory with APR[-Util] include files")
 SET(APR_LIBRARIES         ${default_apr_libraries}       CACHE STRING "APR libraries to link with")
 SET(NGHTTP2_INCLUDE_DIR   "${CMAKE_INSTALL_PREFIX}/include" CACHE STRING "Directory with NGHTTP2 include files within nghttp2 subdirectory")
@@ -74,6 +86,8 @@ SET(LIBXML2_ICONV_INCLUDE_DIR     ""                     CACHE STRING "Directory
 SET(LIBXML2_ICONV_LIBRARIES       ""                     CACHE STRING "iconv libraries to link with for libxml2")
 SET(BROTLI_INCLUDE_DIR    "${CMAKE_INSTALL_PREFIX}/include" CACHE STRING "Directory with include files for Brotli")
 SET(BROTLI_LIBRARIES      ${default_brotli_libraries}    CACHE STRING "Brotli libraries to link with")
+SET(CURL_LIBRARIES        "${default_curl_libraries}"    CACHE STRING "Curl libraries to link with")
+SET(JANSSON_LIBRARIES     "${default_jansson_libraries}" CACHE STRING "Jansson libraries to link with")
 # end support library configuration
 
 # Misc. options
@@ -199,6 +213,19 @@ ELSE()
   SET(BROTLI_FOUND FALSE)
 ENDIF()
 
+# See if we have curl
+SET(CURL_FOUND TRUE)
+IF(EXISTS "${CURL_INCLUDE_DIR}/curl/curl.h")
+  FOREACH(onelib ${CURL_LIBRARIES})
+    IF(NOT EXISTS ${onelib})
+      SET(CURL_FOUND FALSE)
+    ENDIF()
+  ENDFOREACH()
+ELSE()
+  SET(CURL_FOUND FALSE)
+ENDIF()
+
+
 MESSAGE(STATUS "")
 MESSAGE(STATUS "Summary of feature detection:")
 MESSAGE(STATUS "")
@@ -208,6 +235,8 @@ MESSAGE(STATUS "NGHTTP2_FOUND ............ : ${NGHTTP2_FOUND}")
 MESSAGE(STATUS "OPENSSL_FOUND ............ : ${OPENSSL_FOUND}")
 MESSAGE(STATUS "ZLIB_FOUND ............... : ${ZLIB_FOUND}")
 MESSAGE(STATUS "BROTLI_FOUND ............. : ${BROTLI_FOUND}")
+MESSAGE(STATUS "CURL_FOUND ............... : ${CURL_FOUND}")
+MESSAGE(STATUS "JANSSON_FOUND ............ : ${JANSSON_FOUND}")
 MESSAGE(STATUS "APR_HAS_LDAP ............. : ${APR_HAS_LDAP}")
 MESSAGE(STATUS "APR_HAS_XLATE ............ : ${APR_HAS_XLATE}")
 MESSAGE(STATUS "APU_HAVE_CRYPTO .......... : ${APU_HAVE_CRYPTO}")
@@ -309,6 +338,7 @@ SET(MODULE_LIST
   "modules/loggers/mod_log_forensic+I+forensic logging"
   "modules/loggers/mod_logio+I+input and output logging"
   "modules/lua/mod_lua+i+Apache Lua Framework"
+  "modules/md/mod_md+i+Apache Managed Domains (Certificates)"
   "modules/mappers/mod_actions+I+Action triggering on requests"
   "modules/mappers/mod_alias+A+mapping of requests to different filesystem parts"
   "modules/mappers/mod_dir+A+directory request handling"
@@ -440,6 +470,24 @@ SET(mod_lua_extra_sources
   modules/lua/lua_vmprep.c           modules/lua/lua_dbd.c
 )
 SET(mod_lua_requires                 LUA51_FOUND)
+SET(mod_md_requires                  OPENSSL_FOUND CURL_FOUND JANSSON_FOUND HAVE_OPENSSL_102)
+SET(mod_md_extra_includes            ${OPENSSL_INCLUDE_DIR} ${CURL_INCLUDE_DIR} ${JANSSON_INCLUDE_DIR})
+SET(mod_md_extra_libs                ${OPENSSL_LIBRARIES} ${CURL_LIBRARIES} ${JANSSON_LIBRARIES} mod_watchdog)
+SET(mod_md_extra_sources
+  modules/md/md_acme.c               modules/md/md_acme_acct.c
+  modules/md/md_acme_authz.c         modules/md/md_acme_drive.c
+  modules/md/md_acmev1_drive.c       modules/md/md_acmev2_drive.c
+  modules/md/md_acme_order.c         modules/md/md_core.c
+  modules/md/md_curl.c               modules/md/md_crypt.c
+  modules/md/md_http.c               modules/md/md_json.c
+  modules/md/md_jws.c                modules/md/md_log.c
+  modules/md/md_result.c             modules/md/md_reg.c
+  modules/md/md_status.c             modules/md/md_store.c
+  modules/md/md_store_fs.c           modules/md/md_time.c
+  modules/md/md_util.c               
+  modules/md/mod_md_config.c         modules/md/mod_md_drive.c
+  modules/md/mod_md_os.c             modules/md/mod_md_status.c
+)
 SET(mod_optional_hook_export_extra_defines AP_DECLARE_EXPORT) # bogus reuse of core API prefix
 SET(mod_proxy_extra_defines          PROXY_DECLARE_EXPORT)
 SET(mod_proxy_extra_sources          modules/proxy/proxy_util.c)
@@ -957,6 +1005,8 @@ MESSAGE(STATUS "  libxml2 iconv prereq include dir. : ${LIBXML2_ICONV_INCLUDE_DI
 MESSAGE(STATUS "  libxml2 iconv prereq libraries .. : ${LIBXML2_ICONV_LIBRARIES}")
 MESSAGE(STATUS "  Brotli include directory......... : ${BROTLI_INCLUDE_DIR}")
 MESSAGE(STATUS "  Brotli libraries ................ : ${BROTLI_LIBRARIES}")
+MESSAGE(STATUS "  Curl include directory........... : ${CURL_INCLUDE_DIR}")
+MESSAGE(STATUS "  Jansson libraries ............... : ${JANSSON_LIBRARIES}")
 MESSAGE(STATUS "  Extra include directories ....... : ${EXTRA_INCLUDES}")
 MESSAGE(STATUS "  Extra compile flags ............. : ${EXTRA_COMPILE_FLAGS}")
 MESSAGE(STATUS "  Extra libraries ................. : ${EXTRA_LIBS}")
index 7834308bda2203adc11b514fbe95049b21dd7b5d..5ae5d98188ac3f58b7ee50456a58306d20e49901 100644 (file)
     <summary>
         <p>
         This module manages common properties of domains for one or more virtual hosts. 
-        Specifically it can use the ACME protocol
-        (<a href="https://datatracker.ietf.org/doc/draft-ietf-acme-acme/">RFC Draft</a>) 
-        to automate certificate provisioning. These will be configured for managed domains and
-        their virtual hosts automatically. This includes renewal of certificates before they
-        expire. The most famous Certificate Authority currently implementing the ACME protocol
-        is <a href="https://letsencrypt.org/">Let's Encrypt</a>.</p>
+        Its main feature is the use of the ACME protocol
+        (<a href="https://tools.ietf.org/html/rfc8555">RFC 8555</a>) 
+        to automate certificate provisioning. Certificates will be renewed 
+        by the module ahead of their expiration to account for disruption in internet
+        services. There are ways to monitor the status of all Managed Domains
+        and configurations that will run your own notification commands on renewal,
+        expiration and errors.
+        </p>
+        <p>
+        The default ACME Certificate Authority is 
+        <a href="https://letsencrypt.org/">Let's Encrypt</a>, but it is possible 
+        to configure another CA that supports the protocol.
+        </p>
         
         <note type="warning"><title>Warning</title>
             <p>This module is experimental. Its behaviors, directives, and 
@@ -79,17 +86,17 @@ MDomain example.org
         <p>
             This module requires <module>mod_watchdog</module> to be loaded as well.
         </p><p>
-            Certificate signup and renewal with Let's Encrypt requires your server to be
+            Certificate sign-up and renewal with Let's Encrypt requires your server to be
             reachable on port 80 (http:) from the outside. The alternative method over
             port 443 (https:) is currently disabled for security reasons (status from
             2018-01-14).
         </p><p>
             The module will select from the methods offered by Let's Encrypt. If LE decides
-            at one point in the future, to re-enable it again, <module>mod_md</module> will
+            at one point in the future, to re-enable it again, mod_md will
             use it when suitable.
         </p><p>
             But for now, only the port 80 variant is available (termed "http-01"). Only
-            when LE can reach your server on port 80 will <module>mod_md</module> work for
+            when LE can reach your server on port 80 will mod_md work for
             you. For now, at least.
         </p><p>
             If you do not want to offer any sites on port 80 any more, you may leave it open
@@ -99,6 +106,120 @@ MDomain example.org
             from Let's Encrypt. 
         </p>
         </note>
+
+        <note><title>Wildcard Certificates</title>
+        <p>
+            Wildcard certificates are possible with version 2.x of `mod_md``. But they are 
+            not straight-forward. Let's Encrypt requires the `dns-01` challenge verification 
+            for those. No other is considered good enough.
+        </p><p>
+            The difficulty here is that Apache cannot do that on its own. (which is also 
+            a security benefit, since corrupting a web server or the communication path to 
+            it is the scenario `dns-01` protects against). As the name implies, `dns-01` 
+            requires you to show some specific DNS records for your domain that contain 
+            some challenge data. So you need to _write_ your domain's DNS records.
+        </p><p>
+            If you know how to do that, you can integrated this with `mod_md`. Let's 
+            say you have a script for that in `/usr/bin/acme-setup-dns` you configure 
+            Apache with:
+        </p>
+        <highlight language="config">
+MDChallengeDns01 /usr/bin/acme-setup-dns
+        </highlight>
+        <p>
+            and Apache will call this script when it needs to setup/teardown a DNS challenge 
+            record for a domain. 
+        </p><p>
+            Assuming you want a certificate for `*.mydomain.com`, mod_md will call:
+        </p>
+        <highlight language="config">
+/usr/bin/acme-setup-dns setup mydomain.com challenge-data
+# this needs to remove all existing DNS TXT records for 
+# _acme-challenge.mydomain.com and create a new one with 
+# content "challenge-data"
+        </highlight>
+        <p>
+            and afterwards it will call
+        </p>
+        <highlight language="config">
+/usr/bin/acme-setup-dns teardown mydomain.com
+# this needs to remove all existing DNS TXT records for 
+# _acme-challenge.mydomain.com
+        </highlight>
+        </note>
+
+        <note><title>Monitoring</title>
+            <p>
+                Apache has a standard module for monitoring: <module>mod_status</module>.
+                mod_md contributes a section and makes monitoring your 
+                domains easy.
+            </p><p>
+                You see all your MDs listed alphabetically, the domain names they contain, 
+                an overall status, expiration times and specific settings. The settings 
+                show your selection of renewal times (or the default), the CA that is used, 
+                etc.
+            </p><p>
+                The 'Renewal' column will show activity and error descriptions for certificate 
+                renewals. This should make life easier for people to find out if everything 
+                is all right or what went wrong.
+            </p><p>
+                If there is an error with an MD it will be shown here as well. This let's 
+                you assess problems without digging through your server logs.
+            </p><p>
+                There is also a new 'md-status' handler available to give you the MD information 
+                from 'server-status' in JSON format. You configure it as
+            </p>
+            <highlight language="config">
+&lt;Location "/md-status">
+  SetHandler md-status
+&lt;/Location>
+            </highlight>
+            <p>
+                on your server. As with 'server-status' you will want to add 
+                authorization for this. 
+            </p><p>
+                If you just want to check the JSON status of a specific domain, simply append 
+                that to your status url:
+            </p>
+            <highlight language="config">
+> curl https://&lt;yourhost>/md-status/another-domain.org
+{
+  "name": "another-domain.org",
+  "domains": [
+    "another-domain.org",
+    "www.another-domain.org"
+  ],
+  ...
+            </highlight>
+            <p>
+                This JSON status also shows a log of activities when domains are renewed:
+            </p>
+            <highlight language="config">
+{
+"when": "Wed, 19 Jun 2019 14:45:58 GMT",
+"type": "progress", "detail": "The certificate for the managed domain has been renewed successfully and can be used. A graceful server restart now is recommended."
+},{
+"when": "Wed, 19 Jun 2019 14:45:58 GMT",
+"type": "progress", "detail": "Retrieving certificate chain for test-901-003-1560955549.org"
+},{
+"when": "Wed, 19 Jun 2019 14:45:58 GMT",
+"type": "progress", "detail": "Waiting for finalized order to become valid"
+},{
+"when": "Wed, 19 Jun 2019 14:45:50 GMT",
+"type": "progress", "detail": "Submitting CSR to CA for test-901-003-1560955549.org"
+},
+...
+            </highlight>
+            <p>
+                You will also find this information in the file `job.json` in your staging and, 
+                when activated, domains directory. This allows you to inspect these at
+                any later point in time as well. 
+            </p><p>
+                In addition, there is <directive module="mod_md">MDCertificateStatus</directive> which
+                gives access to relevant certificate information in JSON format.
+            </p>
+        </note>
+
     </summary>
     
     <directivesynopsis>
@@ -112,7 +233,7 @@ MDomain example.org
         <usage>
             <p>
                 All the names in the list are managed as one Managed Domain (MD). 
-                <module>mod_md</module> will request one single certificate that is valid for all these names. This
+                mod_md will request one single certificate that is valid for all these names. This
                 directive uses the global settings (see other MD directives below). If you
                 need specific settings for one MD, use
                 the <directive module="mod_md" type="section">MDomainSet</directive>.
@@ -126,14 +247,13 @@ MDomain example.org
                 changes in its service or status of your certificates.
             </p><p>
                 The second setting, <directive module="mod_md">MDCertificateAgreement</directive>, 
-                is the URL of the Terms of Service of the CA. When you configure the URL, 
-                you confirm that you have read and agree to the terms described in the linked 
-                document. Before you do that, the CA will  not hand out certificates to you.
+                should have the value "accepted". By specifying this, you confirm that your
+                accept the Terms of Service of the CA. 
             </p>
             <example><title>Example</title>
                 <highlight language="config">
 ServerAdmin mailto:admin@example.org
-MDCertificateAgreement https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf
+MDCertificateAgreement accepted
 MDomain example.org www.example.org
 
 &lt;VirtualHost *:443&gt;
@@ -155,7 +275,7 @@ MDomain example.org www.example.org
                 There are two special names that you may use in this directive: 'manual'
                 and 'auto'. This determines if a Managed Domain shall have exactly the 
                 name list as is configured ('manual') or offer more convenience. With 'auto'
-                all names of a virtual host are added to a MD. Conventiently, 'auto' is also
+                all names of a virtual host are added to a MD. Conveniently, 'auto' is also
                 the default.
             </p>
             <example><title>Example</title>
@@ -201,46 +321,48 @@ MDomain example2.org auto
         
         <usage>
             <p>
-                This directive allows you to define a Managed Domain (MD) with specific
-                settings, different from the global MD* ones. For example, you can have
-                such an MD use another CA then Let's Encrypt, have its unique renewal duration
-                etc.
+                This is the directive <directive module="mod_md">MDomain</directive>
+                with the added possibility to add setting just for this MD. In fact,
+                you may also use "&lt;MDomain ..>" as a shortcut.
+            </p>
+            <p>
+                This allows you to configure an MD that uses another Certificate Authority,
+                have other renewal requirements, etc.
             </p>
             <example><title>Example</title>
                 <highlight language="config">
-&lt;MDomainSet sandbox.example.org&gt;
+&lt;MDomain sandbox.example.org&gt;
     MDCertificateAuthority   https://someotherca.com/ACME
-    MDCertificateAgreement   https://someotherca.com/terms/v_1.02.pdf
-&lt;/MDomainSet&gt;
+&lt;/MDomain&gt;
+                </highlight>
+            </example>
+        <p>
+            A common use case is to configure https: requirements separately for
+            your domains.
+        </p>
+            <example><title>Example</title>
+                <highlight language="config">
+&lt;MDomain example.org&gt;
+    MDRequireHttps temporary
+&lt;/MDomain&gt;
                 </highlight>
             </example>
-        <p>This is a specialized version of <directive module="mod_md">MDomain</directive>,
-        it should be used only when a fine grained configuration is required.
-        <directive module="mod_md">MDomain</directive> is the suggested choice
-        for the general use case.</p>
         </usage>
     </directivesynopsis>
 
     <directivesynopsis>
         <name>MDCertificateAgreement</name>
-        <description>The URL of the Terms-of-Service document, that the CA server requires you to accept.</description>
-        <syntax>MDCertificateAgreement <var>url-of-terms-of-service</var></syntax>
+        <description>You confirm that you accepted the Terms of Service of the Certificate
+        Authority.</description>
+        <syntax>MDCertificateAgreement accepted</syntax>
         <contextlist>
             <context>server config</context>
         </contextlist>
         <usage>
-            <p>When you use <module>mod_md</module> to obtain a certificate, you become a customer of the CA (e.g. Let's Encrypt). That means you need to read and agree to their Terms of Service, 
+            <p>When you use mod_md to obtain a certificate, you become a customer of the CA (e.g. Let's Encrypt). That means you need to read and agree to their Terms of Service, 
             so that you understand what they offer and what they might exclude or require from you. 
-            <module>mod_md</module> cannot, by itself, agree to such a thing. 
+            mod_md cannot, by itself, agree to such a thing. 
             </p>
-            <p>In case of Let's Encrypt, their current <a href="https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf">Terms of Service are here</a>. 
-            Those terms might (and probably will) change over time. So, the certificate renewal might require you to update this agreement URL.</p>
-            <example><title>Example</title>
-                <highlight language="config">
-MDCertificateAgreement https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf
-MDomain example.org www.example.org mail.example.org
-                </highlight>
-            </example>
         </usage>
     </directivesynopsis>
 
@@ -248,7 +370,7 @@ MDomain example.org www.example.org mail.example.org
         <name>MDCertificateAuthority</name>
         <description>The URL of the ACME Certificate Authority service.</description>
         <syntax>MDCertificateAuthority <var>url</var></syntax>
-        <default>MDCertificateAuthority https://acme-v01.api.letsencrypt.org/directory</default>
+        <default>MDCertificateAuthority https://acme-v02.api.letsencrypt.org/directory</default>
         <contextlist>
             <context>server config</context>
         </contextlist>
@@ -256,15 +378,19 @@ MDomain example.org www.example.org mail.example.org
             <p>
                 The URL where the CA offers its service.
             </p><p>
-                Let's Encrypt offers, right now, two such URLs. One for the real certificates and
-                one for testing (their staging area, at https://acme-staging.api.letsencrypt.org/directory).
-                In order to have <module>mod_md</module> use this testing service, configure your
-                server like this: 
+                Let's Encrypt offers, right now, four such URLs. Two for
+                the own legacy version of the ACME protocol, commonly named ACMEv1.
+                And two for the RFC 8555 version, named ACMEv2.
+            </p><p>
+                Each version has 2 endpoints, as their is a production endpoint and a
+                "staging" endpoint for testing. The testing endpoint works the same, but will
+                not give you certificates recognized by browsers. However, it also has
+                very relaxed rate limits. This allows testing of the service repeatedly
+                without you blocking yourself.
             </p>
             <example><title>LE Staging Setup</title>
                 <highlight language="config">
-MDCertificateAuthority https://acme-staging.api.letsencrypt.org/directory
-MDCertificateAgreement https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf
+MDCertificateAuthority https://acme-staging-v02.api.letsencrypt.org/directory
                 </highlight>
             </example>
         </usage>
@@ -279,33 +405,55 @@ MDCertificateAgreement https://letsencrypt.org/documents/LE-SA-v1.2-November-15-
             <context>server config</context>
         </contextlist>
         <usage>
-            <p>Specifies the protocol to use. Currently, only <code>ACME</code> is supported.</p>
+            <p>
+                Specifies the protocol to use. Currently, only <code>ACME</code> is supported.
+            </p>
         </usage>
     </directivesynopsis>
 
     <directivesynopsis>
         <name>MDDriveMode</name>
-        <description>Control when it is allowed to obtain/renew certificates.</description>
+        <description>former name of MDRenewMode.</description>
         <syntax>MDDriveMode always|auto|manual</syntax>
         <default>MDDriveMode auto</default>
         <contextlist>
             <context>server config</context>
         </contextlist>
         <usage>
-            <p>In 'auto' mode, <module>mod_md</module> will <em>drive</em> a Managed Domain's
-            properties (e.g. certificate management) whenever necessary. When a MD is not used
-            in any virtual host, the module will do nothing. When a certificate is missing, it
-            will try to get one. When a certificate expires soon (see 
-            <directive module="mod_md">MDRenewWindow</directive>), it will
-            renew it.
-            </p><p>
-            In 'manual' mode, it is your duty to do all this. The module will provide the existing
-            certificate to <module>mod_ssl</module>, if available. But it will not contact the CA for signup/renewal.
-            This can be useful in clustered setups where you want just one node to perform
-            the driving.
-            </p><p>
-            The third mode 'always' is like 'auto', with the difference that
-            <module>mod_md</module> will not check if the MD is actually used. 
+            <p>This directive exists for backward compatibility as the old name for
+            <directive module="mod_md">MDRenewMode</directive>.
+            </p>
+        </usage>
+    </directivesynopsis>
+
+    <directivesynopsis>
+        <name>MDRenewMode</name>
+        <description>Controls if certificates shall be renewed.</description>
+        <syntax>MDRenewMode always|auto|manual</syntax>
+        <default>MDRenewMode auto</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                In the default 'auto' mode, the module will do what makes most sense
+                of each Managed Domain. For a domain without any certificates, it will
+                obtain them from the Certificate Authority. 
+            </p>
+            <p>
+                However, if you have defined an MD that is not used by any of Apache's 
+                VirtualHosts, it will not bother. And for MDs with static certificate
+                files (see <directive module="mod_md">MDCertificateFile</directive>), 
+                it assumes that you have your own source, and will not renew them either.
+            </p>
+            <p>
+                You can override this default in either way. If you specify 'always',
+                the module will renew certificates for an MD, irregardless if the
+                domains are in use or if there are static files.
+            </p>
+            <p>
+                For the opposite effect, configure 'manual' and no renewal will
+                be attempted.
             </p>
         </usage>
     </directivesynopsis>
@@ -334,15 +482,15 @@ MDCertificateAgreement https://letsencrypt.org/documents/LE-SA-v1.2-November-15-
         <usage>
             <p>
             Instead of listing all dns names on the same line, you may use
-            <directive>MDMember</directive> to add such names
+            <directive module="mod_md">MDMember</directive> to add such names
             to a managed domain.
             </p>
             <example><title>Example</title>
                 <highlight language="config">
-&lt;MDomainSet example.org&gt;
+&lt;MDomain example.org&gt;
     MDMember www.example.org
     MDMember mail.example.org
-&lt;/MDomainSet&gt;
+&lt;/MDomain&gt;
                 </highlight>
             </example>
             <p>
@@ -390,16 +538,17 @@ MDCertificateAgreement https://letsencrypt.org/documents/LE-SA-v1.2-November-15-
 
     <directivesynopsis>
         <name>MDNotifyCmd</name>
-        <description>Run a program when Managed Domain are ready.</description>
+        <description>Run a program when a Managed Domain is ready.</description>
         <syntax>MDNotifyCmd <var>path</var> [ <var>args</var> ]</syntax>
         <contextlist>
             <context>server config</context>
         </contextlist>
         <usage>
-            <p>The configured executable is run when Managed Domains have signed up or
-            renewed their certificates. It is given the names of the processed MDs as
-            additional arguments (after the parameters specified here). It should 
-            return status code 0 to indicate that it has run successfully.
+            <p>
+                The configured executable is run when a Managed Domain has signed up or
+                renewed its certificate. It is given the name of the processed MD as
+                additional arguments (after the parameters specified here). It should 
+                return status code 0 to indicate that it has run successfully.
             </p>
         </usage>
     </directivesynopsis>
@@ -408,36 +557,44 @@ MDCertificateAgreement https://letsencrypt.org/documents/LE-SA-v1.2-November-15-
         <name>MDPortMap</name>
         <description>Map external to internal ports for domain ownership verification.</description>
         <syntax>MDPortMap <var>map1</var> [ <var>map2</var> ]</syntax>
-        <default>MDPortMap 80:80 443:443</default>
+        <default>MDPortMap http:80 https:443</default>
         <contextlist>
             <context>server config</context>
         </contextlist>
         <usage>
             <p>
-                The ACME protocol provides two methods to verify domain ownership: one that uses
-                port 80 and one for port 443. If your server is not reachable by at least one
-                of the two, ACME will not work for you.
+                The ACME protocol provides two methods to verify domain ownership via
+                HTTP: one that uses 'http:' urls (port 80) and one for 'https:' urls
+                (port 443). If your server is not reachable by at least one
+                of the two, ACME may only work by configuring your DNS server,
+                see <directive module="mod_md">MDChallengeDns01</directive>.
             </p><p>
-                <module>mod_md</module> will look at your server configuration and try to figure
-                out which of those are available. Then it can select the proper ACME challenge
-                to create a certificate for your site.
+                On most public facing servers, 'http:' arrives on port 80 and
+                'https:' on port 443. The module checks the ports your Apache server
+                is listening on and assumes those are available. This means that
+                when your server does not listen on port 80, it assumes that
+                'http:' requests from the internet will not work.
             </p><p>
-                However if you have some fancy port forwarding in place, your server may be
-                reachable from the Internet on port 443, but the local port that httpd uses is
-                another one. Your server might only listen on ports 5001 and 5002, but be reached
-                on ports 443 and 80. How should <module>mod_md</module> figure that one out?
-            </p><p>
-                With <directive>MDPortMap</directive> you can tell it which 'Internet port'
-                corresponds to which local port.
+                This is a good guess, but it may be wrong. For example, your Apache
+                might listen to port 80, but your firewall might block it. 'http:' 
+                is only available in your intranet. So, the module will falsely assume
+                that Let's Encrypt can use 'http:' challenges with your server. This 
+                will then fail, because your firewall will drop those.
             </p>
             <example><title>Example</title>
                 <highlight language="config">
-MDPortMap 80:- 443:5002
+MDPortMap http:- https:8433
                 </highlight>
             </example>
             <p>
-                This example says that the server is not reachable on port 80 from the outside, but
-                local port 5002 is the one responding to https: requests.
+                The above example shows how you can specify that 'http:' requests from 
+                the internet will never arrive. In addition it says that 'https:' requests
+                will arrive on local port 8433.
+            </p><p>
+                This is necessary if you have port forwarding in place, your server may be
+                reachable from the Internet on port 443, but the local port that httpd uses is
+                another one. Your server might only listen on ports 8443 and 8000, but be reached
+                on ports 443 and 80 (from the internet).
             </p>
         </usage>
     </directivesynopsis>
@@ -486,10 +643,10 @@ MDPrivateKeys RSA 3072
         </contextlist>
         <usage>
             <p>
-            If the validity of the certificate falls below duration, <module>mod_md</module>
+            If the validity of the certificate falls below duration, mod_md
             will get a new signed certificate.
             </p><p>
-            Normally, certificates are valid for around 90 days and <module>mod_md</module> will renew 
+            Normally, certificates are valid for around 90 days and mod_md will renew 
             them the earliest 33% of their complete lifetime before they expire (so for 
             90 days validity, 30 days before it expires). If you think this is not what 
             you need, you can specify either the exact time, as in:
@@ -562,7 +719,7 @@ MDRequireHttps permanent
             <p>You can achieve the same with <module>mod_alias</module> and some
             <directive module="mod_alias">Redirect</directive> configuration,
             basically. If you do it yourself, please make sure to exclude the paths 
-            /.well-known/* from your redirection, otherwise <module>mod_md</module> 
+            /.well-known/* from your redirection, otherwise mod_md 
             might have trouble signing on new certificates.
             </p>
             <p>If you set this globally, it applies to all managed domains. If you want 
@@ -570,9 +727,9 @@ MDRequireHttps permanent
             </p>
             <example><title>Example</title>
                 <highlight language="config">
-&lt;MDomainSet xxx.yyy&gt;
+&lt;MDomain xxx.yyy&gt;
   MDRequireHttps temporary
-&lt;/MDomainSet&gt;
+&lt;/MDomain&gt;
                 </highlight>
             </example>
         </usage>
@@ -604,7 +761,7 @@ MDRequireHttps permanent
         <name>MDCAChallenges</name>
         <description>Type of ACME challenge used to prove domain ownership.</description>
         <syntax>MDCAChallenges <var>name</var> [ <var>name</var> ... ]</syntax>
-        <default>MDCAChallenges tls-sni-01 http-01</default>
+        <default>MDCAChallenges tls-alpn-01 http-01 dns-01</default>
         <contextlist>
             <context>server config</context>
         </contextlist>
@@ -612,8 +769,8 @@ MDRequireHttps permanent
             <p>
                 Sets challenge types and their execution order when proving domain ownership.
                 The names are protocol specific.
-                The current ACME protocol version implemented by Let's Encrypt defines two challenge
-                types that are supported by <module>mod_md</module>. By default, it will try
+                The current ACME protocol version implemented by Let's Encrypt defines three challenge
+                types that are supported by mod_md. By default, it will try
                 the one on port 443 when available.
             </p>
         </usage>
@@ -630,12 +787,214 @@ MDRequireHttps permanent
         <usage>
             <p>
             Controls if the base server, the one outside all VirtualHosts should be managed by 
-            <module>mod_md</module> or not. Default is to not do this, for the very reason that 
+            mod_md or not. By default, it will not. For the very reason that 
             it may have confusing side-effects. It is recommended that you have virtual hosts 
             for all managed domains and do not rely on the global, fallback server configuration.
             </p>
         </usage>
     </directivesynopsis>
 
+    <directivesynopsis>
+        <name>MDCertificateFile</name>
+        <description>Specify a static certificate file for the MD.</description>
+        <syntax>MDCertificateFile path-to-pem-file</syntax>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                This is used inside a <directive module="mod_md">MDomainSet</directive> and specifies
+                the file holding the certificate chain for the Managed Domain. The matching
+                key is specified via <directive module="mod_md">MDCertificateKeyFile</directive>.
+            </p>
+            <example><title>Example</title>
+                <highlight language="config">
+&lt;MDomain mydomain.com>
+  MDCertificateFile /etc/ssl/my.cert
+  MDCertificateKeyFile /etc/ssl/my.key
+&lt;/MDomain>
+                </highlight>
+            </example>
+
+            <p>
+                This is that equivalent of the mod_ssl 
+                <directive module="mod_ssl">SSLCertificateFile</directive> directive. It 
+                has several uses. 
+            </p><p>
+                If you want to migrate an existing domain, using static files, to
+                automated Let's Encrypt certificates, for one. You define the
+                <directive module="mod_md">MDomainSet</directive>, add the files here and remove
+                the <directive module="mod_ssl">SSLCertificateFile</directive> from
+                your VirtualHosts. 
+            </p><p>
+                This will give you the same as before, with maybe less repeating lines
+                in your configuration. Then you can add <directive module="mod_md">MDRenewMode</directive>
+                'always' to it and the module will get a new certificate before
+                the one from the file expires. When it has done so, you remove the
+                <directive module="mod_md">MDCertificateFile</directive> and reload the server.
+            </p><p>
+                Another use case is that you renew your Let's Encrypt certificates with
+                another ACME clients, for example the excellent 
+                <a href="https://certbot.eff.org">certbot</a>. Then let your MDs point
+                to the files from certbot and have both working together.
+            </p>
+        </usage>
+    </directivesynopsis>
+
+    <directivesynopsis>
+        <name>MDCertificateKeyFile</name>
+        <description>Specify a static private key for for the static cerrtificate.</description>
+        <syntax>MDCertificateKeyFile path-to-file</syntax>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                This is used inside a <directive module="mod_md">MDomainSet</directive> and specifies
+                the file holding the private key for the Managed Domain. The matching
+                certificate is specified via <directive module="mod_md">MDCertificateFile</directive>.
+            </p><p>
+                This is that equivalent of the mod_ssl 
+                <directive module="mod_ssl">SSLCertificateKeyFile</directive> directive.
+            </p>
+        </usage>
+    </directivesynopsis>
+
+    <directivesynopsis>
+        <name>MDCertificateStatus</name>
+        <description>Exposes public certificate information in JSON.</description>
+        <syntax>MDCertificateStatus on|off</syntax>
+        <default>MDCertificateStatus on</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                When enabled, a resources is available in Managed Domains at
+                'https://domain/.httpd/certificate-status' that returns a JSON
+                document list key properties of the current and of a renewed
+                certificate - when available.
+            </p>
+            <example><title>Example</title>
+                <highlight language="config">
+{
+  "valid-until": "Thu, 29 Aug 2019 16:06:35 GMT",
+  "valid-from": "Fri, 31 May 2019 16:06:35 GMT",
+  "serial": "03039C464D454EDE79FCD2CAE859F668F269",
+  "sha256-fingerprint": "1ff3bfd2c7c199489ed04df6e29a9b4ea6c015fe8a1b0ce3deb88afc751e352d"
+  "renewal" : { ...renewed cert information... }
+}                
+                </highlight>
+            </example>
+        </usage>
+    </directivesynopsis>
+
+
+    <directivesynopsis>
+        <name>MDChallengeDns01</name>
+        <description></description>
+        <syntax>MDChallengeDns01 path-to-command</syntax>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                Define a program to be called when the `dns-01` challenge needs to be setup/torn down. 
+                The program is given the argument `setup` or `teardown` followed by the domain name. 
+                For `setup` the challenge content is additionally given.
+            </p><p>
+                You do not need to specify this, as long as a 'http:' or 'https:' challenge
+                method is possible. However, Let's Encrypt makes 'dns-01' the only
+                challenge available for wildcard certificates. If you require
+                one of those, you need to configure this.
+            </p><p>
+                See the section about wildcard certificates above for more details.
+            </p>
+        </usage>
+    </directivesynopsis>
+
+    <directivesynopsis>
+        <name>MDMessageCmd</name>
+        <description>Handle events for Manage Domains</description>
+        <syntax>MDMessageCmd path-to-cmd optional-args</syntax>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                This command gets called when one of the following events happen for
+                a Managed Domain: "renewed", "expiring", "errored". The command may
+                be invoked for more than these in the future and ignore events
+                it is not prepared to handle.
+            </p><p>
+                This is the more flexible companion to <directive module="mod_md">MDNotifyCmd</directive>.
+            </p>
+            <example><title>Example</title>
+MDMessageCmd /etc/apache/md-message
+
+# will be invoked when a new certificate for mydomain.org is available as:
+/etc/apache/md-message renewed mydomain.com
+                <highlight language="config">
+                </highlight>
+            </example>
+            <p>
+                The program should not block, as the module will wait for it to finish. A
+                return code other than 0 is regarded as an error. 
+            </p><p>
+                'errored' is no immediate cause for concern since renewal is attempted
+                early enough to allow the internet to come back. 
+            </p><p>
+                'expiring' should be taken serious. It is issued when the
+                <directive module="mod_md">MDWarnWindow</directive> is reached. By default this is
+                10% of the certificate lifetime, so for Let's Encrypt this currently
+                means 9 days before it expires. The warning is repeated at most once
+                a day. 
+            </p>
+        </usage>
+    </directivesynopsis>
+
+    <directivesynopsis>
+        <name>MDWarnWindow</name>
+        <description>Define the time window when you want to be warned about an expiring certificate.</description>
+        <syntax>MDWarnWindow duration</syntax>
+        <default>MDWarnWindow 10%</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                See <directive module="mod_md">MDRenewWindow</directive> for a description on
+                how you can specify the time.
+            </p><p>
+                The modules checks the remaining lifetime of certificates and invokes
+                <directive module="mod_md">MDMessageCmd</directive> when there is less than the warn
+                window left. With the default, this mean 9 days for certificates from
+                Let's Encrypt.
+            </p><p>
+                It also applies to Managed Domains with static certificate files (
+                see <directive module="mod_md">MDCertificateFile</directive>).
+            </p>
+        </usage>
+    </directivesynopsis>
+
+    <directivesynopsis>
+        <name>MDServerStatus</name>
+        <description>Control if Managed Domain information is added to server-status.</description>
+        <syntax>MDServerStatus on|off</syntax>
+        <default>MDServerStatus on</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                Apaches 'server-status' handler allows you configure a resource to monitor
+                what is going on. This includes now a section listing all Managed Domains
+                with the DNS names, renewal status, lifetimes and main properties.
+            </p><p>
+                You can switch that off using this directive.
+            </p>
+        </usage>
+    </directivesynopsis>
+
 
 </modulesynopsis>
index a2c83030a0e9772c416e055bbd0e4445920193de..d26d0659cffcc1ff1d6e0fd6187285b1122930ae 100644 (file)
@@ -248,6 +248,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
@@ -255,13 +258,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 ba4e8659adc7567b7f85d1bceb98c3ac7f730836..4b29e4b044bfe7fe2c0bf1ec21bfeeb08631e5cb 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,39 @@ 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) {
-            get_up_link(d, res->headers);
-        }
+    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");
+        apr_array_clear(ad->certs);
+        APR_ARRAY_PUSH(ad->certs, md_cert_t*) = cert;
+        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 +332,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 +378,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 +387,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,83 +401,106 @@ 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 (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;
-    ad->driver = d;
     
+    ad->driver = d;
     ad->authz_monitor_timeout = apr_time_from_sec(30);
     ad->cert_poll_timeout = apr_time_from_sec(30);
-
+    ad->ca_challenges = apr_array_make(d->p, 3, sizeof(const char*));
+    ad->certs = apr_array_make(d->p, 5, sizeof(md_cert_t*));
+    
     /* 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);
+    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,46 +509,67 @@ 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 (!d->can_https) {
-        ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_TLSSNI01, 0);
+    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 (!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;
-    }
-    
-    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;
+    apr_array_header_t *staged_certs;
+    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, "
@@ -690,7 +578,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? */
@@ -702,220 +593,182 @@ 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)
+        && APR_SUCCESS == md_pubcert_load(d->store, MD_SG_STAGING, d->md->name, &staged_certs, d->p)) {
+        apr_array_cat(ad->certs, staged_certs);
+    }
+    
+    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;
@@ -923,97 +776,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..9314f20
--- /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 %d", 
+                                     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 f3585da875573109e3a77e9fc88b8cdaaa607ce0..e53d96798cc830bd12424ddce6ac0125f6d665f5 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..4bdd508
--- /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)) {
+        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 f399cea101eab60f8393e1890ca625b255bdbb4a..e75566194841536b4d1fa61fd35eeb61bedde0ed 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;
             }
index 4167c9bc9582c144a48a162c11659b7f658efb3c..dcdb89785069c8211e25ae117dfb9ab36aebcfe3 100644 (file)
@@ -56,7 +56,7 @@ typedef enum {
 } md_store_fs_ev_t; 
 
 typedef apr_status_t md_store_fs_cb(void *baton, struct md_store_t *store,
-                                    md_store_fs_ev_t ev, int group, 
+                                    md_store_fs_ev_t ev, unsigned int group, 
                                     const char *fname, apr_filetype_e ftype,  
                                     apr_pool_t *p);
                                     
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 a7e2e51c28f19b42ce8ebd4ea86c497f6de76ef6..21286a261610099cdc2dfe0b9a77b031d05bce4a 100644 (file)
@@ -27,7 +27,7 @@
  * @macro
  * Version number of the md module as c string
  */
-#define MOD_MD_VERSION "1.1.19"
+#define MOD_MD_VERSION "2.0.7"
 
 /**
  * @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 0x020007
 
-#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 249a0f000e571e51912ce3935520e17b98487a72..d26ec793993fdebdde1c7eb440d98ea456ffd7b8 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(10168)
+                         "%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(10169)
+                         "%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, 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, level, rv, log_server, "%s",buffer);
-        }
-        else {
-            ap_log_perror(file, line, APLOG_MODULE_INDEX, 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(10170)
+                         "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(10171)
+                         "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(10172)
+                         "%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(10173) 
+                             "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(10174)
+                     "%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 c685f540439978f8ef61e7001d23905708d0618d..b52b185f7698335cd1e19b9bf962c22a1c7a7163 100644 (file)
@@ -109,10 +109,46 @@ SOURCE=./mod_md_config.c
 # End Source File\r
 # Begin Source File\r
 \r
+SOURCE=./mod_md_drive.c\r
+# End Source File\r
+# Begin Source File\r
+\r
 SOURCE=./mod_md_os.c\r
 # End Source File\r
 # Begin Source File\r
 \r
+SOURCE=./mod_md_status.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=./md_acme.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=./md_acme_acct.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=./md_acme_authz.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=./md_acme_drive.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=./md_acme_order.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=./md_acmev1_drive.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=./md_acmev2_drive.c\r
+# End Source File\r
+# Begin Source File\r
+\r
 SOURCE=./md_core.c\r
 # End Source File\r
 # Begin Source File\r
@@ -145,35 +181,30 @@ SOURCE=./md_reg.c
 # End Source File\r
 # Begin Source File\r
 \r
-SOURCE=./md_store.c\r
-# End Source File\r
-# Begin Source File\r
-\r
-SOURCE=./md_store_fs.c\r
+SOURCE=./md_result.c\r
 # End Source File\r
 # Begin Source File\r
 \r
-SOURCE=./md_util.c\r
+SOURCE=./md_status.c\r
 # End Source File\r
 # Begin Source File\r
 \r
-SOURCE=./md_acme.c\r
+SOURCE=./md_store.c\r
 # End Source File\r
 # Begin Source File\r
 \r
-SOURCE=./md_acme_acct.c\r
+SOURCE=./md_store_fs.c\r
 # End Source File\r
 # Begin Source File\r
 \r
-SOURCE=./md_acme_authz.c\r
+SOURCE=./md_time.c\r
 # End Source File\r
 # Begin Source File\r
 \r
-SOURCE=./md_acme_drive.c\r
+SOURCE=./md_util.c\r
 # End Source File\r
 # Begin Source File\r
 \r
-\r
 SOURCE=..\..\build\win32\httpd.rc\r
 # End Source File\r
 # End Target\r
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 336a21ba5c3d61c3f0705bc05d0cd7aac0bf0d72..ba79fd773a22027df0b470a4ceea241c24d1e554 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)
 
+#ifndef MD_DEFAULT_BASE_DIR
+#define MD_DEFAULT_BASE_DIR "md"
+#endif
+
 /* Default settings for the global conf */
 static md_mod_conf_t defmc = {
-    NULL,
-    "md",
-    NULL,
-    NULL,
-    80,
-    443,
-    0,
-    0,
-    0,
-    MD_HSTS_MAX_AGE_DEFAULT,
-    NULL,
-    NULL,
-    NULL,
+    NULL,                      /* list of mds */
+#if AP_MODULE_MAGIC_AT_LEAST(20180906, 2)
+    NULL,                      /* base dirm by default state-dir-relative */
+#else
+    MD_DEFAULT_BASE_DIR,
+#endif
+    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;
@@ -112,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);
     }
     
@@ -125,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;
@@ -140,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;
@@ -155,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;
@@ -194,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;
@@ -227,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) {
@@ -238,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) {
@@ -424,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);
@@ -447,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;
 }
 
@@ -460,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)
@@ -480,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)
@@ -521,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)
@@ -640,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";
@@ -654,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";
         }
@@ -784,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)
 };
@@ -921,7 +954,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:
@@ -937,14 +970,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 f96d5667c0a9074814606698e0e08427dc45f016..2e52e428eec74ff1ed31407c2c21aba38b9fa4db 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 
@@ -58,10 +64,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..9ceadb6
--- /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(10175)
+                      "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 */