]> granicus.if.org Git - apache/commitdiff
*) mod_md: Adding the several new features.
authorStefan Eissing <icing@apache.org>
Wed, 16 Oct 2019 12:31:43 +0000 (12:31 +0000)
committerStefan Eissing <icing@apache.org>
Wed, 16 Oct 2019 12:31:43 +0000 (12:31 +0000)
     The module offers an implementation of OCSP Stapling that can replace fully or
     for a limited set of domains the existing one from mod_ssl. OCSP handling
     is part of mod_md's monitoring and message notifications. If can be used
     for sites that do not have ACME certificates.
     The url for a CTLog Monitor can be configured. It is used in the server-status
     to link to the external status page of a certicate.
     The MDMessageCmd is called with argument "installed" when a new certificate
     has been activated on server restart/reload. This allows for processing of
     the new certificate, for example to applications that require it in different
     locations or formats.

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

51 files changed:
CHANGES
CMakeLists.txt
build/apr_common.m4
docs/manual/mod/mod_md.xml
modules/md/config2.m4
modules/md/md.h
modules/md/md_acme.c
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_order.c
modules/md/md_acmev1_drive.c
modules/md/md_acmev2_drive.c
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_jws.h
modules/md/md_ocsp.c [new file with mode: 0644]
modules/md/md_ocsp.h [new file with mode: 0644]
modules/md/md_reg.c
modules/md/md_reg.h
modules/md/md_result.c
modules/md/md_result.h
modules/md/md_status.c
modules/md/md_status.h
modules/md/md_store.c
modules/md/md_store.h
modules/md/md_store_fs.c
modules/md/md_time.c
modules/md/md_time.h
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_config.c
modules/md/mod_md_config.h
modules/md/mod_md_drive.c
modules/md/mod_md_drive.h
modules/md/mod_md_ocsp.c [new file with mode: 0644]
modules/md/mod_md_ocsp.h [new file with mode: 0644]
modules/md/mod_md_status.c
modules/md/mod_md_status.h

diff --git a/CHANGES b/CHANGES
index 793d6388dbf1375203ba3683c336d1cb4acf02f3..4e5de8bfcbb699eaf2eb6d7be35a9f948fe69c21 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,20 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.5.1
 
+  *) mod_md: Adding the several new features.
+     The module offers an implementation of OCSP Stapling that can replace fully or
+     for a limited set of domains the existing one from mod_ssl. OCSP handling
+     is part of mod_md's monitoring and message notifications. If can be used
+     for sites that do not have ACME certificates.
+     The url for a CTLog Monitor can be configured. It is used in the server-status
+     to link to the external status page of a certicate.
+     The MDMessageCmd is called with argument "installed" when a new certificate
+     has been activated on server restart/reload. This allows for processing of
+     the new certificate, for example to applications that require it in different
+     locations or formats.
+     [Stefan Eissing] 
+     
+
   *) mod_deflate, mod_brotli: honor "Accept-Encoding: foo;q=0" as per RFC 7231; which
      means 'foo' is "not acceptable".  PR 58158 [Chistophe Jaillet]
 
index 546afe1a672c397e449347c658fbc1ffd0c34cd9..1c5621fb4fd24a762d5de33253a20ed9da33b780 100644 (file)
@@ -517,9 +517,10 @@ SET(mod_md_extra_sources
   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/md_ocsp.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
+  modules/md/mod_md_ocsp.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)
index f4e2dfd0a7cbc406fe327585d1461d6bd79e72b6..6b5c0f033b9e2aca5343a44ef26086f4e4cf1a66 100644 (file)
@@ -511,9 +511,9 @@ AC_DEFUN([APR_TRY_COMPILE_NO_WARNING],
    [int main(int argc, const char *const *argv) {]
    [[$2]]
    [   return 0; }]
-  )], [CFLAGS=$apr_save_CFLAGS
-$3],  [CFLAGS=$apr_save_CFLAGS
-$4])
+  )],
+  [$3], [$4])
+ CFLAGS=$apr_save_CFLAGS
 ])
 
 dnl
@@ -974,45 +974,12 @@ fi
 AC_SUBST(MKDEP)
 ])
 
-dnl
-dnl APR_CHECK_TYPES_FMT_COMPATIBLE(TYPE-1, TYPE-2, FMT-TAG, 
-dnl                                [ACTION-IF-TRUE], [ACTION-IF-FALSE])
-dnl
-dnl Try to determine whether two types are the same and accept the given
-dnl printf formatter (bare token, e.g. literal d, ld, etc).
-dnl
-AC_DEFUN([APR_CHECK_TYPES_FMT_COMPATIBLE], [
-define([apr_cvname], apr_cv_typematch_[]translit([$1], [ ], [_])_[]translit([$2], [ ], [_])_[][$3])
-AC_CACHE_CHECK([whether $1 and $2 use fmt %$3], apr_cvname, [
-APR_TRY_COMPILE_NO_WARNING([#include <sys/types.h>
-#include <stdio.h>
-#ifdef HAVE_STDINT_H
-#include <stdint.h>
-#endif
-], [
-    $1 chk1, *ptr1;
-    $2 chk2, *ptr2 = &chk1;
-    ptr1 = &chk2;
-    *ptr1 = *ptr2 = 0;
-    printf("%$3 %$3", chk1, chk2);
-], [apr_cvname=yes], [apr_cvname=no])])
-if test "$apr_cvname" = "yes"; then
-    :
-    $4
-else
-    :
-    $5
-fi
-])
-
 dnl
 dnl APR_CHECK_TYPES_COMPATIBLE(TYPE-1, TYPE-2, [ACTION-IF-TRUE])
 dnl
 dnl Try to determine whether two types are the same. Only works
 dnl for gcc and icc.
 dnl
-dnl @deprecated @see APR_CHECK_TYPES_FMT_COMPATIBLE
-dnl
 AC_DEFUN([APR_CHECK_TYPES_COMPATIBLE], [
 define([apr_cvname], apr_cv_typematch_[]translit([$1], [ ], [_])_[]translit([$2], [ ], [_]))
 AC_CACHE_CHECK([whether $1 and $2 are the same], apr_cvname, [
index 972e371d445ac125a9b175ec2c83cb43aef10bfd..4f2fd499e24764d1e267773a492947b2f0045057 100644 (file)
     <summary>
         <p>
         This module manages common properties of domains for one or more virtual hosts. 
-        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 
+        Its serves two main purposes: for one, supervise/renew https: certificates via the 
+        ACME protocol (<a href="https://tools.ietf.org/html/rfc8555">RFC 8555</a>). 
+        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 
+        certififcates managed this way and configurations that will run your own 
+        notification commands on renewal, expiration and errors.
+        </p><p>
+        Second, mod_md offers an alternate OCSP Stapling implementation. This works with
+        managed certificates as well as with certificates you configure yourself. OCSP
+        Stapling is a necessary component for any https: site, influencing page load
+        times and, depending on other setups, page availability. More in the
+        stapling section below.
+        </p><p>
+        The default ACME Authority for managing certificates is 
         <a href="https://letsencrypt.org/">Let's Encrypt</a>, but it is possible 
         to configure another CA that supports the protocol.
         </p>
@@ -234,6 +238,53 @@ MDChallengeDns01 /usr/bin/acme-setup-dns
             </p>
         </note>
 
+        <note><title>Stapling</title>
+            <p>
+                If you want to try the stapling in one Managed Domain alone at first,
+                configure:
+            </p>
+            <highlight language="config">
+&lt;MDomain mydomain.net>
+  MDStapling on
+&lt;/MDomain>            
+            </highlight>
+            <p>
+                and use the 'server-status' and/or MDMessageCmd to see how it operates. You will
+                see if Stapling information is there, how long it is valid, from where it came and
+                when it will be refreshed.
+            </p><p>
+                If this all works to your satisfaction, you can switch it on for all your
+                certificates or just your managed ones.
+            </p><p>
+                The existing stapling implementation by mod_ssl is used by many sites
+                for years. There are two main differences between the mod_ssl and mod_md
+                one:
+            </p>
+            <ol>
+                <li>On demand vs. scheduled: mod_ssl retrieves the stapling information
+                when it is requested, e.g. on a new connection. mod_md retrieves it
+                right at server start and after 2/3rds of its lifetime.</li>
+                <li>In memory vs. persisted: mod_ssl <em>can</em> persist this
+                information, but most example configurations use a memory cache. mod_md
+                always stores in the file system.</li>
+            </ol>
+            <p>
+                If you are unlucky and restart your server during an outage of your CA's
+                OCSP service, your users may no longer reach your sites. Without persistence
+                your server cannot provide the client with the data and the client browser
+                cannot get it as well, since the OCSP service is not responding. 
+            </p><p>
+                The implementation in mod_md will have peristed it, load it again after
+                restart and have it available for incoming connections. A day or two before
+                this information expires, it will renew it, making it able to copy with
+                a long OCSP service downtime.
+            </p><p>
+                Due to backward compatibility, the existing implementation in mod_ssl could
+                not be changed drastically. For example, mod_ssl is unable to add a dependency 
+                to mod_watchdog without braking many existing installations (that do not load it).
+            </p>
+        </note>
+
     </summary>
     
     <directivesynopsis>
@@ -781,22 +832,24 @@ MDRequireHttps permanent
         </contextlist>
         <usage>
             <p>
-                Sets challenge types and their execution order when proving domain ownership
-                and overrides any guesswork and sanity checks by the module.
-                The names are protocol specific.
-                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 https: based one on port 443 when available.
+                Sets challenge types (in order of preference) when proving domain ownership.
+                Supported by the module are the challenge methods 'tls-alpn-01', 'dns-01'  
+                and 'http-01'. The module will look at the overall configuation of the server 
+                to find out which methods can be used. 
+            </p><p>
+                If the server listens on port 80, for example, the 'http-01' method is available. 
+                The prerequisite for 'dns-01' is a configured  'MDChallengeDns01' command. 
+                'tls-alpn-01' is described above in 'https: Challenges'.
             </p><p>
-                To repeat: using this directive overrides the module selection. If you specify
-                the 'http-01' challenge, the module will no longer check if the server listens
-                on port 80. It will just use the challenge with Let's Encrypt (if LE offers
-                it).
+                This auto selection works for most setups. But since Apache is a very powerful 
+                server with many configuration options, the situation is not clear for all 
+                possible cases. For example: it may listen on multiple IP addresses where some 
+                are reachable on `https:` and some not.
             </p><p>
-                If your configuration choices here are unworkable, LE will fail
-                your domain verification  after a while and give up. This error will
-                be reported on your server-status and md-status. You will then have to figure 
-                out why it did not work.
+                If you configure 'MDCAChallenges' directly, this auto selection is disabled. 
+                Instead, the module will use the configured challenge list when talking to 
+                the ACME server (a challenge type must be offered by the server as well). 
+                This challenges are examined in the order specified.
             </p>
         </usage>
     </directivesynopsis>
@@ -948,7 +1001,7 @@ MDRequireHttps permanent
         <usage>
             <p>
                 This command gets called when one of the following events happen for
-                a Managed Domain: "renewed", "expiring", "errored". The command may
+                a Managed Domain: "renewed", "installed", "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>
@@ -967,13 +1020,25 @@ MDMessageCmd /etc/apache/md-message
                 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. 
+                early enough to allow the internet to come back. This is reported at most
+                once per hour. 
             </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><p>
+                'renewed' means that a new certificate has been obtained and is stored
+                in the 'staging' area in the MD store. It will be activated on the next
+                server restart/reload.
+            </p><p>
+                'installed' is triggered when a new certificate has been transferred from
+                staging into the domains location in MD store. This happens at server
+                startup/reload. Different to all other invocations, MDMessageCmd is run
+                with root permissions (on *nix systems) and has access to the certificate
+                files (and keys). Certificates needed for other applications or
+                in different formats can be processed on this event.
             </p>
         </usage>
     </directivesynopsis>
@@ -1021,5 +1086,134 @@ MDMessageCmd /etc/apache/md-message
         </usage>
     </directivesynopsis>
 
+    <directivesynopsis>
+        <name>MDCertificateMonitor</name>
+        <description>The URL of a certificate log monitor.</description>
+        <syntax>MDCertificateMonitor name url</syntax>
+        <default>crt.sh https://crt.sh?q=</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                This is part of the 'server-status' HTML user interface and has nothing to 
+                do with the core functioning itself. It defines the link offered on that 
+                page for easy checking of a certificate monitor. The SHA256 fingerprint 
+                of the certificate is appended to the configured url.
+            </p><p>
+                Certificate Monitors offer supervision of Certificate Transparency (CT) 
+                Logs to track the use of certificates for domains. The least you may see 
+                is that Let's Encrypt (or whichever CA you have configured) has entered 
+                your certificates into the CTLogs.
+            </p><p>
+                Caveat: certificate logs update and monitor's intakes of those
+                updates suffer some delay. This varies between logs and monitors. A
+                brand new certificate will not be known immediately.
+            </p>
+        </usage>
+    </directivesynopsis>
+
+    <directivesynopsis>
+        <name>MDStapling</name>
+        <description>Enable stapling for all or a particular MDomain.</description>
+        <syntax>MDStapling on|off</syntax>
+        <default>off</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                mod_md offers an implementation for providing OCSP stapling information. 
+                This is an alternative to the one provided by 'mod_ssl'. For backward 
+                compatiblity, this is disabled by default.
+            </p><p>
+                The stapling can be switched on for all certificates on the server or 
+                for an individual MDomain. This will replace any stapling configurtion 
+                in `mod_ssl` for these hosts. When disabled, the 'mod_ssl' stapling 
+                will do the work (if it is itself enabled, of course). This allows for
+                a gradual shift over from one implementation to the other.
+            </p><p>
+                The stapling of `mod_md` will also work for domains where the certificates 
+                are not managed by this module (see MDStapleOthers for how to control this). 
+                This allows use of the new stapling without using any ACME certificate 
+                management.
+            </p>
+        </usage>
+    </directivesynopsis>
+
+    <directivesynopsis>
+        <name>MDStapleOthers</name>
+        <description>Enable stapling for certificates not managed by mod_md.</description>
+        <syntax>MDStaplingOthers on|off</syntax>
+        <default>on</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                This setting only takes effect when `MDStapling` is enabled. It controls 
+                if `mod_md` should also provide stapling information for certificates 
+                that are not directly controlled by it, e.g. renewed via an ACME CA.
+            </p>
+        </usage>
+    </directivesynopsis>
+
+    <directivesynopsis>
+        <name>MDStaplingKeepResponse</name>
+        <description>Controls when old responses should be removed.</description>
+        <syntax>MDStaplingKeepResponse duration</syntax>
+        <default>7d</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                This time window specifies when OCSP response data used in stapling 
+                shall be removed from the store again. Response information older than 
+                7 days (default) is deleted on server restart/reload. This keeps the store 
+                from growing when certificates are renewed/reconfigured frequently.
+            </p><p>
+            </p>
+        </usage>
+    </directivesynopsis>
+
+    <directivesynopsis>
+        <name>MDStaplingRenewWindow</name>
+        <description>Control when the stapling responses will be renewed.</description>
+        <syntax>MDStaplingRenewWindow duration</syntax>
+        <default>33%</default>
+        <contextlist>
+            <context>server config</context>
+        </contextlist>
+        <usage>
+            <p>
+                If the validity of the OCSP response used in stapling falls below 'duration', 
+                mod_md will obtain a new OCSP response.
+            </p><p>
+                The CA issueing a certificate commonly also operates the OCSP responder 
+                service and determines how long its signed response about the validity 
+                of a certificate are itself valid. The longer a response is valid, the longer 
+                it can be cached which mean better overall performance for everyone. 
+                The shorter the life time, the more rapidly certificate revocations
+                spread to clients. Also, service reliability is a consideration.
+            </p><p>
+                By adjusting the stapling renew window you can control parts of this yourself. 
+                If you make the renew time short (e.g. a short time before the current
+                information expires), you gain maximum cache time. But a service outage
+                (down for maintenance, for example) will affect you. If you renew a long
+                time before expiry, updates will be made more frequent, cause more load
+                on the CA server infrastructure and also more coordination between
+                the child processes of your server.
+            </p><p>
+                The default is chosen as 33%, which means renewal is started when only 
+                a third of the response lifetime is left. For a CA that issues OCSP 
+                responses with lifetime of 3 days, this means 2 days of caching and 1 day 
+                for renewal attempts. A service outage would have to last full 24 hours 
+                to affect your domains.
+            </p><p>
+                Setting an absolute renew window, like `2d` (2 days), is also possible. 
+            </p>
+        </usage>
+    </directivesynopsis>
 
 </modulesynopsis>
index 0d8d678780113d53211fcd178135c2137eb785f2..14898c51df54ced570bfd9774f8a03b563444973 100644 (file)
@@ -150,6 +150,7 @@ md_http.lo dnl
 md_json.lo dnl
 md_jws.lo dnl
 md_log.lo dnl
+md_ocsp.lo dnl
 md_result.lo dnl
 md_reg.lo dnl
 md_status.lo dnl
@@ -160,6 +161,7 @@ md_util.lo dnl
 mod_md.lo dnl
 mod_md_config.lo dnl
 mod_md_drive.lo dnl
+mod_md_ocsp.lo dnl
 mod_md_os.lo dnl
 mod_md_status.lo dnl
 "
index f617dd31dbbe12cc42a73e38ce137277d29053fd..182d00b484d17e351e0e9b0588da0b2260db1103 100644 (file)
@@ -24,7 +24,9 @@ struct apr_array_header_t;
 struct apr_hash_t;
 struct md_json_t;
 struct md_cert_t;
+struct md_job_t;
 struct md_pkey_t;
+struct md_result_t;
 struct md_store_t;
 struct md_srv_conf_t;
 struct md_pkey_spec_t;
@@ -41,6 +43,9 @@ struct md_pkey_spec_t;
 #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))
+#define MD_TIME_OCSP_KEEP_NORM      (apr_time_from_sec(7 * MD_SECS_PER_DAY))
+
+#define MD_OTHER                "other"
 
 typedef enum {
     MD_S_UNKNOWN = 0,               /* MD has not been analysed yet */
@@ -58,25 +63,6 @@ typedef enum {
     MD_REQUIRE_PERMANENT,
 } md_require_t;
 
-typedef enum {
-    MD_SV_TEXT,
-    MD_SV_JSON,
-    MD_SV_CERT,
-    MD_SV_PKEY,
-    MD_SV_CHAIN,
-} md_store_vtype_t;
-
-typedef enum {
-    MD_SG_NONE,
-    MD_SG_ACCOUNTS,
-    MD_SG_CHALLENGES,
-    MD_SG_DOMAINS,
-    MD_SG_STAGING,
-    MD_SG_ARCHIVE,
-    MD_SG_TMP,
-    MD_SG_COUNT,
-} md_store_group_t;
-
 typedef enum {
     MD_RENEW_DEFAULT = -1,          /* default value */
     MD_RENEW_MANUAL,                /* manually triggered renewal of certificate */
@@ -96,8 +82,8 @@ struct md_t {
     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 */
-    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 */
+    md_timeslice_t *renew_window;  /* time before expiration that starts renewal */
+    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) */
@@ -110,7 +96,9 @@ struct md_t {
     md_state_t state;               /* state of this MD */
     
     struct apr_array_header_t *acme_tls_1_domains; /* domains supporting "acme-tls/1" protocol */
+    int stapling;                   /* if OCSP stapling is enabled */
     
+    int watched;               /* if certificate is supervised (renew or expiration warning) */
     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 */
@@ -120,6 +108,7 @@ struct md_t {
 
 #define MD_KEY_ACCOUNT          "account"
 #define MD_KEY_ACME_TLS_1       "acme-tls/1"
+#define MD_KEY_ACTIVATION_DELAY "activation-delay"
 #define MD_KEY_ACTIVITY         "activity"
 #define MD_KEY_AGREEMENT        "agreement"
 #define MD_KEY_AUTHORIZATIONS   "authorizations"
@@ -143,10 +132,13 @@ struct md_t {
 #define MD_KEY_DOMAINS          "domains"
 #define MD_KEY_ENTRIES          "entries"
 #define MD_KEY_ERRORED          "errored"
+#define MD_KEY_ERROR            "error"
 #define MD_KEY_ERRORS           "errors"
 #define MD_KEY_EXPIRES          "expires"
 #define MD_KEY_FINALIZE         "finalize"
 #define MD_KEY_FINISHED         "finished"
+#define MD_KEY_FROM             "from"
+#define MD_KEY_GOOD             "good"
 #define MD_KEY_HTTP             "http"
 #define MD_KEY_HTTPS            "https"
 #define MD_KEY_ID               "id"
@@ -163,6 +155,8 @@ struct md_t {
 #define MD_KEY_NAME             "name"
 #define MD_KEY_NEXT_RUN         "next-run"
 #define MD_KEY_NOTIFIED         "notified"
+#define MD_KEY_OCSP             "ocsp"
+#define MD_KEY_OCSPS            "ocsps"
 #define MD_KEY_ORDERS           "orders"
 #define MD_KEY_PERMANENT        "permanent"
 #define MD_KEY_PKEY             "privkey"
@@ -172,41 +166,39 @@ struct md_t {
 #define MD_KEY_READY            "ready"
 #define MD_KEY_REGISTRATION     "registration"
 #define MD_KEY_RENEW            "renew"
+#define MD_KEY_RENEW_AT         "renew-at"
 #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_RESPONSE         "response"
+#define MD_KEY_REVOKED          "revoked"
 #define MD_KEY_SERIAL           "serial"
 #define MD_KEY_SHA256_FINGERPRINT  "sha256-fingerprint"
+#define MD_KEY_STAPLING         "stapling"
 #define MD_KEY_STATE            "state"
 #define MD_KEY_STATUS           "status"
 #define MD_KEY_STORE            "store"
+#define MD_KEY_SUBPROBLEMS      "subproblems"
 #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_UNKNOWN          "unknown"
+#define MD_KEY_UNTIL            "until"
 #define MD_KEY_URL              "url"
 #define MD_KEY_URI              "uri"
+#define MD_KEY_VALID            "valid"
 #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_WATCHED          "watched"
 #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_HTTPD_JSON        "httpd.json"
-
-#define MD_FN_FALLBACK_PKEY     "fallback-privkey.pem"
-#define MD_FN_FALLBACK_CERT     "fallback-cert.pem"
-
 /* Check if a string member of a new MD (n) has 
  * a value and if it differs from the old MD o
  */
@@ -259,12 +251,6 @@ md_t *md_get_by_domain(struct apr_array_header_t *mds, const char *domain);
  */
 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(struct apr_array_header_t *mds, const md_t *md);
-
 /**
  * Create and empty md record, structures initialized.
  */
@@ -302,6 +288,12 @@ int md_is_covered_by_alt_names(const md_t *md, const struct apr_array_header_t*
 #define LE_ACMEv2_STAGING   "https://acme-staging-v02.api.letsencrypt.org/directory"
 
 
+/**************************************************************************************************/
+/* notifications */
+
+typedef apr_status_t md_job_notify_cb(struct md_job_t *job, const char *reason, 
+                                      struct md_result_t *result, apr_pool_t *p, void *baton);
+
 /**************************************************************************************************/
 /* domain credentials */
 
index d2cc00a10ef4ac4cb648156ab595d24d9c7249e7..d42ea72230c6c9f28a92712d04d9508cbeaf1a8f 100644 (file)
@@ -99,16 +99,16 @@ static void req_update_nonce(md_acme_t *acme, apr_table_t *hdrs)
     }
 }
 
-static apr_status_t http_update_nonce(const md_http_response_t *res)
+static apr_status_t http_update_nonce(const md_http_response_t *res, void *data)
 {
+    md_acme_t *acme = data;
     if (res->headers) {
         const char *nonce = apr_table_get(res->headers, "Replay-Nonce");
         if (nonce) {
-            md_acme_t *acme = res->req->baton;
             acme->nonce = apr_pstrdup(acme->p, nonce);
         }
     }
-    return res->rv;
+    return APR_SUCCESS;
 }
 
 static md_acme_req_t *md_acme_req_create(md_acme_t *acme, const char *method, const char *url)
@@ -144,12 +144,12 @@ static md_acme_req_t *md_acme_req_create(md_acme_t *acme, const char *method, co
  
 static apr_status_t acmev1_new_nonce(md_acme_t *acme)
 {
-    return md_http_HEAD(acme->http, acme->api.v1.new_reg, NULL, http_update_nonce, acme);
+    return md_http_HEAD_perform(acme->http, acme->api.v1.new_reg, NULL, http_update_nonce, acme);
 }
 
 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);
+    return md_http_HEAD_perform(acme->http, acme->api.v2.new_nonce, NULL, http_update_nonce, acme);
 }
 
 
@@ -175,7 +175,10 @@ 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);
+            md_result_problem_set(req->result, req->rv, ptype, pdetail,
+                                  md_json_getj(problem, MD_KEY_SUBPROBLEMS, NULL));
+            
+            
             
             if (APR_STATUS_IS_EAGAIN(req->rv)) {
                 md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, req->rv, req->p,
@@ -189,23 +192,21 @@ static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t
         }
     }
     
-    if (APR_SUCCESS == res->rv) {
-        switch (res->status) {
-            case 400:
-                return APR_EINVAL;
-            case 403:
-                return APR_EACCES;
-            case 404:
-                return APR_ENOENT;
-            default:
-                md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, req->p,
-                              "acme problem unknown: http status %d", res->status);
-                md_result_printf(req->result, APR_EGENERAL, "unexpected http status: %d",
-                                 res->status);
-                return req->result->status;
-        }
+    switch (res->status) {
+        case 400:
+            return APR_EINVAL;
+        case 403:
+            return APR_EACCES;
+        case 404:
+            return APR_ENOENT;
+        default:
+            md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, req->p,
+                          "acme problem unknown: http status %d", res->status);
+            md_result_printf(req->result, APR_EGENERAL, "unexpected http status: %d",
+                             res->status);
+            return req->result->status;
     }
-    return res->rv;
+    return APR_SUCCESS;
 }
 
 /**************************************************************************************************/
@@ -213,51 +214,49 @@ static apr_status_t inspect_problem(md_acme_req_t *req, const md_http_response_t
 
 static apr_status_t acmev1_req_init(md_acme_req_t *req, md_json_t *jpayload)
 {
-    const char *payload;
-    size_t payload_len;
+    md_data_t payload;
     
     if (!req->acme->acct) {
         return APR_EINVAL;
     }
     if (jpayload) {
-        payload = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
-        if (!payload) {
+        payload.data = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
+        if (!payload.data) {
             return APR_EINVAL;
         }
     }
     else {
-        payload = "";
+        payload.data = "";
     }
 
-    payload_len = strlen(payload);
+    payload.len = strlen(payload.data);
     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,
+                  "acme payload(len=%" APR_SIZE_T_FMT "): %s", payload.len, payload.data);
+    return md_jws_sign(&req->req_json, req->p, &payload,
                        req->prot_hdrs, req->acme->acct_key, NULL);
 }
 
 static apr_status_t acmev2_req_init(md_acme_req_t *req, md_json_t *jpayload)
 {
-    const char *payload;
-    size_t payload_len;
+    md_data_t payload;
     
     if (!req->acme->acct) {
         return APR_EINVAL;
     }
     if (jpayload) {
-        payload = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
-        if (!payload) {
+        payload.data = md_json_writep(jpayload, req->p, MD_JSON_FMT_COMPACT);
+        if (!payload.data) {
             return APR_EINVAL;
         }
     }
     else {
-        payload = "";
+        payload.data = "";
     }
 
-    payload_len = strlen(payload);
+    payload.len = strlen(payload.data);
     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,
+                  "acme payload(len=%" APR_SIZE_T_FMT "): %s", payload.len, payload.data);
+    return md_jws_sign(&req->req_json, req->p, &payload,
                        req->prot_hdrs, req->acme->acct_key, req->acme->acct->url);
 }
 
@@ -284,14 +283,10 @@ static apr_status_t md_acme_req_done(md_acme_req_t *req, apr_status_t rv)
     return rv;
 }
 
-static apr_status_t on_response(const md_http_response_t *res)
+static apr_status_t on_response(const md_http_response_t *res, void *data)
 {
-    md_acme_req_t *req = res->req->baton;
-    apr_status_t rv = res->rv;
-    
-    if (APR_SUCCESS != rv) {
-        goto out;
-    }
+    md_acme_req_t *req = data;
+    apr_status_t rv = APR_SUCCESS;
     
     req->resp_hdrs = apr_table_clone(req->p, res->headers);
     req_update_nonce(req->acme, res->headers);
@@ -340,7 +335,6 @@ static apr_status_t on_response(const md_http_response_t *res)
         return rv;
     }
 
-out:
     md_acme_req_done(req, rv);
     return rv;
 }
@@ -355,7 +349,7 @@ 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_data_t *body = NULL;
     md_result_t *result;
 
     assert(acme->url);
@@ -406,15 +400,17 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req)
     if (APR_SUCCESS != rv) goto leave;
     
     if (req->req_json) {
-        body = md_json_writep(req->req_json, req->p, MD_JSON_FMT_INDENT);
+        body = apr_pcalloc(req->p, sizeof(*body));
+        body->data = md_json_writep(req->req_json, req->p, MD_JSON_FMT_INDENT);
         if (!body) {
             rv = APR_EINVAL; goto leave;
         }
+        body->len = strlen(body->data);
     }
 
     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);
+                      "req: %s %s, body:\n%s", req->method, req->url, body->data);
     }
     else {
         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->p, 
@@ -422,14 +418,14 @@ static apr_status_t md_acme_req_send(md_acme_req_t *req)
     }
     
     if (!strcmp("GET", req->method)) {
-        rv = md_http_GET(req->acme->http, req->url, NULL, on_response, req);
+        rv = md_http_GET_perform(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);
+        rv = md_http_POSTd_perform(req->acme->http, req->url, NULL, "application/jose+json",  
+                                   body, on_response, req);
     }
     else if (!strcmp("HEAD", req->method)) {
-        rv = md_http_HEAD(req->acme->http, req->url, NULL, on_response, req);
+        rv = md_http_HEAD_perform(req->acme->http, req->url, NULL, on_response, req);
     }
     else {
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, req->p, 
@@ -501,7 +497,8 @@ void md_acme_report_result(md_acme_t *acme, apr_status_t rv, struct md_result_t
         md_result_set(result, rv, NULL);
     }
     else {
-        md_result_problem_set(result, acme->last->status, acme->last->problem, acme->last->detail);
+        md_result_problem_set(result, acme->last->status, acme->last->problem, 
+                              acme->last->detail, acme->last->subproblems);
     }
 }
 
@@ -658,7 +655,7 @@ apr_status_t md_acme_create(md_acme_t **pacme, apr_pool_t *p, const char *url,
     acme->version = MD_ACME_VERSION_UNKNOWN;
     acme->last = md_result_make(acme->p, APR_SUCCESS);
     
-    *pacme = (APR_SUCCESS == rv)? acme : NULL;
+    *pacme = acme;
     return rv;
 }
 
@@ -667,17 +664,16 @@ typedef struct {
     md_result_t *result;
 } update_dir_ctx;
 
-static apr_status_t update_directory(const md_http_response_t *res)
+static apr_status_t update_directory(const md_http_response_t *res, void *data)
 {
     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_acme_t *acme = ((update_dir_ctx *)data)->acme;
+    md_result_t *result = ((update_dir_ctx *)data)->result;
+    apr_status_t 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);
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, 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 "
@@ -691,7 +687,8 @@ static apr_status_t update_directory(const md_http_response_t *res)
             "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); 
+            acme->url, res->status);
+        rv = result->status;
         goto leave;
     }
     
@@ -762,13 +759,18 @@ apr_status_t md_acme_setup(md_acme_t *acme, md_result_t *result)
                                                            acme->user_agent, acme->proxy_url))) {
         return rv;
     }
+    /* TODO: maybe this should be configurable. Let's take some reasonable 
+     * defaults for now that protect our client */
     md_http_set_response_limit(acme->http, 1024*1024);
+    md_http_set_timeout_default(acme->http, apr_time_from_sec(10 * 60));
+    md_http_set_connect_timeout_default(acme->http, apr_time_from_sec(30));
+    md_http_set_stalling_default(acme->http, 10, apr_time_from_sec(30));
     
     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);
+    rv = md_http_GET_perform(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 */
index e1e36dfff8e7679bd1fb99341b1c38782b1abe48..98443d2fd8d3fe088b61426994407ede877bc404 100644 (file)
@@ -107,19 +107,13 @@ md_json_t *md_acme_acct_to_json(md_acme_acct_t *acct, apr_pool_t *p)
             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);
-    }
+    if (s) md_json_sets(s, jacct, MD_KEY_STATUS, NULL);
+    if (acct->url) md_json_sets(acct->url, jacct, MD_KEY_URL, NULL);
+    if (acct->ca_url) md_json_sets(acct->ca_url, jacct, MD_KEY_CA_URL, NULL);
+    if (acct->contacts) md_json_setsa(acct->contacts, jacct, MD_KEY_CONTACT, NULL);
+    if (acct->registration) 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;
 }
index 2b552add758708421aa0d6b10e358d2d652a3d25..c2cf64c7e48980e8b818b9f257acc9b6220e78fe 100644 (file)
@@ -21,6 +21,7 @@ struct md_acme_req;
 struct md_json_t;
 struct md_pkey_t;
 
+#include "md_store.h"
 
 /** 
  * An ACME account at an ACME server.
@@ -71,7 +72,7 @@ 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);
+apr_status_t md_acme_acct_validate(md_acme_t *acme, md_store_t *store, apr_pool_t *p);
 
 /**
  * Agree to the given Terms-of-Service url for the current account.
@@ -103,23 +104,23 @@ 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_status_t md_acme_find_acct(md_acme_t *acme, 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, 
+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);
 
 /**
  * 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_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);
 
-apr_status_t md_acme_acct_save(struct md_store_t *store, apr_pool_t *p, md_acme_t *acme,  
+apr_status_t md_acme_acct_save(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);
                                
@@ -129,7 +130,7 @@ apr_status_t md_acme_acct_save(struct md_store_t *store, apr_pool_t *p, md_acme_
 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, 
+                               md_store_t *store, md_store_group_t group, 
                                const char *name, apr_pool_t *p);
 
 #endif /* md_acme_acct_h */
index ddb4e91973d41787844a5b5ae5daa3fb53c0447a..66aa865b5b2f8fc73d4b76bdba4a1ba2ed1ef31c 100644 (file)
@@ -144,12 +144,31 @@ apr_status_t md_acme_authz_retrieve(md_acme_t *acme, apr_pool_t *p, const char *
     return rv;
 }
 
+typedef struct {
+    apr_pool_t *p;
+    md_acme_authz_t *authz;
+} error_ctx_t;
+
+static int copy_challenge_error(void *baton, size_t index, md_json_t *json)
+{
+    error_ctx_t *ctx = baton;
+    
+    (void)index;
+    if (md_json_has_key(json, MD_KEY_ERROR, NULL)) {
+        ctx->authz->error_type = md_json_dups(ctx->p, json, MD_KEY_ERROR, MD_KEY_TYPE, NULL);
+        ctx->authz->error_detail = md_json_dups(ctx->p, json, MD_KEY_ERROR, MD_KEY_DETAIL, NULL);
+        ctx->authz->error_subproblems = md_json_dupj(ctx->p, json, MD_KEY_ERROR, MD_KEY_SUBPROBLEMS, NULL);
+    }
+    return 1;
+}
+
 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;
+    error_ctx_t ctx;
     
     assert(acme);
     assert(acme->http);
@@ -158,6 +177,8 @@ apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, apr_p
 
     authz->state = MD_ACME_AUTHZ_S_UNKNOWN;
     json = NULL;
+    authz->error_type = authz->error_detail = NULL;
+    authz->error_subproblems = NULL;
     err = "unable to parse response";
     log_level = MD_LOG_ERR;
     
@@ -177,7 +198,10 @@ apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, apr_p
             log_level = MD_LOG_DEBUG;
         }
         else if (!strcmp(s, "invalid")) {
+            ctx.p = p;
+            ctx.authz = authz;
             authz->state = MD_ACME_AUTHZ_S_INVALID;
+            md_json_itera(copy_challenge_error, &ctx, json, MD_KEY_CHALLENGES, NULL);
             err = "challenge 'invalid'";
         }
     }
@@ -189,7 +213,7 @@ apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme, apr_p
     
     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->url,
+                      "Exact response was: %s", err, authz->domain, authz->url,
                       json? md_json_writep(json, p, MD_JSON_FMT_COMPACT) : "not available");
     }
     
@@ -243,7 +267,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->url);
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ctx->p, "updated authz %s", ctx->authz->url);
     return APR_SUCCESS;
 }
 
@@ -323,7 +347,7 @@ static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_auth
     const char *acme_id, *token;
     apr_status_t rv;
     int notify_server;
-    md_data data;
+    md_data_t data;
     
     (void)env;
     if (md_array_str_index(acme_tls_1_domains, authz->domain, 0, 0) < 0) {
@@ -400,7 +424,7 @@ static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *
     apr_status_t rv;
     int exit_code, notify_server;
     authz_req_ctx ctx;
-    md_data data;
+    md_data_t data;
     
     (void)store;
     (void)key_spec;
@@ -582,7 +606,7 @@ apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_s
                     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, 
+                        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
                                       "%s: set up challenge '%s'", 
                                       authz->domain, fctx.accepted->type);
                         challenge_setup = CHA_TYPES[i].name; 
index 4a3b453b29160fa68ac0a62da416a16bc26857b8..fe1abe552839d04ea528171d2ac93ace472a638b 100644 (file)
@@ -49,6 +49,9 @@ struct md_acme_authz_t {
     const char *url;
     md_acme_authz_state_t state;
     apr_time_t expires;
+    const char *error_type;
+    const char *error_detail;
+    const struct md_json_t *error_subproblems;
     struct md_json_t *resource;
 };
 
index 4b29e4b044bfe7fe2c0bf1ec21bfeeb08631e5cb..b9c0c6d18562331839a029e0e3ffbd5cd398fac2 100644 (file)
@@ -94,7 +94,7 @@ apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result)
         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, "re-using staged account");
     }
     else if (!APR_STATUS_IS_ENOENT(rv)) {
-        goto out;
+        goto leave;
     }
     
     /* Get an account for the ACME server for this MD */
@@ -107,7 +107,7 @@ apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result)
             update_md = 1;
         }
         else if (APR_SUCCESS != rv) {
-            goto out;
+            goto leave;
         }
     }
 
@@ -130,10 +130,11 @@ apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result)
                       d->proto->protocol);
         
         if (!ad->md->contacts || apr_is_empty_array(md->contacts)) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, APR_EINVAL, d->p, 
-                          "no contact information for md %s", md->name);            
             rv = APR_EINVAL;
-            goto out;
+            md_result_printf(result, rv, "No contact information is available for MD %s. "
+                             "Configure one using the ServerAdmin directive.", md->name);            
+            md_result_log(result, MD_LOG_ERR);
+            goto leave;
         }
         
         /* ACMEv1 allowed registration of accounts without accepted Terms-of-Service.
@@ -150,18 +151,24 @@ apr_status_t md_acme_drive_set_acct(md_proto_driver_t *d, md_result_t *result)
                   ad->acme->ca_agreement);
             md_result_log(result, MD_LOG_ERR);
             rv = result->status;
-            goto out;
+            goto leave;
         }
     
         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) {
+            if (APR_SUCCESS != ad->acme->last->status) {
+                md_result_dup(result, ad->acme->last);
+                md_result_log(result, MD_LOG_ERR);
+            }
+            goto leave;
         }
+
+        md->ca_account = NULL;
+        update_md = 1;
+        update_acct = 1;
     }
     
-out:
+leave:
     /* 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);
@@ -255,7 +262,7 @@ apr_status_t md_acme_drive_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->order->certificate);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "poll for cert at %s", ad->order->certificate);
     return rv;
 }
 
@@ -477,11 +484,9 @@ out:
 /**************************************************************************************************/
 /* ACME driver init */
 
-static apr_status_t acme_driver_init(md_proto_driver_t *d, md_result_t *result)
+static apr_status_t acme_driver_preload_init(md_proto_driver_t *d, md_result_t *result)
 {
     md_acme_driver_t *ad;
-    int dis_http, dis_https, dis_alpn_acme, dis_dns;
-    const char *challenge;
     
     md_result_set(result, APR_SUCCESS, NULL);
     
@@ -495,6 +500,23 @@ static apr_status_t acme_driver_init(md_proto_driver_t *d, md_result_t *result)
     ad->ca_challenges = apr_array_make(d->p, 3, sizeof(const char*));
     ad->certs = apr_array_make(d->p, 5, sizeof(md_cert_t*));
     
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, result->status, d->p, 
+                  "%s: init_base driver", d->md->name);
+    return result->status;
+}
+
+static apr_status_t acme_driver_init(md_proto_driver_t *d, md_result_t *result)
+{
+    md_acme_driver_t *ad;
+    int dis_http, dis_https, dis_alpn_acme, dis_dns;
+    const char *challenge;
+
+    acme_driver_preload_init(d, result);
+    md_result_set(result, APR_SUCCESS, NULL);
+    if (APR_SUCCESS != result->status) goto leave;
+    
+    ad = d->baton;
+
     /* 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. */
@@ -511,49 +533,49 @@ static apr_status_t acme_driver_init(md_proto_driver_t *d, md_result_t *result)
         APR_ARRAY_PUSH(ad->ca_challenges, const char*) = MD_AUTHZ_TYPE_HTTP01;
         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_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;
-    }
-    
-    dis_http = dis_https = dis_alpn_acme = dis_dns = 0;
-    if (!d->can_http && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_HTTP01, 0, 1) >= 0) {
-        ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_HTTP01, 0);
-        dis_http = 1;
-    }
-    if (!d->can_https && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0, 1) >= 0) {
-        ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0);
-        dis_https = 1;
-    }
-    if (apr_is_empty_array(d->md->acme_tls_1_domains)
-        && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0, 1) >= 0) {
-        ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0);
-        dis_alpn_acme = 1;
-    }
-    if (!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_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;
-    }
+        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;
+        }
+
+        dis_http = dis_https = dis_alpn_acme = dis_dns = 0;
+        if (!d->can_http && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_HTTP01, 0, 1) >= 0) {
+            ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_HTTP01, 0);
+            dis_http = 1;
+        }
+        if (!d->can_https && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0, 1) >= 0) {
+            ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0);
+            dis_https = 1;
+        }
+        if (apr_is_empty_array(d->md->acme_tls_1_domains)
+            && md_array_str_index(ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0, 1) >= 0) {
+            ad->ca_challenges = md_array_str_remove(d->p, ad->ca_challenges, MD_AUTHZ_TYPE_TLSALPN01, 0);
+            dis_alpn_acme = 1;
+        }
+        if (!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_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 public port 80." : "",
+                             dis_https? " The https: challenge 'tls-alpn-01' is disabled because the server seems not reachable on public 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;
@@ -570,11 +592,11 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
     apr_time_t now;
     apr_array_header_t *staged_certs;
     char ts[APR_RFC822_DATE_LEN];
-
+    int first = 0;
+    
     if (md_log_is_level(d->p, MD_LOG_DEBUG)) {
         md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: staging started, "
-                      "state=%d, can_http=%d, can_https=%d, challenges='%s'",
-                      d->md->name, d->md->state, d->can_http, d->can_https,
+                      "state=%d, challenges='%s'", d->md->name, d->md->state, 
                       apr_array_pstrcat(d->p, ad->ca_challenges, ' '));
     }
 
@@ -623,8 +645,15 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
 
         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;
+            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_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, d->p, "%s: all data staged", d->md->name);
+                rv = APR_SUCCESS;
+                goto ready;
+            }
         }
     }
     
@@ -644,7 +673,7 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
     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_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 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;
@@ -687,11 +716,11 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
     
     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, 
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 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.");
+            md_result_printf(result, rv, "Unable to retrieve certificate chain.");
             goto out;
         }
         
@@ -707,13 +736,19 @@ static apr_status_t acme_renew(md_proto_driver_t *d, md_result_t *result)
     /* 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);
-
+    
+    /* first time this job ran through */
+    first = 1;    
 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);
     
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, 
+                  "%s: certificate ready, activation delay set to %s", 
+                  d->md->name, md_duration_format(d->p, d->activation_delay));
+    
     /* 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*)));
 
@@ -725,10 +760,20 @@ ready:
         const md_pubcert_t *pub;
         apr_time_t valid_until, delay_activation;
         
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, d->p, 
+                      "%s: state is COMPLETE, checking existing certificate", d->md->name);
         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 (d->activation_delay < 0) {
+                /* special simulation for test case */
+                if (first) {
+                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, d->p, 
+                                  "%s: delay ready_at to now+1s", d->md->name);
+                    md_result_delay_set(result, apr_time_now() + apr_time_from_sec(1));
+                }
+            }
+            else if (valid_until > now) {            
+                delay_activation = d->activation_delay;
                 if (delay_activation > (valid_until - now)) {
                     delay_activation = (valid_until - now);
                 }
@@ -742,13 +787,12 @@ ready:
     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));
+            "be used from %s on.", ts);
     }
     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.");
+            "be used (valid since %s). A graceful server restart now is recommended.", ts);
     }
 
 out:
@@ -884,7 +928,8 @@ static apr_status_t acme_driver_preload(md_proto_driver_t *d,
 }
 
 static md_proto_t ACME_PROTO = {
-    MD_PROTO_ACME, acme_driver_init, acme_driver_renew, acme_driver_preload
+    MD_PROTO_ACME, acme_driver_init, acme_driver_renew, 
+    acme_driver_preload_init, acme_driver_preload
 };
  
 apr_status_t md_acme_protos_add(apr_hash_t *protos, apr_pool_t *p)
index 9314f2067807e9f5f8c3f2f7784cab73a8ed77fb..2907d5296c6ae6a7d47d45a9a71c229ce3b6b4f9 100644 (file)
@@ -447,7 +447,7 @@ apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *a
         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;
+            goto leave;
         }
 
         switch (authz->state) {
@@ -459,21 +459,28 @@ apr_status_t md_acme_order_start_challenges(md_acme_order_t *order, md_acme_t *a
                                            md->pkey_spec, md->acme_tls_1_domains,
                                            env, p, &setup_token, result);
                 if (APR_SUCCESS != rv) {
-                    goto out;
+                    goto leave;
                 }
                 add_setup_token(order, setup_token);
                 md_acme_order_save(store, p, MD_SG_STAGING, md->name, order, 0);
                 break;
                 
+            case MD_ACME_AUTHZ_S_INVALID:
+                rv = APR_EINVAL;
+                if (authz->error_type) {
+                    md_result_problem_set(result, rv, authz->error_type, authz->error_detail, NULL);
+                    goto leave;
+                }
+                /* fall through */
             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;
+                goto leave;
         }
     }
-out:    
+leave:    
     return rv;
 }
 
@@ -502,6 +509,16 @@ static apr_status_t check_challenges(void *baton, int attempt)
                     md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ctx->p, 
                                   "%s: status pending at %s", authz->domain, authz->url);
                     goto leave;
+                case MD_ACME_AUTHZ_S_INVALID:
+                    rv = APR_EINVAL;
+                    if (!authz->error_type) {
+                        md_result_printf(ctx->result, rv, 
+                                         "domain authorization for %s failed, CA consideres "
+                                         "answer to challenge invalid, no error given", 
+                                         authz->domain);
+                    } 
+                    md_result_log(ctx->result, MD_LOG_ERR);
+                    goto leave;
                 default:
                     rv = APR_EINVAL;
                     md_result_printf(ctx->result, rv, 
@@ -531,7 +548,7 @@ apr_status_t md_acme_order_monitor_authzs(md_acme_order_t *order, md_acme_t *acm
     
     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);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: checked authorizations", md->name);
     return rv;
 }
 
index d52e1957556fc2dc4c1889b933848c020bdf23d6..30e2add19ddea267f3b80e4f81526e3a83ca29e0 100644 (file)
@@ -139,7 +139,7 @@ apr_status_t md_acmev1_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, m
     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);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 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;
@@ -147,7 +147,7 @@ apr_status_t md_acmev1_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, m
     /* 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, 
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 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);
index cd5214d5d45be0f3f9f1fcbc2330dd2562a957dd..342e60a90f523129f12b09d0f237735e21a3eeb5 100644 (file)
@@ -95,7 +95,7 @@ apr_status_t md_acmev2_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, m
 {
     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);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 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);
@@ -149,7 +149,7 @@ apr_status_t md_acmev2_drive_renew(md_acme_driver_t *ad, md_proto_driver_t *d, m
     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);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 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);
index 080e5426961e126100db004423fdefe03b173186..5d59b3d5931d68f8fc88ff07776c2f256517ab60 100644 (file)
@@ -107,6 +107,7 @@ md_t *md_create_empty(apr_pool_t *p)
         md->must_staple = -1;
         md->transitive = -1;
         md->acme_tls_1_domains = apr_array_make(p, 5, sizeof(const char *));
+        md->stapling = -1;
         md->defn_name = "unknown";
         md->defn_line_number = 0;
     }
@@ -143,38 +144,6 @@ int md_contains_domains(const md_t *md1, const md_t *md2)
     return 0;
 }
 
-md_t *md_find_closest_match(apr_array_header_t *mds, const md_t *md)
-{
-    md_t *candidate, *m;
-    apr_size_t cand_n, n;
-    int i;
-    
-    candidate = md_get_by_name(mds, md->name);
-    if (!candidate) {
-        /* try to find an instance that contains all domain names from md */ 
-        for (i = 0; i < mds->nelts; ++i) {
-            m = APR_ARRAY_IDX(mds, i, md_t *);
-            if (md_contains_domains(m, md)) {
-                return m;
-            }
-        }
-        /* no matching name and no md in the list has all domains.
-         * We consider that managed domain as closest match that contains at least one
-         * domain name from md, ONLY if there is no other one that also has.
-         */
-        cand_n = 0;
-        for (i = 0; i < mds->nelts; ++i) {
-            m = APR_ARRAY_IDX(mds, i, md_t *);
-            n = md_common_name_count(md, m);
-            if (n > cand_n) {
-                candidate = m;
-                cand_n = n;
-            }
-        }
-    }
-    return candidate;
-}
-
 md_t *md_get_by_name(struct apr_array_header_t *mds, const char *name)
 {
     int i;
@@ -268,6 +237,7 @@ md_t *md_clone(apr_pool_t *p, const md_t *src)
             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);
+        md->stapling = src->stapling;
         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);
     }    
@@ -315,10 +285,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_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);
+        md_json_setb(md->stapling > 0, json, MD_KEY_STAPLING, NULL);
         return json;
     }
     return NULL;
@@ -365,6 +335,7 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p)
             
         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); 
+        md->stapling = (int)md_json_getb(json, MD_KEY_STAPLING, NULL);
         
         return md;
     }
index 57c32a2bfbc728a1fa90d0cb8acdbd0374d457c8..55155975de46ce6024b637288eb8bf8e6ed88e6a 100644 (file)
@@ -155,7 +155,7 @@ apr_status_t md_crypt_init(apr_pool_t *pool)
 
 static apr_status_t fwrite_buffer(void *baton, apr_file_t *f, apr_pool_t *p) 
 {
-    md_data *buf = baton;
+    md_data_t *buf = baton;
     apr_size_t wlen;
     
     (void)p;
@@ -253,6 +253,11 @@ static apr_time_t md_asn1_time_get(const ASN1_TIME* time)
 #endif
 }
 
+apr_time_t md_asn1_generalized_time_get(void *ASN1_GENERALIZEDTIME)
+{
+    return md_asn1_time_get(ASN1_GENERALIZEDTIME);
+}
+
 
 /**************************************************************************************************/
 /* private keys */
@@ -384,7 +389,7 @@ apr_status_t md_pkey_fload(md_pkey_t **ppkey, apr_pool_t *p,
     return rv;
 }
 
-static apr_status_t pkey_to_buffer(md_data *buf, md_pkey_t *pkey, apr_pool_t *p,
+static apr_status_t pkey_to_buffer(md_data_t *buf, md_pkey_t *pkey, apr_pool_t *p,
                                    const char *pass, apr_size_t pass_len)
 {
     BIO *bio = BIO_new(BIO_s_mem());
@@ -435,7 +440,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)
 {
-    md_data buffer;
+    md_data_t buffer;
     apr_status_t rv;
     
     if (APR_SUCCESS == (rv = pkey_to_buffer(&buffer, pkey, p, pass_phrase, pass_len))) {
@@ -507,12 +512,14 @@ static void RSA_get0_key(const RSA *r,
 static const char *bn64(const BIGNUM *b, apr_pool_t *p) 
 {
     if (b) {
-         apr_size_t len = (apr_size_t)BN_num_bytes(b);
-         char *buffer = apr_pcalloc(p, len);
-         if (buffer) {
-            BN_bn2bin(b, (unsigned char *)buffer);
-            return md_util_base64url_encode(buffer, len, p);
-         }
+        md_data_t buffer;
+
+        buffer.len = (apr_size_t)BN_num_bytes(b);
+        buffer.data = apr_pcalloc(p, buffer.len);
+        if (buffer.data) {
+            BN_bn2bin(b, (unsigned char *)buffer.data);
+            return md_util_base64url_encode(&buffer, p);
+        }
     }
     return NULL;
 }
@@ -545,21 +552,23 @@ apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *
                              const char *d, size_t dlen)
 {
     EVP_MD_CTX *ctx = NULL;
-    char *buffer;
+    md_data_t buffer;
     unsigned int blen;
     const char *sign64 = NULL;
     apr_status_t rv = APR_ENOMEM;
     
-    buffer = apr_pcalloc(p, (apr_size_t)EVP_PKEY_size(pkey->pkey));
-    if (buffer) {
+    buffer.len = (apr_size_t)EVP_PKEY_size(pkey->pkey);
+    buffer.data = apr_pcalloc(p, buffer.len);
+    if (buffer.data) {
         ctx = EVP_MD_CTX_create();
         if (ctx) {
             rv = APR_ENOTIMPL;
             if (EVP_SignInit_ex(ctx, EVP_sha256(), NULL)) {
                 rv = APR_EGENERAL;
                 if (EVP_SignUpdate(ctx, d, dlen)) {
-                    if (EVP_SignFinal(ctx, (unsigned char*)buffer, &blen, pkey->pkey)) {
-                        sign64 = md_util_base64url_encode(buffer, blen, p);
+                    if (EVP_SignFinal(ctx, (unsigned char*)buffer.data, &blen, pkey->pkey)) {
+                        buffer.len = blen;
+                        sign64 = md_util_base64url_encode(&buffer, p);
                         if (sign64) {
                             rv = APR_SUCCESS;
                         }
@@ -581,10 +590,10 @@ apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *
     return rv;
 }
 
-static apr_status_t sha256_digest(md_data **pdigest, apr_pool_t *p, const md_data *buf)
+static apr_status_t sha256_digest(md_data_t **pdigest, apr_pool_t *p, const md_data_t *buf)
 {
     EVP_MD_CTX *ctx = NULL;
-    md_data *digest;
+    md_data_t *digest;
     apr_status_t rv = APR_ENOMEM;
     unsigned int dlen;
 
@@ -614,14 +623,14 @@ leave:
     return rv;
 }
 
-apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p, const md_data *d)
+apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p, const md_data_t *d)
 {
     const char *digest64 = NULL;
-    md_data *digest;
+    md_data_t *digest;
     apr_status_t rv;
     
     if (APR_SUCCESS == (rv = sha256_digest(&digest, p, d))) {
-        if (NULL == (digest64 = md_util_base64url_encode(digest->data, digest->len, p))) {
+        if (NULL == (digest64 = md_util_base64url_encode(digest, p))) {
             rv = APR_EGENERAL;
         }
     }
@@ -630,9 +639,9 @@ apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p, con
 }
 
 apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p, 
-                                        const md_data *data)
+                                        const md_data_t *data)
 {
-    md_data *digest;
+    md_data_t *digest;
     apr_status_t rv;
     
     if (APR_SUCCESS == (rv = sha256_digest(&digest, p, data))) {
@@ -661,19 +670,19 @@ static apr_status_t cert_cleanup(void *data)
     return APR_SUCCESS;
 }
 
-static md_cert_t *make_cert(apr_pool_t *p, X509 *x509) 
+md_cert_t *md_cert_wrap(apr_pool_t *p, void *x509) 
 {
     md_cert_t *cert = apr_pcalloc(p, sizeof(*cert));
     cert->pool = p;
     cert->x509 = x509;
-    apr_pool_cleanup_register(p, cert, cert_cleanup, apr_pool_cleanup_null);
-    
     return cert;
 }
 
-void md_cert_free(md_cert_t *cert)
+md_cert_t *md_cert_make(apr_pool_t *p, void *x509) 
 {
-    cert_cleanup(cert);
+    md_cert_t *cert = md_cert_wrap(p, x509);
+    apr_pool_cleanup_register(p, cert, cert_cleanup, apr_pool_cleanup_null);
+    return cert;
 }
 
 void *md_cert_get_X509(const md_cert_t *cert)
@@ -684,16 +693,15 @@ void *md_cert_get_X509(const md_cert_t *cert)
 const char *md_cert_get_serial_number(const md_cert_t *cert, apr_pool_t *p)
 {
     const char *s = "";
+    BIGNUM *bn; 
+    const char *serial;
     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);
+        serial = BN_bn2hex(bn);
+        s = apr_pstrdup(p, serial);
+        OPENSSL_free((void*)serial);
         OPENSSL_free((void*)bn);
-        OPENSSL_free((void*)hex);
     }
     return s;
 }
@@ -831,7 +839,7 @@ apr_status_t md_cert_fload(md_cert_t **pcert, apr_pool_t *p, const char *fname)
         x509 = PEM_read_X509(f, NULL, NULL, NULL);
         rv = fclose(f);
         if (x509 != NULL) {
-            cert =  make_cert(p, x509);
+            cert =  md_cert_make(p, x509);
         }
         else {
             rv = APR_EINVAL;
@@ -842,7 +850,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(md_data *buffer, const md_cert_t *cert, apr_pool_t *p)
+static apr_status_t cert_to_buffer(md_data_t *buffer, const md_cert_t *cert, apr_pool_t *p)
 {
     BIO *bio = BIO_new(BIO_s_mem());
     int i;
@@ -871,7 +879,7 @@ static apr_status_t cert_to_buffer(md_data *buffer, const md_cert_t *cert, apr_p
 apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p, 
                            const char *fname, apr_fileperms_t perms)
 {
-    md_data buffer;
+    md_data_t buffer;
     apr_status_t rv;
     
     if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) {
@@ -882,20 +890,20 @@ apr_status_t md_cert_fsave(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)
 {
-    md_data buffer;
+    md_data_t buffer;
     apr_status_t rv;
     
     if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) {
-        *ps64 = md_util_base64url_encode(buffer.data, buffer.len, p);
+        *ps64 = md_util_base64url_encode(&buffer, p);
         return APR_SUCCESS;
     }
     *ps64 = NULL;
     return rv;
 }
 
-apr_status_t md_cert_to_sha256_digest(md_data **pdigest, const md_cert_t *cert, apr_pool_t *p)
+apr_status_t md_cert_to_sha256_digest(md_data_t **pdigest, const md_cert_t *cert, apr_pool_t *p)
 {
-    md_data *digest;
+    md_data_t *digest;
     unsigned int dlen;
     apr_status_t rv = APR_ENOMEM;
     
@@ -914,7 +922,7 @@ leave:
 
 apr_status_t md_cert_to_sha256_fingerprint(const char **pfinger, const md_cert_t *cert, apr_pool_t *p)
 {
-    md_data *digest;
+    md_data_t *digest;
     apr_status_t rv;
     
     rv = md_cert_to_sha256_digest(&digest, cert, p);
@@ -937,7 +945,7 @@ static int md_cert_read_pem(BIO *bf, apr_pool_t *p, md_cert_t **pcert)
         rv = APR_ENOENT;
         goto out;
     }
-    cert = make_cert(p, x509);
+    cert = md_cert_make(p, x509);
     rv = APR_SUCCESS;
     
 out:
@@ -974,7 +982,7 @@ apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p,
                 goto out;
             }
             else {
-                cert = make_cert(p, x509);
+                cert = md_cert_make(p, x509);
                 rv = APR_SUCCESS;
                 md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "cert parsed");
             }
@@ -1065,7 +1073,7 @@ apr_status_t md_chain_fappend(struct apr_array_header_t *certs, apr_pool_t *p, c
     if (rv == APR_SUCCESS) {
         ERR_clear_error();
         while (NULL != (x509 = PEM_read_X509(f, NULL, NULL, NULL))) {
-            cert = make_cert(p, x509);
+            cert = md_cert_make(p, x509);
             APR_ARRAY_PUSH(certs, md_cert_t *) = cert;
         }
         fclose(f);
@@ -1248,12 +1256,13 @@ 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;
+    const char *s, *csr_der_64 = NULL;
     const unsigned char *domain;
     X509_REQ *csr;
     X509_NAME *n = NULL;
     STACK_OF(X509_EXTENSION) *exts = NULL;
     apr_status_t rv;
+    md_data_t csr_der;
     int csr_der_len;
     
     assert(domains->nelts > 0);
@@ -1305,12 +1314,13 @@ apr_status_t md_cert_req_create(const char **pcsr_der_64, const char *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);
+    csr_der.len = (apr_size_t)csr_der_len;
+    s = csr_der.data = apr_pcalloc(p, 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", name);
         rv = APR_EGENERAL; goto out;
     }
-    csr_der_64 = md_util_base64url_encode(csr_der, (apr_size_t)csr_der_len, p);
+    csr_der_64 = md_util_base64url_encode(&csr_der, p);
     rv = APR_SUCCESS;
     
 out:
@@ -1418,7 +1428,7 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
         rv = APR_EGENERAL; goto out;
     }
 
-    cert = make_cert(p, x);
+    cert = md_cert_make(p, x);
     rv = APR_SUCCESS;
     
 out:
@@ -1470,7 +1480,7 @@ apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain,
         rv = APR_EGENERAL; goto out;
     }
 
-    cert = make_cert(p, x);
+    cert = md_cert_make(p, x);
     rv = APR_SUCCESS;
     
 out:
index cc024533e8207ba8ac0efa7d0a1cd87439d3abc3..7d60af3ae297da1cbcf5b915807c849f4bd3a375 100644 (file)
@@ -24,7 +24,7 @@ struct md_t;
 struct md_http_response_t;
 struct md_cert_t;
 struct md_pkey_t;
-struct md_data;
+struct md_data_t;
 
 
 /**************************************************************************************************/
@@ -32,12 +32,14 @@ struct md_data;
 
 apr_status_t md_rand_bytes(unsigned char *buf, apr_size_t len, apr_pool_t *p);
 
+apr_time_t md_asn1_generalized_time_get(void *ASN1_GENERALIZEDTIME);
+
 /**************************************************************************************************/
 /* digests */
 apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p, 
-                                      const struct md_data *data);
+                                      const struct md_data_t *data);
 apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p, 
-                                        const struct md_data *data);
+                                        const struct md_data_t *data);
 
 #define MD_DATA_SET_STR(d, s)       do { (d)->data = (s); (d)->len = strlen(s); } while(0)
 
@@ -97,7 +99,18 @@ typedef enum {
     MD_CERT_EXPIRED
 } md_cert_state_t;
 
-void md_cert_free(md_cert_t *cert);
+/**
+ * Create a holder of the certificate that will free its memmory when the
+ * pool is destroyed.
+ */
+md_cert_t *md_cert_make(apr_pool_t *p, void *x509);
+
+/**
+ * Wrap a x509 certificate into our own structure, without taking ownership
+ * of its memory. The caller remains responsible.
+ */
+md_cert_t *md_cert_wrap(apr_pool_t *p, void *x509);
+
 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);
@@ -136,7 +149,7 @@ apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t
 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_digest(struct md_data_t **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);
@@ -181,9 +194,9 @@ typedef struct md_sct md_sct;
 struct md_sct {
     int version;
     apr_time_t timestamp;
-    struct md_data *logid;
+    struct md_data_t *logid;
     int signature_type_nid;
-    struct md_data *signature;
+    struct md_data_t *signature;
 };
 
 #endif /* md_crypt_h */
index a7aad7d279218868558ad89f49999655dda02c39..9c4be13b691f91c0149da96daf31b85535654446 100644 (file)
 
 #include "md_http.h"
 #include "md_log.h"
+#include "md_util.h"
 #include "md_curl.h"
 
 /**************************************************************************************************/
 /* md_http curl implementation */
 
 
-static apr_status_t curl_status(int curl_code)
+static apr_status_t curl_status(unsigned int curl_code)
 {
     switch (curl_code) {
         case CURLE_OK:                   return APR_SUCCESS;
@@ -49,6 +50,15 @@ static apr_status_t curl_status(int curl_code)
     }
 }
 
+typedef struct {
+    CURL *curl;
+    CURLM *curlm;
+    struct curl_slist *req_hdrs;
+    md_http_response_t *response;
+    apr_status_t rv;
+    int status_fired;
+} md_curl_internals_t;
+
 static size_t req_data_cb(void *data, size_t len, size_t nmemb, void *baton)
 {
     apr_bucket_brigade *body = baton;
@@ -92,7 +102,8 @@ static size_t req_data_cb(void *data, size_t len, size_t nmemb, void *baton)
 
 static size_t resp_data_cb(void *data, size_t len, size_t nmemb, void *baton)
 {
-    md_http_response_t *res = baton;
+    md_curl_internals_t *internals = baton;
+    md_http_response_t *res = internals->response;
     size_t blen = len * nmemb;
     apr_status_t rv;
     
@@ -100,7 +111,7 @@ static size_t resp_data_cb(void *data, size_t len, size_t nmemb, void *baton)
         if (res->req->resp_limit) {
             apr_off_t body_len = 0;
             apr_brigade_length(res->body, 0, &body_len);
-            if (body_len + (apr_off_t)len > res->req->resp_limit) {
+            if (body_len + (apr_off_t)blen > res->req->resp_limit) {
                 return 0; /* signal curl failure */
             }
         }
@@ -115,7 +126,8 @@ static size_t resp_data_cb(void *data, size_t len, size_t nmemb, void *baton)
 
 static size_t header_cb(void *buffer, size_t elen, size_t nmemb, void *baton)
 {
-    md_http_response_t *res = baton;
+    md_curl_internals_t *internals = baton;
+    md_http_response_t *res = internals->response;
     size_t len, clen = elen * nmemb;
     const char *name = NULL, *value = "", *b = buffer;
     apr_size_t i;
@@ -142,24 +154,6 @@ static size_t header_cb(void *buffer, size_t elen, size_t nmemb, void *baton)
     return clen;
 }
 
-static apr_status_t curl_init(md_http_request_t *req)
-{
-    CURL *curl = curl_easy_init();
-    if (!curl) {
-        return APR_EGENERAL;
-    }
-    
-    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
-    curl_easy_setopt(curl, CURLOPT_HEADERDATA, NULL);
-    curl_easy_setopt(curl, CURLOPT_READFUNCTION, req_data_cb);
-    curl_easy_setopt(curl, CURLOPT_READDATA, NULL);
-    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_data_cb);
-    curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
-    
-    req->internals = curl;
-    return APR_SUCCESS;
-}
-
 typedef struct {
     md_http_request_t *req;
     struct curl_slist *hdrs;
@@ -181,24 +175,96 @@ static int curlify_headers(void *baton, const char *key, const char *value)
     return 1;
 }
 
-static apr_status_t curl_perform(md_http_request_t *req)
+/* Convert timeout values for curl. Since curl uses 0 to disable
+ * timeout, return at least 1 if the apr_time_t value is non-zero. */
+static long timeout_msec(apr_time_t timeout)
 {
-    apr_status_t rv = APR_SUCCESS;
-    CURLcode curle;
-    md_http_response_t *res;
-    CURL *curl;
-    struct curl_slist *req_hdrs = NULL;
+    long ms = (long)apr_time_as_msec(timeout);
+    return ms? ms : (timeout? 1 : 0);
+}
 
-    if (APR_SUCCESS != (rv = curl_init(req))) return rv;
-    curl = req->internals;
+static long timeout_sec(apr_time_t timeout)
+{
+    long s = (long)apr_time_sec(timeout);
+    return s? s : (timeout? 1 : 0);
+}
+
+static int curl_debug_log(CURL *curl, curl_infotype type, char *data, size_t size, void *baton)
+{
+    md_http_request_t *req = baton;
     
-    res = apr_pcalloc(req->pool, sizeof(*res));
+    (void)curl;
+    switch (type) {
+        case CURLINFO_TEXT:
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool, 
+                          "req[%d]: info %s", req->id, apr_pstrndup(req->pool, data, size));
+            break;
+        case CURLINFO_HEADER_OUT:
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool, 
+                          "req[%d]: header --> %s", req->id, apr_pstrndup(req->pool, data, size));
+            break;
+        case CURLINFO_HEADER_IN:
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool, 
+                          "req[%d]: header <-- %s", req->id, apr_pstrndup(req->pool, data, size));
+            break;
+        case CURLINFO_DATA_OUT:
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool, 
+                          "req[%d]: data --> %ld bytes", req->id, (long)size);
+            if (md_log_is_level(req->pool, MD_LOG_TRACE5)) {
+                md_data_t d;
+                const char *s;
+                d.data = data;
+                d.len = size;
+                md_data_to_hex(&s, 0, req->pool, &d);
+                md_log_perror(MD_LOG_MARK, MD_LOG_TRACE5, 0, req->pool, 
+                              "req[%d]: data(hex) -->  %s", req->id, s);
+            }
+            break;
+        case CURLINFO_DATA_IN:
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE4, 0, req->pool, 
+                          "req[%d]: data <-- %ld bytes", req->id, (long)size);
+            if (md_log_is_level(req->pool, MD_LOG_TRACE5)) {
+                md_data_t d;
+                const char *s;
+                d.data = data;
+                d.len = size;
+                md_data_to_hex(&s, 0, req->pool, &d);
+                md_log_perror(MD_LOG_MARK, MD_LOG_TRACE5, 0, req->pool, 
+                              "req[%d]: data(hex) <-- %s", req->id, s);
+            }
+            break;
+        default:
+            break;
+    }
+    return 0;
+}
+
+static apr_status_t internals_setup(md_http_request_t *req)
+{
+    md_curl_internals_t *internals;
+    CURL *curl;
+    apr_status_t rv = APR_SUCCESS;
     
-    res->req = req;
-    res->rv = APR_SUCCESS;
-    res->status = 400;
-    res->headers = apr_table_make(req->pool, 5);
-    res->body = apr_brigade_create(req->pool, req->bucket_alloc);
+    curl = curl_easy_init();
+    if (!curl) {
+        rv = APR_EGENERAL;
+        goto leave;
+    }
+    internals = apr_pcalloc(req->pool, sizeof(*internals));
+    internals->curl = curl;
+        
+    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_cb);
+    curl_easy_setopt(curl, CURLOPT_HEADERDATA, NULL);
+    curl_easy_setopt(curl, CURLOPT_READFUNCTION, req_data_cb);
+    curl_easy_setopt(curl, CURLOPT_READDATA, NULL);
+    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, resp_data_cb);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
+
+    internals->response = apr_pcalloc(req->pool, sizeof(md_http_response_t));
+    internals->response->req = req;
+    internals->response->status = 400;
+    internals->response->headers = apr_table_make(req->pool, 5);
+    internals->response->body = apr_brigade_create(req->pool, req->bucket_alloc);
     
     curl_easy_setopt(curl, CURLOPT_URL, req->url);
     if (!apr_strnatcasecmp("GET", req->method)) {
@@ -213,9 +279,20 @@ static apr_status_t curl_perform(md_http_request_t *req)
     else {
         curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, req->method);
     }
-    curl_easy_setopt(curl, CURLOPT_HEADERDATA, res);
+    curl_easy_setopt(curl, CURLOPT_HEADERDATA, internals);
     curl_easy_setopt(curl, CURLOPT_READDATA, req->body);
-    curl_easy_setopt(curl, CURLOPT_WRITEDATA, res);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, internals);
+    
+    if (req->timeout.overall > 0) {
+        curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout_msec(req->timeout.overall));
+    }
+    if (req->timeout.connect > 0) {
+        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, timeout_msec(req->timeout.connect));
+    }
+    if (req->timeout.stalled > 0) {
+        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, req->timeout.stall_bytes_per_sec);
+        curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, timeout_sec(req->timeout.stalled));
+    }
     
     if (req->user_agent) {
         curl_easy_setopt(curl, CURLOPT_USERAGENT, req->user_agent);
@@ -230,47 +307,244 @@ static apr_status_t curl_perform(md_http_request_t *req)
         ctx.hdrs = NULL;
         ctx.rv = APR_SUCCESS;
         apr_table_do(curlify_headers, &ctx, req->headers, NULL);
-        req_hdrs = ctx.hdrs;
+        internals->req_hdrs = ctx.hdrs;
         if (ctx.rv == APR_SUCCESS) {
-            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, req_hdrs);
+            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, internals->req_hdrs);
         }
     }
     
-    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->pool, 
-                  "request --> %s %s", req->method, req->url);
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, req->pool, 
+                  "req[%d]: %s %s", req->id, req->method, req->url);
     
-    if (md_log_is_level(req->pool, MD_LOG_TRACE3)) {
+    if (md_log_is_level(req->pool, MD_LOG_TRACE4)) {
         curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+        curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_debug_log);
+        curl_easy_setopt(curl, CURLOPT_DEBUGDATA, req);
     }
     
-    curle = curl_easy_perform(curl);
-    res->rv = curl_status(curle);
+leave:
+    req->internals = (APR_SUCCESS == rv)? internals : NULL;
+    return rv;
+}
+
+static apr_status_t update_status(md_http_request_t *req)
+{
+    md_curl_internals_t *internals = req->internals;
+    long l;
+    apr_status_t rv = APR_SUCCESS;
+
+    if (internals) {
+        rv = curl_status(curl_easy_getinfo(internals->curl, CURLINFO_RESPONSE_CODE, &l));
+        if (APR_SUCCESS == rv) {
+            internals->response->status = (int)l;
+        }
+    }
+    return rv;
+}
+
+static void fire_status(md_http_request_t *req, apr_status_t rv)
+{
+    md_curl_internals_t *internals = req->internals;
+        
+    if (internals && !internals->status_fired) {
+        internals->status_fired = 1;
+        
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, req->pool, 
+                      "req[%d] fire callbacks", req->id);
+        if ((APR_SUCCESS == rv) && req->cb.on_response) {
+            rv = req->cb.on_response(internals->response, req->cb.on_response_data);
+        }
     
-    if (APR_SUCCESS == res->rv) {
-        long l;
-        res->rv = curl_status(curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &l));
-        if (APR_SUCCESS == res->rv) {
-            res->status = (int)l;
+        internals->rv = rv;
+        if (req->cb.on_status) {
+            req->cb.on_status(req, rv, req->cb.on_status_data);
         }
-        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, res->rv, req->pool, 
-                      "request <-- %d", res->status);
     }
-    else {
-        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, res->rv, req->pool, 
-                      "request failed(%d): %s", curle, 
-                      curl_easy_strerror(curle));
+}
+
+static apr_status_t md_curl_perform(md_http_request_t *req)
+{
+    apr_status_t rv = APR_SUCCESS;
+    CURLcode curle;
+    md_curl_internals_t *internals;
+    long l;
+
+    if (APR_SUCCESS != (rv = internals_setup(req))) goto leave;
+    internals = req->internals;
+    
+    curle = curl_easy_perform(internals->curl);
+    
+    rv = curl_status(curle);
+    if (APR_SUCCESS != rv) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, req->pool, 
+                      "request failed(%d): %s", curle, curl_easy_strerror(curle));
+        goto leave;
     }
     
-    if (req->cb) {
-        res->rv = req->cb(res);
+    rv = curl_status(curl_easy_getinfo(internals->curl, CURLINFO_RESPONSE_CODE, &l));
+    if (APR_SUCCESS == rv) {
+        internals->response->status = (int)l;
     }
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, req->pool, "request <-- %d", 
+                  internals->response->status);
     
-    rv = res->rv;
+    if (req->cb.on_response) {
+        rv = req->cb.on_response(internals->response, req->cb.on_response_data);
+        req->cb.on_response = NULL;
+    }
+    
+leave:
+    fire_status(req, rv);
     md_http_req_destroy(req);
-    if (req_hdrs) {
-        curl_slist_free_all(req_hdrs);
+    return rv;
+}
+
+static md_http_request_t *find_curl_request(apr_array_header_t *requests, CURL *curl)
+{
+    md_http_request_t *req;
+    md_curl_internals_t *internals;
+    int i;
+    
+    for (i = 0; i < requests->nelts; ++i) {
+        req = APR_ARRAY_IDX(requests, i, md_http_request_t*);
+        internals = req->internals;
+        if (internals && internals->curl == curl) {
+            return req;
+        }
+    }
+    return NULL;
+}
+
+static void add_to_curlm(md_http_request_t *req, CURLM *curlm)
+{
+    md_curl_internals_t *internals = req->internals;
+    
+    if (curlm && internals && internals->curlm == NULL) {
+        curl_multi_add_handle(curlm, internals->curl);
+        internals->curlm = curlm;
+    }
+}
+
+static void remove_from_curlm(md_http_request_t *req, CURLM *curlm)
+{
+    md_curl_internals_t *internals = req->internals;
+
+    if (curlm && internals && internals->curlm == curlm) {
+        curl_multi_remove_handle(curlm, internals->curl);
+        internals->curlm = NULL;
+    }
+}
+    
+static apr_status_t md_curl_multi_perform(md_http_t *http, apr_pool_t *p,
+                                          md_http_next_req *nextreq, void *baton)
+{
+    md_http_request_t *req;
+    CURLM *curlm = NULL;
+    CURLMcode mc;
+    struct CURLMsg *curlmsg;
+    apr_array_header_t *requests;
+    int i, running, numfds, slowdown, msgcount;
+    apr_status_t rv;
+    
+    requests = apr_array_make(p, 10, sizeof(md_http_request_t*));
+    curlm = curl_multi_init();
+    if (!curlm) {
+        rv = APR_ENOMEM;
+        goto leave;
     }
     
+    running = 1;
+    slowdown = 0;
+    while(1) {
+        while (1) {
+            /* fetch as many requests as nextreq gives us */
+            rv = nextreq(&req, baton, http, requests->nelts);
+            
+            if (APR_SUCCESS == rv) {
+                if (APR_SUCCESS != (rv = internals_setup(req))) {
+                    if (req->cb.on_status) req->cb.on_status(req, rv, req->cb.on_status_data);
+                    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, 
+                                  "multi_perform[%d reqs]: setup failed", requests->nelts);
+                }
+                else {
+                    APR_ARRAY_PUSH(requests, md_http_request_t*) = req;
+                    add_to_curlm(req, curlm);
+                    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, 
+                                  "multi_perform[%d reqs]: added request", requests->nelts);
+                }
+                continue;
+            }
+            else if (APR_STATUS_IS_ENOENT(rv)) {
+                md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, 
+                              "multi_perform[%d reqs]: no more requests", requests->nelts);
+                if (!running) {
+                    goto leave;
+                }
+                break;
+            }
+            else {
+                md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, 
+                              "multi_perform[%d reqs]: nextreq() failed", requests->nelts);
+                goto leave;
+            }
+        }
+    
+        mc = curl_multi_perform(curlm, &running);
+        if (CURLM_OK == mc) {
+            mc = curl_multi_wait(curlm, NULL, 0, 1000, &numfds);
+            if (numfds) slowdown = 0;
+        }
+        if (CURLM_OK != mc) {
+            rv = APR_ECONNABORTED;
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
+                          "multi_perform[%d reqs] failed(%d): %s", 
+                          requests->nelts, mc, curl_multi_strerror(mc));
+            goto leave;
+        }
+        if (!numfds) {
+            /* no activity on any connection, timeout */
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, 
+                          "multi_perform[%d reqs]: slowdown %d", requests->nelts, slowdown);
+            if (slowdown) apr_sleep(apr_time_from_msec(100));
+            ++slowdown;
+        }
+
+        /* process status messages, e.g. that a request is done */
+        while (1) {
+            curlmsg = curl_multi_info_read(curlm, &msgcount);
+            if (!curlmsg) break;
+            if (curlmsg->msg == CURLMSG_DONE) {
+                req = find_curl_request(requests, curlmsg->easy_handle);
+                if (req) {
+                    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, 
+                                  "multi_perform[%d reqs]: req[%d] done", 
+                                  requests->nelts, req->id);
+                    update_status(req);
+                    fire_status(req, curl_status(curlmsg->data.result));
+                    remove_from_curlm(req, curlm);
+                    md_array_remove(requests, req);
+                    md_http_req_destroy(req);
+                }
+                else {
+                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                                  "multi_perform[%d reqs]: req done, but not found by handle", 
+                                  requests->nelts);
+                }
+            }
+        }
+        assert(running == requests->nelts);
+    };
+
+leave:
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, 
+                  "multi_perform[%d reqs]: leaving", requests->nelts);
+    for (i = 0; i < requests->nelts; ++i) {
+        req = APR_ARRAY_IDX(requests, i, md_http_request_t*);
+        fire_status(req, APR_SUCCESS);
+        remove_from_curlm(req, curlm);
+        md_http_req_destroy(req);
+    }
+    if (curlm) curl_multi_cleanup(curlm);
     return rv;
 }
 
@@ -284,18 +558,21 @@ static apr_status_t md_curl_init(void) {
     return APR_SUCCESS;
 }
 
-static void curl_req_cleanup(md_http_request_t *req) 
+static void md_curl_req_cleanup(md_http_request_t *req) 
 {
-    if (req->internals) {
-        curl_easy_cleanup(req->internals);
+    md_curl_internals_t *internals = req->internals;
+    if (internals) {
+        if (internals->curl) curl_easy_cleanup(internals->curl);
+        if (internals->req_hdrs) curl_slist_free_all(internals->req_hdrs);
         req->internals = NULL;
     }
 }
 
 static md_http_impl_t impl = {
     md_curl_init,
-    curl_req_cleanup,
-    curl_perform
+    md_curl_req_cleanup,
+    md_curl_perform,
+    md_curl_multi_perform,
 };
 
 md_http_impl_t * md_curl_get_impl(apr_pool_t *p)
index c9383f88c670c40724a1dc5dfe7a3842971813f5..027de593d5c008413084cb2e8a7ebaa9154d54b5 100644 (file)
 
 #include "md_http.h"
 #include "md_log.h"
+#include "md_util.h"
 
 struct md_http_t {
     apr_pool_t *pool;
     apr_bucket_alloc_t *bucket_alloc;
+    int next_id;
     apr_off_t resp_limit;
     md_http_impl_t *impl;
     const char *user_agent;
     const char *proxy_url;
+    md_http_timeouts_t timeout;
 };
 
 static md_http_impl_t *cur_impl;
@@ -81,9 +84,82 @@ void md_http_set_response_limit(md_http_t *http, apr_off_t resp_limit)
     http->resp_limit = resp_limit;
 }
 
+void md_http_set_timeout_default(md_http_t *http, apr_time_t timeout)
+{
+    http->timeout.overall = timeout;
+}
+
+void md_http_set_timeout(md_http_request_t *req, apr_time_t timeout)
+{
+    req->timeout.overall = timeout;
+}
+
+void md_http_set_connect_timeout_default(md_http_t *http, apr_time_t timeout)
+{
+    http->timeout.connect = timeout;
+}
+
+void md_http_set_connect_timeout(md_http_request_t *req, apr_time_t timeout)
+{
+    req->timeout.connect = timeout;
+}
+
+void md_http_set_stalling_default(md_http_t *http, long bytes_per_sec, apr_time_t timeout)
+{
+    http->timeout.stall_bytes_per_sec = bytes_per_sec;
+    http->timeout.stalled = timeout;
+}
+
+void md_http_set_stalling(md_http_request_t *req, long bytes_per_sec, apr_time_t timeout)
+{
+    req->timeout.stall_bytes_per_sec = bytes_per_sec;
+    req->timeout.stalled = timeout;
+}
+
+static apr_status_t req_set_body(md_http_request_t *req, const char *content_type,
+                                 apr_bucket_brigade *body, apr_off_t body_len,
+                                 int detect_len)
+{
+    apr_status_t rv = APR_SUCCESS;
+    
+    if (body && detect_len) {
+        rv = apr_brigade_length(body, 1, &body_len);
+        if (rv != APR_SUCCESS) {
+            return rv;
+        }
+    }
+
+    req->body = body;
+    req->body_len = body? body_len : 0;
+    if (content_type) {
+        apr_table_set(req->headers, "Content-Type", content_type); 
+    }
+    else {
+        apr_table_unset(req->headers, "Content-Type"); 
+    }
+    return rv;
+}
+
+static apr_status_t req_set_body_data(md_http_request_t *req, const char *content_type,
+                                      const md_data_t *body)
+{
+    apr_bucket_brigade *bbody = NULL;
+    apr_status_t rv;
+    
+    if (body && body->len > 0) {
+        bbody = apr_brigade_create(req->pool, req->http->bucket_alloc);
+        rv = apr_brigade_write(bbody, NULL, NULL, body->data, body->len);
+        if (rv != APR_SUCCESS) {
+            md_http_req_destroy(req);
+            return rv;
+        }
+    }
+    return req_set_body(req, content_type, bbody, body? (apr_off_t)body->len : 0, 0);
+}
+
 static apr_status_t req_create(md_http_request_t **preq, md_http_t *http, 
-                               const char *method, const char *url, struct apr_table_t *headers,
-                               md_http_cb *cb, void *baton)
+                               const char *method, const char *url, 
+                               struct apr_table_t *headers)
 {
     md_http_request_t *req;
     apr_pool_t *pool;
@@ -96,17 +172,16 @@ static apr_status_t req_create(md_http_request_t **preq, md_http_t *http,
     
     req = apr_pcalloc(pool, sizeof(*req));
     req->pool = pool;
+    req->id = http->next_id++;
     req->bucket_alloc = http->bucket_alloc;
     req->http = http;
     req->method = method;
     req->url = url;
     req->headers = headers? apr_table_copy(req->pool, headers) : apr_table_make(req->pool, 5);
     req->resp_limit = http->resp_limit;
-    req->cb = cb;
-    req->baton = baton;
     req->user_agent = http->user_agent;
     req->proxy_url = http->proxy_url;
-
+    req->timeout = http->timeout;
     *preq = req;
     return rv;
 }
@@ -120,107 +195,157 @@ void md_http_req_destroy(md_http_request_t *req)
     apr_pool_destroy(req->pool);
 }
 
-static apr_status_t schedule(md_http_request_t *req, 
-                             apr_bucket_brigade *body, int detect_clen) 
+void md_http_set_on_status_cb(md_http_request_t *req, md_http_status_cb *cb, void *baton)
 {
-    apr_status_t rv;
-    
-    req->body = body;
-    req->body_len = body? -1 : 0;
+    req->cb.on_status = cb;
+    req->cb.on_status_data = baton;
+}
 
-    if (req->body && detect_clen) {
-        rv = apr_brigade_length(req->body, 1, &req->body_len);
-        if (rv != APR_SUCCESS) {
-            md_http_req_destroy(req);
-            return rv;
-        }
-    }
-    
+void md_http_set_on_response_cb(md_http_request_t *req, md_http_response_cb *cb, void *baton)
+{
+    req->cb.on_response = cb;
+    req->cb.on_response_data = baton;
+}
+
+static void req_init_cl(md_http_request_t *req)
+{
     if (req->body_len == 0 && apr_strnatcasecmp("GET", req->method)) {
         apr_table_setn(req->headers, "Content-Length", "0");
     }
     else if (req->body_len > 0) {
         apr_table_setn(req->headers, "Content-Length", apr_off_t_toa(req->pool, req->body_len));
     }
-    
+}
+
+apr_status_t md_http_perform(md_http_request_t *req)
+{
+    req_init_cl(req);
     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)
+typedef struct {
+    md_http_next_req *nextreq;
+    void *baton;
+} nextreq_proxy_t;
+
+static apr_status_t proxy_nextreq(md_http_request_t **preq, void *baton, 
+                                      md_http_t *http, int in_flight)
 {
-    md_http_request_t *req;
+    nextreq_proxy_t *proxy = baton;
     apr_status_t rv;
     
-    rv = req_create(&req, http, "GET", url, headers, cb, baton);
-    if (rv != APR_SUCCESS) {
-        return rv;
-    }
+    rv = proxy->nextreq(preq, proxy->baton, http, in_flight);
+    if (APR_SUCCESS == rv) req_init_cl(*preq);
+    return rv;
+}
+
+apr_status_t md_http_multi_perform(md_http_t *http, md_http_next_req *nextreq, void *baton)
+{
+    nextreq_proxy_t proxy;
     
-    return schedule(req, NULL, 0);
+    proxy.nextreq = nextreq;
+    proxy.baton = baton;
+    return http->impl->multi_perform(http, http->pool, proxy_nextreq, &proxy);
 }
 
-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)
+apr_status_t md_http_GET_create(md_http_request_t **preq, md_http_t *http, const char *url, 
+                                struct apr_table_t *headers)
 {
     md_http_request_t *req;
     apr_status_t rv;
     
-    rv = req_create(&req, http, "HEAD", url, headers, cb, baton);
-    if (rv != APR_SUCCESS) {
-        return rv;
-    }
-    
-    return schedule(req, NULL, 0);
+    rv = req_create(&req, http, "GET", url, headers);
+    *preq = (APR_SUCCESS == rv)? req : NULL;
+    return rv;
 }
 
-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)
+apr_status_t md_http_HEAD_create(md_http_request_t **preq, md_http_t *http, const char *url, 
+                                 struct apr_table_t *headers)
 {
     md_http_request_t *req;
     apr_status_t rv;
     
-    rv = req_create(&req, http, "POST", url, headers, cb, baton);
-    if (rv != APR_SUCCESS) {
-        return rv;
-    }
-    
-    if (content_type) {
-        apr_table_set(req->headers, "Content-Type", content_type); 
-    }
-    return schedule(req, body, 1);
+    rv = req_create(&req, http, "HEAD", url, headers);
+    *preq = (APR_SUCCESS == rv)? req : NULL;
+    return rv;
 }
 
-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)
+apr_status_t md_http_POST_create(md_http_request_t **preq, md_http_t *http, const char *url, 
+                                 struct apr_table_t *headers, const char *content_type, 
+                                 struct apr_bucket_brigade *body, int detect_len)
 {
     md_http_request_t *req;
     apr_status_t rv;
-    apr_bucket_brigade *body = NULL;
     
-    rv = req_create(&req, http, "POST", url, headers, cb, baton);
-    if (rv != APR_SUCCESS) {
-        return rv;
+    rv = req_create(&req, http, "POST", url, headers);
+    if (APR_SUCCESS == rv) {
+        rv = req_set_body(req, content_type, body, -1, detect_len);
     }
+    *preq = (APR_SUCCESS == rv)? req : NULL;
+    return rv;
+}
 
-    if (data && data_len > 0) {
-        body = apr_brigade_create(req->pool, req->http->bucket_alloc);
-        rv = apr_brigade_write(body, NULL, NULL, data, data_len);
-        if (rv != APR_SUCCESS) {
-            md_http_req_destroy(req);
-            return rv;
-        }
-    }
+apr_status_t md_http_POSTd_create(md_http_request_t **preq, md_http_t *http, const char *url, 
+                                  struct apr_table_t *headers, const char *content_type, 
+                                  const struct md_data_t *body)
+{
+    md_http_request_t *req;
+    apr_status_t rv;
     
-    if (content_type) {
-        apr_table_set(req->headers, "Content-Type", content_type); 
+    rv = req_create(&req, http, "POST", url, headers);
+    if (APR_SUCCESS == rv) {
+        rv = req_set_body_data(req, content_type, body);
     }
-     
-    return schedule(req, body, 1);
+    *preq = (APR_SUCCESS == rv)? req : NULL;
+    return rv;
+}
+
+apr_status_t md_http_GET_perform(struct md_http_t *http, 
+                                 const char *url, struct apr_table_t *headers,
+                                 md_http_response_cb *cb, void *baton)
+{
+    md_http_request_t *req;
+    apr_status_t rv;
+
+    rv = md_http_GET_create(&req, http, url, headers);
+    if (APR_SUCCESS == rv) md_http_set_on_response_cb(req, cb, baton);
+    return (APR_SUCCESS == rv)? md_http_perform(req) : rv;
+}
+
+apr_status_t md_http_HEAD_perform(struct md_http_t *http, 
+                                  const char *url, struct apr_table_t *headers,
+                                  md_http_response_cb *cb, void *baton)
+{
+    md_http_request_t *req;
+    apr_status_t rv;
+
+    rv = md_http_HEAD_create(&req, http, url, headers);
+    if (APR_SUCCESS == rv) md_http_set_on_response_cb(req, cb, baton);
+    return (APR_SUCCESS == rv)? md_http_perform(req) : rv;
+}
+
+apr_status_t md_http_POST_perform(struct md_http_t *http, const char *url, 
+                                  struct apr_table_t *headers, const char *content_type, 
+                                  apr_bucket_brigade *body, int detect_len, 
+                                  md_http_response_cb *cb, void *baton)
+{
+    md_http_request_t *req;
+    apr_status_t rv;
+
+    rv = md_http_POST_create(&req, http, url, headers, content_type, body, detect_len);
+    if (APR_SUCCESS == rv) md_http_set_on_response_cb(req, cb, baton);
+    return (APR_SUCCESS == rv)? md_http_perform(req) : rv;
+}
+
+apr_status_t md_http_POSTd_perform(md_http_t *http, const char *url, 
+                                   struct apr_table_t *headers, const char *content_type, 
+                                   const md_data_t *body, 
+                                   md_http_response_cb *cb, void *baton)
+{
+    md_http_request_t *req;
+    apr_status_t rv;
+
+    rv = md_http_POSTd_create(&req, http, url, headers, content_type, body);
+    if (APR_SUCCESS == rv) md_http_set_on_response_cb(req, cb, baton);
+    return (APR_SUCCESS == rv)? md_http_perform(req) : rv;
 }
index 47c7cc4b572413680c526328f5c7b166ebd82da6..23187b6dfa50e9bc0220276d98a928608c80cf4c 100644 (file)
 struct apr_table_t;
 struct apr_bucket_brigade;
 struct apr_bucket_alloc_t;
+struct md_data_t;
 
 typedef struct md_http_t md_http_t;
 
 typedef struct md_http_request_t md_http_request_t;
 typedef struct md_http_response_t md_http_response_t;
 
-typedef apr_status_t md_http_cb(const md_http_response_t *res);
+/**
+ * Callback invoked once per request, either when an error was encountered
+ * or when everything succeeded and the request is about to be released. Only
+ * in the last case will the status be APR_SUCCESS.
+ */
+typedef apr_status_t md_http_status_cb(const md_http_request_t *req, apr_status_t status, void *data);
+
+/**
+ * Callback invoked when the complete response has been received.
+ */
+typedef apr_status_t md_http_response_cb(const md_http_response_t *res, void *data);
+
+typedef struct md_http_callbacks_t md_http_callbacks_t;
+struct md_http_callbacks_t {
+    md_http_status_cb *on_status;
+    void *on_status_data;
+    md_http_response_cb *on_response;
+    void *on_response_data;
+};
+
+typedef struct md_http_timeouts_t md_http_timeouts_t;
+struct md_http_timeouts_t {
+    apr_time_t overall;
+    apr_time_t connect;
+    long stall_bytes_per_sec;
+    apr_time_t stalled;
+};
 
 struct md_http_request_t {
     md_http_t *http;
     apr_pool_t *pool;
+    int id;
     struct apr_bucket_alloc_t *bucket_alloc;
     const char *method;
     const char *url;
@@ -40,14 +68,13 @@ struct md_http_request_t {
     struct apr_bucket_brigade *body;
     apr_off_t body_len;
     apr_off_t resp_limit;
-    md_http_cb *cb;
-    void *baton;
+    md_http_timeouts_t timeout;
+    md_http_callbacks_t cb;
     void *internals;
 };
 
 struct md_http_response_t {
     md_http_request_t *req;
-    apr_status_t rv;
     int status;
     apr_table_t *headers;
     struct apr_bucket_brigade *body;
@@ -58,38 +85,154 @@ apr_status_t md_http_create(md_http_t **phttp, apr_pool_t *p, const char *user_a
 
 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);
+/**
+ * Set the timeout for the complete reqest. This needs to take everything from
+ * DNS looksups, to conntects, to transfer of all data into account and should
+ * be sufficiently large.
+ * Set to 0 the have no timeout for this.
+ */
+void md_http_set_timeout_default(md_http_t *http, apr_time_t timeout);
+void md_http_set_timeout(md_http_request_t *req, apr_time_t timeout);
+
+/**
+ * Set the timeout for establishing a connection. 
+ * Set to 0 the have no special timeout for this.
+ */
+void md_http_set_connect_timeout_default(md_http_t *http, apr_time_t timeout);
+void md_http_set_connect_timeout(md_http_request_t *req, apr_time_t timeout);
 
-apr_status_t md_http_HEAD(md_http_t *http, 
-                          const char *url, struct apr_table_t *headers,
-                          md_http_cb *cb, void *baton);
+/**
+ * Set the condition for when a transfer is considered "stalled", e.g. does not
+ * progress at a sufficient rate and will be aborted.
+ * Set to 0 the have no stall detection in place.
+ */
+void md_http_set_stalling_default(md_http_t *http, long bytes_per_sec, apr_time_t timeout);
+void md_http_set_stalling(md_http_request_t *req, long bytes_per_sec, apr_time_t timeout);
+
+/**
+ * Perform the request. Then this function returns, the request and
+ * all its memory has been freed and must no longer be used.
+ */
+apr_status_t md_http_perform(md_http_request_t *request);
 
-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);
+/**
+ * Set the callback to be invoked once the status of a request is known.
+ * @param req       the request
+ * @param cb        the callback to invoke on the response
+ * @param baton     data passed to the callback    
+ */
+void md_http_set_on_status_cb(md_http_request_t *req, md_http_status_cb *cb, void *baton);
+
+/**
+ * Set the callback to be invoked when the complete 
+ * response has been successfully received. The HTTP status may
+ * be 500, however.
+ * @param req       the request
+ * @param cb        the callback to invoke on the response
+ * @param baton     data passed to the callback    
+ */
+void md_http_set_on_response_cb(md_http_request_t *req, md_http_response_cb *cb, void *baton);
+
+/**
+ * Create a GET reqest.
+ * @param preq      the created request after success
+ * @param http      the md_http instance 
+ * @param url       the url to GET
+ * @param headers   request headers
+ */
+apr_status_t md_http_GET_create(md_http_request_t **preq, md_http_t *http, const char *url, 
+                                struct apr_table_t *headers);
+
+/**
+ * Create a HEAD reqest.
+ * @param preq      the created request after success
+ * @param http      the md_http instance 
+ * @param url       the url to GET
+ * @param headers   request headers
+ */
+apr_status_t md_http_HEAD_create(md_http_request_t **preq, md_http_t *http, const char *url, 
+                                 struct apr_table_t *headers);
+
+/**
+ * Create a POST reqest with a bucket brigade as request body.
+ * @param preq      the created request after success
+ * @param http      the md_http instance 
+ * @param url       the url to GET
+ * @param headers   request headers
+ * @param content_type the content_type of the body or NULL
+ * @param body      the body of the request or NULL
+ * @param detect_len scan the body to detect its length
+ */
+apr_status_t md_http_POST_create(md_http_request_t **preq, md_http_t *http, const char *url, 
+                                 struct apr_table_t *headers, const char *content_type, 
+                                 struct apr_bucket_brigade *body, int detect_len);
+
+/**
+ * Create a POST reqest with known request body data.
+ * @param preq      the created request after success
+ * @param http      the md_http instance 
+ * @param url       the url to GET
+ * @param headers   request headers
+ * @param content_type the content_type of the body or NULL
+ * @param body      the body of the request or NULL
+ */
+apr_status_t md_http_POSTd_create(md_http_request_t **preq, md_http_t *http, const char *url, 
+                                  struct apr_table_t *headers, const char *content_type, 
+                                  const struct md_data_t *body);
 
-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);
+/*
+ * Convenience functions for create+perform.
+ */
+apr_status_t md_http_GET_perform(md_http_t *http, const char *url, 
+                                 struct apr_table_t *headers,
+                                 md_http_response_cb *cb, void *baton);
+apr_status_t md_http_HEAD_perform(md_http_t *http, const char *url, 
+                                  struct apr_table_t *headers,
+                                  md_http_response_cb *cb, void *baton);
+apr_status_t md_http_POST_perform(md_http_t *http, const char *url, 
+                                  struct apr_table_t *headers, const char *content_type, 
+                                  struct apr_bucket_brigade *body, int detect_len, 
+                                  md_http_response_cb *cb, void *baton);
+apr_status_t md_http_POSTd_perform(md_http_t *http, const char *url, 
+                                   struct apr_table_t *headers, const char *content_type, 
+                                   const struct md_data_t *body, 
+                                   md_http_response_cb *cb, void *baton);
 
 void md_http_req_destroy(md_http_request_t *req);
 
+/** Return the next request for processing on APR_SUCCESS. Return ARP_ENOENT
+ * when no request is available. Anything else is an error.
+ */
+typedef apr_status_t md_http_next_req(md_http_request_t **preq, void *baton, 
+                                      md_http_t *http, int in_flight);
+
+/**
+ * Perform requests in parallel as retrieved from the nextreq function.
+ * There are as many requests in flight as the nextreq functions provides. 
+ *
+ * To limit the number of parallel requests, nextreq should return APR_ENOENT when the limit
+ * is reached. It will be called again when the number of in_flight requests changes.
+ * 
+ * When all reqests are done, nextreq will be called one more time. Should it not
+ * return anything, this function returns.
+ */
+apr_status_t md_http_multi_perform(md_http_t *http, md_http_next_req *nextreq, void *baton);
+
 /**************************************************************************************************/
 /* interface to implementation */
 
 typedef apr_status_t md_http_init_cb(void);
 typedef void md_http_req_cleanup_cb(md_http_request_t *req);
 typedef apr_status_t md_http_perform_cb(md_http_request_t *req);
+typedef apr_status_t md_http_multi_perform_cb(md_http_t *http, apr_pool_t *p, 
+                                              md_http_next_req *nextreq, void *baton);
 
 typedef struct md_http_impl_t md_http_impl_t;
 struct md_http_impl_t {
     md_http_init_cb *init;
     md_http_req_cleanup_cb *req_cleanup;
     md_http_perform_cb *perform;
+    md_http_multi_perform_cb *multi_perform;
 };
 
 void md_http_use_implementation(md_http_impl_t *impl);
index 56654795659d70acf7e8fc79f490673ea1281dbd..73120d78ed20db863d900c3c3825562543264c87 100644 (file)
 #include <apr_lib.h>
 #include <apr_strings.h>
 #include <apr_buckets.h>
+#include <apr_date.h>
 
 #include "md_json.h"
 #include "md_log.h"
 #include "md_http.h"
+#include "md_time.h"
 #include "md_util.h"
 
 /* jansson thinks everyone compiles with the platform's cc in its fullest capabilities
@@ -106,12 +108,12 @@ 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_copy(apr_pool_t *pool, const md_json_t *json)
 {
     return json_create(pool, json_copy(json->j));
 }
 
-md_json_t *md_json_clone(apr_pool_t *pool, md_json_t *json)
+md_json_t *md_json_clone(apr_pool_t *pool, const md_json_t *json)
 {
     return json_create(pool, json_deep_copy(json->j));
 }
@@ -168,18 +170,16 @@ static apr_status_t jselect_add(json_t *val, md_json_t *json, va_list ap)
     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);
+        json_object_set(j, key, aj);
     }
     
     if (!json_is_array(aj)) {
-        json_decref(val);
         return APR_EINVAL;
     }
 
@@ -202,7 +202,7 @@ static apr_status_t jselect_insert(json_t *val, size_t index, md_json_t *json, v
     aj = json_object_get(j, key);
     if (!aj) {
         aj = json_array();
-        json_object_set_new(j, key, aj);
+        json_object_set(j, key, aj);
     }
     
     if (!json_is_array(aj)) {
@@ -227,13 +227,11 @@ static apr_status_t jselect_set(json_t *val, md_json_t *json, va_list ap)
     j = jselect_parent(&key, 1, json, ap);
     
     if (!j) {
-        json_decref(val);
         return APR_EINVAL;
     }
     
     if (key) {
         if (!json_is_object(j)) {
-            json_decref(val);
             return APR_EINVAL;
         }
         json_object_set(j, key, val);
@@ -313,6 +311,19 @@ int md_json_is(const md_json_type_t jtype, md_json_t *json, ...)
     return 0;
 }
 
+static const char *md_json_type_name(const md_json_t *json)
+{
+    json_t *j = json->j;
+    if (json_is_object(j)) return "object";
+    if (json_is_array(j)) return "array";
+    if (json_is_string(j)) return "string";
+    if (json_is_real(j)) return "real";
+    if (json_is_integer(j)) return "integer";
+    if (json_is_true(j)) return "true";
+    if (json_is_false(j)) return "false";
+    return "unknown";
+}
+
 /**************************************************************************************************/
 /* booleans */
 
@@ -427,6 +438,35 @@ apr_status_t md_json_sets(const char *value, md_json_t *json, ...)
     return rv;
 }
 
+/**************************************************************************************************/
+/* time */
+
+apr_time_t md_json_get_time(const md_json_t *json, ...)
+{
+    json_t *j;
+    va_list ap;
+    
+    va_start(ap, json);
+    j = jselect(json, ap);
+    va_end(ap);
+
+    if (!j || !json_is_string(j)) return 0;
+    return apr_date_parse_rfc(json_string_value(j));
+}
+
+apr_status_t md_json_set_time(apr_time_t value, md_json_t *json, ...)
+{
+    char ts[APR_RFC822_DATE_LEN];
+    va_list ap;
+    apr_status_t rv;
+    
+    apr_rfc822_date(ts, value);
+    va_start(ap, json);
+    rv = jselect_set_new(json_string(ts), json, ap);
+    va_end(ap);
+    return rv;
+}
+
 /**************************************************************************************************/
 /* json itself */
 
@@ -449,6 +489,22 @@ md_json_t *md_json_getj(md_json_t *json, ...)
     return NULL;
 }
 
+md_json_t *md_json_dupj(apr_pool_t *p, const md_json_t *json, ...)
+{
+    json_t *j;
+    va_list ap;
+    
+    va_start(ap, json);
+    j = jselect(json, ap);
+    va_end(ap);
+    
+    if (j) {
+        json_incref(j);
+        return json_create(p, j);
+    }
+    return NULL;
+}
+
 const md_json_t *md_json_getcj(const md_json_t *json, ...)
 {
     json_t *j;
@@ -468,7 +524,7 @@ const md_json_t *md_json_getcj(const md_json_t *json, ...)
     return NULL;
 }
 
-apr_status_t md_json_setj(md_json_t *value, md_json_t *json, ...)
+apr_status_t md_json_setj(const md_json_t *value, md_json_t *json, ...)
 {
     va_list ap;
     apr_status_t rv;
@@ -496,7 +552,7 @@ apr_status_t md_json_setj(md_json_t *value, md_json_t *json, ...)
     return rv;
 }
 
-apr_status_t md_json_addj(md_json_t *value, md_json_t *json, ...)
+apr_status_t md_json_addj(const md_json_t *value, md_json_t *json, ...)
 {
     va_list ap;
     apr_status_t rv;
@@ -518,6 +574,25 @@ apr_status_t md_json_insertj(md_json_t *value, size_t index, md_json_t *json, ..
     return rv;
 }
 
+apr_size_t md_json_limita(size_t max_elements, md_json_t *json, ...)
+{
+    json_t *j;
+    va_list ap;
+    apr_size_t n = 0;
+    
+    va_start(ap, json);
+    j = jselect(json, ap);
+    va_end(ap);
+
+    if (j && json_is_array(j)) {
+        n = json_array_size(j);
+        while (n > max_elements) {
+            json_array_remove(j, n-1);
+            n = json_array_size(j);
+        }
+    }
+    return n;
+}
 
 /**************************************************************************************************/
 /* arrays / objects */
@@ -642,7 +717,7 @@ apr_status_t md_json_clone_to(void *value, md_json_t *json, apr_pool_t *p, void
     return md_json_setj(md_json_clone(p, value), json, NULL);
 }
 
-apr_status_t md_json_clone_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
+apr_status_t md_json_clone_from(void **pvalue, const md_json_t *json, apr_pool_t *p, void *baton)
 {
     (void)baton;
     *pvalue = md_json_clone(p, json);
@@ -843,7 +918,7 @@ apr_status_t md_json_setsa(apr_array_header_t *a, md_json_t *json, ...)
 /* formatting, parsing */
 
 typedef struct {
-    md_json_t *json;
+    const md_json_t *json;
     md_json_fmt_t fmt;
     const char *fname;
     apr_file_t *f;
@@ -868,7 +943,7 @@ static int dump_cb(const char *buffer, size_t len, void *baton)
     return (rv == APR_SUCCESS)? 0 : -1;
 }
 
-apr_status_t md_json_writeb(md_json_t *json, md_json_fmt_t fmt, apr_bucket_brigade *bb)
+apr_status_t md_json_writeb(const md_json_t *json, md_json_fmt_t fmt, apr_bucket_brigade *bb)
 {
     int rv = json_dump_callback(json->j, dump_cb, bb, fmt_to_flags(fmt));
     return rv? APR_EGENERAL : APR_SUCCESS;
@@ -877,14 +952,18 @@ apr_status_t md_json_writeb(md_json_t *json, md_json_fmt_t fmt, apr_bucket_briga
 static int chunk_cb(const char *buffer, size_t len, void *baton)
 {
     apr_array_header_t *chunks = baton;
-    char *chunk = apr_pcalloc(chunks->pool, len+1);
+    char *chunk;
     
-    memcpy(chunk, buffer, len);
-    APR_ARRAY_PUSH(chunks, const char *) = chunk;
+    if (len > 0) {
+        chunk = apr_palloc(chunks->pool, len+1);
+        memcpy(chunk, buffer, len);
+        chunk[len] = '\0';
+        APR_ARRAY_PUSH(chunks, const char*) = chunk;
+    }
     return 0;
 }
 
-const char *md_json_writep(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt)
+const char *md_json_writep(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt)
 {
     apr_array_header_t *chunks;
     int rv;
@@ -901,13 +980,13 @@ const char *md_json_writep(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt)
         case 0:
             return "";
         case 1:
-            return APR_ARRAY_IDX(chunks, 0, const char *);
+            return APR_ARRAY_IDX(chunks, 0, const char*);
         default:
             return apr_array_pstrcat(p, chunks, 0);
     }
 }
 
-apr_status_t md_json_writef(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, apr_file_t *f)
+apr_status_t md_json_writef(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, apr_file_t *f)
 {
     apr_status_t rv;
     const char *s;
@@ -920,12 +999,13 @@ apr_status_t md_json_writef(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, a
     }
     else {
         rv = APR_EINVAL;
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p, "md_json_writef: error dumping json");
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, json->p, 
+                      "md_json_writef: error dumping json (%s)", md_json_dump_state(json, p));
     }
     return rv;
 }
 
-apr_status_t md_json_fcreatex(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, 
+apr_status_t md_json_fcreatex(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, 
                               const char *fpath, apr_fileperms_t perms)
 {
     apr_status_t rv;
@@ -949,7 +1029,7 @@ static apr_status_t write_json(void *baton, apr_file_t *f, apr_pool_t *p)
     return rv;
 }
 
-apr_status_t md_json_freplace(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, 
+apr_status_t md_json_freplace(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, 
                               const char *fpath, apr_fileperms_t perms)
 {
     j_write_ctx ctx;
@@ -1076,11 +1156,9 @@ apr_status_t md_json_readf(md_json_t **pjson, apr_pool_t *p, const char *fpath)
 apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool, const md_http_response_t *res)
 {
     apr_status_t rv = APR_ENOENT;
-    if (res->rv == APR_SUCCESS) {
-        const char *ctype = apr_table_get(res->headers, "content-type");
-        if (ctype && res->body && (strstr(ctype, "/json") || strstr(ctype, "+json"))) {
-            rv = md_json_readb(pjson, pool, res->body);
-        }
+    const char *ctype = apr_table_get(res->headers, "content-type");
+    if (ctype && res->body && (strstr(ctype, "/json") || strstr(ctype, "+json"))) {
+        rv = md_json_readb(pjson, pool, res->body);
     }
     return rv;
 }
@@ -1091,9 +1169,9 @@ typedef struct {
     md_json_t *json;
 } resp_data;
 
-static apr_status_t json_resp_cb(const md_http_response_t *res)
+static apr_status_t json_resp_cb(const md_http_response_t *res, void *data)
 {
-    resp_data *resp = res->req->baton;
+    resp_data *resp = data;
     return md_json_read_http(&resp->json, resp->pool, res);
 }
 
@@ -1106,7 +1184,7 @@ apr_status_t md_json_http_get(md_json_t **pjson, apr_pool_t *pool,
     memset(&resp, 0, sizeof(resp));
     resp.pool = pool;
     
-    rv = md_http_GET(http, url, NULL, json_resp_cb, &resp);
+    rv = md_http_GET_perform(http, url, NULL, json_resp_cb, &resp);
     
     if (rv == APR_SUCCESS) {
         *pjson = resp.json;
@@ -1134,3 +1212,63 @@ apr_status_t md_json_copy_to(md_json_t *dest, const md_json_t *src, ...)
     }
     return rv;
 }
+
+const char *md_json_dump_state(const md_json_t *json, apr_pool_t *p)
+{
+    if (!json) return "NULL";
+    return apr_psprintf(p, "%s, refc=%ld", md_json_type_name(json), (long)json->j->refcount);
+}
+
+apr_status_t md_json_set_timeperiod(const md_timeperiod_t *tp, md_json_t *json, ...)
+{
+    char ts[APR_RFC822_DATE_LEN];
+    json_t *jn, *j;
+    va_list ap;
+    const char *key;
+    apr_status_t rv;
+    
+    if (!tp || tp->start || tp->end) {
+        jn = json_object();
+        apr_rfc822_date(ts, tp->start);
+        json_object_set_new(jn, "from", json_string(ts));
+        apr_rfc822_date(ts, tp->end);
+        json_object_set_new(jn, "until", json_string(ts));
+        
+        va_start(ap, json);
+        rv = jselect_set_new(jn, json, ap);
+        va_end(ap);
+        return rv;
+    }
+    else {
+        va_start(ap, json);
+        j = jselect_parent(&key, 0, json, ap);
+        va_end(ap);
+        
+        if (key && j && json_is_object(j)) {
+            json_object_del(j, key);
+        }
+        return APR_SUCCESS;
+    }
+}
+
+apr_status_t md_json_get_timeperiod(md_timeperiod_t *tp, md_json_t *json, ...)
+{
+    json_t *j, *jts;
+    va_list ap;
+    
+    va_start(ap, json);
+    j = jselect(json, ap);
+    va_end(ap);
+    
+    memset(tp, 0, sizeof(*tp));
+    if (!j) goto not_found;
+    jts = json_object_get(j, "from");
+    if (!jts || !json_is_string(jts)) goto not_found;
+    tp->start = apr_date_parse_rfc(json_string_value(jts)); 
+    jts = json_object_get(j, "until");
+    if (!jts || !json_is_string(jts)) goto not_found;
+    tp->end = apr_date_parse_rfc(json_string_value(jts)); 
+    return APR_SUCCESS;
+not_found:
+    return APR_ENOENT;
+}
index 480ab7ca568d48678efa6146526896bf31a15060..95a828b3514cb2df8e0740ec1cc2f8260d2d430c 100644 (file)
@@ -24,7 +24,7 @@ struct apr_file_t;
 
 struct md_http_t;
 struct md_http_response_t;
-
+struct md_timeperiod_t;
 
 typedef struct md_json_t md_json_t;
 
@@ -47,8 +47,8 @@ typedef enum {
 md_json_t *md_json_create(apr_pool_t *pool);
 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);
+md_json_t *md_json_copy(apr_pool_t *pool, const md_json_t *json);
+md_json_t *md_json_clone(apr_pool_t *pool, const md_json_t *json);
 
 
 int md_json_has_key(const md_json_t *json, ...);
@@ -72,17 +72,25 @@ 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, ...);
 
+/* timestamp manipulation */
+apr_time_t md_json_get_time(const md_json_t *json, ...);
+apr_status_t md_json_set_time(apr_time_t value, md_json_t *json, ...);
+
 /* json manipulation */
 md_json_t *md_json_getj(md_json_t *json, ...);
+md_json_t *md_json_dupj(apr_pool_t *p, const 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_setj(const md_json_t *value, md_json_t *json, ...);
+apr_status_t md_json_addj(const 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, ...);
 apr_status_t md_json_del(md_json_t *json, ...);
 
+/* Remove all array elements beyond max_elements */ 
+apr_size_t md_json_limita(size_t max_elements, md_json_t *json, ...);
+
 /* conversion function from and to json */
 typedef apr_status_t md_json_to_cb(void *value, md_json_t *json, apr_pool_t *p, void *baton);
 typedef apr_status_t md_json_from_cb(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton);
@@ -93,7 +101,7 @@ apr_status_t md_json_pass_from(void **pvalue, md_json_t *json, apr_pool_t *p, vo
 
 /* conversions from json to json in specified pool */
 apr_status_t md_json_clone_to(void *value, md_json_t *json, apr_pool_t *p, void *baton);
-apr_status_t md_json_clone_from(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton);
+apr_status_t md_json_clone_from(void **pvalue, const md_json_t *json, apr_pool_t *p, void *baton);
 
 /* Manipulating/Iteration on generic Arrays */
 apr_status_t md_json_geta(apr_array_header_t *a, md_json_from_cb *cb, 
@@ -115,13 +123,13 @@ 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, ...);
 
 /* serialization & parsing */
-apr_status_t md_json_writeb(md_json_t *json, md_json_fmt_t fmt, struct apr_bucket_brigade *bb);
-const char *md_json_writep(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt);
-apr_status_t md_json_writef(md_json_t *json, apr_pool_t *p, 
+apr_status_t md_json_writeb(const md_json_t *json, md_json_fmt_t fmt, struct apr_bucket_brigade *bb);
+const char *md_json_writep(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt);
+apr_status_t md_json_writef(const md_json_t *json, apr_pool_t *p, 
                             md_json_fmt_t fmt, struct apr_file_t *f);
-apr_status_t md_json_fcreatex(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, 
+apr_status_t md_json_fcreatex(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, 
                               const char *fpath, apr_fileperms_t perms);
-apr_status_t md_json_freplace(md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, 
+apr_status_t md_json_freplace(const md_json_t *json, apr_pool_t *p, md_json_fmt_t fmt, 
                               const char *fpath, apr_fileperms_t perms);
 
 apr_status_t md_json_readb(md_json_t **pjson, apr_pool_t *pool, struct apr_bucket_brigade *bb);
@@ -137,4 +145,9 @@ apr_status_t md_json_read_http(md_json_t **pjson, apr_pool_t *pool,
 
 apr_status_t md_json_copy_to(md_json_t *dest, const md_json_t *src, ...);
 
+const char *md_json_dump_state(const md_json_t *json, apr_pool_t *p);
+
+apr_status_t md_json_set_timeperiod(const struct md_timeperiod_t *tp, md_json_t *json, ...);
+apr_status_t md_json_get_timeperiod(struct md_timeperiod_t *tp, md_json_t *json, ...);
+
 #endif /* md_json_h */
index 810c151c2f41056cbc2b88fd54c07d84ce255bb7..cfbe8da89b4befd7a5e5335dad59cb785fc5fdd6 100644 (file)
@@ -32,13 +32,13 @@ static int header_set(void *data, const char *key, const char *val)
 }
 
 apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
-                         const char *payload, size_t len, 
-                         struct apr_table_t *protected, 
+                         md_data_t *payload, struct apr_table_t *protected, 
                          struct md_pkey_t *pkey, const char *key_id)
 {
     md_json_t *msg, *jprotected;
     const char *prot64, *pay64, *sign64, *sign, *prot;
     apr_status_t rv = APR_SUCCESS;
+    md_data_t data;
 
     *pmsg = NULL;
     
@@ -64,9 +64,11 @@ apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
     }
     
     if (rv == APR_SUCCESS) {
-        prot64 = md_util_base64url_encode(prot, strlen(prot), p);
+        data.data = prot;
+        data.len = strlen(prot);
+        prot64 = md_util_base64url_encode(&data, p);
         md_json_sets(prot64, msg, "protected", NULL);
-        pay64 = md_util_base64url_encode(payload, len, p);
+        pay64 = md_util_base64url_encode(payload, p);
 
         md_json_sets(pay64, msg, "payload", NULL);
         sign = apr_psprintf(p, "%s.%s", prot64, pay64);
@@ -91,7 +93,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;
+    md_data_t data;
     apr_status_t rv;
     
     e64 = md_pkey_get_rsa_e64(pkey, p);
index e7c145ee38dbcc46ec33cca627455e68ea3d931c..7121308aaed952ba47c4ed06d2f71af619ce8b2b 100644 (file)
 struct apr_table_t;
 struct md_json_t;
 struct md_pkey_t;
+struct md_data_t;
 
 apr_status_t md_jws_sign(md_json_t **pmsg, apr_pool_t *p,
-                         const char *payload, size_t len, struct apr_table_t *protected, 
+                         struct md_data_t *payload, struct apr_table_t *protected, 
                          struct md_pkey_t *pkey, const char *key_id);
 
 apr_status_t md_jws_pkey_thumb(const char **pthumb, apr_pool_t *p, struct md_pkey_t *pkey);
diff --git a/modules/md/md_ocsp.c b/modules/md/md_ocsp.c
new file mode 100644 (file)
index 0000000..90fb332
--- /dev/null
@@ -0,0 +1,1037 @@
+/* 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 <stdlib.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_hash.h>
+#include <apr_time.h>
+#include <apr_date.h>
+#include <apr_strings.h>
+#include <apr_thread_mutex.h>
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/ocsp.h>
+#include <openssl/pem.h>
+#include <openssl/x509v3.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_log.h"
+#include "md_http.h"
+#include "md_json.h"
+#include "md_result.h"
+#include "md_status.h"
+#include "md_store.h"
+#include "md_util.h"
+#include "md_ocsp.h"
+
+#define MD_OCSP_ID_LENGTH   SHA_DIGEST_LENGTH
+   
+struct md_ocsp_reg_t {
+    apr_pool_t *p;
+    md_store_t *store;
+    const char *user_agent;
+    const char *proxy_url;
+    apr_hash_t *hash;
+    apr_thread_mutex_t *mutex;
+    md_timeslice_t renew_window;
+    md_job_notify_cb *notify;
+    void *notify_ctx;
+};
+
+typedef struct md_ocsp_status_t md_ocsp_status_t; 
+struct md_ocsp_status_t {
+    md_data_t id;
+    const char *hexid;
+    const char *hex_sha256;
+    OCSP_CERTID *certid;
+    const char *responder_url;
+    
+    apr_time_t next_run;      /* when the responder shall be asked again */
+    int errors;               /* consecutive failed attempts */
+
+    md_ocsp_cert_stat_t resp_stat;
+    md_data_t resp_der;
+    md_timeperiod_t resp_valid;
+    
+    md_data_t req_der;
+    OCSP_REQUEST *ocsp_req;
+    md_ocsp_reg_t *reg;
+
+    const char *md_name;
+    const char *file_name;
+    
+    apr_time_t resp_mtime;
+    apr_time_t resp_last_check;
+};
+
+const char *md_ocsp_cert_stat_name(md_ocsp_cert_stat_t stat)
+{
+    switch (stat) {
+        case MD_OCSP_CERT_ST_GOOD: return "good";
+        case MD_OCSP_CERT_ST_REVOKED: return "revoked";
+        default: return "unknown";
+    }
+}
+
+md_ocsp_cert_stat_t md_ocsp_cert_stat_value(const char *name)
+{
+    if (name && !strcmp("good", name)) return MD_OCSP_CERT_ST_GOOD;
+    if (name && !strcmp("revoked", name)) return MD_OCSP_CERT_ST_REVOKED;
+    return MD_OCSP_CERT_ST_UNKNOWN;
+}
+
+static apr_status_t init_cert_id(md_data_t *data, const md_cert_t *cert)
+{
+    X509 *x = md_cert_get_X509(cert);
+    unsigned int ulen = 0;
+    
+    assert(data->len == SHA_DIGEST_LENGTH);
+    if (X509_digest(x, EVP_sha1(), (unsigned char*)data->data, &ulen) != 1) {
+        return APR_EGENERAL;
+    }
+    data->len = ulen;
+    return APR_SUCCESS;
+}
+
+static void ostat_req_cleanup(md_ocsp_status_t *ostat)
+{
+    if (ostat->ocsp_req) {
+        OCSP_REQUEST_free(ostat->ocsp_req);
+        ostat->ocsp_req = NULL;
+    }
+    if (ostat->req_der.data) {
+        OPENSSL_free((void*)ostat->req_der.data);
+        ostat->req_der.data = NULL;
+        ostat->req_der.len = 0;
+    }
+}
+
+static int ostat_cleanup(void *ctx, const void *key, apr_ssize_t klen, const void *val)
+{
+    md_ocsp_reg_t *reg = ctx;
+    md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+    
+    (void)reg;
+    (void)key;
+    (void)klen;
+    ostat_req_cleanup(ostat);
+    if (ostat->certid) {
+        OCSP_CERTID_free(ostat->certid);
+        ostat->certid = NULL;
+    }
+    if (ostat->resp_der.data) {
+        OPENSSL_free((void*)ostat->resp_der.data);
+        ostat->resp_der.data = NULL;
+        ostat->resp_der.len = 0;
+    }
+    return 1;
+}
+
+static int ostat_should_renew(md_ocsp_status_t *ostat) 
+{
+    md_timeperiod_t renewal;
+    
+    renewal = md_timeperiod_slice_before_end(&ostat->resp_valid, &ostat->reg->renew_window);
+    return md_timeperiod_has_started(&renewal, apr_time_now());
+}  
+
+static apr_status_t ostat_set(md_ocsp_status_t *ostat, md_ocsp_cert_stat_t stat,
+                              md_data_t *der, md_timeperiod_t *valid, apr_time_t mtime)
+{
+    apr_status_t rv = APR_SUCCESS;
+    char *s = (char*)der->data;
+    
+    if (der->len) {
+        s = OPENSSL_malloc(der->len);
+        if (!s) {
+            rv = APR_ENOMEM;
+            goto leave;
+        }
+        memcpy((char*)s, der->data, der->len);
+    }
+    if (ostat->resp_der.data) {
+        OPENSSL_free((void*)ostat->resp_der.data);
+        ostat->resp_der.data = NULL;
+        ostat->resp_der.len = 0;
+    }
+    
+    ostat->resp_stat = stat;
+    ostat->resp_der.data = s;
+    ostat->resp_der.len = der->len;
+    ostat->resp_valid = *valid;
+    ostat->resp_mtime = mtime;
+    
+    ostat->errors = 0;
+    ostat->next_run = md_timeperiod_slice_before_end(
+        &ostat->resp_valid, &ostat->reg->renew_window).start;
+    
+leave:
+    return rv;
+}
+
+static apr_status_t ostat_from_json(md_ocsp_cert_stat_t *pstat, 
+                                    md_data_t *resp_der, md_timeperiod_t *resp_valid, 
+                                    md_json_t *json, apr_pool_t *p)
+{
+    const char *s;
+    md_timeperiod_t valid;
+    apr_status_t rv = APR_ENOENT;
+    
+    memset(resp_der, 0, sizeof(*resp_der));
+    memset(resp_valid, 0, sizeof(*resp_valid));
+    s = md_json_dups(p, json, MD_KEY_VALID, MD_KEY_FROM, NULL);
+    if (s && *s) valid.start = apr_date_parse_rfc(s);
+    s = md_json_dups(p, json, MD_KEY_VALID, MD_KEY_UNTIL, NULL);
+    if (s && *s) valid.end = apr_date_parse_rfc(s);
+    s = md_json_dups(p, json, MD_KEY_RESPONSE, NULL);
+    if (!s || !*s) goto leave;
+    md_util_base64url_decode(resp_der, s, p);
+    *pstat = md_ocsp_cert_stat_value(md_json_gets(json, MD_KEY_STATUS, NULL));
+    *resp_valid = valid;
+    rv = APR_SUCCESS;
+leave:
+    return rv;
+}
+
+static void ostat_to_json(md_json_t *json, md_ocsp_cert_stat_t stat,
+                          const md_data_t *resp_der, const md_timeperiod_t *resp_valid, 
+                          apr_pool_t *p)
+{
+    const char *s = NULL;
+
+    if (resp_der->len > 0) {
+        md_json_sets(md_util_base64url_encode(resp_der, p), json, MD_KEY_RESPONSE, NULL);
+        s = md_ocsp_cert_stat_name(stat);
+        if (s) md_json_sets(s, json, MD_KEY_STATUS, NULL);
+        md_json_set_timeperiod(resp_valid, json, MD_KEY_VALID, NULL);
+    }
+}
+
+static apr_status_t ocsp_status_refresh(md_ocsp_status_t *ostat, apr_pool_t *ptemp)
+{
+    md_store_t *store = ostat->reg->store;
+    md_json_t *jprops;
+    apr_time_t mtime;
+    apr_status_t rv = APR_EAGAIN;
+    md_data_t resp_der;
+    md_timeperiod_t resp_valid;
+    md_ocsp_cert_stat_t resp_stat;
+    /* Check if the store holds a newer response than the one we have */
+    mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp);
+    if (mtime <= ostat->resp_mtime) goto leave;
+    rv = md_store_load_json(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, &jprops, ptemp);
+    if (APR_SUCCESS != rv) goto leave;
+    rv = ostat_from_json(&resp_stat, &resp_der, &resp_valid, jprops, ptemp);
+    if (APR_SUCCESS != rv) goto leave;
+    rv = ostat_set(ostat, resp_stat, &resp_der, &resp_valid, mtime);
+    if (APR_SUCCESS != rv) goto leave;
+leave:
+    return rv;
+}
+
+
+static apr_status_t ocsp_status_save(md_ocsp_cert_stat_t stat, const md_data_t *resp_der, 
+                                     const md_timeperiod_t *resp_valid,
+                                     md_ocsp_status_t *ostat, apr_pool_t *ptemp)
+{
+    md_store_t *store = ostat->reg->store;
+    md_json_t *jprops;
+    apr_time_t mtime;
+    apr_status_t rv;
+    
+    jprops = md_json_create(ptemp);
+    ostat_to_json(jprops, stat, resp_der, resp_valid, ptemp);
+    rv = md_store_save_json(store, ptemp, MD_SG_OCSP, ostat->md_name, ostat->file_name, jprops, 0);
+    if (APR_SUCCESS != rv) goto leave;
+    mtime = md_store_get_modified(store, MD_SG_OCSP, ostat->md_name, ostat->file_name, ptemp);
+    if (mtime) ostat->resp_mtime = mtime;
+leave:
+    return rv;
+}
+
+static apr_status_t ocsp_reg_cleanup(void *data)
+{
+    md_ocsp_reg_t *reg = data;
+    
+    /* free all OpenSSL structures that we hold */
+    apr_hash_do(ostat_cleanup, reg, reg->hash);
+    return APR_SUCCESS;
+}
+
+apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p, md_store_t *store, 
+                              const md_timeslice_t *renew_window,
+                              const char *user_agent, const char *proxy_url)
+{
+    md_ocsp_reg_t *reg;
+    apr_status_t rv = APR_SUCCESS;
+    
+    reg = apr_palloc(p, sizeof(*reg));
+    if (!reg) {
+        rv = APR_ENOMEM;
+        goto leave;
+    }
+    reg->p = p;
+    reg->store = store;
+    reg->user_agent = user_agent;
+    reg->proxy_url = proxy_url;
+    reg->hash = apr_hash_make(p);
+    reg->renew_window = *renew_window;
+    
+    rv = apr_thread_mutex_create(&reg->mutex, APR_THREAD_MUTEX_NESTED, p);
+    if (APR_SUCCESS != rv) goto leave;
+
+    apr_pool_cleanup_register(p, reg, ocsp_reg_cleanup, apr_pool_cleanup_null);
+leave:
+    *preg = (APR_SUCCESS == rv)? reg : NULL;
+    return rv;
+}
+
+apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *cert, md_cert_t *issuer, const md_t *md)
+{
+    char iddata[MD_OCSP_ID_LENGTH];
+    md_ocsp_status_t *ostat;
+    STACK_OF(OPENSSL_STRING) *ssk = NULL;
+    const char *name, *s;
+    md_data_t id;
+    apr_status_t rv;
+    
+    /* Called during post_config. no mutex protection needed */
+    name = md? md->name : MD_OTHER;
+    id.data = iddata; id.len = sizeof(iddata);
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, reg->p, 
+                  "md[%s]: priming OCSP status", name);
+    rv = init_cert_id(&id, cert);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len);
+    if (ostat) goto leave; /* already seen it, cert is used in >1 server_rec */
+    
+    ostat = apr_pcalloc(reg->p, sizeof(*ostat));
+    md_data_assign_pcopy(&ostat->id, &id, reg->p);
+    ostat->reg = reg;
+    ostat->md_name = name;
+    md_data_to_hex(&ostat->hexid, 0, reg->p, &ostat->id);
+    ostat->file_name = apr_psprintf(reg->p, "ocsp-%s.json", ostat->hexid);
+    rv = md_cert_to_sha256_fingerprint(&ostat->hex_sha256, cert, reg->p); 
+    if (APR_SUCCESS != rv) goto leave;
+
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                  "md[%s]: getting ocsp responder from cert", name);
+    ssk = X509_get1_ocsp(md_cert_get_X509(cert));
+    if (!ssk) {
+        rv = APR_ENOENT;
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p, 
+                      "md[%s]: certificate with serial %s has not OCSP responder URL", 
+                      name, md_cert_get_serial_number(cert, reg->p));
+        goto leave;
+    }
+    s = sk_OPENSSL_STRING_value(ssk, 0);
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                  "md[%s]: ocsp responder found '%s'", name, s);
+    ostat->responder_url = apr_pstrdup(reg->p, s);
+    X509_email_free(ssk);
+
+    ostat->certid = OCSP_cert_to_id(NULL, md_cert_get_X509(cert), md_cert_get_X509(issuer));
+    if (!ostat->certid) {
+        rv = APR_EGENERAL;
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, reg->p, 
+                      "md[%s]: unable to create OCSP certid for certificate with serial %s", 
+                      name, md_cert_get_serial_number(cert, reg->p));
+        goto leave;
+    }
+    
+    /* See, if we have something in store */
+    ocsp_status_refresh(ostat, reg->p);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, reg->p, 
+                  "md[%s]: adding ocsp info (responder=%s)", 
+                  name, ostat->responder_url);
+    apr_hash_set(reg->hash, ostat->id.data, (apr_ssize_t)ostat->id.len, ostat);
+    rv = APR_SUCCESS;
+leave:
+    return rv;
+}
+
+apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen,
+                                md_ocsp_reg_t *reg, const md_cert_t *cert,
+                                apr_pool_t *p, const md_t *md)
+{
+    char iddata[MD_OCSP_ID_LENGTH];
+    md_ocsp_status_t *ostat;
+    const char *name;
+    apr_status_t rv;
+    int locked = 0;
+    md_data_t id;
+    
+    (void)p;
+    (void)md;
+    id.data = iddata; id.len = sizeof(iddata);
+    *pder = NULL;
+    *pderlen = 0;
+    name = md? md->name : MD_OTHER;
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                  "md[%s]: OCSP, get_status", name);
+    rv = init_cert_id(&id, cert);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len);
+    if (!ostat) {
+        rv = APR_ENOENT;
+        goto leave;
+    }
+    
+    /* While the ostat instance itself always exists, the response data it holds
+     * may vary over time and we need locked access to make a copy. */
+    apr_thread_mutex_lock(reg->mutex);
+    locked = 1;
+    
+    if (ostat->resp_der.len <= 0) {
+        /* No response known, check store for new response. */
+        ocsp_status_refresh(ostat, p);
+        if (ostat->resp_der.len <= 0) {
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                          "md[%s]: OCSP, no response available", name);
+            goto leave;
+        }
+    }
+    /* We have a response */
+    if (ostat_should_renew(ostat)) {
+        /* But it is up for renewal. A watchdog should be busy with
+         * retrieving a new one. In case of outages, this might take
+         * a while, however. Pace the frequency of checks with the
+         * urgency of a new response based on the remaining time. */
+        long secs = (long)apr_time_sec(md_timeperiod_remaining(&ostat->resp_valid, apr_time_now()));
+        apr_time_t waiting_time; 
+        
+        /* every hour, every minute, every second */
+        waiting_time = ((secs >= MD_SECS_PER_DAY)?
+                        apr_time_from_sec(60 * 60) : ((secs >= 60)? 
+                        apr_time_from_sec(60) : apr_time_from_sec(1)));
+        if ((apr_time_now() - ostat->resp_last_check) >= waiting_time) {
+            ostat->resp_last_check = apr_time_now();
+            ocsp_status_refresh(ostat, p);
+        }
+    }
+    
+    *pder = OPENSSL_malloc(ostat->resp_der.len);
+    if (*pder == NULL) {
+        rv = APR_ENOMEM;
+        goto leave;
+    }
+    memcpy(*pder, ostat->resp_der.data, ostat->resp_der.len);
+    *pderlen = (int)ostat->resp_der.len;
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                  "md[%s]: OCSP, returning %ld bytes of response", 
+                  name, (long)ostat->resp_der.len);
+leave:
+    if (locked) apr_thread_mutex_unlock(reg->mutex);
+    return rv;
+}
+
+static void ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid, 
+                          md_ocsp_reg_t *reg, md_ocsp_status_t *ostat, apr_pool_t *p)
+{
+    apr_thread_mutex_lock(reg->mutex);
+    if (ostat->resp_der.len <= 0) {
+        /* No resonse known, check the store if out watchdog retrieved one 
+         * in the meantime. */
+        ocsp_status_refresh(ostat, p);
+    }
+    *pvalid = ostat->resp_valid;
+    *pstat = ostat->resp_stat;
+    apr_thread_mutex_unlock(reg->mutex);
+}
+
+apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid,
+                              md_ocsp_reg_t *reg, const md_cert_t *cert,
+                              apr_pool_t *p, const md_t *md)
+{
+    char iddata[MD_OCSP_ID_LENGTH];
+    md_ocsp_status_t *ostat;
+    const char *name;
+    apr_status_t rv;
+    md_timeperiod_t valid;
+    md_ocsp_cert_stat_t stat;
+    md_data_t id;
+    
+    (void)p;
+    (void)md;
+    id.data = iddata; id.len = sizeof(iddata);
+    name = md? md->name : MD_OTHER;
+    memset(&valid, 0, sizeof(valid));
+    stat = MD_OCSP_CERT_ST_UNKNOWN;
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, reg->p, 
+                  "md[%s]: OCSP, get_status", name);
+    
+    rv = init_cert_id(&id, cert);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    ostat = apr_hash_get(reg->hash, id.data, (apr_ssize_t)id.len);
+    if (!ostat) {
+        rv = APR_ENOENT;
+        goto leave;
+    }
+    ocsp_get_meta(&stat, &valid, reg, ostat, p);
+leave:
+    *pstat = stat;
+    *pvalid = valid;  
+    return rv;
+}
+
+apr_size_t md_ocsp_count(md_ocsp_reg_t *reg)
+{
+    return apr_hash_count(reg->hash);
+}
+
+static const char *certid_as_hex(const OCSP_CERTID *certid, apr_pool_t *p)
+{
+    md_data_t der;
+    const char *hex;
+    
+    memset(&der, 0, sizeof(der));
+    der.len = (apr_size_t)i2d_OCSP_CERTID((OCSP_CERTID*)certid, (unsigned char**)&der.data);
+    md_data_to_hex(&hex, 0, p, &der);
+    OPENSSL_free((void*)der.data);
+    return hex;
+}
+
+static const char *certid_summary(const OCSP_CERTID *certid, apr_pool_t *p)
+{
+    const char *serial, *issuer, *key, *s;
+    ASN1_INTEGER *aserial;
+    ASN1_OCTET_STRING *aname_hash, *akey_hash;
+    ASN1_OBJECT *amd_nid;
+    BIGNUM *bn; 
+    md_data_t data;
+    
+    serial = issuer = key = "???";
+    OCSP_id_get0_info(&aname_hash, &amd_nid, &akey_hash, &aserial, (OCSP_CERTID*)certid);
+    if (aname_hash) {
+        data.len = (apr_size_t)aname_hash->length;
+        data.data = (const char*)aname_hash->data;
+        md_data_to_hex(&issuer, 0, p, &data);
+    }
+    if (akey_hash) {
+        data.len = (apr_size_t)akey_hash->length;
+        data.data = (const char*)akey_hash->data;
+        md_data_to_hex(&key, 0, p, &data);
+    }
+    if (aserial) {
+        bn = ASN1_INTEGER_to_BN(aserial, NULL);
+        s = BN_bn2hex(bn);
+        serial = apr_pstrdup(p, s);
+        OPENSSL_free((void*)bn);
+        OPENSSL_free((void*)s);
+    }
+    return apr_psprintf(p, "certid[der=%s, issuer=%s, key=%s, serial=%s]",
+                        certid_as_hex(certid, p), issuer, key, serial);
+}
+
+static const char *certstatus_string(int status)
+{
+    switch (status) {
+        case V_OCSP_CERTSTATUS_GOOD: return "good";
+        case V_OCSP_CERTSTATUS_REVOKED: return "revoked";
+        case V_OCSP_CERTSTATUS_UNKNOWN: return "unknown";
+        default: return "???";
+    }
+
+}
+
+static const char *single_resp_summary(OCSP_SINGLERESP* resp, apr_pool_t *p)
+{
+    const OCSP_CERTID *certid;
+    int status, reason = 0;
+    ASN1_GENERALIZEDTIME *bup = NULL, *bnextup = NULL;
+    md_timeperiod_t valid;
+    
+    certid = OCSP_SINGLERESP_get0_id(resp);
+    status = OCSP_single_get0_status(resp, &reason, NULL, &bup, &bnextup);
+    valid.start = bup? md_asn1_generalized_time_get(bup) : apr_time_now();
+    valid.end = md_asn1_generalized_time_get(bnextup);
+
+    return apr_psprintf(p, "ocsp-single-resp[%s, status=%s, reason=%d, valid=%s]",
+                        certid_summary(certid, p),
+                        certstatus_string(status), reason,
+                        md_timeperiod_print(p, &valid));
+}
+
+typedef struct {
+    apr_pool_t *p;
+    md_ocsp_status_t *ostat;
+    md_result_t *result;
+    md_job_t *job;
+} md_ocsp_update_t;
+
+static apr_status_t ostat_on_resp(const md_http_response_t *resp, void *baton)
+{
+    md_ocsp_update_t *update = baton;
+    md_ocsp_status_t *ostat = update->ostat;
+    md_http_request_t *req = resp->req;
+    OCSP_RESPONSE *ocsp_resp = NULL;
+    OCSP_BASICRESP *basic_resp = NULL;
+    OCSP_SINGLERESP *single_resp;
+    apr_status_t rv = APR_SUCCESS;
+    int n, breason = 0, bstatus;
+    ASN1_GENERALIZEDTIME *bup = NULL, *bnextup = NULL;
+    md_data_t der, new_der;
+    md_timeperiod_t valid;
+    md_ocsp_cert_stat_t nstat;
+    
+    der.data = new_der.data = NULL;
+    der.len  = new_der.len = 0;
+
+    md_result_activity_printf(update->result, "status of certid %s, reading response", 
+                              ostat->hexid);
+    if (APR_SUCCESS != (rv = apr_brigade_pflatten(resp->body, (char**)&der.data, 
+                                                  &der.len, req->pool))) {
+        goto leave;
+    }
+    if (NULL == (ocsp_resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char**)&der.data, 
+                                               (long)der.len))) {
+        rv = APR_EINVAL;
+        md_result_set(update->result, rv, "response body does not parse as OCSP response");
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    /* got a response! but what does it say? */
+    n = OCSP_response_status(ocsp_resp);
+    if (OCSP_RESPONSE_STATUS_SUCCESSFUL != n) {
+        rv = APR_EINVAL;
+        md_result_printf(update->result, rv, "OCSP response status is, unsuccessfully, %d", n);
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    basic_resp = OCSP_response_get1_basic(ocsp_resp);
+    if (!basic_resp) {
+        rv = APR_EINVAL;
+        md_result_set(update->result, rv, "OCSP response has no basicresponse");
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    /* The notion of nonce enabled freshness in OCSP responses, e.g. that the response
+     * contains the signed nonce we sent to the responder, does not scale well. Responders
+     * like to return cached response bytes and therefore do not add a nonce to it.
+     * So, in reality, we can only detect a mismatch when present and otherwise have
+     * to accept it. */
+    switch ((n = OCSP_check_nonce(ostat->ocsp_req, basic_resp))) {
+        case 1:
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, req->pool, 
+                          "req[%d]: OCSP respoonse nonce does match", req->id);
+            break;
+        case 0:
+            rv = APR_EINVAL;
+            md_result_printf(update->result, rv, "OCSP nonce mismatch in response", n);
+            md_result_log(update->result, MD_LOG_WARNING);
+            goto leave;
+            
+        case -1:
+            md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, req->pool, 
+                          "req[%d]: OCSP respoonse did not return the nonce", req->id);
+            break;
+        default:
+            break;
+    }
+    
+    if (!OCSP_resp_find_status(basic_resp, ostat->certid, &bstatus,
+                               &breason, NULL, &bup, &bnextup)) {
+        const char *prefix, *slist = "", *sep = "";
+        int i;
+        
+        rv = APR_EINVAL;
+        prefix = apr_psprintf(req->pool, "OCSP response, no matching status reported for  %s",
+                              certid_summary(ostat->certid, req->pool));
+        for (i = 0; i < OCSP_resp_count(basic_resp); ++i) {
+            single_resp = OCSP_resp_get0(basic_resp, i);
+            slist = apr_psprintf(req->pool, "%s%s%s", slist, sep, 
+                                 single_resp_summary(single_resp, req->pool));
+            sep = ", ";
+        }
+        md_result_printf(update->result, rv, "%s, status list [%s]", prefix, slist);
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    if (V_OCSP_CERTSTATUS_UNKNOWN == bstatus) {
+        rv = APR_ENOENT;
+        md_result_set(update->result, rv, "OCSP basicresponse says cert is unknown");
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    if (!bnextup) {
+        rv = APR_EINVAL;
+        md_result_set(update->result, rv, "OCSP basicresponse reports not valid dates");
+        md_result_log(update->result, MD_LOG_DEBUG);
+        goto leave;
+    }
+    
+    /* Coming here, we have a response for our certid and it is either GOOD
+     * or REVOKED. Both cases we want to remember and use in stapling. */
+    n = i2d_OCSP_RESPONSE(ocsp_resp, (unsigned char**)&new_der.data);
+    if (n <= 0) {
+        rv = APR_EGENERAL;
+        md_result_set(update->result, rv, "error DER encoding OCSP response");
+        md_result_log(update->result, MD_LOG_WARNING);
+        goto leave;
+    }
+    nstat = (bstatus == V_OCSP_CERTSTATUS_GOOD)? MD_OCSP_CERT_ST_GOOD : MD_OCSP_CERT_ST_REVOKED;
+    new_der.len = (apr_size_t)n;
+    valid.start = bup? md_asn1_generalized_time_get(bup) : apr_time_now();
+    valid.end = md_asn1_generalized_time_get(bnextup);
+    
+    /* First, update the instance with a copy */
+    apr_thread_mutex_lock(ostat->reg->mutex);
+    ostat_set(ostat, nstat, &new_der, &valid, apr_time_now());
+    apr_thread_mutex_unlock(ostat->reg->mutex);
+    
+    /* Next, save the original response */
+    rv = ocsp_status_save(nstat, &new_der, &valid, ostat, req->pool); 
+    if (APR_SUCCESS != rv) {
+        md_result_set(update->result, rv, "error saving OCSP status");
+        md_result_log(update->result, MD_LOG_ERR);
+        goto leave;
+    }
+    
+    md_result_printf(update->result, rv, "certificate status is %s, status valid %s", 
+                     (nstat == MD_OCSP_CERT_ST_GOOD)? "GOOD" : "REVOKED",
+                     md_timeperiod_print(req->pool, &ostat->resp_valid));
+    md_result_log(update->result, MD_LOG_DEBUG);
+
+leave:
+    if (new_der.data) OPENSSL_free((void*)new_der.data);
+    if (basic_resp) OCSP_BASICRESP_free(basic_resp);
+    if (ocsp_resp) OCSP_RESPONSE_free(ocsp_resp);
+    return rv;
+}
+
+static apr_status_t ostat_on_req_status(const md_http_request_t *req, apr_status_t status, 
+                                        void *baton)
+{
+    md_ocsp_update_t *update = baton;
+    md_ocsp_status_t *ostat = update->ostat;
+
+    (void)req;
+    md_job_end_run(update->job, update->result);
+    if (APR_SUCCESS != status) {
+        ++ostat->errors;
+        ostat->next_run = apr_time_now() + md_job_delay_on_errors(ostat->errors); 
+        md_result_printf(update->result, status, "OCSP status update failed (%d. time)",  
+                         ostat->errors);
+        md_result_log(update->result, MD_LOG_DEBUG);
+        md_job_log_append(update->job, "ocsp-error", 
+                          update->result->problem, update->result->detail);
+        md_job_holler(update->job, "ocsp-errored");
+        goto leave;
+    }
+    md_job_notify(update->job, "ocsp-renewed", update->result);
+
+leave:
+    md_job_save(update->job, update->result, update->p);
+    ostat_req_cleanup(ostat);
+    return APR_SUCCESS;
+}
+
+typedef struct {
+    md_ocsp_reg_t *reg;
+    apr_array_header_t *todos;
+    apr_pool_t *ptemp;
+    apr_time_t time;
+    int max_parallel;
+} md_ocsp_todo_ctx_t;
+
+static apr_status_t next_todo(md_http_request_t **preq, void *baton, 
+                              md_http_t *http, int in_flight)
+{
+    md_ocsp_todo_ctx_t *ctx = baton;
+    md_ocsp_update_t *update, **pupdate;    
+    md_ocsp_status_t *ostat;
+    OCSP_CERTID *certid = NULL;
+    md_http_request_t *req = NULL;
+    apr_status_t rv = APR_ENOENT;
+    apr_table_t *headers;
+    int len;
+    
+    if (in_flight < ctx->max_parallel) {
+        pupdate = apr_array_pop(ctx->todos);
+        if (pupdate) {
+            update = *pupdate;
+            ostat = update->ostat;
+            
+            update->job = md_ocsp_job_make(ctx->reg, ostat->md_name, update->p);
+            md_job_load(update->job);
+            md_job_start_run(update->job, update->result, ctx->reg->store);
+             
+            if (!ostat->ocsp_req) {
+                ostat->ocsp_req = OCSP_REQUEST_new();
+                if (!ostat->ocsp_req) goto leave;
+                certid = OCSP_CERTID_dup(ostat->certid);
+                if (!certid) goto leave;
+                if (!OCSP_request_add0_id(ostat->ocsp_req, certid)) goto leave;
+                OCSP_request_add1_nonce(ostat->ocsp_req, 0, -1);
+                certid = NULL;
+            }
+            if (0 == ostat->req_der.len) {
+                len = i2d_OCSP_REQUEST(ostat->ocsp_req, (unsigned char**)&ostat->req_der.data);
+                if (len < 0) goto leave;
+                ostat->req_der.len = (apr_size_t)len;
+            }
+            md_result_activity_printf(update->result, "status of certid %s, "
+                                      "contacting %s", ostat->hexid, ostat->responder_url);
+            headers = apr_table_make(ctx->ptemp, 5);
+            apr_table_set(headers, "Expect", "");
+            rv = md_http_POSTd_create(&req, http, ostat->responder_url, headers, 
+                                      "application/ocsp-request", &ostat->req_der);
+            if (APR_SUCCESS != rv) goto leave;
+            md_http_set_on_status_cb(req, ostat_on_req_status, update);
+            md_http_set_on_response_cb(req, ostat_on_resp, update);
+            rv = APR_SUCCESS;
+        }
+    }
+leave:
+    *preq = (APR_SUCCESS == rv)? req : NULL;
+    if (certid) OCSP_CERTID_free(certid);
+    return rv;
+}
+
+static int select_updates(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+    md_ocsp_todo_ctx_t *ctx = baton;
+    md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+    md_ocsp_update_t *update;
+    
+    (void)key;
+    (void)klen;
+    if (ostat->next_run <= ctx->time) {
+        update = apr_pcalloc(ctx->ptemp, sizeof(*update));
+        update->p = ctx->ptemp;
+        update->ostat = ostat;
+        update->result = md_result_md_make(update->p, ostat->md_name);
+        update->job = NULL;
+        APR_ARRAY_PUSH(ctx->todos, md_ocsp_update_t*) = update;
+    }
+    return 1;
+}
+
+static int select_next_run(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+    md_ocsp_todo_ctx_t *ctx = baton;
+    md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+    
+    (void)key;
+    (void)klen;
+    if (ostat->next_run < ctx->time && ostat->next_run > apr_time_now()) {
+        ctx->time = ostat->next_run;
+    }
+    return 1;
+}
+
+void md_ocsp_renew(md_ocsp_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, apr_time_t *pnext_run)
+{
+    md_ocsp_todo_ctx_t ctx;
+    md_http_t *http;
+    apr_status_t rv = APR_SUCCESS;
+    
+    (void)p;
+    (void)pnext_run;
+    
+    ctx.reg = reg;
+    ctx.ptemp = ptemp;
+    ctx.todos = apr_array_make(ptemp, (int)md_ocsp_count(reg), sizeof(md_ocsp_status_t*));
+    ctx.max_parallel = 6; /* the magic number in HTTP */
+    
+    /* Create a list of update tasks that are needed now or in the next minute */
+    ctx.time = apr_time_now() + apr_time_from_sec(60);;
+    apr_hash_do(select_updates, &ctx, reg->hash);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                  "OCSP status updates due: %d",  ctx.todos->nelts);
+    if (!ctx.todos->nelts) goto leave;
+    
+    rv = md_http_create(&http, ptemp, reg->user_agent, reg->proxy_url);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    rv = md_http_multi_perform(http, next_todo, &ctx);
+
+leave:
+    /* When do we need to run next? *pnext_run contains the planned schedule from
+     * the watchdog. We can make that earlier if we need it. */
+    ctx.time = *pnext_run;
+    apr_hash_do(select_next_run, &ctx, reg->hash);
+
+    /* sanity check and return */
+    if (ctx.time < apr_time_now()) ctx.time = apr_time_now() + apr_time_from_sec(1);
+    *pnext_run = ctx.time;
+
+    if (APR_SUCCESS != rv) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "ocsp_renew done");
+    }
+    return;
+}
+
+apr_status_t md_ocsp_remove_responses_older_than(md_ocsp_reg_t *reg, apr_pool_t *p, 
+                                                 apr_time_t timestamp)
+{
+    return md_store_remove_not_modified_since(reg->store, p, timestamp, 
+                                              MD_SG_OCSP, "*", "ocsp*.json");
+}
+
+typedef struct {
+    apr_pool_t *p;
+    md_ocsp_reg_t *reg;
+    int good;
+    int revoked;
+    int unknown;
+} ocsp_summary_ctx_t;
+
+static int add_to_summary(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+    ocsp_summary_ctx_t *ctx = baton;
+    md_ocsp_status_t *ostat = (md_ocsp_status_t *)val;
+    md_ocsp_cert_stat_t stat;
+    md_timeperiod_t valid;
+    
+    (void)key;
+    (void)klen;
+    ocsp_get_meta(&stat, &valid, ctx->reg, ostat, ctx->p);
+    switch (stat) {
+        case MD_OCSP_CERT_ST_GOOD: ++ctx->good; break;
+        case MD_OCSP_CERT_ST_REVOKED: ++ctx->revoked; break;
+        case MD_OCSP_CERT_ST_UNKNOWN: ++ctx->unknown; break;
+    }
+    return 1;
+}
+
+void  md_ocsp_get_summary(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+    md_json_t *json;
+    ocsp_summary_ctx_t ctx;
+    
+    memset(&ctx, 0, sizeof(ctx));
+    ctx.p = p;
+    ctx.reg = reg;
+    apr_hash_do(add_to_summary, &ctx, reg->hash);
+
+    json = md_json_create(p);
+    md_json_setl(ctx.good+ctx.revoked+ctx.unknown, json, MD_KEY_TOTAL, NULL);
+    md_json_setl(ctx.good, json, MD_KEY_GOOD, NULL);
+    md_json_setl(ctx.revoked, json, MD_KEY_REVOKED, NULL);
+    md_json_setl(ctx.unknown, json, MD_KEY_UNKNOWN, NULL);
+    *pjson = json;
+}
+
+static apr_status_t job_loadj(md_json_t **pjson, const char *name, 
+                              md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+    return md_store_load_json(reg->store, MD_SG_OCSP, name, MD_FN_JOB, pjson, p);
+}
+
+typedef struct {
+    apr_pool_t *p;
+    md_ocsp_reg_t *reg;
+    apr_array_header_t *ostats;
+} ocsp_status_ctx_t;
+
+static md_json_t *mk_jstat(md_ocsp_status_t *ostat, md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+    md_ocsp_cert_stat_t stat;
+    md_timeperiod_t valid, renewal;
+    md_json_t *json, *jobj;
+    apr_status_t rv;
+    
+    json = md_json_create(p);
+    md_json_sets(ostat->md_name, json, MD_KEY_DOMAIN, NULL);
+    md_json_sets(ostat->hexid, json, MD_KEY_ID, NULL);
+    ocsp_get_meta(&stat, &valid, reg, ostat, p);
+    md_json_sets(md_ocsp_cert_stat_name(stat), json, MD_KEY_STATUS, NULL);
+    md_json_sets(ostat->hex_sha256, json, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL);
+    md_json_sets(ostat->responder_url, json, MD_KEY_URL, NULL);
+    md_json_set_timeperiod(&valid, json, MD_KEY_VALID, NULL);
+    renewal = md_timeperiod_slice_before_end(&valid, &reg->renew_window);
+    md_json_set_time(renewal.start, json, MD_KEY_RENEW_AT, NULL);
+    if ((MD_OCSP_CERT_ST_UNKNOWN == stat) || renewal.start < apr_time_now()) {
+        /* We have no answer yet, or it should be in renew now. Add job information */
+        rv = job_loadj(&jobj, ostat->md_name, reg, p);
+        if (APR_SUCCESS == rv) {
+            md_json_setj(jobj, json, MD_KEY_RENEWAL, NULL);
+        }
+    }
+    return json;
+}
+
+static int add_ostat(void *baton, const void *key, apr_ssize_t klen, const void *val)
+{
+    ocsp_status_ctx_t *ctx = baton;
+    const md_ocsp_status_t *ostat = val;
+    
+    (void)key;
+    (void)klen;
+    APR_ARRAY_PUSH(ctx->ostats, const md_ocsp_status_t*) = ostat;
+    return 1;
+}
+
+static int md_ostat_cmp(const void *v1, const void *v2)
+{
+    int n;
+    n = strcmp((*(md_ocsp_status_t**)v1)->md_name, (*(md_ocsp_status_t**)v2)->md_name);
+    if (!n) {
+        n = strcmp((*(md_ocsp_status_t**)v1)->hexid, (*(md_ocsp_status_t**)v2)->hexid);
+    }
+    return n;
+}
+
+void md_ocsp_get_status_all(md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p)
+{
+    md_json_t *json;
+    ocsp_status_ctx_t ctx;
+    md_ocsp_status_t *ostat;
+    int i;
+    
+    memset(&ctx, 0, sizeof(ctx));
+    ctx.p = p;
+    ctx.reg = reg;
+    ctx.ostats = apr_array_make(p, (int)apr_hash_count(reg->hash), sizeof(md_ocsp_status_t*));
+    json = md_json_create(p);
+    
+    apr_hash_do(add_ostat, &ctx, reg->hash);
+    qsort(ctx.ostats->elts, (size_t)ctx.ostats->nelts, sizeof(md_json_t*), md_ostat_cmp);
+    
+    for (i = 0; i < ctx.ostats->nelts; ++i) {
+        ostat = APR_ARRAY_IDX(ctx.ostats, i, md_ocsp_status_t*);
+        md_json_addj(mk_jstat(ostat, reg, p), json, MD_KEY_OCSPS, NULL);
+    }
+    *pjson = json;
+}
+
+void md_ocsp_set_notify_cb(md_ocsp_reg_t *ocsp, md_job_notify_cb *cb, void *baton)
+{
+    ocsp->notify = cb;
+    ocsp->notify_ctx = baton;
+}
+
+md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p)
+{
+    md_job_t *job;
+    
+    job = md_job_make(p, ocsp->store, MD_SG_OCSP, mdomain);
+    md_job_set_notify_cb(job, ocsp->notify, ocsp->notify_ctx);
+    return job;
+}
diff --git a/modules/md/md_ocsp.h b/modules/md/md_ocsp.h
new file mode 100644 (file)
index 0000000..9f0c0fd
--- /dev/null
@@ -0,0 +1,66 @@
+/* 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_ocsp_h
+#define md_ocsp_h
+
+struct md_job_t;
+struct md_json_t;
+struct md_result_t;
+struct md_store_t;
+struct md_timeslice_t;
+
+typedef enum {
+    MD_OCSP_CERT_ST_UNKNOWN,
+    MD_OCSP_CERT_ST_GOOD,
+    MD_OCSP_CERT_ST_REVOKED,
+} md_ocsp_cert_stat_t;
+
+const char *md_ocsp_cert_stat_name(md_ocsp_cert_stat_t stat);
+md_ocsp_cert_stat_t md_ocsp_cert_stat_value(const char *name);
+
+typedef struct md_ocsp_reg_t md_ocsp_reg_t;
+
+apr_status_t md_ocsp_reg_make(md_ocsp_reg_t **preg, apr_pool_t *p,
+                              struct md_store_t *store, 
+                              const md_timeslice_t *renew_window,
+                              const char *user_agent, const char *proxy_url);
+
+apr_status_t md_ocsp_prime(md_ocsp_reg_t *reg, md_cert_t *x, 
+                           md_cert_t *issuer, const md_t *md);
+
+apr_status_t md_ocsp_get_status(unsigned char **pder, int *pderlen,
+                                md_ocsp_reg_t *reg, const md_cert_t *cert,
+                                apr_pool_t *p, const md_t *md);
+
+apr_status_t md_ocsp_get_meta(md_ocsp_cert_stat_t *pstat, md_timeperiod_t *pvalid,
+                              md_ocsp_reg_t *reg, const md_cert_t *cert,
+                              apr_pool_t *p, const md_t *md);
+
+apr_size_t md_ocsp_count(md_ocsp_reg_t *reg);
+
+void md_ocsp_renew(md_ocsp_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, apr_time_t *pnext_run);
+
+apr_status_t md_ocsp_remove_responses_older_than(md_ocsp_reg_t *reg, apr_pool_t *p, 
+                                                 apr_time_t timestamp);
+
+void md_ocsp_get_summary(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p);
+void md_ocsp_get_status_all(struct md_json_t **pjson, md_ocsp_reg_t *reg, apr_pool_t *p);
+
+void md_ocsp_set_notify_cb(md_ocsp_reg_t *reg, md_job_notify_cb *cb, void *baton);
+struct md_job_t *md_ocsp_job_make(md_ocsp_reg_t *ocsp, const char *mdomain, apr_pool_t *p);
+
+#endif /* md_ocsp_h */
index 0c4c9244a86a47b945187072a813e7854d242279..4092edeb6ee560ea9da465cc508aecf58a464cce 100644 (file)
@@ -46,8 +46,10 @@ struct md_reg_t {
     int can_https;
     const char *proxy_url;
     int domains_frozen;
-    const md_timeslice_t *renew_window;
-    const md_timeslice_t *warn_window;
+    md_timeslice_t *renew_window;
+    md_timeslice_t *warn_window;
+    md_job_notify_cb *notify;
+    void *notify_ctx;
 };
 
 /**************************************************************************************************/
@@ -292,11 +294,6 @@ md_t *md_reg_get(md_reg_t *reg, const char *name, apr_pool_t *p)
     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;
@@ -417,7 +414,7 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v
         return APR_ENOENT;
     }
     
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "update md %s", name);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "md[%s]: update store", name);
     
     if (do_checks && APR_SUCCESS != (rv = check_values(reg, ptemp, updates, fields))) {
         return rv;
@@ -455,11 +452,11 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v
     }
     if (MD_UPD_RENEW_WINDOW & fields) {
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update renew-window: %s", name);
-        nmd->renew_window = updates->renew_window;
+        *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;
+        *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);
@@ -489,6 +486,10 @@ 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 proto: %s", name);
         nmd->acme_tls_1_domains = updates->acme_tls_1_domains;
     }
+    if (MD_UPD_STAPLING & fields) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ptemp, "update stapling: %s", name);
+        nmd->stapling = updates->stapling;
+    }
     
     if (fields && APR_SUCCESS == (rv = md_save(reg->store, p, MD_SG_DOMAINS, nmd, 0))) {
         rv = state_init(reg, ptemp, nmd);
@@ -496,17 +497,11 @@ static apr_status_t p_md_update(void *baton, apr_pool_t *p, apr_pool_t *ptemp, v
     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)
+                           const char *name, const md_t *md, int fields, 
+                           int do_checks)
 {
-    return update_md(reg, p, name, md, fields, 1);
+    return md_util_pool_vdo(p_md_update, reg, p, name, md, fields, do_checks, NULL);
 }
 
 apr_status_t md_reg_delete_acct(md_reg_t *reg, apr_pool_t *p, const char *acct_id) 
@@ -612,16 +607,16 @@ apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile
     return APR_SUCCESS;
 }
 
-int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p) 
+apr_time_t md_reg_renew_at(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;
+    if (md->state == MD_S_INCOMPLETE) return apr_time_now();
     rv = md_reg_get_pubcert(&pub, reg, md, p);
-    if (APR_STATUS_IS_ENOENT(rv)) return 1;
+    if (APR_STATUS_IS_ENOENT(rv)) return apr_time_now();
     if (APR_SUCCESS == rv) {
         cert = APR_ARRAY_IDX(pub->certs, 0, const md_cert_t*);
         certlife.start = md_cert_get_not_before(cert);
@@ -629,16 +624,24 @@ int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p)
 
         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_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 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 renewal.start;
     }
     return 0;
 }
 
+int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p) 
+{
+    apr_time_t renew_at;
+    
+    renew_at = md_reg_renew_at(reg, md, p);
+    return renew_at && (renew_at <= apr_time_now());
+}
+
 int md_reg_should_warn(md_reg_t *reg, const md_t *md, apr_pool_t *p)
 {
     const md_pubcert_t *pub;
@@ -656,7 +659,7 @@ int md_reg_should_warn(md_reg_t *reg, const md_t *md, apr_pool_t *p)
 
         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_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, 
                           "md[%s]: cert-life[%s] warn[%s]", md->name, 
                           md_timeperiod_print(p, &certlife),
                           md_timeperiod_print(p, &warn));
@@ -669,33 +672,6 @@ int md_reg_should_warn(md_reg_t *reg, const md_t *md, apr_pool_t *p)
 /**************************************************************************************************/
 /* synching */
 
-typedef struct {
-    apr_pool_t *p;
-    apr_array_header_t *store_mds;
-} sync_ctx;
-
-static int do_add_md(void *baton, md_store_t *store, md_t *md, apr_pool_t *ptemp)
-{
-    sync_ctx *ctx = baton;
-
-    (void)store;
-    (void)ptemp;
-    APR_ARRAY_PUSH(ctx->store_mds, const md_t*) = md_clone(ctx->p, md);
-    return 1;
-}
-
-static apr_status_t read_store_mds(md_reg_t *reg, sync_ctx *ctx)
-{
-    int rv;
-    
-    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) || APR_STATUS_IS_EINVAL(rv)) {
-        rv = APR_SUCCESS;
-    }
-    return rv;
-}
-
 apr_status_t md_reg_set_props(md_reg_t *reg, apr_pool_t *p, int can_http, int can_https)
 {
     if (reg->can_http != can_http || reg->can_https != can_https) {
@@ -714,192 +690,211 @@ 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:
- * 1. Collect all defined "managed domains" (MD). It does not matter where a MD is defined. 
- *    All MDs need to be unique and have no overlaps in their domain names. 
- *    Fail the config otherwise. Also, if a vhost matches an MD, it
- *    needs to *only* have ServerAliases from that MD. There can be no more than one
- *    matching MD for a vhost. But an MD can apply to several vhosts.
- * 2. Synchronize with the persistent store. Iterate over all configured MDs and 
- *   a. create them in the store if they do not already exist, neither under the
- *      name or with a common domain.
- *   b. compare domain lists from store and config, if
- *      - store has dns name in other MD than from config, remove dns name from store def,
- *        issue WARNING.
- *      - store misses dns name from config, add dns name and update store
- *   c. compare MD acme url/protocol, update if changed
+static md_t *find_closest_match(apr_array_header_t *mds, const md_t *md)
+{
+    md_t *candidate, *m;
+    apr_size_t cand_n, n;
+    int i;
+    
+    candidate = md_get_by_name(mds, md->name);
+    if (!candidate) {
+        /* try to find an instance that contains all domain names from md */ 
+        for (i = 0; i < mds->nelts; ++i) {
+            m = APR_ARRAY_IDX(mds, i, md_t *);
+            if (md_contains_domains(m, md)) {
+                return m;
+            }
+        }
+        /* no matching name and no md in the list has all domains.
+         * We consider that managed domain as closest match that contains at least one
+         * domain name from md, ONLY if there is no other one that also has.
+         */
+        cand_n = 0;
+        for (i = 0; i < mds->nelts; ++i) {
+            m = APR_ARRAY_IDX(mds, i, md_t *);
+            n = md_common_name_count(md, m);
+            if (n > cand_n) {
+                candidate = m;
+                cand_n = n;
+            }
+        }
+    }
+    return candidate;
+}
+
+typedef struct {
+    apr_pool_t *p;
+    apr_array_header_t *master_mds;
+    apr_array_header_t *store_names;
+    apr_array_header_t *maybe_new_mds;
+    apr_array_header_t *new_mds;
+    apr_array_header_t *unassigned_mds;
+} sync_ctx_v2;
+
+static int iter_add_name(void *baton, const char *dir, const char *name, 
+                         md_store_vtype_t vtype, void *value, apr_pool_t *ptemp)
+{
+    sync_ctx_v2 *ctx = baton;
+    
+    (void)dir;
+    (void)value;
+    (void)ptemp;
+    (void)vtype;
+    APR_ARRAY_PUSH(ctx->store_names, const char*) = apr_pstrdup(ctx->p, name);
+    return APR_SUCCESS;
+}
+
+/* A better scaling version:
+ *  1. The consistency of the MDs in 'master_mds' has already been verified. E.g.
+ *     that no domain lists overlap etc.
+ *  2. All MD storage that exists will be overwritten by the settings we have.
+ *     And "exists" meaning that "store/MD_SG_DOMAINS/name" exists.
+ *  3. For MDs that have no directory in "store/MD_SG_DOMAINS", we load all MDs
+ *     outside the list of known names from MD_SG_DOMAINS. In this list, we
+ *     look for the MD with the most domain overlap. 
+ *      - if we find it, we assume this is a rename and move the old MD to the new name.
+ *      - if not, MD is completely new.
+ *  4. Any MD in store that does not match the "master_mds" will just be left as is. 
  */
-apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, 
-                         apr_array_header_t *master_mds) 
+apr_status_t md_reg_sync_start(md_reg_t *reg, apr_array_header_t *master_mds, apr_pool_t *p) 
 {
-    sync_ctx ctx;
+    sync_ctx_v2 ctx;
     apr_status_t rv;
-
-    ctx.p = ptemp;
-    ctx.store_mds = apr_array_make(ptemp, 100, sizeof(md_t *));
-    rv = read_store_mds(reg, &ctx);
+    md_t *md, *oldmd;
+    const char *name;
+    int i, idx;
     
-    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;
-        const char *common;
-        
-        for (i = 0; i < master_mds->nelts; ++i) {
-            md = APR_ARRAY_IDX(master_mds, i, md_t *);
-            
-            /* find the store md that is closest match for the configured md */
-            smd = md_find_closest_match(ctx.store_mds, md);
-            if (smd) {
-                fields = 0;
-                
-                /* 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);
-                }
-                
-                /* Make the stored domain list *exactly* the same, even if
-                 * someone only changed upper/lowercase, we'd like to persist that. */
-                if (!md_equal_domains(md, smd, 1)) {
-                    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, 
-                                 "%s: domains changed", smd->name);
-                    smd->domains = md_array_str_clone(ptemp, md->domains);
-                    fields |= MD_UPD_DOMAINS;
-                }
-                
-                /* Look for other store mds which have domains now being part of smd */
-                while (APR_SUCCESS == rv && (omd = md_get_by_dns_overlap(ctx.store_mds, md))) {
-                    /* find the name now duplicate */
-                    common = md_common_name(md, omd);
-                    assert(common);
-                    
-                    /* Is this md still configured or has it been abandoned in the config? */
-                    config_md = md_get_by_name(master_mds, omd->name);
-                    if (config_md && md_contains(config_md, common, 0)) {
-                        /* domain used in two configured mds, not allowed */
-                        rv = APR_EINVAL;
-                        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, 
-                                      "domain %s used in md %s and %s", 
-                                      common, md->name, omd->name);
-                    }
-                    else {
-                        /* remove it from the other md and update store, or, if it
-                         * is now empty, move it into the archive */
-                        omd->domains = md_array_str_remove(ptemp, omd->domains, common, 0);
-                        if (apr_is_empty_array(omd->domains)) {
-                            md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, 
-                                          "All domains of the MD %s have moved elsewhere, "
-                                          " moving it to the archive. ", omd->name);
-                            md_reg_remove(reg, ptemp, omd->name, 1); /* best effort */
-                        }
-                        else {
-                            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;
-                }
-                if (MD_SVAL_UPDATE(md, smd, ca_proto)) {
-                    smd->ca_proto = md->ca_proto;
-                    fields |= MD_UPD_CA_PROTO;
-                }
-                if (MD_SVAL_UPDATE(md, smd, ca_agreement)) {
-                    smd->ca_agreement = md->ca_agreement;
-                    fields |= MD_UPD_AGREEMENT;
-                }
-                if (MD_VAL_UPDATE(md, smd, transitive)) {
-                    smd->transitive = md->transitive;
-                    fields |= MD_UPD_TRANSITIVE;
-                }
-                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) 
-                    && !md_array_str_eq(md->contacts, smd->contacts, 0)) {
-                    smd->contacts = md->contacts;
-                    fields |= MD_UPD_CONTACTS;
-                }
-                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 
-                        || !md_array_str_eq(md->ca_challenges, smd->ca_challenges, 0)) {
-                        smd->ca_challenges = apr_array_copy(ptemp, md->ca_challenges);
-                        fields |= MD_UPD_CA_CHALLENGES;
-                    }
-                }
-                else if (smd->ca_challenges) {
-                    smd->ca_challenges = NULL;
-                    fields |= MD_UPD_CA_CHALLENGES;
-                }
-                if (!md_pkey_spec_eq(md->pkey_spec, smd->pkey_spec)) {
-                    fields |= MD_UPD_PKEY_SPEC;
-                    smd->pkey_spec = NULL;
-                    if (md->pkey_spec) {
-                        smd->pkey_spec = apr_pmemdup(p, md->pkey_spec, sizeof(md_pkey_spec_t));
-                    }
-                }
-                if (MD_VAL_UPDATE(md, smd, require_https)) {
-                    smd->require_https = md->require_https;
-                    fields |= MD_UPD_REQUIRE_HTTPS;
-                }
-                if (MD_VAL_UPDATE(md, smd, must_staple)) {
-                    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 = 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 */
-                /* 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);
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "sync MDs, start");
+     
+    ctx.p = p;
+    ctx.master_mds = master_mds;
+    ctx.store_names = apr_array_make(p, master_mds->nelts + 100, sizeof(const char*));
+    ctx.maybe_new_mds = apr_array_make(p, master_mds->nelts, sizeof(md_t*));
+    ctx.new_mds = apr_array_make(p, master_mds->nelts, sizeof(md_t*));
+    ctx.unassigned_mds = apr_array_make(p, master_mds->nelts, sizeof(md_t*));
+    
+    rv = md_store_iter_names(iter_add_name, &ctx, reg->store, p, MD_SG_DOMAINS, "*");
+    if (APR_SUCCESS != rv) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "listing existing store MD names"); 
+        goto leave;
+    }
+    
+    /* Get all MDs that are not already present in store */
+    for (i = 0; i < ctx.master_mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(ctx.master_mds, i, md_t*);
+        idx = md_array_str_index(ctx.store_names, md->name, 0, 1);
+        if (idx < 0) {
+            APR_ARRAY_PUSH(ctx.maybe_new_mds, md_t*) = md;
+            md_array_remove_at(ctx.store_names, idx);
+        }
+    }
+    
+    if (ctx.maybe_new_mds->nelts == 0) goto leave; /* none new */
+    if (ctx.store_names->nelts == 0) goto leave;   /* all new */
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                  "sync MDs, %d potentially new MDs detected, looking for renames among "
+                  "the %d unassigned store domains", (int)ctx.maybe_new_mds->nelts,
+                  (int)ctx.store_names->nelts);
+    for (i = 0; i < ctx.store_names->nelts; ++i) {
+        name = APR_ARRAY_IDX(ctx.store_names, i, const char*);
+        if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, name, &md, p)) {
+            APR_ARRAY_PUSH(ctx.unassigned_mds, md_t*) = md;
+        } 
+    }
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                  "sync MDs, %d MDs maybe new, checking store", (int)ctx.maybe_new_mds->nelts);
+    for (i = 0; i < ctx.maybe_new_mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(ctx.maybe_new_mds, i, md_t*);
+        oldmd = find_closest_match(ctx.unassigned_mds, md);
+        if (oldmd) {
+            /* found the rename, move the domains and possible staging directory */
+            md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                          "sync MDs, found MD %s under previous name %s", md->name, oldmd->name);
+            rv = md_store_rename(reg->store, p, MD_SG_DOMAINS, oldmd->name, md->name);
+            if (APR_SUCCESS != rv) {
+                md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, 
+                              "sync MDs, renaming MD %s to %s failed", oldmd->name, md->name);
+                /* ignore it? */
             }
+            md_store_rename(reg->store, p, MD_SG_STAGING, oldmd->name, md->name);
+            md_array_remove(ctx.unassigned_mds, oldmd);
+        }
+        else {
+            APR_ARRAY_PUSH(ctx.new_mds, md_t*) = md;
         }
     }
-    else {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "loading mds");
+
+leave:
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, 
+                  "sync MDs, %d existing, %d moved, %d new.", 
+                  (int)ctx.master_mds->nelts - ctx.maybe_new_mds->nelts,
+                  (int)ctx.maybe_new_mds->nelts - ctx.new_mds->nelts,
+                  (int)ctx.new_mds->nelts);
+    return rv;
+}
+
+/** 
+ * Finish synching an MD with the store. 
+ * 1. if there are changed properties (or if the MD is new), save it.
+ * 2. read any existing certificate and init the state of the memory MD
+ */
+apr_status_t md_reg_sync_finish(md_reg_t *reg, md_t *md, apr_pool_t *p, apr_pool_t *ptemp)
+{
+    md_t *old;
+    apr_status_t rv;
+    int changed = 1;
+    
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "sync MDs, finish start");
+    
+    if (!md->ca_url) {
+        md->ca_url = MD_ACME_DEF_URL;
+        md->ca_proto = MD_PROTO_ACME; 
     }
     
+    rv = state_init(reg, ptemp, md);
+    if (APR_SUCCESS != rv) goto leave;
+    
+    if (APR_SUCCESS == md_load(reg->store, MD_SG_DOMAINS, md->name, &old, ptemp)) {
+        /* Some parts are kept from old, lacking new values */
+        if ((!md->contacts || apr_is_empty_array(md->contacts)) && old->contacts) {
+            md->contacts = md_array_str_clone(p, old->contacts);
+        } 
+        if (md->ca_challenges && old->ca_challenges) {
+            if (!md_array_str_eq(md->ca_challenges, old->ca_challenges, 0)) {
+                md->ca_challenges = md_array_str_compact(p, md->ca_challenges, 0);
+            }
+        }
+        if (!md->ca_account && old->ca_account) {
+            md->ca_account = apr_pstrdup(p, old->ca_account);
+        }
+        
+        /* if everything remains the same, spare the write back */
+        if (!MD_VAL_UPDATE(md, old, state)
+            && !MD_SVAL_UPDATE(md, old, ca_url)
+            && !MD_SVAL_UPDATE(md, old, ca_proto)
+            && !MD_SVAL_UPDATE(md, old, ca_agreement)
+            && !MD_VAL_UPDATE(md, old, transitive)
+            && md_equal_domains(md, old, 1)
+            && !MD_VAL_UPDATE(md, old, renew_mode)
+            && md_timeslice_eq(md->renew_window, old->renew_window)
+            && md_timeslice_eq(md->warn_window, old->warn_window)
+            && md_pkey_spec_eq(md->pkey_spec, old->pkey_spec)
+            && !MD_VAL_UPDATE(md, old, require_https)
+            && !MD_VAL_UPDATE(md, old, must_staple)
+            && md_array_str_eq(md->acme_tls_1_domains, old->acme_tls_1_domains, 0)
+            && !MD_VAL_UPDATE(md, old, stapling)
+            && md_array_str_eq(md->contacts, old->contacts, 0)
+            && md_array_str_eq(md->ca_challenges, old->ca_challenges, 0)) {
+            changed = 0;
+        }
+    }
+    if (changed) {
+        rv = md_save(reg->store, ptemp, MD_SG_DOMAINS, md, 0);
+    }
+leave:
+    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "sync MDs, finish done");
     return rv;
 }
 
@@ -970,11 +965,14 @@ static apr_status_t run_init(void *baton, apr_pool_t *p, ...)
     md_proto_driver_t *driver, **pdriver;
     md_result_t *result;
     apr_table_t *env;
+    const char *s;
+    int preload;
     
     (void)p;
     va_start(ap, p);
     pdriver = va_arg(ap, md_proto_driver_t **);
     md = va_arg(ap, const md_t *);
+    preload = va_arg(ap, int);
     env = va_arg(ap, apr_table_t *);
     result = va_arg(ap, md_result_t *); 
     va_end(ap);
@@ -989,6 +987,11 @@ static apr_status_t run_init(void *baton, apr_pool_t *p, ...)
     driver->md = md;
     driver->can_http = reg->can_http;
     driver->can_https = reg->can_https;
+    
+    s = apr_table_get(driver->env, MD_KEY_ACTIVATION_DELAY);
+    if (!s || APR_SUCCESS != md_duration_parse(&driver->activation_delay, s, "d")) {
+        driver->activation_delay = apr_time_from_sec(MD_SECS_PER_DAY);
+    }
 
     if (!md->ca_proto) {
         md_result_printf(result, APR_EGENERAL, "CA protocol is not defined"); 
@@ -1002,7 +1005,12 @@ static apr_status_t run_init(void *baton, apr_pool_t *p, ...)
         goto leave;
     }
     
-    result->status = driver->proto->init(driver, result);
+    if (preload) {
+        result->status = driver->proto->init_preload(driver, result);
+    }
+    else {
+        result->status = driver->proto->init(driver, result);
+    }
 
 leave:
     if (APR_SUCCESS != result->status) {
@@ -1027,7 +1035,7 @@ static apr_status_t run_test_init(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
     env = va_arg(ap, apr_table_t *);
     result = va_arg(ap, md_result_t *); 
 
-    return run_init(baton, ptemp, &driver, md, env, result, NULL);
+    return run_init(baton, ptemp, &driver, md, 0, env, result, NULL);
 }
 
 apr_status_t md_reg_test_init(md_reg_t *reg, const md_t *md, struct apr_table_t *env, 
@@ -1051,7 +1059,7 @@ static apr_status_t run_renew(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_
     reset = va_arg(ap, int); 
     result = va_arg(ap, md_result_t *); 
 
-    rv = run_init(baton, ptemp, &driver, md, env, result, NULL);
+    rv = run_init(baton, ptemp, &driver, md, 0, 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;
@@ -1089,11 +1097,11 @@ static apr_status_t run_load_staging(void *baton, apr_pool_t *p, apr_pool_t *pte
     result =  va_arg(ap, md_result_t*);
     
     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);
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, ptemp, "%s: nothing staged", md->name);
         goto out;
     }
     
-    rv = run_init(baton, ptemp, &driver, md, env, result, NULL);
+    rv = run_init(baton, ptemp, &driver, md, 1, env, result, NULL);
     if (APR_SUCCESS != rv) goto out;
     
     apr_hash_set(reg->certs, md->name, (apr_ssize_t)strlen(md->name), NULL);
@@ -1102,9 +1110,10 @@ static apr_status_t run_load_staging(void *baton, apr_pool_t *p, apr_pool_t *pte
     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);
+    job = md_reg_job_make(reg, md->name, ptemp);
+    if (APR_SUCCESS == md_job_load(job)) {
+        md_job_set_group(job, MD_SG_TMP);
+        md_job_save(job, NULL, ptemp);
     }
     
     /* swap */
@@ -1118,9 +1127,13 @@ static apr_status_t run_load_staging(void *baton, apr_pool_t *p, apr_pool_t *pte
     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");
-
+    md_job_notify(job, "installed", result);
+    if (job->dirty) md_job_save(job, result, ptemp);
+    
 out:
-    md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: load done", md->name);
+    if (!APR_STATUS_IS_ENOENT(rv)) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ptemp, "%s: load done", md->name);
+    }
     return rv;
 }
 
@@ -1150,12 +1163,27 @@ leave:
     return rv;
 }
 
-void md_reg_set_renew_window_default(md_reg_t *reg, const md_timeslice_t *renew_window)
+void md_reg_set_renew_window_default(md_reg_t *reg, md_timeslice_t *renew_window)
+{
+    *reg->renew_window = *renew_window;
+}
+
+void md_reg_set_warn_window_default(md_reg_t *reg, md_timeslice_t *warn_window)
 {
-    reg->renew_window = renew_window;
+    *reg->warn_window = *warn_window;
 }
 
-void md_reg_set_warn_window_default(md_reg_t *reg, const md_timeslice_t *warn_window)
+void md_reg_set_notify_cb(md_reg_t *reg, md_job_notify_cb *cb, void *baton)
 {
-    reg->warn_window = warn_window;
+    reg->notify = cb;
+    reg->notify_ctx = baton;
+}
+
+md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p)
+{
+    md_job_t *job;
+    
+    job = md_job_make(p, reg->store, MD_SG_STAGING, mdomain);
+    md_job_set_notify_cb(job, reg->notify, reg->notify_ctx);
+    return job;
 }
index c026af1db1c9392871bace34c94fe049c6d706d3..adf2b1ef462f5734eba6435965a235b91cfd5237 100644 (file)
 
 struct apr_hash_t;
 struct apr_array_header_t;
-struct md_store_t;
 struct md_pkey_t;
 struct md_cert_t;
 struct md_result_t;
 
+#include "md_store.h"
+
 /**
  * A registry for managed domains with a md_store_t as persistence.
  *
@@ -33,10 +34,10 @@ typedef struct md_reg_t md_reg_t;
 /**
  * Create the MD registry, using the pool and store.
  */
-apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *pm, struct md_store_t *store,
+apr_status_t md_reg_create(md_reg_t **preg, apr_pool_t *pm, md_store_t *store,
                            const char *proxy_url);
 
-struct md_store_t *md_reg_store_get(md_reg_t *reg);
+md_store_t *md_reg_store_get(md_reg_t *reg);
 
 apr_status_t md_reg_set_props(md_reg_t *reg, apr_pool_t *p, int can_http, int can_https);
 
@@ -65,11 +66,6 @@ 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);
 
-/**
- * Re-compute the state of the MD, given current store contents.
- */
-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.
  */
@@ -85,21 +81,22 @@ int md_reg_do(md_reg_do_cb *cb, void *baton, md_reg_t *reg, apr_pool_t *p);
 /**
  * Bitmask for fields that are updated.
  */
-#define MD_UPD_DOMAINS       0x0001
-#define MD_UPD_CA_URL        0x0002
-#define MD_UPD_CA_PROTO      0x0004
-#define MD_UPD_CA_ACCOUNT    0x0008
-#define MD_UPD_CONTACTS      0x0010
-#define MD_UPD_AGREEMENT     0x0020
-#define MD_UPD_DRIVE_MODE    0x0080
-#define MD_UPD_RENEW_WINDOW  0x0100
-#define MD_UPD_CA_CHALLENGES 0x0200
-#define MD_UPD_PKEY_SPEC     0x0400
-#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_DOMAINS       0x00001
+#define MD_UPD_CA_URL        0x00002
+#define MD_UPD_CA_PROTO      0x00004
+#define MD_UPD_CA_ACCOUNT    0x00008
+#define MD_UPD_CONTACTS      0x00010
+#define MD_UPD_AGREEMENT     0x00020
+#define MD_UPD_DRIVE_MODE    0x00080
+#define MD_UPD_RENEW_WINDOW  0x00100
+#define MD_UPD_CA_CHALLENGES 0x00200
+#define MD_UPD_PKEY_SPEC     0x00400
+#define MD_UPD_REQUIRE_HTTPS 0x00800
+#define MD_UPD_TRANSITIVE    0x01000
+#define MD_UPD_MUST_STAPLE   0x02000
+#define MD_UPD_PROTO         0x04000
+#define MD_UPD_WARN_WINDOW   0x08000
+#define MD_UPD_STAPLING      0x10000
 #define MD_UPD_ALL           0x7FFFFFFF
 
 /**
@@ -107,7 +104,8 @@ int md_reg_do(md_reg_do_cb *cb, void *baton, md_reg_t *reg, apr_pool_t *p);
  * values from the given md, all other values remain unchanged.
  */
 apr_status_t md_reg_update(md_reg_t *reg, apr_pool_t *p, 
-                           const char *name, const md_t *md, int fields);
+                           const char *name, const md_t *md, 
+                           int fields, int check_consistency);
 
 /**
  * Get the chain of public certificates of the managed domain md, starting with the cert
@@ -127,8 +125,13 @@ apr_status_t md_reg_get_cred_files(const char **pkeyfile, const char **pcertfile
 /**
  * Synchronise the give master mds with the store.
  */
-apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, 
-                         apr_array_header_t *master_mds);
+apr_status_t md_reg_sync_start(md_reg_t *reg, apr_array_header_t *master_mds, apr_pool_t *p);
+
+/**
+ * Re-compute the state of the MD, given current store contents.
+ */
+apr_status_t md_reg_sync_finish(md_reg_t *reg, md_t *md, 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);
 
@@ -163,6 +166,12 @@ apr_status_t md_reg_freeze_domains(md_reg_t *reg, apr_array_header_t *mds);
  */
 int md_reg_should_renew(md_reg_t *reg, const md_t *md, apr_pool_t *p);
 
+/**
+ * Return the timestamp when the certificate should be renewed. A value of 0
+ * indicates that that renewal is not configured (see renew_mode).
+ */
+apr_time_t md_reg_renew_at(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 
@@ -188,17 +197,19 @@ struct md_proto_driver_t {
     struct apr_table_t *env;
 
     md_reg_t *reg;
-    struct md_store_t *store;
+    md_store_t *store;
     const char *proxy_url;
     const md_t *md;
 
     int can_http;
     int can_https;
     int reset;
+    apr_interval_time_t activation_delay;
 };
 
 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_init_preload_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);
 
@@ -206,6 +217,7 @@ struct md_proto_t {
     const char *protocol;
     md_proto_init_cb *init;
     md_proto_renew_cb *renew;
+    md_proto_init_preload_cb *init_preload;
     md_proto_preload_cb *preload;
 };
 
@@ -239,7 +251,10 @@ apr_status_t md_reg_renew(md_reg_t *reg, const md_t *md,
 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);
+void md_reg_set_renew_window_default(md_reg_t *reg, md_timeslice_t *renew_window);
+void md_reg_set_warn_window_default(md_reg_t *reg, md_timeslice_t *warn_window);
+
+void md_reg_set_notify_cb(md_reg_t *reg, md_job_notify_cb *cb, void *baton);
+struct md_job_t *md_reg_job_make(md_reg_t *reg, const char *mdomain, apr_pool_t *p);
 
 #endif /* mod_md_md_reg_h */
index 4076d5b565436fb39e22f0a4af70cd3ba8b275ae..7d8370faddb4023b55586c6a66d8bb375436da0e 100644 (file)
@@ -42,14 +42,15 @@ md_result_t *md_result_make(apr_pool_t *p, apr_status_t status)
     
     result = apr_pcalloc(p, sizeof(*result));
     result->p = p;
+    result->md_name = MD_OTHER;
     result->status = status;
     return result;
 }
 
-md_result_t *md_result_md_make(apr_pool_t *p, const struct md_t *md)
+md_result_t *md_result_md_make(apr_pool_t *p, const char *md_name)
 {
     md_result_t *result = md_result_make(p, APR_SUCCESS);
-    result->md = md;
+    result->md_name = md_name;
     return result;
 }
 
@@ -74,6 +75,7 @@ void md_result_activity_setn(md_result_t *result, const char *activity)
 {
     result->activity = activity;
     result->problem = result->detail = NULL;
+    result->subproblems = NULL;
     on_change(result);
 }
 
@@ -91,15 +93,18 @@ 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;
+    result->subproblems = NULL;
     on_change(result);
 }
 
 void md_result_problem_set(md_result_t *result, apr_status_t status,
-                           const char *problem, const char *detail)
+                           const char *problem, const char *detail, 
+                           const md_json_t *subproblems)
 {
     result->status = status;
     result->problem = dup_trim(result->p, problem);
     result->detail = apr_pstrdup(result->p, detail);
+    result->subproblems = subproblems? md_json_clone(result->p, subproblems) : NULL;
     on_change(result);
 }
 
@@ -114,6 +119,7 @@ void md_result_problem_printf(md_result_t *result, apr_status_t status,
     va_start(ap, fmt);
     result->detail = apr_pvsprintf(result->p, fmt, ap);
     va_end(ap);
+    result->subproblems = NULL;
     on_change(result);
 }
 
@@ -125,6 +131,7 @@ void md_result_printf(md_result_t *result, apr_status_t status, const char *fmt,
     va_start(ap, fmt);
     result->detail = apr_pvsprintf(result->p, fmt, ap);
     va_end(ap);
+    result->subproblems = NULL;
     on_change(result);
 }
 
@@ -146,7 +153,7 @@ md_result_t*md_result_from_json(const struct md_json_t *json, apr_pool_t *p)
     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);
-
+    result->subproblems = md_json_dupj(p, json, MD_KEY_SUBPROBLEMS, NULL);
     return result;
 }
 
@@ -169,6 +176,9 @@ struct md_json_t *md_result_to_json(const md_result_t *result, apr_pool_t *p)
         apr_rfc822_date(ts, result->ready_at);
         md_json_sets(ts, json, MD_KEY_VALID_FROM, NULL);
     }
+    if (result->subproblems) {
+        md_json_setj(result->subproblems, json, MD_KEY_SUBPROBLEMS, NULL);
+    }
     return json;
 }
 
@@ -200,6 +210,7 @@ void md_result_assign(md_result_t *dest, const md_result_t *src)
    dest->detail = src->detail;
    dest->activity = src->activity;
    dest->ready_at = src->ready_at;
+   dest->subproblems = src->subproblems;
 }
 
 void md_result_dup(md_result_t *dest, const md_result_t *src)
@@ -209,17 +220,18 @@ void md_result_dup(md_result_t *dest, const md_result_t *src)
    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;
+   dest->subproblems = src->subproblems? md_json_clone(dest->p, src->subproblems) : NULL;
    on_change(dest);
 }
 
-void md_result_log(md_result_t *result, int level)
+void md_result_log(md_result_t *result, unsigned 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);
+        if (result->md_name) {
+            msg = apr_psprintf(result->p, "md[%s]", result->md_name);
             sep = " ";
         }
         if (result->activity) {
@@ -234,6 +246,11 @@ void md_result_log(md_result_t *result, int level)
             msg = apr_psprintf(result->p, "%s%sdetail[%s]", msg, sep, result->detail);
             sep = " ";
         }
+        if (result->subproblems) {
+            msg = apr_psprintf(result->p, "%s%ssubproblems[%s]", msg, sep, 
+                md_json_writep(result->subproblems, result->p, MD_JSON_FMT_COMPACT));
+            sep = " ";
+        }
         md_log_perror(MD_LOG_MARK, (md_log_level_t)level, result->status, result->p, "%s", msg);
     }
 }
index 13c5cd2daf07f107302e3736feacc9503ae28870..58e903e08a05e4e95c4c86c9d0b50cf62d076252 100644 (file)
@@ -26,10 +26,11 @@ 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;
+    const char *md_name;
     apr_status_t status;
     const char *problem;
     const char *detail;
+    const struct md_json_t *subproblems;
     const char *activity;
     apr_time_t ready_at;
     md_result_change_cb *on_change;
@@ -37,7 +38,7 @@ struct md_result_t {
 };
 
 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);
+md_result_t *md_result_md_make(apr_pool_t *p, const char *md_name);
 void md_result_reset(md_result_t *result);
 
 void md_result_activity_set(md_result_t *result, const char *activity);
@@ -46,7 +47,8 @@ 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);
+                           const char *problem, const char *detail, 
+                           const struct md_json_t *subproblems);
 void md_result_problem_printf(md_result_t *result, apr_status_t status,
                               const char *problem, const char *fmt, ...);
 
@@ -64,7 +66,7 @@ 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_log(md_result_t *result, unsigned int level);
 
 void md_result_on_change(md_result_t *result, md_result_change_cb *cb, void *data);
 
index 4bdd508199eb9e6b0c37e2225394616c66ac4f5b..3c3d801400ba5adc30dbb2cb103de271a764ff08 100644 (file)
@@ -27,6 +27,7 @@
 #include "md.h"
 #include "md_crypt.h"
 #include "md_log.h"
+#include "md_ocsp.h"
 #include "md_store.h"
 #include "md_result.h"
 #include "md_reg.h"
 
 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_timeperiod_t valid;
     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);
+    valid.start = md_cert_get_not_before(cert);
+    valid.end = md_cert_get_not_after(cert);
+    md_json_set_timeperiod(&valid, json, MD_KEY_VALID, 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);
@@ -102,7 +102,7 @@ static apr_status_t get_staging_cert_json(md_json_t **pjson, apr_pool_t *p,
         rv = APR_SUCCESS;
         goto leave;
     }
-    else if (APR_SUCCESS != rv) {
+    else if (APR_SUCCESS != rv || certs->nelts == 0) {
         goto leave;
     }
     cert = APR_ARRAY_IDX(certs, 0, md_cert_t *);
@@ -112,52 +112,88 @@ leave:
     return rv;
 }
 
-static apr_status_t job_loadj(md_json_t **pjson, const char *name, 
-                              struct md_reg_t *reg, apr_pool_t *p)
+static apr_status_t job_loadj(md_json_t **pjson, md_store_group_t group, const char *name, 
+                              struct md_reg_t *reg, int with_log, apr_pool_t *p)
 {
+    apr_status_t rv;
+    
     md_store_t *store = md_reg_store_get(reg);
-    return md_store_load_json(store, MD_SG_STAGING, name, MD_FN_JOB, pjson, p);
+    rv = md_store_load_json(store, group, name, MD_FN_JOB, pjson, p);
+    if (APR_SUCCESS == rv && !with_log) md_json_del(*pjson, MD_KEY_LOG, NULL);
+    return rv;
 }
 
-apr_status_t md_status_get_md_json(md_json_t **pjson, const md_t *md, 
-                                   md_reg_t *reg, apr_pool_t *p)
+static apr_status_t status_get_md_json(md_json_t **pjson, const md_t *md, 
+                                       md_reg_t *reg, md_ocsp_reg_t *ocsp, 
+                                       int with_logs, apr_pool_t *p)
 {
     md_json_t *mdj, *jobj, *certj;
     int renew;
     const md_pubcert_t *pubcert;
-    const md_cert_t *cert;
+    const md_cert_t *cert = NULL;
+    md_ocsp_cert_stat_t cert_stat;
+    md_timeperiod_t ocsp_valid; 
     apr_status_t rv = APR_SUCCESS;
+    apr_time_t renew_at;
 
     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;
+        if (md->stapling && ocsp) {
+            rv = md_ocsp_get_meta(&cert_stat, &ocsp_valid, ocsp, cert, p, md);
+            if (APR_SUCCESS == rv) {
+                md_json_sets(md_ocsp_cert_stat_name(cert_stat), certj, MD_KEY_OCSP, MD_KEY_STATUS, NULL);
+                md_json_set_timeperiod(&ocsp_valid, certj, MD_KEY_OCSP, MD_KEY_VALID, NULL);
+            }
+            else if (!APR_STATUS_IS_ENOENT(rv)) goto leave;
+            rv = APR_SUCCESS;
+            if (APR_SUCCESS == job_loadj(&jobj, MD_SG_OCSP, md->name, reg, with_logs, p)) {
+                md_json_setj(jobj, certj, MD_KEY_OCSP, MD_KEY_RENEWAL, NULL);
+            }
+        }
         md_json_setj(certj, mdj, MD_KEY_CERT, NULL);
+
+        renew_at = md_reg_renew_at(reg, md, p);
+        if (renew_at) {
+            md_json_set_time(renew_at, mdj, MD_KEY_RENEW_AT, NULL);
+        }
     }
     
+    md_json_setb(md->stapling, mdj, MD_KEY_STAPLING, NULL);
+    md_json_setb(md->watched, mdj, MD_KEY_WATCHED, 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);
+        md_json_setb(renew, mdj, MD_KEY_RENEW, NULL);
+        rv = job_loadj(&jobj, MD_SG_STAGING, md->name, reg, with_logs, 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);
+            if (APR_SUCCESS == get_staging_cert_json(&certj, p, reg, md)) {
+                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;
+    if (APR_SUCCESS != rv) {
+        md_json_setl(rv, mdj, MD_KEY_ERROR, NULL);
+    }
+    *pjson = mdj;
     return rv;
 }
 
+apr_status_t md_status_get_md_json(md_json_t **pjson, const md_t *md, 
+                                   md_reg_t *reg, md_ocsp_reg_t *ocsp, apr_pool_t *p)
+{
+    return status_get_md_json(pjson, md, reg, ocsp, 1, p);
+}
+
 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_reg_t *reg, md_ocsp_reg_t *ocsp, apr_pool_t *p) 
 {
     md_json_t *json, *mdj;
-    apr_status_t rv = APR_SUCCESS;
     const md_t *md;
     int i;
     
@@ -165,33 +201,41 @@ apr_status_t md_status_get_json(md_json_t **pjson, apr_array_header_t *mds,
     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;
+        status_get_md_json(&mdj, md, reg, ocsp, 0, p);
         md_json_addj(mdj, json, MD_KEY_MDS, NULL);
     }
-leave:
-    *pjson = (APR_SUCCESS == rv)? json : NULL;
-    return rv;
+    *pjson = json;
+    return APR_SUCCESS;
 }
 
 /**************************************************************************************************/
 /* drive job persistence */
 
-md_job_t *md_job_make(apr_pool_t *p, const char *name)
+md_job_t *md_job_make(apr_pool_t *p, md_store_t *store,
+                      md_store_group_t group, const char *name)
 {
     md_job_t *job = apr_pcalloc(p, sizeof(*job));
-    job->name = apr_pstrdup(p, name);
+    job->group = group;
+    job->mdomain = apr_pstrdup(p, name);
+    job->store = store;
     job->p = p;
+    job->max_log = 128;
     return job;
 }
 
+void md_job_set_group(md_job_t *job, md_store_group_t group)
+{
+    job->group = group;
+}
+
 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->mdomain = md_json_gets(json, MD_KEY_NAME, NULL);*/
     job->finished = md_json_getb(json, MD_KEY_FINISHED, NULL);
+    job->notified = md_json_getb(json, MD_KEY_NOTIFIED, 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);
@@ -210,8 +254,9 @@ static void job_to_json(md_json_t *json, const md_job_t *job,
 {
     char ts[APR_RFC822_DATE_LEN];
 
-    md_json_sets(job->name, json, MD_KEY_NAME, NULL);
+    md_json_sets(job->mdomain, json, MD_KEY_NAME, NULL);
     md_json_setb(job->finished, json, MD_KEY_FINISHED, NULL);
+    md_json_setb(job->notified, json, MD_KEY_NOTIFIED, NULL);
     if (job->next_run > 0) {
         apr_rfc822_date(ts, job->next_run);
         md_json_sets(ts, json, MD_KEY_NEXT_RUN, NULL);
@@ -232,31 +277,27 @@ static void job_to_json(md_json_t *json, const md_job_t *job,
     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)
+apr_status_t md_job_load(md_job_t *job)
 {
-    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);
+    rv = md_store_load_json(job->store, job->group, job->mdomain, MD_FN_JOB, &jprops, job->p);
     if (APR_SUCCESS == rv) {
-        md_job_from_json(job, jprops, p);
+        md_job_from_json(job, jprops, job->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)
+apr_status_t md_job_save(md_job_t *job, 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);
+    rv = md_store_save_json(job->store, p, job->group, job->mdomain, MD_FN_JOB, jprops, 0);
+    if (APR_SUCCESS == rv) job->dirty = 0;
     return rv;
 }
 
@@ -274,6 +315,8 @@ void md_job_log_append(md_job_t *job, const char *type,
     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);
+    md_json_limita(job->max_log, job->log, MD_KEY_ENTRIES, NULL);
+    job->dirty = 1;
 }
 
 typedef struct {
@@ -301,9 +344,10 @@ md_json_t *md_job_log_get_latest(md_job_t *job, const char *type)
 
 {
     log_find_ctx ctx;
+
+    memset(&ctx, 0, sizeof(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;
 }
@@ -325,7 +369,7 @@ 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;
+    md_job_t *job;
     int i, complete, renewing, errored, ready, total;
     md_json_t *json;
 
@@ -339,14 +383,13 @@ void  md_status_take_stock(md_json_t **pjson, apr_array_header_t *mds,
             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)) {
+                    job = md_reg_job_make(reg, md->name, p);
+                    if (APR_SUCCESS == md_job_load(job)) {
+                        if (job->error_runs > 0 
+                            || (job->last_result && job->last_result->status != APR_SUCCESS)) {
                             ++errored;
                         }
-                        else if (job.finished) {
+                        else if (job->finished) {
                             ++ready;
                         }
                     }
@@ -362,3 +405,142 @@ void  md_status_take_stock(md_json_t **pjson, apr_array_header_t *mds,
     md_json_setl(ready, json, MD_KEY_READY, NULL);
     *pjson = json;
 }
+
+typedef struct {
+    apr_pool_t *p;
+    md_job_t *job;
+    md_store_t *store;
+    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 (ctx->store && apr_time_as_msec(now - ctx->last_save) > 500) {
+                md_job_save(ctx->job, result, ctx->p);
+                ctx->last_save = now;
+            }
+        }
+    }
+}
+
+static void job_observation_start(md_job_t *job, md_result_t *result, md_store_t *store)
+{
+    md_job_result_ctx *ctx;
+
+    if (job->observing) md_result_on_change(job->observing, NULL, NULL);
+    job->observing = result;
+    
+    ctx = apr_pcalloc(result->p, sizeof(*ctx));
+    ctx->p = result->p;
+    ctx->job = job;
+    ctx->store = store;
+    ctx->last = md_result_md_make(result->p, APR_SUCCESS);
+    md_result_assign(ctx->last, result);
+    md_result_on_change(result, job_result_update, ctx);
+}
+
+static void job_observation_end(md_job_t *job)
+{
+    if (job->observing) md_result_on_change(job->observing, NULL, NULL);
+    job->observing = NULL; 
+} 
+
+void md_job_start_run(md_job_t *job, md_result_t *result, md_store_t *store)
+{
+    job->fatal_error = 0;
+    job->last_run = apr_time_now();
+    job_observation_start(job, result, store);
+    md_job_log_append(job, "starting", NULL, NULL);
+}
+
+apr_time_t md_job_delay_on_errors(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;
+}
+
+void md_job_end_run(md_job_t *job, md_result_t *result)
+{
+    if (APR_SUCCESS == result->status) {
+        job->finished = 1;
+        job->valid_from = result->ready_at;
+        job->error_runs = 0;
+        job->dirty = 1;
+        md_job_log_append(job, "finished", NULL, NULL);
+    }
+    else {
+        ++job->error_runs;
+        job->dirty = 1;
+        job->next_run = apr_time_now() + md_job_delay_on_errors(job->error_runs);
+    }
+    job_observation_end(job);
+}
+
+void md_job_retry_at(md_job_t *job, apr_time_t later)
+{
+    job->next_run = later;
+    job->dirty = 1;
+}
+
+apr_status_t md_job_notify(md_job_t *job, const char *reason, md_result_t *result)
+{
+    if (job->notify) return job->notify(job, reason, result, job->p, job->notify_ctx);
+    job->dirty = 1;
+    if (APR_SUCCESS == result->status) {
+        job->notified = 1;
+        job->error_runs = 0;
+    }
+    else {
+        ++job->error_runs;
+        job->next_run = apr_time_now() + md_job_delay_on_errors(job->error_runs);
+    }
+    return result->status;
+}
+
+void md_job_holler(md_job_t *job, const char *reason)
+{
+    md_result_t *result;
+    
+    if (job->notify) {
+        result = md_result_make(job->p, APR_SUCCESS);
+        job->notify(job, reason, result, job->p, job->notify_ctx);
+    }
+}
+
+void md_job_set_notify_cb(md_job_t *job, md_job_notify_cb *cb, void *baton)
+{
+    job->notify = cb;
+    job->notify_ctx = baton;
+}
index c74d680573572d02a7a6dbefbd2d4f9e25f4174b..ac25ccda7deb1553169d1d1536a244ac1e7b7296 100644 (file)
 struct md_json_t;
 struct md_reg_t;
 struct md_result_t;
+struct md_ocsp_reg_t;
+
+#include "md_store.h"
 
 /** 
  * 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);
+                                   struct md_reg_t *reg, struct md_ocsp_reg_t *ocsp,
+                                   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);
+                                struct md_reg_t *reg, struct md_ocsp_reg_t *ocsp,
+                                apr_pool_t *p);
 
 /**
  * Take stock of all MDs given for a short overview. The JSON returned
@@ -41,38 +46,50 @@ apr_status_t md_status_get_json(struct md_json_t **pjson, apr_array_header_t *md
 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 */
+    md_store_group_t group;/* group where job is persisted */
+    const char *mdomain;   /* Name of the MD this job is about */
+    md_store_t *store;     /* store where it is persisted */
     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 */
+    int notified;          /* true iff notifications were handled 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 */
+    int fatal_error;       /* a fatal error is remedied by retrying */
     md_json_t *log;        /* array of log objects with minimum fields
-                              MD_KEY_WHEN (timestamp) and MD_KEY_TYPE (string) */   
+                              MD_KEY_WHEN (timestamp) and MD_KEY_TYPE (string) */
+    apr_size_t max_log;    /* max number of log entries, new ones replace oldest */
+    int dirty;
+    struct md_result_t *observing;
+    
+    md_job_notify_cb *notify;
+    void *notify_ctx;
 };
 
 /**
- * Create a new job instance for the given MD name. Job load/save will work
- * on the MD_SG_STAGING for the name.
+ * Create a new job instance for the given MD name. 
+ * Job load/save will work using the name.
  */
-md_job_t *md_job_make(apr_pool_t *p, const char *name);
+md_job_t *md_job_make(apr_pool_t *p, md_store_t *store, 
+                      md_store_group_t group, const char *name);
+
+void md_job_set_group(md_job_t *job, md_store_group_t group);
 
 /**
- * Update the job from storage in <group>/job->name.
+ * Update the job from storage in <group>/job->mdomain.
  */
-apr_status_t md_job_load(md_job_t *job, struct md_reg_t *reg, 
-                         md_store_group_t group, apr_pool_t *p);
+apr_status_t md_job_load(md_job_t *job);
 
 /**
- * Update storage from job in <group>/job->name.
+ * Update storage from job in <group>/job->mdomain.
  */
-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);
+apr_status_t md_job_save(md_job_t *job, struct md_result_t *result, apr_pool_t *p);
 
 /**
  * Append to the job's log. Timestamp is automatically added.
@@ -94,4 +111,16 @@ md_json_t *md_job_log_get_latest(md_job_t *job, const char *type);
  */
 apr_time_t md_job_log_get_time_of_latest(md_job_t *job, const char *type);
 
+void md_job_start_run(md_job_t *job, struct md_result_t *result, md_store_t *store);
+void md_job_end_run(md_job_t *job, struct md_result_t *result);
+void md_job_retry_at(md_job_t *job, apr_time_t later);
+
+/* Given the number of errors encountered, recommend a delay for the next attempt */
+apr_time_t md_job_delay_on_errors(int err_count);
+
+void md_job_set_notify_cb(md_job_t *job, md_job_notify_cb *cb, void *baton);
+apr_status_t md_job_notify(md_job_t *job, const char *reason, struct md_result_t *result);
+/* Same as notify but without checks on success and no change to job */
+void md_job_holler(md_job_t *job, const char *reason);
+
 #endif /* md_status_h */
index c66890eec0694b05880aaa0dcb038a9a6a0bd5a3..ad5bb28cf2d72a3430adf6af3a8bb311bee1e1d5 100644 (file)
@@ -55,22 +55,18 @@ static const char *GROUP_NAME[] = {
     "staging",
     "archive",
     "tmp",
+    "ocsp",
     NULL
 };
 
-const char *md_store_group_name(int group)
+const char *md_store_group_name(unsigned int group)
 {
-    if ((size_t)group < sizeof(GROUP_NAME)/sizeof(GROUP_NAME[0])) {
+    if (group < sizeof(GROUP_NAME)/sizeof(GROUP_NAME[0])) {
         return GROUP_NAME[group];
     }
     return "UNKNOWN";
 }
 
-void md_store_destroy(md_store_t *store)
-{
-    if (store->destroy) store->destroy(store);
-}
-
 apr_status_t md_store_load(md_store_t *store, md_store_group_t group, 
                            const char *name, const char *aspect, 
                            md_store_vtype_t vtype, void **pdata, 
@@ -145,12 +141,33 @@ 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_time_t md_store_get_modified(md_store_t *store, md_store_group_t group,  
+                                 const char *name, const char *aspect, apr_pool_t *p)
+{
+    return store->get_modified(store, group, 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);
 }
 
+apr_status_t md_store_remove_not_modified_since(md_store_t *store, apr_pool_t *p, 
+                                                apr_time_t modified,
+                                                md_store_group_t group, 
+                                                const char *name, 
+                                                const char *aspect)
+{
+    return store->remove_nms(store, p, modified, group, name, aspect);
+}
+
+apr_status_t md_store_rename(md_store_t *store, apr_pool_t *p,
+                             md_store_group_t group, const char *name, const char *to)
+{
+    return store->rename(store, p, group, name, to);
+}
+
 /**************************************************************************************************/
 /* convenience */
 
index 7dec9f39fe8c01a631dfcc2689265c70df0c8b51..dfe9f32d6deb89664826637f597c9a0e6ba018c6 100644 (file)
@@ -21,107 +21,183 @@ struct apr_array_header_t;
 struct md_cert_t;
 struct md_pkey_t;
 
-typedef struct md_store_t md_store_t;
-
-typedef void md_store_destroy_cb(md_store_t *store);
-
-const char *md_store_group_name(int group);
+const char *md_store_group_name(unsigned int group);
 
+typedef struct md_store_t md_store_t;
 
-typedef apr_status_t md_store_load_cb(md_store_t *store, md_store_group_t group, 
-                                      const char *name, const char *aspect, 
-                                      md_store_vtype_t vtype, void **pvalue, 
-                                      apr_pool_t *p);
-typedef apr_status_t md_store_save_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
-                                      const char *name, const char *aspect, 
-                                      md_store_vtype_t vtype, void *value, 
-                                      int create);
-typedef apr_status_t md_store_remove_cb(md_store_t *store, md_store_group_t group, 
-                                        const char *name, const char *aspect,  
-                                        apr_pool_t *p, int force);
-typedef apr_status_t md_store_purge_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
-                                        const char *name);
-
-typedef int md_store_inspect(void *baton, const char *name, const char *aspect, 
-                             md_store_vtype_t vtype, void *value, apr_pool_t *ptemp);
-
-typedef apr_status_t md_store_iter_cb(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);
-
-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);
-
-typedef apr_status_t md_store_get_fname_cb(const char **pfname, 
-                                           md_store_t *store, md_store_group_t group, 
-                                           const char *name, const char *aspect, 
-                                           apr_pool_t *p);
-
-typedef int md_store_is_newer_cb(md_store_t *store, 
-                                 md_store_group_t group1, md_store_group_t group2,  
-                                 const char *name, const char *aspect, apr_pool_t *p);
-
-struct md_store_t {
-    md_store_destroy_cb *destroy;
-
-    md_store_save_cb *save;
-    md_store_load_cb *load;
-    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;
-};
-
-void md_store_destroy(md_store_t *store);
+/**
+ * A store for domain related data.
+ *
+ * The Key for a piece of data is the set of 3 items
+ *   <group> + <domain> + <aspect>
+ *
+ * Examples:
+ * "domains" + "greenbytes.de" + "pubcert.pem"
+ * "ocsp" + "greenbytes.de" + "ocsp-XXXXX.json"
+ *
+ * Storage groups are pre-defined, domain and aspect names can be freely chosen.
+ *
+ * Groups reflect use cases and come with security restrictions. The groups 
+ * DOMAINS, ARCHIVE and NONE are only accessible during the startup 
+ * phase of httpd.
+ *
+ * Private key are stored unencrypted only in restricted groups. Meaning that certificate
+ * keys in group DOMAINS are not encrypted, but only readable at httpd start/reload.
+ * Keys in unrestricted groups are encrypted using a pass phrase generated once and stored
+ * in NONE.
+ */
 
+/** Value types handled by a store */
+typedef enum {
+    MD_SV_TEXT,         /* plain text, value is (char*) */
+    MD_SV_JSON,         /* JSON serialization, value is (md_json_t*) */
+    MD_SV_CERT,         /* PEM x509 certificate, value is (md_cert_t*) */
+    MD_SV_PKEY,         /* PEM private key, value is (md_pkey_t*) */
+    MD_SV_CHAIN,        /* list of PEM x509 certificates, value is 
+                           (apr_array_header_t*) of (md_cert*) */
+} md_store_vtype_t;
+
+/** Store storage groups */
+typedef enum {
+    MD_SG_NONE,         /* top level of store, name MUST be NULL in calls */
+    MD_SG_ACCOUNTS,     /* ACME accounts */
+    MD_SG_CHALLENGES,   /* challenge response data for a domain */ 
+    MD_SG_DOMAINS,      /* live certificates and settings for a domain */
+    MD_SG_STAGING,      /* staged set of certificate and settings, maybe incomplete */
+    MD_SG_ARCHIVE,      /* Archived live sets of a domain */
+    MD_SG_TMP,          /* temporary domain storage */
+    MD_SG_OCSP,         /* OCSP stapling related domain data */
+    MD_SG_COUNT,        /* number of storage groups, used in setups */
+} md_store_group_t;
+
+#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_HTTPD_JSON        "httpd.json"
+
+#define MD_FN_FALLBACK_PKEY     "fallback-privkey.pem"
+#define MD_FN_FALLBACK_CERT     "fallback-cert.pem"
+
+/**
+ * Load the JSON value at key "group/name/aspect", allocated from pool p.
+ * @return APR_ENOENT if there is no such value
+ */
 apr_status_t md_store_load_json(md_store_t *store, md_store_group_t group, 
                                 const char *name, const char *aspect, 
                                 struct md_json_t **pdata, apr_pool_t *p);
+/**
+ * Save the JSON value at key "group/name/aspect". If create != 0, fail if there
+ * already is a value for this key.
+ */
 apr_status_t md_store_save_json(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                                 const char *name, const char *aspect, 
                                 struct md_json_t *data, int create);
 
-
+/**
+ * Load the value of type at key "group/name/aspect", allocated from pool p. Usually, the
+ * type is expected to be the same as used in saving the value. Some conversions will work,
+ * others will fail the format.
+ * @return APR_ENOENT if there is no such value
+ */
 apr_status_t md_store_load(md_store_t *store, md_store_group_t group, 
                            const char *name, const char *aspect, 
                            md_store_vtype_t vtype, void **pdata, 
                            apr_pool_t *p);
+/**
+ * Save the JSON value at key "group/name/aspect". If create != 0, fail if there
+ * already is a value for this key. The provided data MUST be of the correct type.
+ */
 apr_status_t md_store_save(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
                            const char *name, const char *aspect, 
                            md_store_vtype_t vtype, void *data, 
                            int create);
+
+/**
+ * Remove the value stored at key "group/name/aspect". Unless force != 0, a missing
+ * value will cause the call to fail with APR_ENOENT.
+ */ 
 apr_status_t md_store_remove(md_store_t *store, md_store_group_t group, 
                              const char *name, const char *aspect, 
                              apr_pool_t *p, int force);
+/**
+ * Remove everything matching key "group/name".
+ */ 
 apr_status_t md_store_purge(md_store_t *store, apr_pool_t *p, 
                             md_store_group_t group, const char *name);
 
+/**
+ * Remove all items matching the name/aspect patterns that have not been
+ * modified since the given timestamp.
+ */
+apr_status_t md_store_remove_not_modified_since(md_store_t *store, apr_pool_t *p, 
+                                                apr_time_t modified,
+                                                md_store_group_t group, 
+                                                const char *name, 
+                                                const char *aspect);
+
+/**
+ * inspect callback function. Invoked for each matched value. Values allocated from
+ * ptemp may disappear any time after the call returned. If this function returns
+ * 0, the iteration is aborted. 
+ */
+typedef int md_store_inspect(void *baton, const char *name, const char *aspect, 
+                             md_store_vtype_t vtype, void *value, apr_pool_t *ptemp);
 
+/**
+ * Iterator over all existing values matching the name pattern. Patterns are evaluated
+ * using apr_fnmatch() without flags.
+ */
 apr_status_t md_store_iter(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);
 
+/**
+ * Move everything matching key "from/name" from one group to another. If archive != 0,
+ * move any existing "to/name" into a new "archive/new_name" location.
+ */
 apr_status_t md_store_move(md_store_t *store, apr_pool_t *p,
                            md_store_group_t from, md_store_group_t to,
                            const char *name, int archive);
 
+/**
+ * Rename a group member.
+ */
+apr_status_t md_store_rename(md_store_t *store, apr_pool_t *p,
+                             md_store_group_t group, const char *name, const char *to);
+
+/**
+ * Get the filename of an item stored in "group/name/aspect". The item does
+ * not have to exist.
+ */
 apr_status_t md_store_get_fname(const char **pfname, 
                                 md_store_t *store, md_store_group_t group, 
                                 const char *name, const char *aspect, 
                                 apr_pool_t *p);
 
+/**
+ * Make a compare on the modification time of "group1/name/aspect" vs. "group2/name/aspect".
+ */
 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);
 
+/**
+ * Iterate over all names that exist in a group, e.g. there are items matching
+ * "group/pattern". The inspect function is called with the name and NULL aspect
+ * and value.
+ */
 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);
 
+/**
+ * Get the modification time of the item store under "group/name/aspect".
+ * @return modification time or 0 if the item does not exist.
+ */
+apr_time_t md_store_get_modified(md_store_t *store, md_store_group_t group,  
+                                 const char *name, const char *aspect, apr_pool_t *p);
+
+
 
 /**************************************************************************************************/
 /* Storage handling utils */
@@ -153,5 +229,66 @@ apr_status_t md_pubcert_save(md_store_t *store, apr_pool_t *p,
                              md_store_group_t group, const char *name, 
                              struct apr_array_header_t *pubcert, int create);
 
+/**************************************************************************************************/
+/* implementation interface */
+
+typedef apr_status_t md_store_load_cb(md_store_t *store, md_store_group_t group, 
+                                      const char *name, const char *aspect, 
+                                      md_store_vtype_t vtype, void **pvalue, 
+                                      apr_pool_t *p);
+typedef apr_status_t md_store_save_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
+                                      const char *name, const char *aspect, 
+                                      md_store_vtype_t vtype, void *value, 
+                                      int create);
+typedef apr_status_t md_store_remove_cb(md_store_t *store, md_store_group_t group, 
+                                        const char *name, const char *aspect,  
+                                        apr_pool_t *p, int force);
+typedef apr_status_t md_store_purge_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
+                                        const char *name);
+
+typedef apr_status_t md_store_iter_cb(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);
+
+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);
+
+typedef apr_status_t md_store_rename_cb(md_store_t *store, apr_pool_t *p, md_store_group_t group, 
+                                        const char *from, const char *to);
+
+typedef apr_status_t md_store_get_fname_cb(const char **pfname, 
+                                           md_store_t *store, md_store_group_t group, 
+                                           const char *name, const char *aspect, 
+                                           apr_pool_t *p);
+
+typedef int md_store_is_newer_cb(md_store_t *store, 
+                                 md_store_group_t group1, md_store_group_t group2,  
+                                 const char *name, const char *aspect, apr_pool_t *p);
+
+typedef apr_time_t md_store_get_modified_cb(md_store_t *store, md_store_group_t group,  
+                                            const char *name, const char *aspect, apr_pool_t *p);
+
+typedef apr_status_t md_store_remove_nms_cb(md_store_t *store, apr_pool_t *p, 
+                                            apr_time_t modified, md_store_group_t group, 
+                                            const char *name, const char *aspect);
+
+struct md_store_t {
+    md_store_save_cb *save;
+    md_store_load_cb *load;
+    md_store_remove_cb *remove;
+    md_store_move_cb *move;
+    md_store_rename_cb *rename;
+    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;
+    md_store_get_modified_cb *get_modified;
+    md_store_remove_nms_cb *remove_nms;
+};
+
 
 #endif /* mod_md_md_store_h */
index 04c7e6055793123f2b019b6d4aeb909dffd79e07..6553fdd706f904fa23e719058c6f4fb17cf867c2 100644 (file)
@@ -55,8 +55,7 @@ struct md_store_fs_t {
     md_store_fs_cb *event_cb;
     void *event_baton;
     
-    const unsigned char *key;
-    apr_size_t key_len;
+    md_data_t key;
     int plain_pkey[MD_SG_COUNT];
     
     int port_80;
@@ -78,9 +77,14 @@ static apr_status_t fs_remove(md_store_t *store, md_store_group_t group,
                               apr_pool_t *p, int force);
 static apr_status_t fs_purge(md_store_t *store, apr_pool_t *p, 
                              md_store_group_t group, const char *name);
+static apr_status_t fs_remove_nms(md_store_t *store, apr_pool_t *p, 
+                                  apr_time_t modified, md_store_group_t group, 
+                                  const char *name, const char *aspect);
 static apr_status_t fs_move(md_store_t *store, apr_pool_t *p, 
                             md_store_group_t from, md_store_group_t to, 
                             const char *name, int archive);
+static apr_status_t fs_rename(md_store_t *store, apr_pool_t *p, 
+                            md_store_group_t group, const char *from, const char *to);
 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);
@@ -94,23 +98,25 @@ static apr_status_t fs_get_fname(const char **pfname,
 static int fs_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,  
                        const char *name, const char *aspect, apr_pool_t *p);
 
+static apr_time_t fs_get_modified(md_store_t *store, md_store_group_t group,  
+                                  const char *name, const char *aspect, apr_pool_t *p);
+
 static apr_status_t init_store_file(md_store_fs_t *s_fs, const char *fname, 
                                     apr_pool_t *p, apr_pool_t *ptemp)
 {
     md_json_t *json = md_json_create(p);
     const char *key64;
-    unsigned char *key;
     apr_status_t rv;
     
     md_json_setn(MD_STORE_VERSION, json, MD_KEY_STORE, MD_KEY_VERSION, NULL);
 
-    s_fs->key_len = FS_STORE_KLEN;
-    s_fs->key = key = apr_pcalloc(p, FS_STORE_KLEN);
-    if (APR_SUCCESS != (rv = md_rand_bytes(key, s_fs->key_len, p))) {
+    s_fs->key.len = FS_STORE_KLEN;
+    s_fs->key.data = apr_pcalloc(p, FS_STORE_KLEN);
+    if (APR_SUCCESS != (rv = md_rand_bytes((unsigned char*)s_fs->key.data, s_fs->key.len, p))) {
         return rv;
     }
         
-    key64 = md_util_base64url_encode((char *)key, s_fs->key_len, ptemp);
+    key64 = md_util_base64url_encode(&s_fs->key, ptemp);
     md_json_sets(key64, json, MD_KEY_KEY, NULL);
     rv = md_json_fcreatex(json, ptemp, MD_JSON_FMT_INDENT, fname, MD_FPROT_F_UONLY);
     memset((char*)key64, 0, strlen(key64));
@@ -193,7 +199,7 @@ static apr_status_t read_store_file(md_store_fs_t *s_fs, const char *fname,
                                     apr_pool_t *p, apr_pool_t *ptemp)
 {
     md_json_t *json;
-    const char *key64, *key;
+    const char *key64;
     apr_status_t rv;
     double store_version;
     
@@ -214,11 +220,10 @@ static apr_status_t read_store_file(md_store_fs_t *s_fs, const char *fname,
             return APR_EINVAL;
         }
         
-        s_fs->key_len = md_util_base64url_decode(&key, key64, p);
-        s_fs->key = (const unsigned char*)key;
-        if (s_fs->key_len != FS_STORE_KLEN) {
+        md_util_base64url_decode(&s_fs->key, key64, p);
+        if (s_fs->key.len != FS_STORE_KLEN) {
             md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "key length unexpected: %" APR_SIZE_T_FMT, 
-                          s_fs->key_len);
+                          s_fs->key.len);
             return APR_EINVAL;
         }
 
@@ -279,11 +284,14 @@ apr_status_t md_store_fs_init(md_store_t **pstore, apr_pool_t *p, const char *pa
     s_fs->s.save = fs_save;
     s_fs->s.remove = fs_remove;
     s_fs->s.move = fs_move;
+    s_fs->s.rename = fs_rename;
     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;
+    s_fs->s.get_modified = fs_get_modified;
+    s_fs->s.remove_nms = fs_remove_nms;
     
     /* by default, everything is only readable by the current user */ 
     s_fs->def_perms.dir = MD_FPROT_D_UONLY;
@@ -298,6 +306,9 @@ apr_status_t md_store_fs_init(md_store_t **pstore, apr_pool_t *p, const char *pa
     /* challenges dir and files are readable by all, no secrets involved */ 
     s_fs->group_perms[MD_SG_CHALLENGES].dir = MD_FPROT_D_UALL_WREAD;
     s_fs->group_perms[MD_SG_CHALLENGES].file = MD_FPROT_F_UALL_WREAD;
+    /* OCSP data is readable by all, no secrets involved */ 
+    s_fs->group_perms[MD_SG_OCSP].dir = MD_FPROT_D_UALL_WREAD;
+    s_fs->group_perms[MD_SG_OCSP].file = MD_FPROT_F_UALL_WREAD;
 
     s_fs->base = apr_pstrdup(p, path);
     
@@ -392,8 +403,8 @@ static void get_pass(const char **ppass, apr_size_t *plen,
         *plen = 0;
     }
     else {
-        *ppass = (const char *)s_fs->key;
-        *plen = s_fs->key_len;
+        *ppass = (const char *)s_fs->key.data;
+        *plen = s_fs->key.len;
     }
 }
  
@@ -522,7 +533,6 @@ static apr_status_t pfs_is_newer(void *baton, apr_pool_t *p, apr_pool_t *ptemp,
     return rv;
 }
 
 static int fs_is_newer(md_store_t *store, md_store_group_t group1, md_store_group_t group2,  
                        const char *name, const char *aspect, apr_pool_t *p)
 {
@@ -537,6 +547,44 @@ static int fs_is_newer(md_store_t *store, md_store_group_t group1, md_store_grou
     return 0;
 }
 
+static apr_status_t pfs_get_modified(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+    md_store_fs_t *s_fs = baton;
+    const char *fname, *name, *aspect;
+    md_store_group_t group;
+    apr_finfo_t inf;
+    apr_time_t *pmtime;
+    apr_status_t rv;
+    
+    (void)p;
+    group = (md_store_group_t)va_arg(ap, int);
+    name = va_arg(ap, const char*);
+    aspect = va_arg(ap, const char*);
+    pmtime = va_arg(ap, apr_time_t*);
+    
+    *pmtime = 0;
+    if (   MD_OK(fs_get_fname(&fname, &s_fs->s, group, name, aspect, ptemp))
+        && MD_OK(apr_stat(&inf, fname, APR_FINFO_MTIME, ptemp))) {
+        *pmtime = inf.mtime;
+    }
+
+    return rv;
+}
+
+static apr_time_t fs_get_modified(md_store_t *store, md_store_group_t group,  
+                                  const char *name, const char *aspect, apr_pool_t *p)
+{
+    md_store_fs_t *s_fs = FS_STORE(store);
+    apr_time_t mtime;
+    apr_status_t rv;
+    
+    rv = md_util_pool_vdo(pfs_get_modified, s_fs, p, group, name, aspect, &mtime, NULL);
+    if (APR_SUCCESS == rv) {
+        return mtime;
+    }
+    return 0;
+}
 static apr_status_t pfs_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
 {
     md_store_fs_t *s_fs = baton;
@@ -700,6 +748,7 @@ typedef struct {
     md_store_inspect *inspect;
     const char *dirname;
     void *baton;
+    apr_time_t ts;
 } inspect_ctx;
 
 static apr_status_t insp(void *baton, apr_pool_t *p, apr_pool_t *ptemp, 
@@ -796,6 +845,66 @@ static apr_status_t fs_iterate_names(md_store_inspect *inspect, void *baton, md_
     return rv;
 }
 
+static apr_status_t remove_nms_file(void *baton, apr_pool_t *p, apr_pool_t *ptemp, 
+                                    const char *dir, const char *name, apr_filetype_e ftype)
+{
+    inspect_ctx *ctx = baton;
+    const char *fname;
+    apr_finfo_t inf;
+    apr_status_t rv = APR_SUCCESS;
+
+    (void)p;
+    if (APR_DIR == ftype) goto leave;
+    if (APR_SUCCESS != (rv = md_util_path_merge(&fname, ptemp, dir, name, NULL))) goto leave;
+    if (APR_SUCCESS != (rv = apr_stat(&inf, fname, APR_FINFO_MTIME, ptemp))) goto leave;
+    if (inf.mtime >= ctx->ts) goto leave;
+
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, ptemp, "remove_nms file: %s/%s", dir, name);
+    rv = apr_file_remove(fname, ptemp);
+
+leave:
+    return rv;
+}
+
+static apr_status_t remove_nms_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, "remove_nms 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(remove_nms_file, ctx, p, fpath, ctx->aspect, NULL);
+        if (APR_STATUS_IS_ENOENT(rv)) {
+            rv = APR_SUCCESS;
+        }
+    } 
+    return rv;
+}
+
+static apr_status_t fs_remove_nms(md_store_t *store, apr_pool_t *p, 
+                                  apr_time_t modified, md_store_group_t group, 
+                                  const char *name, const char *aspect)
+{
+    const char *groupname;
+    apr_status_t rv;
+    inspect_ctx ctx;
+    
+    ctx.s_fs = FS_STORE(store);
+    ctx.group = group;
+    ctx.pattern = name;
+    ctx.aspect = aspect;
+    ctx.ts = modified;
+    groupname = md_store_group_name(group);
+
+    rv = md_util_files_do(remove_nms_dir, &ctx, p, ctx.s_fs->base, groupname, name, NULL);
+    
+    return rv;
+}
+
 /**************************************************************************************************/
 /* moving */
 
@@ -926,3 +1035,38 @@ static apr_status_t fs_move(md_store_t *store, apr_pool_t *p,
     md_store_fs_t *s_fs = FS_STORE(store);
     return md_util_pool_vdo(pfs_move, s_fs, p, from, to, name, archive, NULL);
 }
+
+static apr_status_t pfs_rename(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+    md_store_fs_t *s_fs = baton;
+    const char *group_name, *from_dir, *to_dir;
+    md_store_group_t group;
+    const char *from, *to;
+    apr_status_t rv;
+    
+    (void)p;
+    group = (md_store_group_t)va_arg(ap, int);
+    from = va_arg(ap, const char*);
+    to = va_arg(ap, const char*);
+    
+    group_name = md_store_group_name(group);
+    if (   !MD_OK(md_util_path_merge(&from_dir, ptemp, s_fs->base, group_name, from, NULL))
+        || !MD_OK(md_util_path_merge(&to_dir, ptemp, s_fs->base, group_name, to, NULL))) {
+        goto out;
+    }
+    
+    if (APR_SUCCESS != (rv = apr_file_rename(from_dir, to_dir, ptemp))) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, ptemp, "rename from %s to %s", 
+                      from_dir, to_dir);
+        goto out;
+    }
+out:
+    return rv;
+}
+
+static apr_status_t fs_rename(md_store_t *store, apr_pool_t *p, 
+                            md_store_group_t group, const char *from, const char *to)
+{
+    md_store_fs_t *s_fs = FS_STORE(store);
+    return md_util_pool_vdo(pfs_rename, s_fs, p, group, from, to, NULL);
+}
index 4f494b3eff6b67148aa0e7d3de48031bbe090d66..8076d5be468a272b52ec2ef316d07bdbfea69118 100644 (file)
@@ -44,6 +44,13 @@ int md_timeperiod_has_ended(const md_timeperiod_t *period, apr_time_t time)
     return (time >= period->start) && (time <= period->end);
 }
 
+apr_interval_time_t md_timeperiod_remaining(const md_timeperiod_t *period, apr_time_t time)
+{
+    if (time < period->start) return md_timeperiod_length(period);
+    if (time < period->end) return period->end - time;
+    return 0;
+}
+
 char *md_timeperiod_print(apr_pool_t *p, const md_timeperiod_t *period)
 {
     char tstart[APR_RFC822_DATE_LEN];
@@ -54,31 +61,38 @@ char *md_timeperiod_print(apr_pool_t *p, const md_timeperiod_t *period)
     return apr_pstrcat(p, tstart, " - ", tend, NULL);
 }
 
-const char *md_duration_print(apr_pool_t *p, apr_interval_time_t duration)
+static const char *duration_print(apr_pool_t *p, int roughly, 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);
     
+    s = roughly? "~" : "";
     if (days > 0) {
-        s = apr_psprintf(p, "%ld days", days);
-        sep = " "; 
+        s = apr_psprintf(p, "%s%ld days", s, days);
+        if (roughly) return s;
+        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); 
+            s = apr_psprintf(p, "%s%s%d hours", s, sep, hours); 
+        if (roughly) return s;
             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); 
+                s = apr_psprintf(p, "%s%s%d minutes", s, sep, minutes); 
+                if (roughly) return s;
+                sep = " "; 
             }
             if (rem > 0) {
-                s = apr_psprintf(p, "%s%s%02d seconds", s, sep, rem); 
+                s = apr_psprintf(p, "%s%s%d seconds", s, sep, rem); 
+                if (roughly) return s;
+                sep = " "; 
             }
         }
     }
@@ -91,6 +105,16 @@ const char *md_duration_print(apr_pool_t *p, apr_interval_time_t duration)
     return s;
 }
 
+const char *md_duration_print(apr_pool_t *p, apr_interval_time_t duration)
+{
+    return duration_print(p, 0, duration);
+}
+
+const char *md_duration_roughly(apr_pool_t *p, apr_interval_time_t duration)
+{
+    return duration_print(p, 1, duration);
+}
+
 static const char *duration_format(apr_pool_t *p, apr_interval_time_t duration)
 {
     const char *s = "0";
@@ -127,6 +151,11 @@ static const char *duration_format(apr_pool_t *p, apr_interval_time_t duration)
     return s;
 }
 
+const char *md_duration_format(apr_pool_t *p, apr_interval_time_t duration)
+{
+    return duration_format(p, duration);
+}
+
 apr_status_t md_duration_parse(apr_interval_time_t *ptimeout, const char *value, 
                                const char *def_unit)
 {
@@ -203,7 +232,7 @@ static apr_status_t percentage_parse(const char *value, int *ppercent)
     return APR_EINVAL;
 }
 
-apr_status_t md_timeslice_create(const md_timeslice_t **pts, apr_pool_t *p,
+apr_status_t md_timeslice_create(md_timeslice_t **pts, apr_pool_t *p,
                                  apr_interval_time_t norm, apr_interval_time_t len)
 {
     md_timeslice_t *ts;
@@ -215,7 +244,7 @@ apr_status_t md_timeslice_create(const md_timeslice_t **pts, apr_pool_t *p,
     return APR_SUCCESS;
 }
 
-const char *md_timeslice_parse(const md_timeslice_t **pts, apr_pool_t *p, 
+const char *md_timeslice_parse(md_timeslice_t **pts, apr_pool_t *p, 
                                const char *val, apr_interval_time_t norm)
 {
     md_timeslice_t *ts;
index 80ba9d93dbbc2e8473bb8317922dc955a5e3ac07..88e28b20cb8735b23dcf0ecfc6e0daf8fd76db33 100644 (file)
 #define MD_SECS_PER_HOUR      (60*60)
 #define MD_SECS_PER_DAY       (24*MD_SECS_PER_HOUR)
 
-typedef struct {
+typedef struct md_timeperiod_t md_timeperiod_t;
+
+struct md_timeperiod_t {
     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);
+apr_interval_time_t md_timeperiod_remaining(const md_timeperiod_t *period, apr_time_t time);
 
 char *md_timeperiod_print(apr_pool_t *p, const md_timeperiod_t *period);
 
@@ -39,6 +42,7 @@ 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);
+const char *md_duration_roughly(apr_pool_t *p, apr_interval_time_t duration);
 
 /**
  * Parse a machine readable string duration in the form of NN[unit], where
@@ -46,18 +50,19 @@ const char *md_duration_print(apr_pool_t *p, apr_interval_time_t duration);
  */
 apr_status_t md_duration_parse(apr_interval_time_t *ptimeout, const char *value, 
                                const char *def_unit);
+const char *md_duration_format(apr_pool_t *p, apr_interval_time_t duration);
 
 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_status_t md_timeslice_create(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 *md_timeslice_parse(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);
 
index 0b5e1920332510e7ee7d57966ff24f6a46a710a5..6bf65e175dfcc6ca85d6199e737b2e505a0ccf1b 100644 (file)
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
  
+#include <assert.h>
 #include <stdio.h>
 
 #include <apr_lib.h>
@@ -70,9 +71,9 @@ apr_status_t md_util_pool_vdo(md_util_vaction *cb, void *baton, apr_pool_t *p, .
 /**************************************************************************************************/
 /* data chunks */
 
-md_data *md_data_create(apr_pool_t *p, const char *data, apr_size_t len)
+md_data_t *md_data_create(apr_pool_t *p, const char *data, apr_size_t len)
 {
-    md_data *d;
+    md_data_t *d;
     
     d = apr_palloc(p, sizeof(*d));
     d->len = len;
@@ -80,6 +81,22 @@ md_data *md_data_create(apr_pool_t *p, const char *data, apr_size_t len)
     return d;
 }
 
+md_data_t *md_data_make(apr_pool_t *p, apr_size_t len)
+{
+    md_data_t *d;
+    
+    d = apr_palloc(p, sizeof(*d));
+    d->len = len;
+    d->data = apr_pcalloc(p, len);
+    return d;
+}
+
+void md_data_assign_pcopy(md_data_t *dest, const md_data_t *src, apr_pool_t *p)
+{
+    dest->data = (src->data && src->len)? apr_pmemdup(p, src->data, src->len) : NULL;
+    dest->len = dest->data? src->len : 0;
+}
+
 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", 
@@ -100,7 +117,7 @@ static const char * const hex_const[] = {
 };
 
 apr_status_t md_data_to_hex(const char **phex, char separator,
-                            apr_pool_t *p, const md_data *data)
+                            apr_pool_t *p, const md_data_t *data)
 {
     char *hex, *cp;
     const char * x;
@@ -121,6 +138,47 @@ apr_status_t md_data_to_hex(const char **phex, char separator,
     return APR_SUCCESS;
 }
 
+/**************************************************************************************************/
+/* generic arrays */
+
+int md_array_remove_at(struct apr_array_header_t *a, int idx)
+{
+    char *ps, *pe;
+
+    if (idx < 0 || idx >= a->nelts) return 0;
+    if (idx+1 == a->nelts) {
+        --a->nelts;
+    }
+    else {
+        ps = (a->elts + (idx * a->elt_size));
+        pe = ps + a->elt_size;
+        memmove(ps, pe, (a->nelts - (idx+1)) * a->elt_size);
+        --a->nelts;
+    }
+    return 1;
+}
+
+int md_array_remove(struct apr_array_header_t *a, void *elem)
+{
+    int i, n, m;
+    void **pe;
+    
+    assert(sizeof(void*) == a->elt_size);
+    n = i = 0;
+    while (i < a->nelts) {
+        pe = &APR_ARRAY_IDX(a, i, void*);
+        if (*pe == elem) {
+            m = a->nelts - (i+1);
+            if (m > 0) memmove(pe, pe+1, (unsigned)m*sizeof(void*));
+            a->nelts--;
+            n++;
+            continue;
+        }
+        ++i;
+    }
+    return n;
+}
+
 /**************************************************************************************************/
 /* string related */
 
@@ -325,7 +383,7 @@ apr_status_t md_util_freplace(const char *fpath, apr_fileperms_t perms, apr_pool
 creat:
     while (i < max && APR_EEXIST == (rv = md_util_fcreatex(&f, tmp, perms, p))) {
         ++i;
-        apr_sleep(apr_time_msec(50));
+        apr_sleep(apr_time_from_msec(50));
     } 
     if (APR_EEXIST == rv 
         && APR_SUCCESS == (rv = apr_file_remove(tmp, p))
@@ -503,7 +561,7 @@ static apr_status_t match_and_do(md_util_fwalk_t *ctx, const char *path, int dep
                           "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");
+                              "need to go deeper");
                 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);
@@ -1023,7 +1081,7 @@ static const unsigned char BASE64URL_CHARS[] = {
 
 #define BASE64URL_CHAR(x)    BASE64URL_CHARS[ (unsigned int)(x) & 0x3fu ]
    
-apr_size_t md_util_base64url_decode(const char **decoded, const char *encoded, 
+apr_size_t md_util_base64url_decode(md_data_t *decoded, const char *encoded, 
                                     apr_pool_t *pool)
 {
     const unsigned char *e = (const unsigned char *)encoded;
@@ -1037,10 +1095,10 @@ apr_size_t md_util_base64url_decode(const char **decoded, const char *encoded,
     }
     len = (int)(p - e);
     mlen = (len/4)*4;
-    *decoded = apr_pcalloc(pool, (apr_size_t)len + 1);
+    decoded->data = apr_pcalloc(pool, (apr_size_t)len + 1);
     
     i = 0;
-    d = (unsigned char*)*decoded;
+    d = (unsigned char*)decoded->data;
     for (; i < mlen; i += 4) {
         n = ((BASE64URL_UINT6[ e[i+0] ] << 18) +
              (BASE64URL_UINT6[ e[i+1] ] << 12) +
@@ -1069,14 +1127,15 @@ apr_size_t md_util_base64url_decode(const char **decoded, const char *encoded,
         default: /* do nothing */
             break;
     }
-    return (apr_size_t)(mlen/4*3 + remain);
+    decoded->len = (apr_size_t)(mlen/4*3 + remain);
+    return decoded->len; 
 }
 
-const char *md_util_base64url_encode(const char *data, apr_size_t dlen, apr_pool_t *pool)
+const char *md_util_base64url_encode(const md_data_t *data, apr_pool_t *pool)
 {
-    int i, len = (int)dlen;
-    apr_size_t slen = ((dlen+2)/3)*4 + 1; /* 0 terminated */
-    const unsigned char *udata = (const unsigned char*)data;
+    int i, len = (int)data->len;
+    apr_size_t slen = ((data->len+2)/3)*4 + 1; /* 0 terminated */
+    const unsigned char *udata = (const unsigned char*)data->data;
     unsigned char *enc, *p = apr_pcalloc(pool, slen);
     
     enc = p;
index bb2667f2d0ceabfaf68855773b8dc10398fb74da..0473cdf4cbc8fa819fa2a40c42ccfd9f07805ab3 100644 (file)
@@ -35,16 +35,36 @@ 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 {
+typedef struct md_data_t md_data_t;
+struct md_data_t {
     const char *data;
     apr_size_t len;
 };
 
-md_data *md_data_create(apr_pool_t *p, const char *data, apr_size_t len);
+#define MD_DATA_CWRAP(d, buffer)       md_data_t d = { buffer, sizeof(buffer) }
+
+md_data_t *md_data_make(apr_pool_t *p, apr_size_t len);
+md_data_t *md_data_create(apr_pool_t *p, const char *data, apr_size_t len);
+
+void md_data_assign_pcopy(md_data_t *dest, const md_data_t *src, apr_pool_t *p);
 
 apr_status_t md_data_to_hex(const char **phex, char separator,
-                            apr_pool_t *p, const md_data *data);
+                            apr_pool_t *p, const md_data_t *data);
+
+/**************************************************************************************************/
+/* generic arrays */
+
+/**
+ * In an array of pointers, remove all entries == elem. Returns the number
+ * of entries removed.
+ */
+int md_array_remove(struct apr_array_header_t *a, void *elem);
+
+/* 
+ * Remove the ith entry from the array.
+ * @return != 0 iff an entry was removed, e.g. idx was not outside range 
+ */
+int md_array_remove_at(struct apr_array_header_t *a, int idx);
 
 /**************************************************************************************************/
 /* string related */
@@ -173,9 +193,8 @@ apr_status_t md_text_freplace(const char *fpath, apr_fileperms_t perms,
 
 /**************************************************************************************************/
 /* base64 url encodings */
-const char *md_util_base64url_encode(const char *data, 
-                                     apr_size_t len, apr_pool_t *pool);
-apr_size_t md_util_base64url_decode(const char **decoded, const char *encoded, 
+const char *md_util_base64url_encode(const md_data_t *data, apr_pool_t *pool);
+apr_size_t md_util_base64url_decode(md_data_t *decoded, const char *encoded, 
                                     apr_pool_t *pool);
 
 /**************************************************************************************************/
index d7ebed23b66ad27c17d869a094b91447747582fc..ebc4375cbf03ed5748b61ceaca600aab9c4605c0 100644 (file)
@@ -27,7 +27,7 @@
  * @macro
  * Version number of the md module as c string
  */
-#define MOD_MD_VERSION "2.0.10"
+#define MOD_MD_VERSION "2.2.0"
 
 /**
  * @macro
@@ -35,7 +35,7 @@
  * release. This is a 24 bit number with 8 bits for major number, 8 bits
  * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
  */
-#define MOD_MD_VERSION_NUM 0x02000a
+#define MOD_MD_VERSION_NUM 0x020200
 
 #define MD_ACME_DEF_URL    "https://acme-v02.api.letsencrypt.org/directory"
 
index 154a08ef11c8e7af60fe4fd25de2c10fe1d4327c..538336f0b94925621daa5043f5816be892b30901 100644 (file)
 #include "md_store.h"
 #include "md_store_fs.h"
 #include "md_log.h"
+#include "md_ocsp.h"
 #include "md_result.h"
 #include "md_reg.h"
+#include "md_status.h"
 #include "md_util.h"
 #include "md_version.h"
 #include "md_acme.h"
@@ -47,6 +49,7 @@
 #include "mod_md.h"
 #include "mod_md_config.h"
 #include "mod_md_drive.h"
+#include "mod_md_ocsp.h"
 #include "mod_md_os.h"
 #include "mod_md_status.h"
 #include "mod_ssl_openssl.h"
@@ -129,7 +132,92 @@ static void init_setups(apr_pool_t *p, server_rec *base_server)
 }
 
 /**************************************************************************************************/
-/* store & registry setup */
+/* notification handling */
+
+typedef struct {
+    const char *reason;         /* what the notification is about */
+    apr_time_t min_interim;     /* minimum time between notifying for this reason */
+} notify_rate;
+
+static notify_rate notify_rates[] = {
+    { "renewed", apr_time_from_sec(28 * MD_SECS_PER_DAY) }, /* once per month */
+    { "installed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */
+    { "expiring", apr_time_from_sec(MD_SECS_PER_DAY) },     /* once per day */
+    { "errored", apr_time_from_sec(MD_SECS_PER_HOUR) },     /* once per hour */
+    { "ocsp-renewed", apr_time_from_sec(MD_SECS_PER_DAY) }, /* once per day */
+    { "ocsp-errored", apr_time_from_sec(MD_SECS_PER_HOUR) }, /* once per hour */
+};
+
+static apr_status_t notify(md_job_t *job, const char *reason, 
+                           md_result_t *result, apr_pool_t *p, void *baton)
+{
+    md_mod_conf_t *mc = baton;
+    const char * const *argv;
+    const char *cmdline;
+    int exit_code;
+    apr_status_t rv = APR_SUCCESS;
+    apr_time_t min_interim = 0;
+    md_timeperiod_t since_last;
+    const char *log_msg_reason;
+    int i;
+    
+    log_msg_reason = apr_psprintf(p, "message-%s", reason);
+    for (i = 0; i < (int)(sizeof(notify_rates)/sizeof(notify_rates[0])); ++i) {
+        if (!strcmp(reason, notify_rates[i].reason)) {
+            min_interim = notify_rates[i].min_interim;
+        }
+    }
+    if (min_interim > 0) {
+        since_last.start = md_job_log_get_time_of_latest(job, log_msg_reason);
+        since_last.end = apr_time_now();
+        if (md_timeperiod_length(&since_last) < min_interim) {
+            /* not enough time has passed since we sent the last notification
+             * for this reason. */
+            return APR_SUCCESS;
+        }
+    }
+    
+    if (!strcmp("renewed", reason)) {
+        if (mc->notify_cmd) {
+            cmdline = apr_psprintf(p, "%s %s", mc->notify_cmd, job->mdomain); 
+            apr_tokenize_to_argv(cmdline, (char***)&argv, p);
+            rv = md_util_exec(p, argv[0], argv, &exit_code);
+            
+            if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL;
+            if (APR_SUCCESS != rv) {
+                md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10108)), 
+                                         "MDNotifyCmd %s failed with exit code %d.", 
+                                         mc->notify_cmd, exit_code);
+                md_result_log(result, MD_LOG_ERR);
+                md_job_log_append(job, "notify-error", result->problem, result->detail);
+                return rv;
+            }
+        }
+        md_log_perror(MD_LOG_MARK, MD_LOG_NOTICE, 0, p, APLOGNO(10059) 
+                     "The Managed Domain %s has been setup and changes "
+                     "will be activated on next (graceful) server restart.", job->mdomain);
+    }
+    if (mc->message_cmd) {
+        cmdline = apr_psprintf(p, "%s %s %s", mc->message_cmd, reason, job->mdomain); 
+        apr_tokenize_to_argv(cmdline, (char***)&argv, p);
+        rv = md_util_exec(p, argv[0], argv, &exit_code);
+        
+        if (APR_SUCCESS == rv && exit_code) rv = APR_EGENERAL;
+        if (APR_SUCCESS != rv) {
+            md_result_problem_printf(result, rv, MD_RESULT_LOG_ID(APLOGNO(10109)), 
+                                     "MDMessageCmd %s failed with exit code %d.", 
+                                     mc->message_cmd, exit_code);
+            md_result_log(result, MD_LOG_ERR);
+            md_job_log_append(job, "message-error", reason, result->detail);
+            return rv;
+        }
+    }
+    md_job_log_append(job, log_msg_reason, NULL, NULL);
+    return APR_SUCCESS;
+}
+
+/**************************************************************************************************/
+/* store setup */
 
 static apr_status_t store_file_ev(void *baton, struct md_store_t *store,
                                     md_store_fs_ev_t ev, unsigned int group, 
@@ -150,6 +238,7 @@ static apr_status_t store_file_ev(void *baton, struct md_store_t *store,
         switch (group) {
             case MD_SG_CHALLENGES:
             case MD_SG_STAGING:
+            case MD_SG_OCSP:
                 rv = md_make_worker_accessible(fname, p);
                 if (APR_ENOTIMPL != rv) {
                     return rv;
@@ -185,19 +274,21 @@ static apr_status_t setup_store(md_store_t **pstore, md_mod_conf_t *mc,
     
     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;
+        goto leave;
     }
 
     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))
+        || APR_SUCCESS != (rv = check_group_dir(*pstore, MD_SG_OCSP, p, s))
         ) {
         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10047) 
                      "setup challenges directory");
+        goto leave;
     }
     
-out:
+leave:
     return rv;
 }
 
@@ -245,15 +336,20 @@ static void merge_srv_config(md_t *md, md_srv_conf_t *base_sc, apr_pool_t *p)
     if (md->must_staple < 0) {
         md->must_staple = md_config_geti(md->sc, MD_CONFIG_MUST_STAPLE);
     }
+    if (md->stapling < 0) {
+        md->stapling = md_config_geti(md->sc, MD_CONFIG_STAPLING);
+    }
 }
 
-static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s, apr_pool_t *p)
+static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s, 
+                                   int *pupdates, apr_pool_t *p)
 {
     if (md_contains(md, domain, 0)) {
         return APR_SUCCESS;
     }
     else if (md->transitive) {
         APR_ARRAY_PUSH(md->domains, const char*) = apr_pstrdup(p, domain);
+        *pupdates |= MD_UPD_DOMAINS;
         return APR_SUCCESS;
     }
     else {
@@ -266,40 +362,27 @@ static apr_status_t check_coverage(md_t *md, const char *domain, server_rec *s,
     }
 }
 
-static apr_status_t md_covers_server(md_t *md, server_rec *s, apr_pool_t *p)
+static apr_status_t md_cover_server(md_t *md, server_rec *s, int *pupdates, apr_pool_t *p)
 {
     apr_status_t rv;
     const char *name;
     int i;
     
-    if (APR_SUCCESS == (rv = check_coverage(md, s->server_hostname, s, p)) && s->names) {
-        for (i = 0; i < s->names->nelts; ++i) {
+    if (APR_SUCCESS == (rv = check_coverage(md, s->server_hostname, s, pupdates, p))) {
+        ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, 
+                     "md[%s]: auto add, covers name %s", md->name, s->server_hostname);
+        for (i = 0; s->names && i < s->names->nelts; ++i) {
             name = APR_ARRAY_IDX(s->names, i, const char*);
-            if (APR_SUCCESS != (rv = check_coverage(md, name, s, p))) {
+            if (APR_SUCCESS != (rv = check_coverage(md, name, s, pupdates, p))) {
                 break;
             }
+            ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, 
+                         "md[%s]: auto add, covers alias %s", md->name, name);
         }
     }
     return rv;
 }
 
-static int matches_port_somewhere(server_rec *s, int port)
-{
-    server_addr_rec *sa;
-    
-    for (sa = s->addrs; sa; sa = sa->next) {
-        if (sa->host_port == port) {
-            /* host_addr might be general (0.0.0.0) or specific, we count this as match */
-            return 1;
-        }
-        if (sa->host_port == 0) {
-            /* wildcard port, answers to all ports. Rare, but may work. */
-            return 1;
-        }
-    }
-    return 0;
-}
-
 static int uses_port(server_rec *s, int port)
 {
     server_addr_rec *sa;
@@ -317,61 +400,94 @@ static int uses_port(server_rec *s, int port)
     return match;
 }
 
-static apr_status_t detect_supported_ports(md_mod_conf_t *mc, server_rec *s, 
-                                           apr_pool_t *p, int log_level)
+static apr_status_t detect_supported_protocols(md_mod_conf_t *mc, server_rec *s, 
+                                               apr_pool_t *p, int log_level)
 {
     ap_listen_rec *lr;
     apr_sockaddr_t *sa;
+    int can_http, can_https;
 
-    mc->can_http = 0;
-    mc->can_https = 0;
+    if (mc->can_http >= 0 && mc->can_https >= 0) goto set_and_leave;
+    
+    can_http = 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;
+                can_http = 1;
             }
             else if (sa->port == mc->local_443
                      && (!lr->protocol || !strncmp("http", lr->protocol, 4))) {
-                mc->can_https = 1;
+                can_https = 1;
             }
         }
     }
-
+    if (mc->can_http < 0) mc->can_http = can_http; 
+    if (mc->can_https < 0) mc->can_https = can_https;
     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);
+                 "server seems%s reachable via http: and%s reachable via https:",
+                 mc->can_http? "" : " not", mc->can_https? "" : " not");
+set_and_leave:
     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)
+static server_rec *get_public_https_server(md_t *md, const char *domain, server_rec *base_server)
 {
     md_srv_conf_t *sc;
     md_mod_conf_t *mc;
     server_rec *s;
     request_rec r;
+    int i;
 
     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;
+    if (!mc->can_https) return NULL;
+    /* find an ssl server matching domain from MD */
+    for (s = base_server; s; s = s->next) {
+        sc = md_config_get(s);
+        if (!sc || !sc->is_ssl || !sc->assigned) continue;
+        if (base_server == s && !mc->manage_base_server) continue;
+        if (base_server != s && mc->local_443 > 0 && !uses_port(s, mc->local_443)) continue;
+        for (i = 0; i < sc->assigned->nelts; ++i) {
+            if (md == APR_ARRAY_IDX(sc->assigned, i, md_t*)) {
+                r.server = s;
+                if (ap_matches_request_vhost(&r, domain, s->port)) {
+                    return s;
+                }
+            }
         }
     }
     return NULL;
 }
 
+static apr_status_t auto_add_domains(md_t *md, server_rec *base_server, apr_pool_t *p)
+{
+    md_srv_conf_t *sc;
+    server_rec *s;
+    apr_status_t rv = APR_SUCCESS;
+    int updates;
+    
+    /* Ad all domain names used in SSL VirtualHosts, if not already there */
+    ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, base_server, 
+                 "md[%s]: auto add domains", md->name);
+    updates = 0;
+    for (s = base_server; s; s = s->next) {
+        sc = md_config_get(s);
+        if (!sc || !sc->is_ssl || !sc->assigned || sc->assigned->nelts != 1) continue;
+        if (md != APR_ARRAY_IDX(sc->assigned, 0, md_t*)) continue;
+        if (APR_SUCCESS != (rv = md_cover_server(md, s, &updates, p))) {
+            return rv;
+        }
+    }
+    return rv;
+}
+
 static void init_acme_tls_1_domains(md_t *md, server_rec *base_server)
 {
+    md_srv_conf_t *sc;
+    md_mod_conf_t *mc;
     server_rec *s;
     int i;
     const char *domain;
@@ -379,10 +495,16 @@ static void init_acme_tls_1_domains(md_t *md, server_rec *base_server)
     /* 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);
+    sc = md_config_get(base_server);
+    mc = sc->mc;    
+    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))) {
+        s = get_public_https_server(md, domain, base_server);
+        /* If we did not find a specific virtualhost for md and manage
+         * the base_server, that one is inspected */
+        if (NULL == s && mc->manage_base_server) s = base_server;
+        if (NULL == s) {
             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10168)
                          "%s: no https server_rec found for %s", md->name, domain);
             continue;
@@ -398,15 +520,13 @@ static void init_acme_tls_1_domains(md_t *md, server_rec *base_server)
 }
 
 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)
+                                       apr_pool_t *p)
 {
-    server_rec *s, *s_https;
+    server_rec *s;
     request_rec r;
     md_srv_conf_t *sc;
-    apr_status_t rv = APR_SUCCESS;
     int i;
-    const char *domain;
-    apr_array_header_t *servers;
+    const char *domain, *uri;
     
     sc = md_config_get(base_server);
 
@@ -414,8 +534,6 @@ static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *
      * is an assigned MD not equal this one, the configuration is in error.
      */
     memset(&r, 0, sizeof(r));
-    servers = apr_array_make(ptemp, 5, sizeof(server_rec*));
-    
     for (s = base_server; s; s = s->next) {
         if (!mc->manage_base_server && s == base_server) {
             /* we shall not assign ourselves to the base server */
@@ -429,65 +547,15 @@ static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *
             if (ap_matches_request_vhost(&r, domain, s->port)) {
                 /* Create a unique md_srv_conf_t record for this server, if there is none yet */
                 sc = md_config_get_unique(s, p);
+                if (!sc->assigned) sc->assigned = apr_array_make(p, 2, sizeof(md_t*));
                 
+                APR_ARRAY_PUSH(sc->assigned, md_t*) = md;
                 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10041)
-                             "Server %s:%d matches md %s (config %s)", 
-                             s->server_hostname, s->port, md->name, sc->name);
-                
-                if (sc->assigned == md) {
-                    /* already matched via another domain name */
-                    goto next_server;
-                }
-                else if (sc->assigned) {
-                    ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10042)
-                                 "conflict: MD %s matches server %s, but MD %s also matches.",
-                                 md->name, s->server_hostname, sc->assigned->name);
-                    return APR_EINVAL;
-                }
-                
-                /* If this server_rec is only for http: requests. Defined
-                 * 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(s, mc->local_80)) {
-                    if (APR_SUCCESS != (rv = md_covers_server(md, s, p))) {
-                        return rv;
-                    }
-                }
-
-                sc->assigned = md;
-                APR_ARRAY_PUSH(servers, server_rec*) = s;
-                
-                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10043)
-                             "Managed Domain %s applies to vhost %s:%d", md->name,
-                             s->server_hostname, s->port);
+                             "Server %s:%d matches md %s (config %s) for domain %s, "
+                             "has now %d MDs", 
+                             s->server_hostname, s->port, md->name, sc->name,
+                             domain, (int)sc->assigned->nelts);
                 
-                goto next_server;
-            }
-        }
-    next_server:
-        continue;
-    }
-
-    if (APR_SUCCESS == rv) {
-        if (apr_is_empty_array(servers)) {
-            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);
-                APR_ARRAY_PUSH(mc->unused_names, const char*)  = md->name;
-            }
-        }
-        else {
-            const char *uri;
-            
-            /* Found matching server_rec's. Collect all 'ServerAdmin's into MD's contact list */
-            apr_array_clear(md->contacts);
-            for (i = 0; i < servers->nelts; ++i) {
-                s = APR_ARRAY_IDX(servers, i, server_rec*);
                 if (s->server_admin && strcmp(DEFAULT_ADMIN, s->server_admin)) {
                     uri = md_util_schemify(p, s->server_admin, "mailto");
                     if (md_array_str_index(md->contacts, uri, 0, 0) < 0) {
@@ -496,50 +564,14 @@ static apr_status_t link_md_to_servers(md_mod_conf_t *mc, md_t *md, server_rec *
                                      "%s: added contact %s", md->name, uri);
                     }
                 }
+                break;
             }
-            
-            if (md->require_https > MD_REQUIRE_OFF) {
-                /* We require https for this MD, but do we have port 443 (or a mapped one)
-                 * available? */
-                if (mc->local_443 <= 0) {
-                    ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10105)
-                                 "MDPortMap says there is no port for https (443), "
-                                 "but MD %s is configured to require https. This "
-                                 "only works when a 443 port is available.", md->name);
-                    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;
+    return APR_SUCCESS;
 }
 
-static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s, 
-                                            apr_pool_t *p, apr_pool_t *ptemp)
+static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
 {
     int i;
     md_t *md;
@@ -548,7 +580,7 @@ static apr_status_t link_mds_to_servers(md_mod_conf_t *mc, server_rec *s,
     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))) {
+        if (APR_SUCCESS != (rv = link_md_to_servers(mc, md, s, p))) {
             goto leave;
         }
     }
@@ -562,7 +594,7 @@ static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p,
     md_srv_conf_t *base_conf;
     md_t *md, *omd;
     const char *domain;
-    const md_timeslice_t *ts;
+    md_timeslice_t *ts;
     apr_status_t rv = APR_SUCCESS;
     int i, j;
 
@@ -612,8 +644,6 @@ static apr_status_t merge_mds_with_conf(md_mod_conf_t *mc, apr_pool_t *p,
             return APR_EINVAL;
         }
 
-        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 "
@@ -635,7 +665,7 @@ static void load_staged_data(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
     
     for (i = 0; i < mc->mds->nelts; ++i) {
         md = APR_ARRAY_IDX(mc->mds, i, md_t *);
-        result = md_result_md_make(p, md);
+        result = md_result_md_make(p, md->name);
         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);
@@ -647,39 +677,84 @@ static void load_staged_data(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
     }
 }
 
-static apr_status_t reinit_mds(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+static apr_status_t check_invalid_duplicates(server_rec *base_server)
 {
-    md_t *md; 
-    apr_status_t rv = APR_SUCCESS;
-    int i;
+    server_rec *s;
+    md_srv_conf_t *sc;
     
-    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;
+    ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, base_server, 
+                 "cecking duplicate ssl assignments");
+    for (s = base_server; s; s = s->next) {
+        sc = md_config_get(s);
+        if (!sc || !sc->assigned) continue;
+        
+        if (sc->assigned->nelts > 1 && sc->is_ssl) {
+            /* duplicate assignment to SSL VirtualHost, not allowed */
+            ap_log_error(APLOG_MARK, APLOG_ERR, 0, base_server, APLOGNO(10042)
+                         "conflict: %d MDs match to SSL VirtualHost %s, there can at most be one.",
+                         (int)sc->assigned->nelts, s->server_hostname);
+            return APR_EINVAL;
+        }
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t check_usage(md_mod_conf_t *mc, md_t *md, server_rec *base_server, 
+                                apr_pool_t *p, apr_pool_t *ptemp)
+{
+    server_rec *s;
+    md_srv_conf_t *sc;
+    apr_status_t rv = APR_SUCCESS;
+    int i, has_ssl;
+    apr_array_header_t *servers;
+
+    (void)p;
+    servers = apr_array_make(ptemp, 5, sizeof(server_rec*));
+    has_ssl = 0;
+    for (s = base_server; s; s = s->next) {
+        sc = md_config_get(s);
+        if (!sc || !sc->assigned) continue;
+        for (i = 0; i < sc->assigned->nelts; ++i) {
+            if (md == APR_ARRAY_IDX(sc->assigned, i, md_t*)) {
+                APR_ARRAY_PUSH(servers, server_rec*) = s;
+                if (sc->is_ssl) has_ssl = 1;
+            }
+        }
+    }
+
+    if (!has_ssl && md->require_https > MD_REQUIRE_OFF) {
+        /* We require https for this MD, but do we have a SSL vhost? */
+        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, base_server, APLOGNO(10105)
+                     "MD %s does not match any VirtualHost with 'SSLEngine on', "
+                     "but is configured to require https. This cannot work.", md->name);
+    }
+    if (apr_is_empty_array(servers)) {
+        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);
+            APR_ARRAY_PUSH(mc->unused_names, const char*)  = md->name;
         }
     }
     return rv;
 }
 
-static void init_watched_names(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *ptemp, server_rec *s)
+static int init_cert_watch_status(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *ptemp, server_rec *s)
 {
-    const md_t *md;
+    md_t *md;
     md_result_t *result;
-    int i;
+    int i, count;
     
     /* 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'
      */
+    count = 0;
     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 = APR_ARRAY_IDX(mc->mds, i, md_t*);
         md_result_set(result, APR_SUCCESS, NULL);
-
+        md->watched = 0;
         if (md->state == MD_S_ERROR) {
             md_result_set(result, APR_EGENERAL, 
                           "in error state, unable to drive forward. This "
@@ -704,8 +779,10 @@ static void init_watched_names(md_mod_conf_t *mc, apr_pool_t *p, apr_pool_t *pte
             }
         }
         
-        APR_ARRAY_PUSH(mc->watched_names, const char *) = md->name; 
+        md->watched = 1;
+        ++count;
     }
+    return count;
 }   
 
 static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
@@ -755,11 +832,21 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10072) "setup md registry");
         goto leave;
     }
+    md_reg_set_notify_cb(mc->reg, notify, mc);
 
+    /* renew on 30% remaining /*/
+    rv = md_ocsp_reg_make(&mc->ocsp, p, store, mc->ocsp_renew_window,
+                          AP_SERVER_BASEVERSION, mc->proxy_url);
+    if (APR_SUCCESS != rv) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10196) "setup ocsp registry");
+        goto leave;
+    }
+    md_ocsp_set_notify_cb(mc->ocsp, notify, mc);
+    
     init_ssl();
 
     /* How to bootstrap this module:
-     * 1. find out if we know where http: and https: requests will arrive
+     * 1. find out if we know if http: and/or 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.
@@ -778,25 +865,63 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
      * 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;
+    if (APR_SUCCESS != (rv = detect_supported_protocols(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;
+    if (APR_SUCCESS != (rv = link_mds_to_servers(mc, s, p))) goto leave;
     /*4*/
-    if (APR_SUCCESS != (rv = md_reg_sync(mc->reg, p, ptemp, mc->mds))) {
+    if (APR_SUCCESS != (rv = md_reg_sync_start(mc->reg, mc->mds, ptemp))) {
         ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10073)
                      "synching %d mds to registry", mc->mds->nelts);
         goto leave;
     }
     /*5*/
     load_staged_data(mc, s, p);
+leave:
+    return rv;
+}
+
+static apr_status_t md_post_config_after_ssl(apr_pool_t *p, apr_pool_t *plog,
+                                             apr_pool_t *ptemp, server_rec *s)
+{
+    md_srv_conf_t *sc;
+    apr_status_t rv = APR_SUCCESS;
+    md_mod_conf_t *mc;
+    int watched, i;
+    md_t *md;
+
+    (void)ptemp;
+    (void)plog;
+    sc = md_config_get(s);
+
     /*6*/
-    if (dry_run) goto leave;
+    if (!sc || !sc->mc || sc->mc->dry_run) goto leave;
+    mc = sc->mc;
+    
     /*7*/
-    if (APR_SUCCESS != (rv = reinit_mds(mc, s, p))) goto leave;
+    if (APR_SUCCESS != (rv = check_invalid_duplicates(s))) {
+        goto leave;
+    }
+    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 = auto_add_domains(md, s, p))) {
+            goto leave;
+        }
+        init_acme_tls_1_domains(md, s);
+        if (APR_SUCCESS != (rv = check_usage(mc, md, s, p, ptemp))) {
+            goto leave;
+        }
+        if (APR_SUCCESS != (rv = md_reg_sync_finish(mc->reg, md, p, ptemp))) {
+            ap_log_error( APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10172)
+                         "md[%s]: error synching to store", md->name);
+            goto leave;
+        }
+    }
     /*8*/
-    init_watched_names(mc, p, ptemp, s);
+    watched = init_cert_watch_status(mc, p, ptemp, s);
     /*9*/
     md_reg_cleanup_challenges(mc->reg, p, ptemp, mc->mds);
     
@@ -804,18 +929,23 @@ static apr_status_t md_post_config_before_ssl(apr_pool_t *p, apr_pool_t *plog,
      * and only staging/challenges may be manipulated */
     md_reg_freeze_domains(mc->reg, mc->mds);
     
-    if (mc->watched_names->nelts > 0) {
+    if (watched) {
         /*10*/
         ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(10074)
-                     "%d out of %d mds need watching", 
-                     mc->watched_names->nelts, mc->mds->nelts);
+                     "%d out of %d mds need watching", watched, mc->mds->nelts);
     
         md_http_use_implementation(md_curl_get_impl(p));
-        rv = md_start_watching(mc, s, p);
+        rv = md_renew_start_watching(mc, s, p);
     }
     else {
-        ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) "no mds to drive");
+        ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075) "no mds to supervise");
     }
+
+    if (!mc->ocsp || md_ocsp_count(mc->ocsp) == 0) goto leave;
+    
+    md_http_use_implementation(md_curl_get_impl(p));
+    rv = md_ocsp_start_watching(mc, s, p);
+    
 leave:
     return rv;
 }
@@ -877,20 +1007,6 @@ static int md_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
 /**************************************************************************************************/
 /* Access API to other httpd components */
 
-static int md_is_managed(server_rec *s)
-{
-    md_srv_conf_t *conf = md_config_get(s);
-
-    if (conf && conf->assigned) {
-        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10076) 
-                     "%s: manages server %s", conf->assigned->name, s->server_hostname);
-        return 1;
-    }
-    ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s,  
-                 "server %s is not managed", s->server_hostname);
-    return 0;
-}
-
 static apr_status_t setup_fallback_cert(md_store_t *store, const md_t *md, 
                                         server_rec *s, apr_pool_t *p)
 {
@@ -938,23 +1054,27 @@ static apr_status_t get_certificate(server_rec *s, apr_pool_t *p, int fallback,
         return APR_ENOENT;
     }
     
+    assert(sc->mc);
+    reg = sc->mc->reg;
+    assert(reg);
+
+    sc->is_ssl = 1;
+
     if (!sc->assigned) {
         /* With the new hooks in mod_ssl, we are invoked for all server_rec. It is
          * therefore normal, when we have nothing to add here. */
         return APR_ENOENT;
     }
-    
-    assert(sc->mc);
-    reg = sc->mc->reg;
-    assert(reg);
-    
-    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 "
-                     "find MD '%s'.", sc->assigned->name);
-        return APR_ENOENT;
+    else if (sc->assigned->nelts != 1) {
+        if (!fallback) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(10042)
+                         "conflict: %d MDs match Virtualhost %s which uses SSL, however "
+                         "there can be at most 1.",
+                         (int)sc->assigned->nelts, s->server_hostname);
+        }
+        return APR_EINVAL;
     }
+    md = APR_ARRAY_IDX(sc->assigned, 0, const md_t*);
     
     rv = md_reg_get_cred_files(pkeyfile, pcertfile, reg, MD_SG_DOMAINS, md, p);
     if (APR_STATUS_IS_ENOENT(rv)) {
@@ -990,12 +1110,6 @@ static apr_status_t get_certificate(server_rec *s, apr_pool_t *p, int fallback,
     return rv;
 }
 
-static apr_status_t md_get_certificate(server_rec *s, apr_pool_t *p,
-                                       const char **pkeyfile, const char **pcertfile)
-{
-    return get_certificate(s, p, 1, pcertfile, pkeyfile);
-}
-
 static int md_add_cert_files(server_rec *s, apr_pool_t *p,
                              apr_array_header_t *cert_files, 
                              apr_array_header_t *key_files)
@@ -1176,51 +1290,62 @@ static int md_require_https_maybe(request_rec *r)
 {
     const md_srv_conf_t *sc;
     apr_uri_t uri;
-    const char *s;
+    const char *s, *host;
+    const md_t *md;
     int status;
     
-    if (opt_ssl_is_https && r->parsed_uri.path
-        && strncmp(WELL_KNOWN_PREFIX, r->parsed_uri.path, sizeof(WELL_KNOWN_PREFIX)-1)) {
+    /* Requests outside the /.well-known path are subject to possible
+     * https: redirects or HSTS header additions.
+     */
+    sc = ap_get_module_config(r->server->module_config, &md_module);
+    if (!sc || !sc->assigned || !sc->assigned->nelts 
+        || !opt_ssl_is_https || !r->parsed_uri.path
+        || !strncmp(WELL_KNOWN_PREFIX, r->parsed_uri.path, sizeof(WELL_KNOWN_PREFIX)-1)) {
+        goto declined;
+    }
         
-        sc = ap_get_module_config(r->server->module_config, &md_module);
-        if (sc && sc->assigned && sc->assigned->require_https > MD_REQUIRE_OFF) {
-            if (opt_ssl_is_https(r->connection)) {
-                /* Using https:
-                 * if 'permanent' and no one else set a HSTS header already, do it */
-                if (sc->assigned->require_https == MD_REQUIRE_PERMANENT 
-                    && sc->mc->hsts_header && !apr_table_get(r->headers_out, MD_HSTS_HEADER)) {
-                    apr_table_setn(r->headers_out, MD_HSTS_HEADER, sc->mc->hsts_header);
-                }
+    host = ap_get_server_name_for_url(r);
+    md = md_get_for_domain(r->server, host);
+    if (!md) goto declined;
+    
+    if (opt_ssl_is_https(r->connection)) {
+        /* Using https:
+         * if 'permanent' and no one else set a HSTS header already, do it */
+        if (md->require_https == MD_REQUIRE_PERMANENT 
+            && sc->mc->hsts_header && !apr_table_get(r->headers_out, MD_HSTS_HEADER)) {
+            apr_table_setn(r->headers_out, MD_HSTS_HEADER, sc->mc->hsts_header);
+        }
+    }
+    else {
+        if (md->require_https > MD_REQUIRE_OFF) {
+            /* Not using https:, but require it. Redirect. */
+            if (r->method_number == M_GET) {
+                /* safe to use the old-fashioned codes */
+                status = ((MD_REQUIRE_PERMANENT == md->require_https)? 
+                          HTTP_MOVED_PERMANENTLY : HTTP_MOVED_TEMPORARILY);
             }
             else {
-                /* Not using https:, but require it. Redirect. */
-                if (r->method_number == M_GET) {
-                    /* safe to use the old-fashioned codes */
-                    status = ((MD_REQUIRE_PERMANENT == sc->assigned->require_https)? 
-                              HTTP_MOVED_PERMANENTLY : HTTP_MOVED_TEMPORARILY);
-                }
-                else {
-                    /* these should keep the method unchanged on retry */
-                    status = ((MD_REQUIRE_PERMANENT == sc->assigned->require_https)? 
-                              HTTP_PERMANENT_REDIRECT : HTTP_TEMPORARY_REDIRECT);
-                }
-                
-                s = ap_construct_url(r->pool, r->uri, r);
-                if (APR_SUCCESS == apr_uri_parse(r->pool, s, &uri)) {
-                    uri.scheme = (char*)"https";
-                    uri.port = 443;
-                    uri.port_str = (char*)"443";
-                    uri.query = r->parsed_uri.query;
-                    uri.fragment = r->parsed_uri.fragment;
-                    s = apr_uri_unparse(r->pool, &uri, APR_URI_UNP_OMITUSERINFO);
-                    if (s && *s) {
-                        apr_table_setn(r->headers_out, "Location", s);
-                        return status;
-                    }
+                /* these should keep the method unchanged on retry */
+                status = ((MD_REQUIRE_PERMANENT == md->require_https)? 
+                          HTTP_PERMANENT_REDIRECT : HTTP_TEMPORARY_REDIRECT);
+            }
+            
+            s = ap_construct_url(r->pool, r->uri, r);
+            if (APR_SUCCESS == apr_uri_parse(r->pool, s, &uri)) {
+                uri.scheme = (char*)"https";
+                uri.port = 443;
+                uri.port_str = (char*)"443";
+                uri.query = r->parsed_uri.query;
+                uri.fragment = r->parsed_uri.fragment;
+                s = apr_uri_unparse(r->pool, &uri, APR_URI_UNP_OMITUSERINFO);
+                if (s && *s) {
+                    apr_table_setn(r->headers_out, "Location", s);
+                    return status;
                 }
             }
         }
     }
+declined:
     return DECLINED;
 }
 
@@ -1248,6 +1373,7 @@ static void md_hooks(apr_pool_t *pool)
      * Run again after mod_ssl is done.
      */
     ap_hook_post_config(md_post_config_before_ssl, NULL, mod_ssl, APR_HOOK_MIDDLE);
+    ap_hook_post_config(md_post_config_after_ssl, mod_ssl, NULL, APR_HOOK_MIDDLE);
     
     /* Run once after a child process has been created.
      */
@@ -1263,22 +1389,18 @@ static void md_hooks(apr_pool_t *pool)
 
     /* 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);
+    APR_OPTIONAL_HOOK(ap, status_hook, md_domains_status_hook, NULL, NULL, APR_HOOK_MIDDLE);
+    APR_OPTIONAL_HOOK(ap, status_hook, md_ocsp_status_hook, NULL, NULL, APR_HOOK_MIDDLE);
     ap_hook_handler(md_status_handler, NULL, NULL, APR_HOOK_MIDDLE);
 
-#ifdef SSL_CERT_HOOKS
-    (void)md_is_managed;
-    (void)md_get_certificate;
+
+#ifndef SSL_CERT_HOOKS
+#error "This version of mod_md requires Apache httpd 2.4.41 or newer."
+#endif
     APR_OPTIONAL_HOOK(ssl, add_cert_files, md_add_cert_files, NULL, NULL, APR_HOOK_MIDDLE);
     APR_OPTIONAL_HOOK(ssl, add_fallback_cert_files, md_add_fallback_cert_files, NULL, NULL, APR_HOOK_MIDDLE);
     APR_OPTIONAL_HOOK(ssl, answer_challenge, md_answer_challenge, NULL, NULL, APR_HOOK_MIDDLE);
-#else
-    (void)md_add_cert_files;
-    (void)md_add_fallback_cert_files;
-    (void)md_answer_challenge;
-    APR_REGISTER_OPTIONAL_FN(md_is_challenge);
-    APR_REGISTER_OPTIONAL_FN(md_is_managed);
-    APR_REGISTER_OPTIONAL_FN(md_get_certificate);
-#endif
+    APR_OPTIONAL_HOOK(ssl, init_stapling_status, md_ocsp_init_stapling_status, NULL, NULL, APR_HOOK_MIDDLE);
+    APR_OPTIONAL_HOOK(ssl, get_stapling_status, md_ocsp_get_stapling_status, NULL, NULL, APR_HOOK_MIDDLE);
 }
 
index f46e864cffa4f8f45b1f3c15825b0fd7ac0597fe..1bc34b2377f63331c0b2a059064ccc197314a37f 100644 (file)
@@ -113,6 +113,10 @@ SOURCE=./mod_md_drive.c
 # End Source File\r
 # Begin Source File\r
 \r
+SOURCE=./mod_md_ocsp.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
@@ -177,6 +181,10 @@ SOURCE=./md_log.c
 # End Source File\r
 # Begin Source File\r
 \r
+SOURCE=./md_ocsp.c\r
+# End Source File\r
+# Begin Source File\r
+\r
 SOURCE=./md_reg.c\r
 # End Source File\r
 # Begin Source File\r
index ffb09470db92ff34d7784760fe0270589550453c..baa20cc37fac4b6d2d93d5d8e7e193686bf314f6 100644 (file)
 #define MD_DEFAULT_BASE_DIR "md"
 #endif
 
+static md_timeslice_t def_ocsp_keep_window = {
+    0,
+    MD_TIME_OCSP_KEEP_NORM,
+};
+
+static md_timeslice_t def_ocsp_renew_window = {
+    MD_TIME_LIFE_NORM,
+    MD_TIME_RENEW_WINDOW_DEF,
+};
+
 /* Default settings for the global conf */
 static md_mod_conf_t defmc = {
     NULL,                      /* list of mds */
 #if AP_MODULE_MAGIC_AT_LEAST(20180906, 2)
-    NULL,                      /* base dir by default state-dir-relative */
+    NULL,                      /* base dirm by default state-dir-relative */
 #else
     MD_DEFAULT_BASE_DIR,
 #endif
     NULL,                      /* proxy url for outgoing http */
-    NULL,                      /* md_reg */
+    NULL,                      /* md_reg_t */
+    NULL,                      /* md_ocsp_reg_t */
     80,                        /* local http: port */
     443,                       /* local https: port */
-    0,                         /* can http: */
-    0,                         /* can https: */
+    -1,                        /* can http: */
+    -1,                        /* 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 */
@@ -67,6 +77,10 @@ static md_mod_conf_t defmc = {
     0,                         /* dry_run flag */
     1,                         /* server_status_enabled */
     1,                         /* certificate_status_enabled */
+    &def_ocsp_keep_window,     /* default time to keep ocsp responses */
+    &def_ocsp_renew_window,    /* default time to renew ocsp responses */
+    "crt.sh",                  /* default cert checker site name */
+    "https://crt.sh?q=",       /* default cert checker site url */
 };
 
 static md_timeslice_t def_renew_window = {
@@ -94,8 +108,11 @@ static md_srv_conf_t defconf = {
     "ACME",                    /* ca protocol */
     NULL,                      /* ca agreemnent */
     NULL,                      /* ca challenges array */
+    0,                         /* stapling */
+    1,                         /* staple others */
     NULL,                      /* currently defined md */
     NULL,                      /* assigned md, post config */
+    0,                         /* is_ssl, set during mod_ssl post_config */
 };
 
 static md_mod_conf_t *mod_md_config;
@@ -118,7 +135,6 @@ 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);
          
@@ -143,6 +159,8 @@ static void srv_conf_props_clear(md_srv_conf_t *sc)
     sc->ca_proto = NULL;
     sc->ca_agreement = NULL;
     sc->ca_challenges = NULL;
+    sc->stapling = DEF_VAL;
+    sc->staple_others = DEF_VAL;
 }
 
 static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
@@ -158,6 +176,8 @@ static void srv_conf_props_copy(md_srv_conf_t *to, const md_srv_conf_t *from)
     to->ca_proto = from->ca_proto;
     to->ca_agreement = from->ca_agreement;
     to->ca_challenges = from->ca_challenges;
+    to->stapling = from->stapling;
+    to->staple_others = from->staple_others;
 }
 
 static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t *p)
@@ -173,6 +193,7 @@ static void srv_conf_props_apply(md_t *md, const md_srv_conf_t *from, apr_pool_t
     if (from->ca_proto) md->ca_proto = from->ca_proto;
     if (from->ca_agreement) md->ca_agreement = from->ca_agreement;
     if (from->ca_challenges) md->ca_challenges = apr_array_copy(p, from->ca_challenges);
+    if (from->stapling != DEF_VAL) md->stapling = from->stapling;
 }
 
 void *md_config_create_svr(apr_pool_t *pool, server_rec *s)
@@ -198,7 +219,6 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv)
     nsc = (md_srv_conf_t *)apr_pcalloc(pool, sizeof(md_srv_conf_t));
     nsc->name = name;
     nsc->mc = add->mc? add->mc : base->mc;
-    nsc->assigned = add->assigned? add->assigned : base->assigned;
 
     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;
@@ -213,8 +233,9 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv)
     nsc->ca_agreement = add->ca_agreement? add->ca_agreement : base->ca_agreement;
     nsc->ca_challenges = (add->ca_challenges? apr_array_copy(pool, add->ca_challenges) 
                     : (base->ca_challenges? apr_array_copy(pool, base->ca_challenges) : NULL));
+    nsc->stapling = (add->stapling != DEF_VAL)? add->stapling : base->stapling;
+    nsc->staple_others = (add->staple_others != DEF_VAL)? add->staple_others : base->staple_others;
     nsc->current = NULL;
-    nsc->assigned = NULL;
     
     return nsc;
 }
@@ -487,6 +508,30 @@ static const char *md_config_set_must_staple(cmd_parms *cmd, void *dc, const cha
     return set_on_off(&config->must_staple, value, cmd->pool);
 }
 
+static const char *md_config_set_stapling(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_srv_conf_t *config = 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(&config->stapling, value, cmd->pool);
+}
+
+static const char *md_config_set_staple_others(cmd_parms *cmd, void *dc, const char *value)
+{
+    md_srv_conf_t *config = md_config_get(cmd->server);
+    const char *err;
+
+    (void)dc;
+    if ((err = ap_check_cmd_context(cmd, GLOBAL_ONLY))) {
+        return err;
+    }
+    return set_on_off(&config->staple_others, value, cmd->pool);
+}
+
 static const char *md_config_set_base_server(cmd_parms *cmd, void *dc, const char *value)
 {
     md_srv_conf_t *config = md_config_get(cmd->server);
@@ -820,6 +865,69 @@ static const char *md_config_set_certificate_status(cmd_parms *cmd, void *dc, co
     return set_on_off(&sc->mc->certificate_status_enabled, value, cmd->pool);
 }
 
+static const char *md_config_set_ocsp_keep_window(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;
+    }
+    err = md_timeslice_parse(&sc->mc->ocsp_keep_window, cmd->pool, value, MD_TIME_OCSP_KEEP_NORM);
+    if (err) return apr_psprintf(cmd->pool, "MDStaplingKeepResponse %s", err);
+    return NULL;
+}
+
+static const char *md_config_set_ocsp_renew_window(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;
+    }
+    err = md_timeslice_parse(&sc->mc->ocsp_renew_window, cmd->pool, value, MD_TIME_LIFE_NORM);
+    if (!err && sc->mc->ocsp_renew_window->norm 
+        && (sc->mc->ocsp_renew_window->len >= sc->mc->ocsp_renew_window->norm)) {
+        err = "with a length of 100% or more is not allowed.";
+    }
+    if (err) return apr_psprintf(cmd->pool, "MDStaplingRenewWindow %s", err);
+    return NULL;
+}
+
+static const char *md_config_set_cert_check(cmd_parms *cmd, void *dc, 
+                                            const char *name, const char *url)
+{
+    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;
+    }
+    sc->mc->cert_check_name = name;
+    sc->mc->cert_check_url = url;
+    return NULL;
+}
+
+static const char *md_config_set_activation_delay(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);
+    apr_interval_time_t delay;
+
+    (void)mconfig;
+    if (err) {
+        return err;
+    }
+    if (md_duration_parse(&delay, arg, "d") != APR_SUCCESS) {
+        return "unrecognized duration format";
+    }
+    apr_table_set(sc->mc->env, MD_KEY_ACTIVATION_DELAY, md_duration_format(cmd->pool, delay));
+    return NULL;
+}
 
 const command_rec md_cmds[] = {
     AP_INIT_TAKE1("MDCertificateAuthority", md_config_set_ca, NULL, RSRC_CONF, 
@@ -860,7 +968,7 @@ const command_rec md_cmds[] = {
     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("MDRenewWindow", md_config_set_renew_window, NULL, RSRC_CONF, 
-                  "Time length for renewal before certificate expires (defaults to days)"),
+                  "Time length for renewal before certificate expires (defaults to days)."),
     AP_INIT_TAKE1("MDRequireHttps", md_config_set_require_https, NULL, RSRC_CONF, 
                   "Redirect non-secure requests to the https: equivalent."),
     AP_INIT_RAW_ARGS("MDNotifyCmd", md_config_set_notify_cmd, NULL, RSRC_CONF, 
@@ -881,6 +989,18 @@ const command_rec md_cmds[] = {
                   "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("MDStapling", md_config_set_stapling, NULL, RSRC_CONF, 
+                  "Enable/Disable OCSP Stapling for this/all Managed Domain(s)."),
+    AP_INIT_TAKE1("MDStapleOthers", md_config_set_staple_others, NULL, RSRC_CONF, 
+                  "Enable/Disable OCSP Stapling for certificates not in Managed Domains."),
+    AP_INIT_TAKE1("MDStaplingKeepResponse", md_config_set_ocsp_keep_window, NULL, RSRC_CONF, 
+                  "The amount of time to keep an OCSP response in the store."),
+    AP_INIT_TAKE1("MDStaplingRenewWindow", md_config_set_ocsp_renew_window, NULL, RSRC_CONF, 
+                  "Time length for renewal before OCSP responses expire (defaults to days)."),
+    AP_INIT_TAKE2("MDCertificateCheck", md_config_set_cert_check, NULL, RSRC_CONF, 
+                  "Set name and URL pattern for a certificate monitoring site."),
+    AP_INIT_TAKE1("MDActivationDelay", md_config_set_activation_delay, NULL, RSRC_CONF, 
+                  "How long to delay activation of new certificates"),
 
     AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
 };
@@ -897,7 +1017,7 @@ apr_status_t md_config_post_config(server_rec *s, apr_pool_t *p)
     if (mc->hsts_max_age > 0) {
         mc->hsts_header = apr_psprintf(p, "max-age=%d", mc->hsts_max_age);
     }
-    
+
 #if AP_MODULE_MAGIC_AT_LEAST(20180906, 2)
     if (mc->base_dir == NULL) {
         mc->base_dir = ap_state_dir_relative(p, MD_DEFAULT_BASE_DIR);
@@ -913,6 +1033,7 @@ static md_srv_conf_t *config_get_int(server_rec *s, apr_pool_t *p)
     ap_assert(sc);
     if (sc->s != s && p) {
         sc = md_config_merge(p, &defconf, sc);
+        sc->s = s;
         sc->name = apr_pstrcat(p, CONF_S_NAME(s), sc->name, NULL);
         sc->mc = md_mod_conf_get(p, 1);
         ap_set_module_config(s->module_config, &md_module, sc);
@@ -961,22 +1082,22 @@ int md_config_geti(const md_srv_conf_t *sc, md_config_var_t var)
     switch (var) {
         case MD_CONFIG_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:
-            return sc->mc->local_443;
         case MD_CONFIG_TRANSITIVE:
             return (sc->transitive != DEF_VAL)? sc->transitive : defconf.transitive;
         case MD_CONFIG_REQUIRE_HTTPS:
             return (sc->require_https != MD_REQUIRE_UNSET)? sc->require_https : defconf.require_https;
         case MD_CONFIG_MUST_STAPLE:
             return (sc->must_staple != DEF_VAL)? sc->must_staple : defconf.must_staple;
+        case MD_CONFIG_STAPLING:
+            return (sc->stapling != DEF_VAL)? sc->stapling : defconf.stapling;
+        case MD_CONFIG_STAPLE_OTHERS:
+            return (sc->staple_others != DEF_VAL)? sc->staple_others : defconf.staple_others;
         default:
             return 0;
     }
 }
 
-void md_config_get_timespan(const md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var)
+void md_config_get_timespan(md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var)
 {
     switch (var) {
         case MD_CONFIG_RENEW_WINDOW:
@@ -990,3 +1111,19 @@ void md_config_get_timespan(const md_timeslice_t **pspan, const md_srv_conf_t *s
     }
 }
 
+const md_t *md_get_for_domain(server_rec *s, const char *domain)
+{
+    md_srv_conf_t *sc;
+    const md_t *md;
+    int i;
+    
+    sc = md_config_get(s);
+    for (i = 0; sc && sc->assigned && i < sc->assigned->nelts; ++i) {
+        md = APR_ARRAY_IDX(sc->assigned, i, const md_t*);
+        if (md_contains(md, domain, 0)) goto leave;
+    }
+    md = NULL;
+leave:
+    return md;
+}
+
index fde919b2ff7d35d9e43a0a10c400b33a41f38888..0f1138d9b341560dc6f8420146e362cd4e09041e 100644 (file)
@@ -20,6 +20,7 @@
 struct apr_hash_t;
 struct md_store_t;
 struct md_reg_t;
+struct md_ocsp_reg_t;
 struct md_pkey_spec_t;
 
 typedef enum {
@@ -28,8 +29,6 @@ typedef enum {
     MD_CONFIG_BASE_DIR,
     MD_CONFIG_CA_AGREEMENT,
     MD_CONFIG_DRIVE_MODE,
-    MD_CONFIG_LOCAL_80,
-    MD_CONFIG_LOCAL_443,
     MD_CONFIG_RENEW_WINDOW,
     MD_CONFIG_WARN_WINDOW,
     MD_CONFIG_TRANSITIVE,
@@ -38,6 +37,8 @@ typedef enum {
     MD_CONFIG_MUST_STAPLE,
     MD_CONFIG_NOTIFY_CMD,
     MD_CONFIG_MESSGE_CMD,
+    MD_CONFIG_STAPLING,
+    MD_CONFIG_STAPLE_OTHERS,
 } md_config_var_t;
 
 typedef struct md_mod_conf_t md_mod_conf_t;
@@ -45,7 +46,8 @@ 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) */
-    struct md_reg_t *reg;              /* md registry instance, singleton, shared */
+    struct md_reg_t *reg;              /* md registry instance */
+    struct md_ocsp_reg_t *ocsp;        /* ocsp status registry */
 
     int local_80;                      /* On which port http:80 arrives */
     int local_443;                     /* On which port https:443 arrives */
@@ -55,7 +57,6 @@ struct md_mod_conf_t {
     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 */
@@ -64,6 +65,10 @@ struct md_mod_conf_t {
     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 */
+    md_timeslice_t *ocsp_keep_window;  /* time that we keep ocsp responses around */
+    md_timeslice_t *ocsp_renew_window; /* time before exp. that we start renewing ocsp resp. */
+    const char *cert_check_name;       /* name of the linked certificate check site */
+    const char *cert_check_url;        /* url "template for" checking a certificate */
 };
 
 typedef struct md_srv_conf_t {
@@ -76,16 +81,20 @@ typedef struct md_srv_conf_t {
     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 */
-    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 */
+    md_timeslice_t *renew_window; /* time before expiration that starts renewal */
+    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) */
     const char *ca_agreement;          /* accepted agreement uri between CA and user */ 
     struct apr_array_header_t *ca_challenges; /* challenge types configured */
+    
+    int stapling;                      /* OCSP stapling enabled */
+    int staple_others;                 /* Provide OCSP stapling for non-MD certificates */
 
     md_t *current;                     /* md currently defined in <MDomainSet xxx> section */
-    md_t *assigned;                    /* post_config: MD that applies to this server or NULL */
+    struct apr_array_header_t *assigned; /* post_config: MDs that apply to this server */
+    int is_ssl;                        /* SSLEngine is enabled here */
 } md_srv_conf_t;
 
 void *md_config_create_svr(apr_pool_t *pool, server_rec *s);
@@ -106,7 +115,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);
 
-void md_config_get_timespan(const md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var);
+void md_config_get_timespan(md_timeslice_t **pspan, const md_srv_conf_t *sc, md_config_var_t var);
 
+const md_t *md_get_for_domain(server_rec *s, const char *domain);
 
 #endif /* md_config_h */
index 31fdc272251b0362b2dc243b2836c661d87d6f9a..e412479c6c132a15f1c381e7cc0aad8a1d3a0d65 100644 (file)
 /**************************************************************************************************/
 /* watchdog based impl. */
 
-#define MD_WATCHDOG_NAME   "_md_"
+#define MD_RENEW_WATCHDOG_NAME   "_md_renew_"
 
 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 {
+struct md_renew_ctx_t {
     apr_pool_t *p;
     server_rec *s;
     md_mod_conf_t *mc;
@@ -68,253 +68,92 @@ struct md_drive_ctx {
     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 = APR_SUCCESS;            
-    
-    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)
+static void process_drive_job(md_renew_ctx_t *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);
+    md_job_load(job);
     /* 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);
+    job->next_run = 0;
+    if (job->finished && job->notified) {
+        /* finished and notification handled, nothing to do. */
+        goto leave;
+    }
+    
+    md = md_get_by_name(dctx->mc->mds, job->mdomain);
     AP_DEBUG_ASSERT(md);
 
-    result = md_result_md_make(ptemp, md);
-    if (job->last_result) md_result_assign(result, job->last_result); 
+    result = md_result_md_make(ptemp, md->name);
+    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;
+        job->fatal_error = 1;
+        job->next_run = 0;
         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;
-        }
-
+    if (md_will_renew_cert(md)) {
         /* 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(%s): state=%d, driving", job->mdomain, md->state);
+
+        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", job->mdomain);
+            goto expiry;
+        }
+    
+        md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg)); 
         md_reg_renew(dctx->mc->reg, md, dctx->mc->env, 0, result, ptemp);
+        md_job_end_run(job, result);
         
-        job_result_observation_end(job, result);
-        if (APR_SUCCESS != result->status) {
+        if (APR_SUCCESS == result->status) {
+            /* 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() < result->ready_at) {
+                md_job_retry_at(job, result->ready_at);
+                goto leave;
+            }
+            
+            if (!job->notified) md_job_notify(job, "renewed", result);
+        }
+        else {
             ap_log_error( APLOG_MARK, APLOG_ERR, result->status, dctx->s, APLOGNO(10056) 
-                         "processing %s: %s", job->name, result->detail);
-            error_run = 1;
+                         "processing %s: %s", job->mdomain, result->detail);
             md_job_log_append(job, "renewal-error", result->problem, result->detail);
-            send_notification(dctx, job, md, "errored", result, ptemp);
-            goto leave;
+            md_job_holler(job, "errored");
+            ap_log_error(APLOG_MARK, APLOG_INFO, 0, dctx->s, APLOGNO(10057) 
+                         "%s: encountered error for the %d. time, next run in %s",
+                         job->mdomain, job->error_runs, 
+                         md_duration_print(ptemp, job->next_run - apr_time_now()));
         }
-        
-        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()));
+
+expiry:
+    if (!job->finished && md_reg_should_warn(dctx->mc->reg, md, dctx->p)) {
+        ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, dctx->s,
+                     "md(%s): warn about expiration", md->name);
+        md_job_start_run(job, result, md_reg_store_get(dctx->mc->reg));
+        if (APR_SUCCESS == md_job_notify(job, "expiring", result)) {
+            md_result_set(result, APR_SUCCESS, NULL);
+        }
+        md_job_end_run(job, result);
     }
-    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);
+
+leave:
+    if (job->dirty) {
+        rv = md_job_save(job, result, ptemp);
+        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, dctx->s, "%s: saving job props", job->mdomain);
     }
 }
 
@@ -337,7 +176,7 @@ static apr_time_t next_run_default(void)
 
 static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
 {
-    md_drive_ctx *dctx = baton;
+    md_renew_ctx_t *dctx = baton;
     md_job_t *job;
     apr_time_t next_run, wait_time;
     int i;
@@ -390,19 +229,18 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
     return APR_SUCCESS;
 }
 
-apr_status_t md_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+apr_status_t md_renew_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
 {
     apr_allocator_t *allocator;
-    md_drive_ctx *dctx;
+    md_renew_ctx_t *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
+     * to monitor the MDs marked as watched, 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
@@ -438,29 +276,28 @@ apr_status_t md_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
     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");
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10062) "md_renew_watchdog: create pool");
         return rv;
     }
     apr_allocator_owner_set(allocator, dctxp);
-    apr_pool_tag(dctxp, "md_drive_ctx");
+    apr_pool_tag(dctxp, "md_renew_watchdog");
 
     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;
+    dctx->jobs = apr_array_make(dctx->p, mc->mds->nelts, sizeof(md_job_t *));
+    for (i = 0; i < mc->mds->nelts; ++i) {
+        md = APR_ARRAY_IDX(mc->mds, i, md_t*);
+        if (!md || !md->watched) continue;
         
-        job = md_job_make(p, md->name);
+        job = md_reg_job_make(mc->reg, md->name, p);
         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(%s): state=%d, created drive job", md->name, md->state);
         
-        md_job_load(job, mc->reg, MD_SG_STAGING, dctx->p);
+        md_job_load(job);
         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.
@@ -470,7 +307,7 @@ apr_status_t md_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
              */
             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);
+                         "area to reset.", md->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;
@@ -484,13 +321,13 @@ apr_status_t md_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
         return APR_SUCCESS;
     }
     
-    if (APR_SUCCESS != (rv = wd_get_instance(&dctx->watchdog, MD_WATCHDOG_NAME, 0, 1, dctx->p))) {
+    if (APR_SUCCESS != (rv = wd_get_instance(&dctx->watchdog, MD_RENEW_WATCHDOG_NAME, 0, 1, dctx->p))) {
         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10066) 
-                     "create md watchdog(%s)", MD_WATCHDOG_NAME);
+                     "create md renew watchdog(%s)", MD_RENEW_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);
+                 "register md renew watchdog(%s)", MD_RENEW_WATCHDOG_NAME);
     return rv;
 }
index be158674c6635f7587b2c4788aff191f2fd5e37f..40d6d676537dc4b5e11066ebd40d292c33c54565 100644 (file)
 struct md_mod_conf_t;
 struct md_reg_t;
 
-typedef struct md_drive_ctx md_drive_ctx;
+typedef struct md_renew_ctx_t md_renew_ctx_t;
 
 int md_will_renew_cert(const md_t *md);
 
 /**
- * Start driving the certificate procotol for the domains mentioned in mc->watched_names.
+ * Start driving the certificate renewal for MDs marked with watched.
  */
-apr_status_t md_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr_pool_t *p);
+apr_status_t md_renew_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr_pool_t *p);
 
 
 
diff --git a/modules/md/mod_md_ocsp.c b/modules/md/mod_md_ocsp.c
new file mode 100644 (file)
index 0000000..6556971
--- /dev/null
@@ -0,0 +1,250 @@
+/* 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_log.h>
+
+#include "mod_watchdog.h"
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_http.h"
+#include "md_json.h"
+#include "md_ocsp.h"
+#include "md_store.h"
+#include "md_log.h"
+#include "md_reg.h"
+#include "md_time.h"
+#include "md_util.h"
+
+#include "mod_md.h"
+#include "mod_md_config.h"
+#include "mod_md_private.h"
+#include "mod_md_ocsp.h"
+
+static int staple_here(md_srv_conf_t *sc) 
+{
+    if (!sc || !sc->mc->ocsp) return 0;
+    if (sc->assigned 
+        && sc->assigned->nelts == 1
+        && APR_ARRAY_IDX(sc->assigned, 0, const md_t*)->stapling) return 1;
+    return (md_config_geti(sc, MD_CONFIG_STAPLING) 
+            && md_config_geti(sc, MD_CONFIG_STAPLE_OTHERS));
+}
+
+apr_status_t md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p, 
+                                          X509 *cert, X509 *issuer)
+{
+    md_srv_conf_t *sc;
+    const md_t *md;
+    apr_status_t rv;
+
+    sc = md_config_get(s);
+    if (!staple_here(sc)) goto declined;
+
+    md = ((sc->assigned || sc->assigned->nelts == 1)?
+          APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL);
+    rv = md_ocsp_prime(sc->mc->ocsp, md_cert_wrap(p, cert), 
+                       md_cert_wrap(p, issuer), md);
+    ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, s, "init stapling for: %s", 
+                 md? md->name : s->server_hostname);
+    if (APR_SUCCESS == rv) {
+        return OK;
+    }
+declined:
+    return DECLINED;
+}
+
+apr_status_t md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen, 
+                                         conn_rec *c, server_rec *s, X509 *cert)
+{
+    md_srv_conf_t *sc;
+    const md_t *md;
+    apr_status_t rv;
+    
+    sc = md_config_get(s);
+    if (!staple_here(sc)) goto declined;
+    
+    md = ((sc->assigned || sc->assigned->nelts == 1)?
+          APR_ARRAY_IDX(sc->assigned, 0, const md_t*) : NULL);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "get stapling for: %s", 
+                  md? md->name : s->server_hostname);
+    rv = md_ocsp_get_status(pder, pderlen, sc->mc->ocsp, 
+                            md_cert_wrap(c->pool, cert), c->pool, md);
+    if (APR_STATUS_IS_ENOENT(rv)) goto declined;
+    return rv;
+    
+declined:
+    return DECLINED;
+}
+                          
+/**************************************************************************************************/
+/* watchdog based impl. */
+
+#define MD_OCSP_WATCHDOG_NAME   "_md_ocsp_"
+
+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_ocsp_ctx_t md_ocsp_ctx_t;
+
+struct md_ocsp_ctx_t {
+    apr_pool_t *p;
+    server_rec *s;
+    md_mod_conf_t *mc;
+    ap_watchdog_t *watchdog;
+};
+
+static apr_time_t next_run_default(void)
+{
+    /* we'd like to run at least hourly */
+    return apr_time_now() + apr_time_from_sec(MD_SECS_PER_HOUR);
+}
+
+static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp)
+{
+    md_ocsp_ctx_t *octx = baton;
+    apr_time_t next_run, wait_time;
+    
+    /* 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, octx->s, APLOGNO(10197)
+                         "md ocsp watchdog start, ocsp stapling %d certificates", 
+                         (int)md_ocsp_count(octx->mc->ocsp));
+            break;
+            
+        case AP_WATCHDOG_STATE_RUNNING:
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10198)
+                         "md ocsp watchdog run, ocsp stapling %d certificates", 
+                         (int)md_ocsp_count(octx->mc->ocsp));
+                         
+            /* 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();
+            
+            md_ocsp_renew(octx->mc->ocsp, octx->p, ptemp, &next_run);
+            
+            wait_time = next_run - apr_time_now();
+            if (APLOGdebug(octx->s)) {
+                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10199)
+                             "md ocsp watchdog next run in %s", 
+                             md_duration_print(ptemp, wait_time));
+            }
+            wd_set_interval(octx->watchdog, wait_time, octx, run_watchdog);
+            break;
+            
+        case AP_WATCHDOG_STATE_STOPPING:
+            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, octx->s, APLOGNO(10200)
+                         "md ocsp watchdog stopping");
+            break;
+    }
+    
+    return APR_SUCCESS;
+}
+
+static apr_status_t ocsp_remove_old_responses(md_mod_conf_t *mc, apr_pool_t *p)
+{
+    md_timeperiod_t keep_norm, keep;
+    
+    keep_norm.end = apr_time_now();
+    keep_norm.start = keep_norm.end - MD_TIME_OCSP_KEEP_NORM;
+    keep = md_timeperiod_slice_before_end(&keep_norm, mc->ocsp_keep_window);
+    /* remove any ocsp response older than keep.start */
+    return md_ocsp_remove_responses_older_than(mc->ocsp, p, keep.start);
+}
+
+apr_status_t md_ocsp_start_watching(md_mod_conf_t *mc, server_rec *s, apr_pool_t *p)
+{
+    apr_allocator_t *allocator;
+    md_ocsp_ctx_t *octx;
+    apr_pool_t *octxp;
+    apr_status_t rv;
+    
+    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(10201) 
+                     "mod_watchdog is required for OCSP stapling");
+        return APR_EGENERAL;
+    }
+    
+    /* 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(&octxp, p, NULL, allocator);
+    if (rv != APR_SUCCESS) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(10205) "md_ocsp_watchdog: create pool");
+        return rv;
+    }
+    apr_allocator_owner_set(allocator, octxp);
+    apr_pool_tag(octxp, "md_ocsp_watchdog");
+
+    octx = apr_pcalloc(octxp, sizeof(*octx));
+    octx->p = octxp;
+    octx->s = s;
+    octx->mc = mc;
+    
+    /* Time for some house keeping, before the server goes live (again):
+     * - we store OCSP responses for each certificate individually by its SHA-1 id
+     * - this means, as long as certificate do not change, the number of response
+     *   files remains stable.
+     * - But when a certificate changes (is replaced), the response is obsolete
+     * - we do not get notified when a certificate is no longer used. An admin
+     *   might just reconfigure or change the content of a file (backup/restore etc.)
+     * - also, certificates might be added by some openssl config commands or other
+     *   modules that we do not immediately see right at startup. We cannot assume
+     *   that any OCSP response we cannot relate to a certificate RIGHT NOW, is no
+     *   longer needed.
+     * - since the response files are relatively small, we have no problem with
+     *   keeping them around for a while. We just do not want an ever growing store. 
+     * - The simplest and effective way seems to be to just remove files older
+     *   a certain amount of time. Take a 7 day default and let the admin configure
+     *   it for very special setups. 
+     */ 
+    ocsp_remove_old_responses(mc, octx->p);
+    
+    rv = wd_get_instance(&octx->watchdog, MD_OCSP_WATCHDOG_NAME, 0, 1, octx->p);
+    if (APR_SUCCESS != rv) {
+        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(10202) 
+                     "create md ocsp watchdog(%s)", MD_OCSP_WATCHDOG_NAME);
+        return rv;
+    }
+    rv = wd_register_callback(octx->watchdog, 0, octx, run_watchdog);
+    ap_log_error(APLOG_MARK, rv? APLOG_CRIT : APLOG_DEBUG, rv, s, APLOGNO(10203) 
+                 "register md ocsp watchdog(%s)", MD_OCSP_WATCHDOG_NAME);
+    return rv;
+}
+
+
+
diff --git a/modules/md/mod_md_ocsp.h b/modules/md/mod_md_ocsp.h
new file mode 100644 (file)
index 0000000..b0894ca
--- /dev/null
@@ -0,0 +1,33 @@
+/* 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_ocsp_h
+#define mod_md_md_ocsp_h
+
+
+apr_status_t md_ocsp_init_stapling_status(server_rec *s, apr_pool_t *p, 
+                                          X509 *cert, X509 *issuer);
+
+apr_status_t md_ocsp_get_stapling_status(unsigned char **pder, int *pderlen, 
+                                         conn_rec *c, server_rec *s, X509 *cert);
+                          
+/**
+ * Start watchdog for retrieving/updating ocsp status.
+ */
+apr_status_t md_ocsp_start_watching(struct md_mod_conf_t *mc, server_rec *s, apr_pool_t *p);
+
+
+#endif /* mod_md_md_ocsp_h */
index 9ceadb6007ee4ef3d744e434e2b248369d14e954..cde13325c750e2c7e9410b0c5541571d1f37a9bc 100644 (file)
@@ -32,6 +32,7 @@
 #include "md_curl.h"
 #include "md_crypt.h"
 #include "md_http.h"
+#include "md_ocsp.h"
 #include "md_json.h"
 #include "md_status.h"
 #include "md_store.h"
@@ -84,8 +85,9 @@ int md_http_cert_status(request_rec *r)
     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)
+    rv = md_status_get_md_json(&mdj, md, sc->mc->reg, sc->mc->ocsp, r->pool);
+    if (APR_SUCCESS != rv) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10204)
                       "loading md status for %s", md->name);
         return HTTP_INTERNAL_SERVER_ERROR;
     }
@@ -95,13 +97,13 @@ int md_http_cert_status(request_rec *r)
 
     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, MD_KEY_UNTIL, NULL)) {
+        md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_UNTIL, NULL), 
+                     resp, MD_KEY_VALID, MD_KEY_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_VALID, MD_KEY_FROM, NULL)) {
+        md_json_sets(md_json_gets(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_FROM, NULL), 
+                     resp, MD_KEY_VALID, MD_KEY_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), 
@@ -156,11 +158,15 @@ struct status_info {
 static void si_val_status(status_ctx *ctx, md_json_t *mdj, const status_info *info)
 {
     const char *s = "unknown";
+    apr_time_t until;
     (void)info;
-    switch (md_json_getl(mdj, MD_KEY_STATE, NULL)) {
+    switch (md_json_getl(mdj, info->key, NULL)) {
         case MD_S_INCOMPLETE: s = "incomplete"; break;
         case MD_S_EXPIRED_DEPRECATED:
-        case MD_S_COMPLETE: s = "ok"; break;
+        case MD_S_COMPLETE:
+            until = md_json_get_time(mdj, MD_KEY_CERT, MD_KEY_VALID, MD_KEY_UNTIL, NULL);
+            s = (!until || until > apr_time_now())? "good" : "expired"; 
+            break;
         case MD_S_ERROR: s = "error"; break;
         case MD_S_MISSING_INFORMATION: s = "missing information"; break;
         default: break;
@@ -168,19 +174,36 @@ static void si_val_status(status_ctx *ctx, md_json_t *mdj, const status_info *in
     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)
+static void si_val_url(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;
+    const char *url, *s;
+    apr_uri_t uri_parsed;
+
+    
+    s = url = md_json_gets(mdj, info->key, NULL);
+    if (!url) return;
+    if (!strcmp(LE_ACMEv2_PROD, url)) {
+        s = "Let's Encrypt";
     }
-    apr_brigade_puts(ctx->bb, NULL, NULL, s);
+    else if (!strcmp(LE_ACMEv2_STAGING, url)) {
+        s = "Let's Encrypt (staging)";
+    }
+    else if (!strcmp(LE_ACMEv1_PROD, url)) {
+        s = "Let's Encrypt (v1)";
+    }
+    else if (!strcmp(LE_ACMEv1_STAGING, url)) {
+        s = "Let's Encrypt (v1,staging)";
+    }
+    else if (APR_SUCCESS == apr_uri_parse(ctx->p, url, &uri_parsed)) {
+        s = uri_parsed.hostname;
+        
+    }
+    apr_brigade_printf(ctx->bb, NULL, NULL, "<a href='%s'>%s</a>", 
+                       ap_escape_html2(ctx->p, url, 1), 
+                       ap_escape_html2(ctx->p, s, 1));
 }
 
-
-static void si_val_date(status_ctx *ctx, apr_time_t timestamp)
+static void print_date(apr_bucket_brigade *bb, apr_time_t timestamp, const char *title)
 {
     if (timestamp > 0) {
         char ts[128];
@@ -189,160 +212,179 @@ static void si_val_date(status_ctx *ctx, apr_time_t timestamp)
         apr_size_t len;
         
         apr_time_exp_gmt(&texp, timestamp);
-        apr_strftime(ts, &len, sizeof(ts)-1, "%Y-%m-%dT%H:%M:%SZ", &texp);
+        apr_strftime(ts, &len, sizeof(ts2)-1, "%Y-%m-%d", &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, 
+        if (!title) {
+            apr_strftime(ts2, &len, sizeof(ts)-1, "%Y-%m-%dT%H:%M:%SZ", &texp);
+            ts2[len] = '\0';
+            title = ts2;
+        }
+        apr_brigade_printf(bb, NULL, NULL, 
                            "<span title='%s' style='white-space: nowrap;'>%s</span>", 
-                           ts, ts2);
-    }
-    else {
-        apr_brigade_puts(ctx->bb, NULL, NULL, "-");
+                           ap_escape_html2(bb->p, title, 1), ts);
     }
 }
 
-static void si_val_time(status_ctx *ctx, apr_time_t timestamp)
+static void print_time(apr_bucket_brigade *bb, const char *label, apr_time_t t)
 {
-    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);
+    apr_time_t now;
+    const char *pre, *post, *sep;
+    char ts[APR_RFC822_DATE_LEN];
+    char ts2[128];
+    apr_time_exp_t texp;
+    apr_size_t len;
+    apr_interval_time_t delta;
+    
+    if (t == 0) {
+        /* timestamp is 0, we use that for "not set" */
+        return;
+    }
+    apr_time_exp_gmt(&texp, t);
+    now = apr_time_now();
+    pre = post = "";
+    sep = (label && strlen(label))? " " : "";
+    delta = 0;
+    apr_rfc822_date(ts, t);
+    if (t > now) {
+        delta = t - now;
+        pre = "in ";
+    }
+    else {
+        delta = now - t;
+        post = " ago";
+    }
+    if (delta >= (4 * apr_time_from_sec(MD_SECS_PER_DAY))) {
+        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);
+        apr_brigade_printf(bb, NULL, NULL, "%s%s<span title='%s' "
+                           "style='white-space: nowrap;'>%s</span>", 
+                           label, sep, ts, ts2); 
     }
     else {
-        apr_brigade_puts(ctx->bb, NULL, NULL, "-");
+        apr_brigade_printf(bb, NULL, NULL, "%s%s<span title='%s'>%s%s%s</span>", 
+                           label, sep, ts, pre, md_duration_roughly(bb->p, delta), post); 
     }
 }
 
-static void si_val_expires(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+static void si_val_valid_time(status_ctx *ctx, md_json_t *mdj, const status_info *info)
 {
-    const char *s;
-    apr_time_t t;
+    const char *sfrom, *suntil, *sep, *title;
+    apr_time_t from, until;
     
-    (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);
+    sep = NULL;
+    sfrom = md_json_gets(mdj, info->key, MD_KEY_FROM, NULL);
+    from = sfrom? apr_date_parse_rfc(sfrom) : 0;
+    suntil = md_json_gets(mdj, info->key, MD_KEY_UNTIL, NULL);
+    until = suntil?apr_date_parse_rfc(suntil) : 0;
+    
+    if (from > apr_time_now()) {
+        apr_brigade_puts(ctx->bb, NULL, NULL, "from ");
+        print_date(ctx->bb, from, sfrom);
+        sep = " ";
+    }
+    if (until) {
+        if (sep) apr_brigade_puts(ctx->bb, NULL, NULL, sep);
+        apr_brigade_puts(ctx->bb, NULL, NULL, "until ");
+        title = sfrom? apr_psprintf(ctx->p, "%s - %s", sfrom, suntil) : suntil;
+        print_date(ctx->bb, until, title);
     }
 }
 
-static void si_val_valid_from(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+static void si_add_header(status_ctx *ctx, 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);
-    }
+    const char *html = ap_escape_html2(ctx->p, info->label, 1);
+    apr_brigade_printf(ctx->bb, NULL, NULL, "<th class=\"%s\">%s</th>", html, html);
 }
-    
-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_cert_valid_time(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    md_json_t *jcert;
+    status_info sub = *info;
+    
+    sub.key = MD_KEY_VALID;
+    jcert = md_json_getj(mdj, info->key, NULL);
+    if (jcert) si_val_valid_time(ctx, jcert, &sub);
 }
 
-static void si_val_renewal(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+static void si_val_ca_url(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    md_json_t *jcert;
+    status_info sub = *info;
+    
+    sub.key = MD_KEY_URL;
+    jcert = md_json_getj(mdj, info->key, NULL);
+    if (jcert) si_val_url(ctx, jcert, &sub);
+}
+    
+static void print_job_summary(apr_bucket_brigade *bb, md_json_t *mdj, const char *key, 
+                              const char *separator)
 {
     char buffer[HUGE_STRING_LEN];
     apr_status_t rv;
     int finished, errors;
     apr_time_t t;
-    const char *s;
+    const char *s, *line;
     
-    (void)info;
-    if (!md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) {
+    if (!md_json_has_key(mdj, key, 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);
+    finished = (int)md_json_getl(mdj, key, MD_KEY_FINISHED, NULL);
+    errors = (int)md_json_getl(mdj, key, MD_KEY_ERRORS, NULL);
+    rv = (apr_status_t)md_json_getl(mdj, key, MD_KEY_LAST, MD_KEY_STATUS, NULL);
     
+    line = separator? separator : "";
+
     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"
+        s = md_json_gets(mdj, key, MD_KEY_LAST, MD_KEY_PROBLEM, NULL);
+        line = apr_psprintf(bb->p, "%s Error[%s]: %s", line
                            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, ".");
+        line = apr_psprintf(bb->p, "%s finished successfully.", line);
     } 
-    
-    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);
+    else {
+        s = md_json_gets(mdj, key, MD_KEY_LAST, MD_KEY_DETAIL, NULL);
+        if (s) line = apr_psprintf(bb->p, "%s %s", line, 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);
+        line = apr_psprintf(bb->p, "%s (%d retr%s) ", line, 
+            errors, (errors > 1)? "y" : "ies");
     } 
     
-    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, ".");
+    apr_brigade_puts(bb, NULL, NULL, line);
+
+    t = md_json_get_time(mdj, key, MD_KEY_NEXT_RUN, NULL);
+    if (t > apr_time_now() && !finished) {
+        print_time(bb, "\nNext run", t);
+    }
+    else if (!strlen(line)) {
+        apr_brigade_puts(bb, NULL, NULL, "\nOngoing...");
+    }
+}
+
+static void si_val_activity(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    apr_time_t t;
+    
+    (void)info;
+    if (md_json_has_key(mdj, MD_KEY_RENEWAL, NULL)) {
+        print_job_summary(ctx->bb, mdj, MD_KEY_RENEWAL, NULL);
+        return;
+    }
+    
+    t = md_json_get_time(mdj, MD_KEY_RENEW_AT, NULL);
+    if (t > apr_time_now()) {
+        print_time(ctx->bb, "Renew", t);
+    }
+    else if (t) {
+        apr_brigade_puts(ctx->bb, NULL, NULL, "Pending");
+    }
+    else if (MD_RENEW_MANUAL == md_json_getl(mdj, MD_KEY_RENEW_MODE, NULL)) {
+        apr_brigade_puts(ctx->bb, NULL, NULL, "Manual renew");
     }
 }
 
@@ -351,27 +393,20 @@ static void si_val_remote_check(status_ctx *ctx, md_json_t *mdj, const status_in
     const char *fingerprint;
     
     (void)info;
-    fingerprint = md_json_gets(mdj, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL);
-    if (fingerprint) {
+    if (ctx->mc->cert_check_name && ctx->mc->cert_check_url) {
+        fingerprint = md_json_gets(mdj, MD_KEY_CERT, MD_KEY_SHA256_FINGERPRINT, NULL);
         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);
+                           "<a href=\"%s%s\">%s</a> ", 
+                           ctx->mc->cert_check_url, fingerprint, ctx->mc->cert_check_name);
     }
 }
 
-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 void si_val_stapling(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    (void)info;
+    if (!md_json_getb(mdj, MD_KEY_STAPLING, NULL)) return;
+    apr_brigade_puts(ctx->bb, NULL, NULL, "on");
+}
 
 static int json_iter_val(void *data, size_t index, md_json_t *json)
 {
@@ -396,6 +431,9 @@ static void add_json_val(status_ctx *ctx, md_json_t *j)
     else if (md_json_is(MD_JSON_TYPE_OBJECT, j, NULL)) {
         md_json_writeb(j, MD_JSON_FMT_COMPACT, ctx->bb);
     }
+    else if (md_json_is(MD_JSON_TYPE_BOOL, j, NULL)) {
+        apr_brigade_puts(ctx->bb, NULL, NULL, md_json_getb(j, NULL)? "on" : "off");
+    }
 }
 
 static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info *info)
@@ -408,6 +446,17 @@ static void add_status_cell(status_ctx *ctx, md_json_t *mdj, const status_info *
     }
 }
 
+static const status_info status_infos[] = {
+    { "Domain", MD_KEY_NAME, NULL },
+    { "Names", MD_KEY_DOMAINS, NULL },
+    { "Status", MD_KEY_STATE, si_val_status },
+    { "Valid", MD_KEY_CERT, si_val_cert_valid_time },
+    { "CA", MD_KEY_CA, si_val_ca_url },
+    { "Stapling", MD_KEY_STAPLING, si_val_stapling },
+    { "Check@", MD_KEY_SHA256_FINGERPRINT, si_val_remote_check },
+    { "Activity",  MD_KEY_NOTIFIED, si_val_activity },
+};
+
 static int add_md_row(void *baton, apr_size_t index, md_json_t *mdj)
 {
     status_ctx *ctx = baton;
@@ -428,7 +477,7 @@ 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)
+int md_domains_status_hook(request_rec *r, int flags)
 {
     const md_srv_conf_t *sc;
     const md_mod_conf_t *mc;
@@ -437,6 +486,7 @@ int md_status_hook(request_rec *r, int flags)
     apr_array_header_t *mds;
     md_json_t *jstatus, *jstock;
     
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for managed domains, start");
     sc = ap_get_module_config(r->server->module_config, &md_module);
     if (!sc) return DECLINED;
     mc = sc->mc;
@@ -452,9 +502,11 @@ int md_status_hook(request_rec *r, int flags)
     qsort(mds->elts, (size_t)mds->nelts, sizeof(md_t *), md_name_cmp);
 
     if (!html) {
-        apr_brigade_puts(ctx.bb, NULL, NULL, "ManagedDomains: ");
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "no-html summary");
+        apr_brigade_puts(ctx.bb, NULL, NULL, "Managed Certificates: ");
         if (mc->mds->nelts > 0) {
             md_status_take_stock(&jstock, mds, mc->reg, r->pool);
+            ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON summary");
             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), 
@@ -468,13 +520,13 @@ int md_status_hook(request_rec *r, int flags)
         apr_brigade_puts(ctx.bb, NULL, NULL, "\n"); 
     }
     else if (mc->mds->nelts > 0) {
-        md_status_get_json(&jstatus, mds, mc->reg, r->pool);
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "html table");
+        md_status_get_json(&jstatus, mds, mc->reg, mc->ocsp, r->pool);
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "got JSON status");
         apr_brigade_puts(ctx.bb, NULL, NULL, 
-                         "<hr>\n<h2>Managed Domains</h2>\n<table class='md_status'><thead><tr>\n");
+                         "<hr>\n<h3>Managed Certificates</h3>\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>");
+            si_add_header(&ctx, &status_infos[i]);
         }
         apr_brigade_puts(ctx.bb, NULL, NULL, "</tr>\n</thead><tbody>");
         md_json_itera(add_md_row, &ctx, jstatus, MD_KEY_MDS, NULL);
@@ -483,12 +535,101 @@ int md_status_hook(request_rec *r, int flags)
 
     ap_pass_brigade(r->output_filters, ctx.bb);
     apr_brigade_cleanup(ctx.bb);
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for managed domains, end");
+    
+    return OK;
+}
+
+static void si_val_ocsp_activity(status_ctx *ctx, md_json_t *mdj, const status_info *info)
+{
+    apr_time_t t;
+    
+    (void)info;
+    t = md_json_get_time(mdj,  MD_KEY_RENEW_AT, NULL);
+    print_time(ctx->bb, "Refresh", t);
+    print_job_summary(ctx->bb, mdj, MD_KEY_RENEWAL, ": ");
+}
+
+static const status_info ocsp_status_infos[] = {
+    { "Domain", MD_KEY_DOMAIN, NULL },
+    { "Certificate ID", MD_KEY_ID, NULL },
+    { "OCSP Status", MD_KEY_STATUS, NULL },
+    { "Stapling Valid", MD_KEY_VALID, si_val_valid_time },
+    { "Responder", MD_KEY_URL, si_val_url },
+    { "Activity",  MD_KEY_NOTIFIED, si_val_ocsp_activity },
+};
+
+static int add_ocsp_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(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) {
+        apr_brigade_puts(ctx->bb, NULL, NULL, "<td>");
+        add_status_cell(ctx, mdj, &ocsp_status_infos[i]);
+        apr_brigade_puts(ctx->bb, NULL, NULL, "</td>");
+    }
+    apr_brigade_puts(ctx->bb, NULL, NULL, "</tr>");
+    return 1;
+}
+
+int md_ocsp_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;
+    md_json_t *jstatus, *jstock;
+    
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for ocsp stapling, start");
+    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 = " ";
+
+    if (!html) {
+        apr_brigade_puts(ctx.bb, NULL, NULL, "Managed Staplings: ");
+        if (md_ocsp_count(mc->ocsp) > 0) {
+            md_ocsp_get_summary(&jstock, mc->ocsp, r->pool);
+            apr_brigade_printf(ctx.bb, NULL, NULL, "total=%d, good=%d revoked=%d unknown=%d",
+                                (int)md_json_getl(jstock, MD_KEY_TOTAL, NULL), 
+                                (int)md_json_getl(jstock, MD_KEY_GOOD, NULL), 
+                                (int)md_json_getl(jstock, MD_KEY_REVOKED, NULL), 
+                                (int)md_json_getl(jstock, MD_KEY_UNKNOWN, NULL));
+        } 
+        else {
+            apr_brigade_puts(ctx.bb, NULL, NULL, "[]"); 
+        }
+        apr_brigade_puts(ctx.bb, NULL, NULL, "\n"); 
+    }
+    else if (md_ocsp_count(mc->ocsp) > 0) {
+        md_ocsp_get_status_all(&jstatus, mc->ocsp, r->pool);
+        apr_brigade_puts(ctx.bb, NULL, NULL, 
+                         "<hr>\n<h3>Managed Staplings</h3>\n<table class='md_ocsp_status'><thead><tr>\n");
+        for (i = 0; i < (int)(sizeof(ocsp_status_infos)/sizeof(ocsp_status_infos[0])); ++i) {
+            si_add_header(&ctx, &ocsp_status_infos[i]);
+        }
+        apr_brigade_puts(ctx.bb, NULL, NULL, "</tr>\n</thead><tbody>");
+        md_json_itera(add_ocsp_row, &ctx, jstatus, MD_KEY_OCSPS, 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);
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "server-status for ocsp stapling, end");
     
     return OK;
 }
 
 /**************************************************************************************************/
-/* Status handler */
+/* Status handlers */
 
 int md_status_handler(request_rec *r)
 {
@@ -523,12 +664,12 @@ int md_status_handler(request_rec *r)
     }
     
     if (md) {
-        md_status_get_md_json(&jstatus, md, mc->reg, r->pool);
+        md_status_get_md_json(&jstatus, md, mc->reg, mc->ocsp, 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);
+        md_status_get_json(&jstatus, mds, mc->reg, mc->ocsp, r->pool);
     }
 
     if (jstatus) {
@@ -542,3 +683,4 @@ int md_status_handler(request_rec *r)
     }
     return DECLINED;
 }
+
index 39db4c29abcda5850d195228d3ffd49f705fd8b8..f347826cdc86b410900ad565cb558b678a23e624 100644 (file)
@@ -19,7 +19,8 @@
 
 int md_http_cert_status(request_rec *r);
 
-int md_status_hook(request_rec *r, int flags);
+int md_domains_status_hook(request_rec *r, int flags);
+int md_ocsp_status_hook(request_rec *r, int flags);
 
 int md_status_handler(request_rec *r);