-*- coding: utf-8 -*-
Changes with Apache 2.5.0
+ *) mod_md: v1.0.0, new config directive 'MDNotifyCmd' to hook in a program when Managed
+ Domains have obtained/renewed their certificates successfully. [Stefan Eissing]
+
*) mod_rewrite, core: add the Vary header when a condition evaluates to true
and the related RewriteRule is used in a Directory context
(triggering an internal redirect). [Luca Toscano]
</usage>
</directivesynopsis>
+ <directivesynopsis>
+ <name>MDNotifyCmd</name>
+ <description>Run a program when Managed Domain are ready.</description>
+ <syntax>MDNotifyCmd path</syntax>
+ <contextlist>
+ <context>server config</context>
+ </contextlist>
+ <usage>
+ <p>The configured executable is run when Managed Domains have signed up or
+ renewed their certificates. It is given the names of the processed MDs as
+ arguments. It should return status code 0 to indicate that it has
+ run successfully.
+ </p>
+ </usage>
+ </directivesynopsis>
+
<directivesynopsis>
<name>MDPortMap</name>
<description>Map external to internal ports for domain ownership verification.</description>
#define MD_KEY_NAME "name"
#define MD_KEY_PERMANENT "permanent"
#define MD_KEY_PKEY "privkey"
+#define MD_KEY_PROCESSED "processed"
#define MD_KEY_PROTO "proto"
#define MD_KEY_REGISTRATION "registration"
#define MD_KEY_RENEW "renew"
#define MD_KEY_VERSION "version"
#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"
#include <apr_lib.h>
#include <apr_strings.h>
-#include <apr_file_io.h>
+#include <apr_portable.h>
#include <apr_file_info.h>
#include <apr_fnmatch.h>
#include <apr_tables.h>
-#include <apr_time.h>
#include <apr_uri.h>
#include "md_log.h"
return rv;
}
-/* retry login ************************************************************************************/
+/* try and retry for a while **********************************************************************/
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,
return rv;
}
+/* execute process ********************************************************************************/
+
+apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv,
+ int *exit_code)
+{
+ apr_status_t rv;
+ apr_procattr_t *procattr;
+ apr_proc_t *proc;
+ apr_exit_why_e ewhy;
+
+ *exit_code = 0;
+ if (!(proc = apr_pcalloc(p, sizeof(*proc)))) {
+ return APR_ENOMEM;
+ }
+ if ( APR_SUCCESS == (rv = apr_procattr_create(&procattr, p))
+ && APR_SUCCESS == (rv = apr_procattr_io_set(procattr, APR_NO_FILE,
+ APR_NO_PIPE, APR_NO_PIPE))
+ && APR_SUCCESS == (rv = apr_procattr_cmdtype_set(procattr, APR_PROGRAM))
+ && APR_SUCCESS == (rv = apr_proc_create(proc, cmd, argv, NULL, procattr, p))
+ && APR_CHILD_DONE == (rv = apr_proc_wait(proc, exit_code, &ewhy, APR_WAIT))) {
+ /* let's not dwell on exit stati, but core should signal something's bad */
+ if (*exit_code > 127 || APR_PROC_SIGNAL_CORE == ewhy) {
+ return APR_EINCOMPLETE;
+ }
+ return APR_SUCCESS;
+ }
+ return rv;
+}
+
+
/* date/time encoding *****************************************************************************/
const char *md_print_duration(apr_pool_t *p, apr_interval_time_t duration)
int md_array_str_add_missing(struct apr_array_header_t *dest,
struct apr_array_header_t *src, int case_sensitive);
+/**************************************************************************************************/
+/* process execution */
+apr_status_t md_util_exec(apr_pool_t *p, const char *cmd, const char * const *argv,
+ int *exit_code);
+
/**************************************************************************************************/
/* dns name check */
* @macro
* Version number of the md module as c string
*/
-#define MOD_MD_VERSION "0.9.9"
+#define MOD_MD_VERSION "1.0.0"
/**
* @macro
* 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 0x000909
+#define MOD_MD_VERSION_NUM 0x010000
#define MD_EXPERIMENTAL 0
#define MD_ACME_DEF_URL "https://acme-v01.api.letsencrypt.org/directory"
#include "md_curl.h"
#include "md_crypt.h"
#include "md_http.h"
+#include "md_json.h"
#include "md_store.h"
#include "md_store_fs.h"
#include "md_log.h"
typedef struct {
apr_pool_t *p;
server_rec *s;
+ md_mod_conf_t *mc;
ap_watchdog_t *watchdog;
apr_time_t next_change;
if (restart) {
const char *action, *names = "";
+ md_json_t *jprops;
+ md_store_t *store = md_reg_store_get(wd->reg);
int n;
for (i = 0, n = 0; i < wd->jobs->nelts; ++i) {
job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *);
if (job->need_restart && !job->restart_processed) {
- names = apr_psprintf(ptemp, "%s%s%s", names, n? ", " : "", job->md->name);
- ++n;
- job->restart_processed = 1;
+ rv = md_store_load_json(store, MD_SG_STAGING, job->md->name,
+ "job.json", &jprops, ptemp);
+ if (APR_SUCCESS == rv) {
+ job->restart_processed = md_json_getb(jprops, MD_KEY_PROCESSED, NULL);
+ }
+ if (!job->restart_processed) {
+ names = apr_psprintf(ptemp, "%s%s%s", names, n? " " : "", job->md->name);
+ ++n;
+ }
}
}
if (n > 0) {
+ int notified = 1;
+
+ /* Run notifiy command for ready MDs (if configured) and persist that
+ * we have done so. This process might be reaped after n requests or die
+ * of another cause. The one taking over the watchdog need to notify again.
+ */
+ if (wd->mc->notify_cmd) {
+ const char * const *argv;
+ const char *cmdline;
+ int exit_code;
+
+ cmdline = apr_psprintf(ptemp, "%s %s", wd->mc->notify_cmd, names);
+ apr_tokenize_to_argv(cmdline, (char***)&argv, ptemp);
+ if (APR_SUCCESS == (rv = md_util_exec(ptemp, argv[0], argv, &exit_code))) {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, wd->s, APLOGNO()
+ "notify command '%s' returned %d",
+ wd->mc->notify_cmd, exit_code);
+ }
+ else {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, wd->s, APLOGNO()
+ "executing configured MDNotifyCmd %s", wd->mc->notify_cmd);
+ notified = 0;
+ }
+ }
+
+ if (notified) {
+ /* persist the jobs that were notified */
+ for (i = 0, n = 0; i < wd->jobs->nelts; ++i) {
+ job = APR_ARRAY_IDX(wd->jobs, i, md_job_t *);
+ if (job->need_restart && !job->restart_processed) {
+ job->restart_processed = 1;
+
+ rv = md_store_load_json(store, MD_SG_STAGING, job->md->name,
+ MD_FN_JOB, &jprops, ptemp);
+ if (APR_SUCCESS == rv) {
+ md_json_setb(1, jprops, MD_KEY_PROCESSED, NULL);
+ rv = md_store_save_json(store, ptemp, MD_SG_STAGING, job->md->name,
+ MD_FN_JOB, jprops, 0);
+ }
+ }
+ }
+ }
+
+ /* FIXME: the server needs to start gracefully to take the new certficate in.
+ * This poses a variety of problems to solve satisfactory for everyone:
+ * - I myself, have no implementation for Windows
+ * - on *NIX, child processes run with less privileges, preventing
+ * the signal based restart trigger to work
+ * - admins want better control of timing windows for restarts, e.g.
+ * during less busy hours/days.
+ */
rv = md_server_graceful(ptemp, wd->s);
if (APR_ENOTIMPL == rv) {
/* self-graceful restart not supported in this setup */
}
static apr_status_t start_watchdog(apr_array_header_t *names, apr_pool_t *p,
- md_reg_t *reg, server_rec *s)
+ md_reg_t *reg, server_rec *s, md_mod_conf_t *mc)
{
apr_allocator_t *allocator;
md_watchdog *wd;
wd->p = wdp;
wd->reg = reg;
wd->s = s;
+ wd->mc = mc;
wd->jobs = apr_array_make(wd->p, 10, sizeof(md_job_t *));
for (i = 0; i < names->nelts; ++i) {
load_stage_sets(drive_names, p, reg, s);
md_http_use_implementation(md_curl_get_impl(p));
- rv = start_watchdog(drive_names, p, reg, s);
+ rv = start_watchdog(drive_names, p, reg, s, mc);
}
else {
ap_log_error( APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(10075)
#define MD_CMD_DRIVEMODE "MDDriveMode"
#define MD_CMD_MEMBER "MDMember"
#define MD_CMD_MEMBERS "MDMembers"
-#define MD_CMD_MUST_STAPLE "MDMustStaple"
+#define MD_CMD_MUSTSTAPLE "MDMustStaple"
#define MD_CMD_PORTMAP "MDPortMap"
#define MD_CMD_PKEYS "MDPrivateKeys"
#define MD_CMD_PROXY "MDHttpProxy"
#define MD_CMD_RENEWWINDOW "MDRenewWindow"
#define MD_CMD_REQUIREHTTPS "MDRequireHttps"
#define MD_CMD_STOREDIR "MDStoreDir"
+#define MD_CMD_NOTIFYCMD "MDNotifyCmd"
#define DEF_VAL (-1)
MD_HSTS_MAX_AGE_DEFAULT,
NULL,
NULL,
+ NULL,
};
/* Default server specific setting */
return apr_pstrcat(cmd->pool, "unsupported private key type \"", ptype, "\"", NULL);
}
+static const char *md_config_set_notify_cmd(cmd_parms *cmd, void *arg, const char *value)
+{
+ md_srv_conf_t *sc = md_config_get(cmd->server);
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err) {
+ return err;
+ }
+ sc->mc->notify_cmd = value;
+ (void)arg;
+ return NULL;
+}
+
const command_rec md_cmds[] = {
AP_INIT_TAKE1( MD_CMD_CA, md_config_set_ca, NULL, RSRC_CONF,
"URL of CA issueing the certificates"),
AP_INIT_TAKE_ARGV( MD_CMD_MEMBERS, md_config_sec_add_members, NULL, RSRC_CONF,
"Define domain name(s) part of the Managed Domain. Use 'auto' or "
"'manual' to enable/disable auto adding names from virtual hosts."),
- AP_INIT_TAKE1( MD_CMD_MUST_STAPLE, md_config_set_must_staple, NULL, RSRC_CONF,
+ AP_INIT_TAKE1( MD_CMD_MUSTSTAPLE, md_config_set_must_staple, NULL, RSRC_CONF,
"Enable/Disable the Must-Staple flag for new certificates."),
AP_INIT_TAKE12( MD_CMD_PORTMAP, md_config_set_port_map, NULL, RSRC_CONF,
"Declare the mapped ports 80 and 443 on the local server. E.g. 80:8000 "
"Time length for renewal before certificate expires (defaults to days)"),
AP_INIT_TAKE1( MD_CMD_REQUIREHTTPS, md_config_set_require_https, NULL, RSRC_CONF,
"Redirect non-secure requests to the https: equivalent."),
+ AP_INIT_TAKE1( MD_CMD_NOTIFYCMD, md_config_set_notify_cmd, NULL, RSRC_CONF,
+ "set the command to run when signup/renew of domain is complete."),
AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
};
return sc->mc->proxy_url;
case MD_CONFIG_CA_AGREEMENT:
return sc->ca_agreement? sc->ca_agreement : defconf.ca_agreement;
+ case MD_CONFIG_NOTIFY_CMD:
+ return sc->mc->notify_cmd;
default:
return NULL;
}
MD_CONFIG_PROXY,
MD_CONFIG_REQUIRE_HTTPS,
MD_CONFIG_MUST_STAPLE,
+ MD_CONFIG_NOTIFY_CMD,
} md_config_var_t;
typedef struct {
int hsts_max_age; /* max-age of HSTS (rfc6797) header */
const char *hsts_header; /* computed HTST header to use or NULL */
apr_array_header_t *unused_names; /* post config, names of all MDs not assigned to a vhost */
+
+ const char *notify_cmd; /* notification command to execute on signup/renew */
} md_mod_conf_t;
typedef struct md_srv_conf_t {