diff --git a/CHANGES b/CHANGES index e15ebbe473..0678d07313 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,18 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 + *) mod_md: v0.7.0: + - LIVE: the real Let's Encrypt CA is now live by default! If you need to experiment, configure + MDCertificateAuthority https://acme-staging.api.letsencrypt.org/directory + - When existing, complete certificates are renewed, the activation of the new ones is + delayed by 24 hours (or until the existing ones expire, whatever is earler) to accomodate + for clients with weird clocks, refs #1. + - Fixed store sync when MDCAChallenges was removed again from an MD. + - Fixed crash when MD matched the base server, fixes #23 + - Fixed watchgod resetting staging when server processes disappeared (e.g. reached + max requests or other limits). + [Stefan Eissing] + *) mod_proxy: loadfactor parameter can now be a decimal number (eg: 1.25). [Jim Jagielski] diff --git a/modules/md/md.h b/modules/md/md.h index 91a2988ff2..d1a7b518df 100644 --- a/modules/md/md.h +++ b/modules/md/md.h @@ -80,7 +80,8 @@ struct md_t { struct apr_array_header_t *ca_challenges; /* challenge types configured for this MD */ md_state_t state; /* state of this MD */ - apr_time_t expires; /* When the credentials for this domain expire. 0 if unknown */ + apr_time_t valid_from; /* When the credentials start to be valid. 0 if unknown */ + apr_time_t expires; /* When the credentials expire. 0 if unknown */ const char *cert_url; /* url where cert has been created, remember during drive */ const struct md_srv_conf_t *sc; /* server config where it was defined or NULL */ @@ -123,6 +124,7 @@ struct md_t { #define MD_KEY_TYPE "type" #define MD_KEY_URL "url" #define MD_KEY_URI "uri" +#define MD_KEY_VALID_FROM "validFrom" #define MD_KEY_VALUE "value" #define MD_KEY_VERSION "version" @@ -138,9 +140,6 @@ struct md_t { #define MD_VAL_UPDATE(n,o,s) ((n)->s != (o)->s) #define MD_SVAL_UPDATE(n,o,s) ((n)->s && (!(o)->s || strcmp((n)->s, (o)->s))) -#define MD_SECS_PER_HOUR (60*60) -#define MD_SECS_PER_DAY (24*MD_SECS_PER_HOUR) - /** * Determine if the Managed Domain contains a specific domain name. */ @@ -213,6 +212,11 @@ md_t *md_clone(apr_pool_t *p, const md_t *src); */ md_t *md_copy(apr_pool_t *p, const md_t *src); +/** + * Create a merged md with the settings of add overlaying the ones from base. + */ +md_t *md_merge(apr_pool_t *p, const md_t *add, const md_t *base); + /** * Convert the managed domain into a JSON representation and vice versa. * diff --git a/modules/md/md_acme_drive.c b/modules/md/md_acme_drive.c index 533f60d0ce..fe8359be3d 100644 --- a/modules/md/md_acme_drive.c +++ b/modules/md/md_acme_drive.c @@ -651,7 +651,7 @@ static apr_status_t acme_stage(md_proto_driver_t *d) rv = md_load(d->store, MD_SG_STAGING, d->md->name, &ad->md, d->p); if (APR_SUCCESS == rv) { /* So, we have a copy in staging, but is it a recent or an old one? */ - if (!md_is_newer(d->store, MD_SG_STAGING, MD_SG_DOMAINS, d->md->name, d->p)) { + if (md_is_newer(d->store, MD_SG_DOMAINS, MD_SG_STAGING, d->md->name, d->p)) { reset_staging = 1; } } @@ -785,6 +785,26 @@ static apr_status_t acme_stage(md_proto_driver_t *d) "%s: retrieving certificate chain", d->md->name); rv = ad_chain_install(d); } + + if (APR_SUCCESS == rv && ad->cert) { + apr_time_t now = apr_time_now(); + apr_interval_time_t max_delay, delay_activation; + + /* determine when this cert should be activated */ + d->stage_valid_from = md_cert_get_not_before(ad->cert); + if (d->md->state == MD_S_COMPLETE && d->md->expires > now) { + /** + * The MD is complete and un-expired. This is a renewal run. + * Give activation 24 hours leeway (if we have that time) to + * accomodate for clients with somewhat weird clocks. + */ + delay_activation = apr_time_from_sec(MD_SECS_PER_DAY); + if (delay_activation > (max_delay = d->md->expires - now)) { + delay_activation = max_delay; + } + d->stage_valid_from += delay_activation; + } + } } out: return rv; diff --git a/modules/md/md_cmd_reg.c b/modules/md/md_cmd_reg.c index 12f7018a5b..8dac2a89b5 100644 --- a/modules/md/md_cmd_reg.c +++ b/modules/md/md_cmd_reg.c @@ -273,7 +273,7 @@ static apr_status_t assess_and_drive(md_cmd_ctx *ctx, md_t *md) } md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: %s", md->name, msg); - if (APR_SUCCESS == (rv = md_reg_stage(ctx->reg, md, challenge, reset, ctx->p))) { + if (APR_SUCCESS == (rv = md_reg_stage(ctx->reg, md, challenge, reset, NULL, ctx->p))) { md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, ctx->p, "%s: loading", md->name); rv = md_reg_load(ctx->reg, md->name, ctx->p); diff --git a/modules/md/md_core.c b/modules/md/md_core.c index 7506ff63ac..f9bbd78033 100644 --- a/modules/md/md_core.c +++ b/modules/md/md_core.c @@ -245,6 +245,26 @@ md_t *md_clone(apr_pool_t *p, const md_t *src) return md; } +md_t *md_merge(apr_pool_t *p, const md_t *add, const md_t *base) +{ + md_t *n = apr_pcalloc(p, sizeof(*n)); + + n->ca_url = add->ca_url? add->ca_url : base->ca_url; + n->ca_proto = add->ca_proto? add->ca_proto : base->ca_proto; + n->ca_agreement = add->ca_agreement? add->ca_agreement : base->ca_agreement; + n->drive_mode = (add->drive_mode != MD_DRIVE_DEFAULT)? add->drive_mode : base->drive_mode; + n->renew_window = (add->renew_window <= 0)? add->renew_window : base->renew_window; + n->transitive = (add->transitive < 0)? add->transitive : base->transitive; + if (add->ca_challenges) { + n->ca_challenges = apr_array_copy(p, add->ca_challenges); + } + else if (base->ca_challenges) { + n->ca_challenges = apr_array_copy(p, base->ca_challenges); + } + return n; +} + + /**************************************************************************************************/ /* format conversion */ @@ -271,6 +291,11 @@ md_json_t *md_to_json(const md_t *md, apr_pool_t *p) apr_rfc822_date(ts, md->expires); md_json_sets(ts, json, MD_KEY_CERT, MD_KEY_EXPIRES, NULL); } + if (md->valid_from > 0) { + char *ts = apr_pcalloc(p, APR_RFC822_DATE_LEN); + apr_rfc822_date(ts, md->valid_from); + md_json_sets(ts, json, MD_KEY_CERT, MD_KEY_VALID_FROM, NULL); + } md_json_setl(apr_time_sec(md->renew_window), json, MD_KEY_RENEW_WINDOW, NULL); if (md->ca_challenges && md->ca_challenges->nelts > 0) { apr_array_header_t *na; @@ -303,6 +328,10 @@ md_t *md_from_json(md_json_t *json, apr_pool_t *p) if (s && *s) { md->expires = apr_date_parse_rfc(s); } + s = md_json_dups(p, json, MD_KEY_CERT, MD_KEY_VALID_FROM, NULL); + if (s && *s) { + md->valid_from = apr_date_parse_rfc(s); + } md->renew_window = apr_time_from_sec(md_json_getl(json, MD_KEY_RENEW_WINDOW, NULL)); if (md_json_has_key(json, MD_KEY_CA, MD_KEY_CHALLENGES, NULL)) { md->ca_challenges = apr_array_make(p, 5, sizeof(const char*)); diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c index 92b1f0c803..4e0b4d521f 100644 --- a/modules/md/md_crypt.c +++ b/modules/md/md_crypt.c @@ -587,6 +587,18 @@ apr_time_t md_cert_get_not_after(md_cert_t *cert) return time; } +apr_time_t md_cert_get_not_before(md_cert_t *cert) +{ + int secs, days; + apr_time_t time = apr_time_now(); + ASN1_TIME *not_after = X509_get_notBefore(cert->x509); + + if (ASN1_TIME_diff(&days, &secs, NULL, not_after)) { + time += apr_time_from_sec((days * MD_SECS_PER_DAY) + secs); + } + return time; +} + int md_cert_covers_domain(md_cert_t *cert, const char *domain_name) { if (!cert->alt_names) { diff --git a/modules/md/md_crypt.h b/modules/md/md_crypt.h index 6f5c98826c..0437f897ea 100644 --- a/modules/md/md_crypt.h +++ b/modules/md/md_crypt.h @@ -88,6 +88,7 @@ int md_cert_has_expired(const md_cert_t *cert); int md_cert_covers_domain(md_cert_t *cert, const char *domain_name); int md_cert_covers_md(md_cert_t *cert, const struct md_t *md); apr_time_t md_cert_get_not_after(md_cert_t *cert); +apr_time_t md_cert_get_not_before(md_cert_t *cert); apr_status_t md_cert_get_issuers_uri(const char **puri, md_cert_t *cert, apr_pool_t *p); apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, md_cert_t *cert, apr_pool_t *p); diff --git a/modules/md/md_reg.c b/modules/md/md_reg.c index 791565fd7d..04921c887f 100644 --- a/modules/md/md_reg.c +++ b/modules/md/md_reg.c @@ -156,7 +156,7 @@ static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md) md_state_t state = MD_S_UNKNOWN; const md_creds_t *creds; const md_cert_t *cert; - apr_time_t expires = 0; + apr_time_t expires = 0, valid_from = 0; apr_status_t rv; int i; @@ -176,6 +176,7 @@ static apr_status_t state_init(md_reg_t *reg, apr_pool_t *p, md_t *md) md->name); } else { + valid_from = md_cert_get_not_before(creds->cert); expires = md_cert_get_not_after(creds->cert); if (md_cert_has_expired(creds->cert)) { state = MD_S_EXPIRED; @@ -221,6 +222,7 @@ out: md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "md{%s}: error", md->name); } md->state = state; + md->valid_from = valid_from; md->expires = expires; return rv; } @@ -774,13 +776,16 @@ apr_status_t md_reg_sync(md_reg_t *reg, apr_pool_t *p, apr_pool_t *ptemp, } 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 = (md->ca_challenges? - apr_array_copy(ptemp, md->ca_challenges) : NULL); + 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 (fields) { rv = md_reg_update(reg, ptemp, smd->name, smd, fields); @@ -839,12 +844,14 @@ static apr_status_t run_stage(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_ int reset; md_proto_driver_t *driver; const char *challenge; + apr_time_t *pvalid_from; apr_status_t rv; proto = va_arg(ap, const md_proto_t *); md = va_arg(ap, const md_t *); challenge = va_arg(ap, const char *); reset = va_arg(ap, int); + pvalid_from = va_arg(ap, apr_time_t*); driver = apr_pcalloc(ptemp, sizeof(*driver)); rv = init_proto_driver(driver, proto, reg, md, challenge, reset, ptemp); @@ -853,13 +860,17 @@ static apr_status_t run_stage(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, ptemp, "%s: run staging", md->name); rv = proto->stage(driver); + + if (APR_SUCCESS == rv && pvalid_from) { + *pvalid_from = driver->stage_valid_from; + } } md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, ptemp, "%s: staging done", md->name); return rv; } apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md, const char *challenge, - int reset, apr_pool_t *p) + int reset, apr_time_t *pvalid_from, apr_pool_t *p) { const md_proto_t *proto; @@ -877,7 +888,7 @@ apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md, const char *challenge, return APR_EINVAL; } - return md_util_pool_vdo(run_stage, reg, p, proto, md, challenge, reset, NULL); + return md_util_pool_vdo(run_stage, reg, p, proto, md, challenge, reset, pvalid_from, NULL); } static apr_status_t run_load(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap) @@ -950,21 +961,3 @@ apr_status_t md_reg_load(md_reg_t *reg, const char *name, apr_pool_t *p) return md_util_pool_vdo(run_load, reg, p, name, NULL); } -apr_status_t md_reg_drive(md_reg_t *reg, md_t *md, const char *challenge, - int reset, int force, apr_pool_t *p) -{ - apr_status_t rv; - int errored, renew; - - if (APR_SUCCESS == (rv = md_reg_assess(reg, md, &errored, &renew, p))) { - if (errored) { - rv = APR_EGENERAL; - } - else if (renew || force) { - if (APR_SUCCESS == (rv = md_reg_stage(reg, md, challenge, reset, p))) { - rv = md_reg_load(reg, md->name, p); - } - } - } - return rv; -} diff --git a/modules/md/md_reg.h b/modules/md/md_reg.h index d3af921903..285ffc7ee1 100644 --- a/modules/md/md_reg.h +++ b/modules/md/md_reg.h @@ -135,6 +135,7 @@ struct md_proto_driver_t { const md_t *md; void *baton; int reset; + apr_time_t stage_valid_from; }; typedef apr_status_t md_proto_init_cb(md_proto_driver_t *driver); @@ -154,7 +155,8 @@ struct md_proto_t { * without interfering with any existing credentials. */ apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md, - const char *challenge, int reset, apr_pool_t *p); + const char *challenge, int reset, + apr_time_t *pvalid_from, apr_pool_t *p); /** * Load a staged set of new credentials for the managed domain. This will archive @@ -164,19 +166,4 @@ apr_status_t md_reg_stage(md_reg_t *reg, const md_t *md, */ apr_status_t md_reg_load(md_reg_t *reg, const char *name, apr_pool_t *p); -/** - * Drive the given managed domain toward completeness. - * This is a convenience method that combines staging and, on success, loading - * of a new managed domain credentials set. - * - * @param reg the md registry - * @param md the managed domain to drive - * @param challenge the challenge type to use or NULL for auto selection - * @param reset remove any staging information that has been collected - * @param force force driving even though it looks unnecessary (e.g. not epxired) - * @param p pool to use - */ -apr_status_t md_reg_drive(md_reg_t *reg, md_t *md, - const char *challenge, int reset, int force, apr_pool_t *p); - #endif /* mod_md_md_reg_h */ diff --git a/modules/md/md_util.c b/modules/md/md_util.c index ac531157f0..01a0dfe945 100644 --- a/modules/md/md_util.c +++ b/modules/md/md_util.c @@ -746,6 +746,17 @@ apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs, return rv; } +/* date/time encoding *****************************************************************************/ + +const char *md_print_duration(apr_pool_t *p, apr_interval_time_t duration) +{ + int secs = (int)(apr_time_sec(duration) % MD_SECS_PER_DAY); + return apr_psprintf(p, "%2d:%02d:%02d hours", + (int)secs/MD_SECS_PER_HOUR, (int)(secs%(MD_SECS_PER_HOUR))/60, + (int)(secs%60)); +} + + /* base64 url encoding ****************************************************************************/ static const int BASE64URL_UINT6[] = { diff --git a/modules/md/md_util.h b/modules/md/md_util.h index 02c72bcf02..dfdc8d89e7 100644 --- a/modules/md/md_util.h +++ b/modules/md/md_util.h @@ -130,4 +130,12 @@ apr_status_t md_util_try(md_util_try_fn *fn, void *baton, int ignore_errs, apr_interval_time_t timeout, apr_interval_time_t start_delay, apr_interval_time_t max_delay, int backoff); +/**************************************************************************************************/ +/* date/time related */ + +#define MD_SECS_PER_HOUR (60*60) +#define MD_SECS_PER_DAY (24*MD_SECS_PER_HOUR) + +const char *md_print_duration(apr_pool_t *p, apr_interval_time_t duration); + #endif /* md_util_h */ diff --git a/modules/md/md_version.h b/modules/md/md_version.h index 7b2063837c..41474e853e 100644 --- a/modules/md/md_version.h +++ b/modules/md/md_version.h @@ -26,7 +26,7 @@ * @macro * Version number of the md module as c string */ -#define MOD_MD_VERSION "0.6.1" +#define MOD_MD_VERSION "0.7.0-git" /** * @macro @@ -34,9 +34,9 @@ * 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 0x000601 +#define MOD_MD_VERSION_NUM 0x000700 -#define MD_EXPERIMENTAL 1 -#define MD_ACME_DEF_URL "https://acme-staging.api.letsencrypt.org/directory" +#define MD_EXPERIMENTAL 0 +#define MD_ACME_DEF_URL "https://acme-v01.api.letsencrypt.org/directory" #endif /* mod_md_md_version_h */ diff --git a/modules/md/mod_md.c b/modules/md/mod_md.c index 1d4abeaf9b..075dbb35c4 100644 --- a/modules/md/mod_md.c +++ b/modules/md/mod_md.c @@ -128,7 +128,7 @@ static apr_status_t apply_to_servers(md_t *md, server_rec *base_server, */ memset(&r, 0, sizeof(r)); sc = NULL; - + /* This MD may apply to 0, 1 or more sever_recs */ for (s = base_server; s; s = s->next) { r.server = s; @@ -145,14 +145,14 @@ static apr_status_t apply_to_servers(md_t *md, server_rec *base_server, "Server %s:%d matches md %s (config %s)", s->server_hostname, s->port, md->name, sc->name); - if (sc->md == md) { + if (sc->assigned == md) { /* already matched via another domain name */ goto next_server; } - else if (sc->md) { + 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->md->name); + md->name, s->server_hostname, sc->assigned->name); rv = APR_EINVAL; goto next_server; } @@ -172,11 +172,12 @@ static apr_status_t apply_to_servers(md_t *md, server_rec *base_server, s->server_admin); } /* remember */ - sc->md = md; + sc->assigned = md; /* This server matches a managed domain. If it contains names or * alias that are not in this md, a generated certificate will not match. */ - if (APR_SUCCESS == (rv2 = check_coverage(md, s->server_hostname, s, p))) { + if (APR_SUCCESS == (rv2 = check_coverage(md, s->server_hostname, s, p)) + && s->names) { for (j = 0; j < s->names->nelts; ++j) { name = APR_ARRAY_IDX(s->names, j, const char*); if (APR_SUCCESS != (rv2 = check_coverage(md, name, s, p))) { @@ -268,7 +269,7 @@ static apr_status_t md_calc_md_list(apr_pool_t *p, apr_pool_t *plog, } ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, base_server, APLOGNO(10039) - "COmpleted MD[%s, CA=%s, Proto=%s, Agreement=%s, Drive=%d, renew=%ld]", + "Completed MD[%s, CA=%s, Proto=%s, Agreement=%s, Drive=%d, renew=%ld]", md->name, md->ca_url, md->ca_proto, md->ca_agreement, md->drive_mode, (long)md->renew_window); } @@ -440,11 +441,13 @@ typedef struct { server_rec *s; ap_watchdog_t *watchdog; int all_valid; + apr_time_t valid_not_before; int error_count; int processed_count; int error_runs; apr_time_t next_change; + apr_time_t next_valid; apr_array_header_t *mds; md_reg_t *reg; @@ -453,31 +456,38 @@ typedef struct { static apr_status_t drive_md(md_watchdog *wd, md_t *md, apr_pool_t *ptemp) { apr_status_t rv = APR_SUCCESS; - apr_time_t renew_time; + apr_time_t renew_time, now, valid_from; int errored, renew; char ts[APR_RFC822_DATE_LEN]; - if (APR_SUCCESS == (rv = md_reg_assess(wd->reg, md, &errored, &renew, wd->p))) { + if (md->state == MD_S_COMPLETE && !md->expires) { + /* This is our indicator that we did already renewed this managed domain + * successfully and only wait on the next restart for it to activate */ + now = apr_time_now(); + ap_log_error( APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO(10051) + "md(%s): has been renewed, should be activated in %s", + md->name, (md->valid_from <= now)? "about now" : + md_print_duration(ptemp, md->valid_from - now)); + } + else if (APR_SUCCESS == (rv = md_reg_assess(wd->reg, md, &errored, &renew, wd->p))) { if (errored) { ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10050) "md(%s): in error state", md->name); } - else if (md->state == MD_S_COMPLETE && !md->expires) { - /* This is our indicator that we did already renew this managed domain - * successfully and only wait on the next restart for it to activate */ - ap_log_error( APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO(10051) - "md(%s): has been renewed, will activate on next restart", md->name); - } else if (renew) { ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10052) "md(%s): state=%d, driving", md->name, md->state); - rv = md_reg_stage(wd->reg, md, NULL, 0, ptemp); + rv = md_reg_stage(wd->reg, md, NULL, 0, &valid_from, ptemp); if (APR_SUCCESS == rv) { md->state = MD_S_COMPLETE; md->expires = 0; + md->valid_from = valid_from; ++wd->processed_count; + if (!wd->next_valid || wd->next_valid > valid_from) { + wd->next_valid = valid_from; + } } } else { @@ -498,7 +508,7 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) md_watchdog *wd = baton; apr_status_t rv = APR_SUCCESS; md_t *md; - apr_interval_time_t interval; + apr_interval_time_t interval, now; int i; switch (state) { @@ -513,9 +523,11 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) interval = apr_time_from_sec(MD_SECS_PER_DAY / 2); wd->all_valid = 1; + wd->valid_not_before = 0; wd->processed_count = 0; wd->error_count = 0; wd->next_change = 0; + wd->next_valid = 0; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, wd->s, APLOGNO(10055) "md watchdog run, auto drive %d mds", wd->mds->nelts); @@ -523,6 +535,7 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) /* Check if all Managed Domains are ok or if we have to do something */ for (i = 0; i < wd->mds->nelts; ++i) { md = APR_ARRAY_IDX(wd->mds, i, md_t *); + if (APR_SUCCESS != (rv = drive_md(wd, md, ptemp))) { wd->all_valid = 0; ++wd->error_count; @@ -534,7 +547,18 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) /* Determine when we want to run next */ wd->error_runs = wd->error_count? (wd->error_runs + 1) : 0; if (wd->all_valid) { - ap_log_error( APLOG_MARK, APLOG_TRACE1, 0, wd->s, "all managed domains are valid"); + now = apr_time_now(); + if (wd->next_valid > now && (wd->next_valid - now < interval)) { + interval = wd->next_valid - now; + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, + "Delaying activation of %d Managed Domain%s by %s", + wd->processed_count, (wd->processed_count > 1)? "s have" : " has", + md_print_duration(ptemp, interval)); + } + else { + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, wd->s, + "all managed domains are valid"); + } } else { /* back off duration, depending on the errors we encounter in a row */ @@ -542,7 +566,7 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) if (interval > apr_time_from_sec(60*60)) { interval = apr_time_from_sec(60*60); } - ap_log_error( APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO(10057) + ap_log_error(APLOG_MARK, APLOG_INFO, 0, wd->s, APLOGNO(10057) "encountered errors for the %d. time, next run in %d seconds", wd->error_runs, (int)apr_time_sec(interval)); } @@ -561,10 +585,8 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) * runs. When you wake up a hibernated machine, the watchdog will not run right away */ if (APLOGdebug(wd->s)) { - int secs = (int)(apr_time_sec(interval) % MD_SECS_PER_DAY); - ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, "next run in %2d:%02d:%02d hours", - (int)secs/MD_SECS_PER_HOUR, (int)(secs%(MD_SECS_PER_HOUR))/60, - (int)(secs%60)); + ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, wd->s, "next run in %s", + md_print_duration(ptemp, interval)); } wd_set_interval(wd->watchdog, interval, wd, run_watchdog); break; @@ -575,14 +597,21 @@ static apr_status_t run_watchdog(int state, void *baton, apr_pool_t *ptemp) } if (wd->processed_count) { + now = apr_time_now(); + if (wd->all_valid) { - rv = md_server_graceful(ptemp, wd->s); - if (APR_ENOTIMPL == rv) { - /* self-graceful restart not supported in this setup */ - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, wd->s, APLOGNO(10059) - "%d Managed Domain%s been setup and changes will be " - "activated on next (graceful) server restart.", - wd->processed_count, (wd->processed_count > 1)? "s have" : " has"); + if (wd->next_valid <= now) { + rv = md_server_graceful(ptemp, wd->s); + if (APR_ENOTIMPL == rv) { + /* self-graceful restart not supported in this setup */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, wd->s, APLOGNO(10059) + "%d Managed Domain%s been setup and changes will be " + "activated on next (graceful) server restart.", + wd->processed_count, (wd->processed_count > 1)? "s have" : " has"); + } + } + else { + /* activation is delayed */ } } else { @@ -796,9 +825,9 @@ static int md_is_managed(server_rec *s) { md_srv_conf_t *conf = md_config_get(s); - if (conf && conf->md) { + if (conf && conf->assigned) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10076) - "%s: manages server %s", conf->md->name, s->server_hostname); + "%s: manages server %s", conf->assigned->name, s->server_hostname); return 1; } ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, s, @@ -821,11 +850,11 @@ static apr_status_t md_get_credentials(server_rec *s, apr_pool_t *p, sc = md_config_get(s); - if (sc && sc->md) { + if (sc && sc->assigned) { assert(sc->mc); assert(sc->mc->store); if (APR_SUCCESS == (rv = md_reg_init(®, p, sc->mc->store))) { - md = md_reg_get(reg, sc->md->name, p); + md = md_reg_get(reg, sc->assigned->name, p); if (md->state != MD_S_COMPLETE) { return APR_EAGAIN; } diff --git a/modules/md/mod_md_config.c b/modules/md/mod_md_config.c index b93d96db98..1b39d81d70 100644 --- a/modules/md/mod_md_config.c +++ b/modules/md/mod_md_config.c @@ -72,6 +72,7 @@ static md_srv_conf_t defconf = { NULL, NULL, NULL, + NULL, }; static md_mod_conf_t *mod_md_config; @@ -147,7 +148,6 @@ void *md_config_create_svr(apr_pool_t *pool, server_rec *s) conf->name = apr_pstrcat(pool, "srv[", CONF_S_NAME(s), "]", NULL); conf->s = s; conf->mc = md_mod_conf_get(pool, 1); - conf->md = apr_pcalloc(pool, sizeof(*conf->md)); srv_conf_props_clear(conf); @@ -166,7 +166,6 @@ static void *md_config_merge(apr_pool_t *pool, void *basev, void *addv) nsc->transitive = (add->transitive != DEF_VAL)? add->transitive : base->transitive; nsc->drive_mode = (add->drive_mode != DEF_VAL)? add->drive_mode : base->drive_mode; - nsc->md = NULL; nsc->renew_window = (add->renew_window != DEF_VAL)? add->renew_window : base->renew_window; nsc->ca_url = add->ca_url? add->ca_url : base->ca_url; @@ -174,6 +173,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->current = NULL; + nsc->assigned = NULL; + return nsc; } @@ -247,6 +249,7 @@ static const char *md_config_sec_start(cmd_parms *cmd, void *mconfig, const char name = ap_getword_white(cmd->pool, &arg); domains = apr_array_make(cmd->pool, 5, sizeof(const char *)); + add_domain_name(domains, name, cmd->pool); while (*arg != '\0') { name = ap_getword_white(cmd->pool, &arg); if (NULL != set_transitive(&transitive, name)) { @@ -267,14 +270,14 @@ static const char *md_config_sec_start(cmd_parms *cmd, void *mconfig, const char * end of this section */ memcpy(&save, sc, sizeof(save)); srv_conf_props_clear(sc); - sc->md = md; + sc->current = md; if (NULL == (err = ap_walk_config(cmd->directive->first_child, cmd, cmd->context))) { srv_conf_props_apply(md, sc, cmd->pool); APR_ARRAY_PUSH(sc->mc->mds, const md_t *) = md; } - sc->md = NULL; + sc->current = NULL; srv_conf_props_copy(sc, &save); return err; @@ -295,10 +298,10 @@ static const char *md_config_sec_add_members(cmd_parms *cmd, void *dc, return err; } - assert(sc->md); + assert(sc->current); for (i = 0; i < argc; ++i) { if (NULL != set_transitive(&sc->transitive, argv[i])) { - add_domain_name(sc->md->domains, argv[i], cmd->pool); + add_domain_name(sc->current->domains, argv[i], cmd->pool); } } return NULL; diff --git a/modules/md/mod_md_config.h b/modules/md/mod_md_config.h index 3407c5512c..4996b5295f 100644 --- a/modules/md/mod_md_config.h +++ b/modules/md/mod_md_config.h @@ -58,7 +58,8 @@ typedef struct md_srv_conf_t { const char *ca_agreement; /* accepted agreement uri between CA and user */ struct apr_array_header_t *ca_challenges; /* challenge types configured */ - md_t *md; /* post_config: MD that applies to this server or NULL */ + md_t *current; /* md currently defined in section */ + md_t *assigned; /* post_config: MD that applies to this server or NULL */ } md_srv_conf_t; void *md_config_create_svr(apr_pool_t *pool, server_rec *s);