summaryrefslogtreecommitdiff
path: root/modules/md/md_acme_authz.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/md/md_acme_authz.c')
-rw-r--r--modules/md/md_acme_authz.c682
1 files changed, 682 insertions, 0 deletions
diff --git a/modules/md/md_acme_authz.c b/modules/md/md_acme_authz.c
new file mode 100644
index 0000000000..83c6ca9609
--- /dev/null
+++ b/modules/md/md_acme_authz.c
@@ -0,0 +1,682 @@
+/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr_lib.h>
+#include <apr_buckets.h>
+#include <apr_file_info.h>
+#include <apr_file_io.h>
+#include <apr_fnmatch.h>
+#include <apr_hash.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+
+#include "md.h"
+#include "md_crypt.h"
+#include "md_json.h"
+#include "md_http.h"
+#include "md_log.h"
+#include "md_jws.h"
+#include "md_store.h"
+#include "md_util.h"
+
+#include "md_acme.h"
+#include "md_acme_authz.h"
+
+md_acme_authz_t *md_acme_authz_create(apr_pool_t *p)
+{
+ md_acme_authz_t *authz;
+ authz = apr_pcalloc(p, sizeof(*authz));
+
+ return authz;
+}
+
+md_acme_authz_set_t *md_acme_authz_set_create(apr_pool_t *p, md_acme_t *acme)
+{
+ md_acme_authz_set_t *authz_set;
+
+ authz_set = apr_pcalloc(p, sizeof(*authz_set));
+ authz_set->authzs = apr_array_make(p, 5, sizeof(md_acme_authz_t *));
+
+ return authz_set;
+}
+
+md_acme_authz_t *md_acme_authz_set_get(md_acme_authz_set_t *set, const char *domain)
+{
+ md_acme_authz_t *authz;
+ int i;
+
+ assert(domain);
+ for (i = 0; i < set->authzs->nelts; ++i) {
+ authz = APR_ARRAY_IDX(set->authzs, i, md_acme_authz_t *);
+ if (!apr_strnatcasecmp(domain, authz->domain)) {
+ return authz;
+ }
+ }
+ return NULL;
+}
+
+apr_status_t md_acme_authz_set_add(md_acme_authz_set_t *set, md_acme_authz_t *authz)
+{
+ md_acme_authz_t *existing;
+
+ assert(authz->domain);
+ if (NULL != (existing = md_acme_authz_set_get(set, authz->domain))) {
+ return APR_EINVAL;
+ }
+ APR_ARRAY_PUSH(set->authzs, md_acme_authz_t*) = authz;
+ return APR_SUCCESS;
+}
+
+apr_status_t md_acme_authz_set_remove(md_acme_authz_set_t *set, const char *domain)
+{
+ md_acme_authz_t *authz;
+ int i;
+
+ assert(domain);
+ for (i = 0; i < set->authzs->nelts; ++i) {
+ authz = APR_ARRAY_IDX(set->authzs, i, md_acme_authz_t *);
+ if (!apr_strnatcasecmp(domain, authz->domain)) {
+ int n = i +1;
+ if (n < set->authzs->nelts) {
+ void **elems = (void **)set->authzs->elts;
+ memmove(elems + i, elems + n, set->authzs->nelts - n);
+ }
+ --set->authzs->nelts;
+ return APR_SUCCESS;
+ }
+ }
+ return APR_ENOENT;
+}
+
+/**************************************************************************************************/
+/* Register a new authorization */
+
+typedef struct {
+ size_t index;
+ const char *type;
+ const char *uri;
+ const char *token;
+ const char *key_authz;
+} md_acme_authz_cha_t;
+
+typedef struct {
+ apr_pool_t *p;
+ md_acme_t *acme;
+ const char *domain;
+ md_acme_authz_t *authz;
+ md_acme_authz_cha_t *challenge;
+} authz_req_ctx;
+
+static void authz_req_ctx_init(authz_req_ctx *ctx, md_acme_t *acme,
+ const char *domain, md_acme_authz_t *authz, apr_pool_t *p)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->p = p;
+ ctx->acme = acme;
+ ctx->domain = domain;
+ ctx->authz = authz;
+}
+
+static apr_status_t on_init_authz(md_acme_req_t *req, void *baton)
+{
+ authz_req_ctx *ctx = baton;
+ md_json_t *jpayload;
+
+ jpayload = md_json_create(req->p);
+ md_json_sets("new-authz", jpayload, MD_KEY_RESOURCE, NULL);
+ md_json_sets("dns", jpayload, MD_KEY_IDENTIFIER, MD_KEY_TYPE, NULL);
+ md_json_sets(ctx->domain, jpayload, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL);
+
+ return md_acme_req_body_init(req, jpayload);
+}
+
+static apr_status_t authz_created(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs,
+ md_json_t *body, void *baton)
+{
+ authz_req_ctx *ctx = baton;
+ const char *location = apr_table_get(hdrs, "location");
+ apr_status_t rv = APR_SUCCESS;
+
+ if (location) {
+ ctx->authz = md_acme_authz_create(ctx->p);
+ ctx->authz->domain = apr_pstrdup(ctx->p, ctx->domain);
+ ctx->authz->location = apr_pstrdup(ctx->p, location);
+ ctx->authz->resource = md_json_clone(ctx->p, body);
+ md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ctx->p, "authz_new at %s", location);
+ }
+ else {
+ rv = APR_EINVAL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, ctx->p, "new authz, no location header");
+ }
+ return rv;
+}
+
+apr_status_t md_acme_authz_register(struct md_acme_authz_t **pauthz, md_acme_t *acme,
+ md_store_t *store, const char *domain, apr_pool_t *p)
+{
+ apr_status_t rv;
+ authz_req_ctx ctx;
+
+ authz_req_ctx_init(&ctx, acme, domain, NULL, p);
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, acme->p, "create new authz");
+ rv = md_acme_POST(acme, acme->new_authz, on_init_authz, authz_created, NULL, &ctx);
+
+ *pauthz = (APR_SUCCESS == rv)? ctx.authz : NULL;
+ return rv;
+}
+
+/**************************************************************************************************/
+/* Update an exiosting authorization */
+
+apr_status_t md_acme_authz_update(md_acme_authz_t *authz, md_acme_t *acme,
+ md_store_t *store, apr_pool_t *p)
+{
+ md_json_t *json;
+ const char *s;
+ apr_status_t rv;
+
+ assert(acme);
+ assert(acme->http);
+ assert(authz);
+ assert(authz->location);
+
+ if (APR_SUCCESS != (rv = md_acme_get_json(&json, acme, authz->location, p))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "update authz for %s at %s",
+ authz->domain, authz->location);
+ return rv;
+ }
+
+ authz->resource = json;
+ s = md_json_gets(json, "identifier", "type", NULL);
+ if (!s || strcmp(s, "dns")) return APR_EINVAL;
+ s = md_json_gets(json, "identifier", "value", NULL);
+ if (!s || strcmp(s, authz->domain)) return APR_EINVAL;
+
+ authz->state = MD_ACME_AUTHZ_S_UNKNOWN;
+ s = md_json_gets(json, "status", NULL);
+ if (s && !strcmp(s, "pending")) {
+ authz->state = MD_ACME_AUTHZ_S_PENDING;
+ }
+ else if (s && !strcmp(s, "valid")) {
+ authz->state = MD_ACME_AUTHZ_S_VALID;
+ }
+ else if (s && !strcmp(s, "invalid")) {
+ authz->state = MD_ACME_AUTHZ_S_INVALID;
+ }
+ else if (s) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, "unknown authz state '%s' "
+ "for %s in %s", s, authz->domain, authz->location);
+ return APR_EINVAL;
+ }
+ return rv;
+}
+
+/**************************************************************************************************/
+/* response to a challenge */
+
+static md_acme_authz_cha_t *cha_from_json(apr_pool_t *p, size_t index, md_json_t *json)
+{
+ md_acme_authz_cha_t * cha;
+
+ cha = apr_pcalloc(p, sizeof(*cha));
+ cha->index = index;
+ cha->type = md_json_dups(p, json, MD_KEY_TYPE, NULL);
+ cha->uri = md_json_dups(p, json, MD_KEY_URI, NULL);
+ cha->token = md_json_dups(p, json, MD_KEY_TOKEN, NULL);
+ cha->key_authz = md_json_dups(p, json, MD_KEY_KEYAUTHZ, NULL);
+
+ return cha;
+}
+
+static apr_status_t on_init_authz_resp(md_acme_req_t *req, void *baton)
+{
+ authz_req_ctx *ctx = baton;
+ md_json_t *jpayload;
+
+ jpayload = md_json_create(req->p);
+ md_json_sets("challenge", jpayload, MD_KEY_RESOURCE, NULL);
+ md_json_sets(ctx->challenge->key_authz, jpayload, MD_KEY_KEYAUTHZ, NULL);
+
+ return md_acme_req_body_init(req, jpayload);
+}
+
+static apr_status_t authz_http_set(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs,
+ md_json_t *body, void *baton)
+{
+ authz_req_ctx *ctx = baton;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "updated authz %s", ctx->authz->location);
+ return APR_SUCCESS;
+}
+
+static apr_status_t setup_key_authz(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
+ md_acme_t *acme, apr_pool_t *p, int *pchanged)
+{
+ const char *thumb64, *key_authz;
+ apr_status_t rv;
+
+ assert(cha);
+ assert(cha->token);
+
+ *pchanged = 0;
+ if (APR_SUCCESS == (rv = md_jws_pkey_thumb(&thumb64, p, acme->acct_key))) {
+ key_authz = apr_psprintf(p, "%s.%s", cha->token, thumb64);
+ if (cha->key_authz) {
+ if (strcmp(key_authz, cha->key_authz)) {
+ /* Hu? Did the account change key? */
+ cha->key_authz = NULL;
+ }
+ }
+ if (!cha->key_authz) {
+ cha->key_authz = key_authz;
+ *pchanged = 1;
+ }
+ }
+ return rv;
+}
+
+static apr_status_t cha_http_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
+ md_acme_t *acme, md_store_t *store, apr_pool_t *p)
+{
+ const char *data;
+ apr_status_t rv;
+ int notify_server;
+
+ if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, &notify_server))) {
+ goto out;
+ }
+
+ rv = md_store_load(store, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01,
+ MD_SV_TEXT, (void**)&data, p);
+ if ((APR_SUCCESS == rv && strcmp(cha->key_authz, data)) || APR_STATUS_IS_ENOENT(rv)) {
+ rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_HTTP01,
+ MD_SV_TEXT, (void*)cha->key_authz, 0);
+ authz->dir = authz->domain;
+ notify_server = 1;
+ }
+
+ if (APR_SUCCESS == rv && notify_server) {
+ authz_req_ctx ctx;
+
+ /* challenge is setup or was changed from previous data, tell ACME server
+ * so it may (re)try verification */
+ authz_req_ctx_init(&ctx, acme, NULL, authz, p);
+ ctx.challenge = cha;
+ rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, &ctx);
+ }
+out:
+ return rv;
+}
+
+static apr_status_t setup_cha_dns(const char **pdns, md_acme_authz_cha_t *cha, apr_pool_t *p)
+{
+ const char *dhex;
+ char *dns;
+ apr_size_t dhex_len;
+ apr_status_t rv;
+
+ rv = md_crypt_sha256_digest_hex(&dhex, p, cha->key_authz, strlen(cha->key_authz));
+ if (APR_SUCCESS == rv) {
+ dhex = md_util_str_tolower((char*)dhex);
+ dhex_len = strlen(dhex);
+ assert(dhex_len > 32);
+ dns = apr_pcalloc(p, dhex_len + 1 + sizeof(MD_TLSSNI01_DNS_SUFFIX));
+ strncpy(dns, dhex, 32);
+ dns[32] = '.';
+ strncpy(dns+33, dhex+32, dhex_len-32);
+ memcpy(dns+(dhex_len+1), MD_TLSSNI01_DNS_SUFFIX, sizeof(MD_TLSSNI01_DNS_SUFFIX));
+ }
+ *pdns = (APR_SUCCESS == rv)? dns : NULL;
+ return rv;
+}
+
+static apr_status_t cha_tls_sni_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
+ md_acme_t *acme, md_store_t *store, apr_pool_t *p)
+{
+ md_cert_t *cha_cert;
+ md_pkey_t *cha_key;
+ const char *cha_dns;
+ apr_status_t rv;
+ int notify_server;
+
+ if ( APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, &notify_server))
+ || APR_SUCCESS != (rv = setup_cha_dns(&cha_dns, cha, p))) {
+ goto out;
+ }
+
+ rv = md_store_load(store, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_CERT,
+ MD_SV_CERT, (void**)&cha_cert, p);
+ if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, cha_dns))
+ || APR_STATUS_IS_ENOENT(rv)) {
+
+ if (APR_SUCCESS != (rv = md_pkey_gen_rsa(&cha_key, p, acme->pkey_bits))) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-sni-01 challgenge key",
+ authz->domain);
+ goto out;
+ }
+
+ /* setup a certificate containing the challenge dns */
+ rv = md_cert_self_sign(&cha_cert, authz->domain, cha_dns, cha_key,
+ apr_time_from_sec(7 * MD_SECS_PER_DAY), p);
+
+ if (APR_SUCCESS != rv) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: setup self signed cert for %s",
+ authz->domain, cha_dns);
+ goto out;
+ }
+
+ rv = md_store_save(store, p, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_PKEY,
+ MD_SV_PKEY, (void*)cha_key, 0);
+ if (APR_SUCCESS == rv) {
+ rv = md_store_save(store, p, MD_SG_CHALLENGES, cha_dns, MD_FN_TLSSNI01_CERT,
+ MD_SV_CERT, (void*)cha_cert, 0);
+ }
+ authz->dir = cha_dns;
+ notify_server = 1;
+ }
+
+ if (APR_SUCCESS == rv && notify_server) {
+ authz_req_ctx ctx;
+
+ /* challenge is setup or was changed from previous data, tell ACME server
+ * so it may (re)try verification */
+ authz_req_ctx_init(&ctx, acme, NULL, authz, p);
+ ctx.challenge = cha;
+ rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, &ctx);
+ }
+out:
+ return rv;
+}
+
+typedef apr_status_t cha_starter(md_acme_authz_cha_t *cha, md_acme_authz_t *authz,
+ md_acme_t *acme, md_store_t *store, apr_pool_t *p);
+
+typedef struct {
+ const char *name;
+ cha_starter *start;
+} cha_type;
+
+static const cha_type CHA_TYPES[] = {
+ { MD_AUTHZ_TYPE_HTTP01, cha_http_01_setup },
+ { MD_AUTHZ_TYPE_TLSSNI01, cha_tls_sni_01_setup },
+};
+static const apr_size_t CHA_TYPES_LEN = (sizeof(CHA_TYPES)/sizeof(CHA_TYPES[0]));
+
+typedef struct {
+ apr_pool_t *p;
+ const char *type;
+ md_acme_authz_cha_t *accepted;
+ apr_array_header_t *offered;
+} cha_find_ctx;
+
+static apr_status_t collect_offered(void *baton, size_t index, md_json_t *json)
+{
+ cha_find_ctx *ctx = baton;
+
+ const char *ctype = md_json_gets(json, MD_KEY_TYPE, NULL);
+ if (ctype) {
+ APR_ARRAY_PUSH(ctx->offered, const char*) = apr_pstrdup(ctx->p, ctype);
+ }
+ return 1;
+}
+
+static apr_status_t find_type(void *baton, size_t index, md_json_t *json)
+{
+ cha_find_ctx *ctx = baton;
+
+ const char *ctype = md_json_gets(json, MD_KEY_TYPE, NULL);
+ if (ctype && !apr_strnatcasecmp(ctx->type, ctype)) {
+ ctx->accepted = cha_from_json(ctx->p, index, json);
+ return 0;
+ }
+ return 1;
+}
+
+apr_status_t md_acme_authz_respond(md_acme_authz_t *authz, md_acme_t *acme, md_store_t *store,
+ apr_array_header_t *challenges, apr_pool_t *p)
+{
+ apr_status_t rv;
+ int i;
+ cha_find_ctx fctx;
+
+ assert(acme);
+ assert(authz);
+ assert(authz->resource);
+
+ fctx.p = p;
+ fctx.accepted = NULL;
+
+ /* Look in the order challenge types are defined */
+ for (i = 0; i < challenges->nelts && !fctx.accepted; ++i) {
+ fctx.type = APR_ARRAY_IDX(challenges, i, const char *);
+ md_json_itera(find_type, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL);
+ }
+
+ if (!fctx.accepted) {
+ rv = APR_EINVAL;
+ fctx.offered = apr_array_make(p, 5, sizeof(const char*));
+ md_json_itera(collect_offered, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL);
+ md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p,
+ "%s: the server offers no ACME challenge that is configured "
+ "for this MD. The server offered '%s' and available for this "
+ "MD are: '%s' (via %s).",
+ authz->domain,
+ apr_array_pstrcat(p, fctx.offered, ' '),
+ apr_array_pstrcat(p, challenges, ' '),
+ authz->location);
+ return rv;
+ }
+
+ for (i = 0; i < CHA_TYPES_LEN; ++i) {
+ if (!apr_strnatcasecmp(CHA_TYPES[i].name, fctx.accepted->type)) {
+ return CHA_TYPES[i].start(fctx.accepted, authz, acme, store, p);
+ }
+ }
+
+ rv = APR_ENOTIMPL;
+ md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p,
+ "%s: no implementation found for challenge '%s'",
+ authz->domain, fctx.accepted->type);
+ return rv;
+}
+
+/**************************************************************************************************/
+/* Delete an existing authz resource */
+
+typedef struct {
+ apr_pool_t *p;
+ md_acme_authz_t *authz;
+} del_ctx;
+
+static apr_status_t on_init_authz_del(md_acme_req_t *req, void *baton)
+{
+ md_json_t *jpayload;
+
+ jpayload = md_json_create(req->p);
+ md_json_sets("deactivated", jpayload, MD_KEY_STATUS, NULL);
+
+ return md_acme_req_body_init(req, jpayload);
+}
+
+static apr_status_t authz_del(md_acme_t *acme, apr_pool_t *p, const apr_table_t *hdrs,
+ md_json_t *body, void *baton)
+{
+ authz_req_ctx *ctx = baton;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_INFO, 0, ctx->p, "deleted authz %s", ctx->authz->location);
+ acme->acct = NULL;
+ return APR_SUCCESS;
+}
+
+apr_status_t md_acme_authz_del(md_acme_authz_t *authz, md_acme_t *acme,
+ md_store_t *store, apr_pool_t *p)
+{
+ authz_req_ctx ctx;
+
+ ctx.p = p;
+ ctx.authz = authz;
+
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "delete authz for %s from %s",
+ authz->domain, authz->location);
+ return md_acme_POST(acme, authz->location, on_init_authz_del, authz_del, NULL, &ctx);
+}
+
+/**************************************************************************************************/
+/* authz conversion */
+
+md_json_t *md_acme_authz_to_json(md_acme_authz_t *a, apr_pool_t *p)
+{
+ md_json_t *json = md_json_create(p);
+ if (json) {
+ md_json_sets(a->domain, json, MD_KEY_DOMAIN, NULL);
+ md_json_sets(a->location, json, MD_KEY_LOCATION, NULL);
+ md_json_sets(a->dir, json, MD_KEY_DIR, NULL);
+ md_json_setl(a->state, json, MD_KEY_STATE, NULL);
+ return json;
+ }
+ return NULL;
+}
+
+md_acme_authz_t *md_acme_authz_from_json(struct md_json_t *json, apr_pool_t *p)
+{
+ md_acme_authz_t *authz = md_acme_authz_create(p);
+ if (authz) {
+ authz->domain = md_json_dups(p, json, MD_KEY_DOMAIN, NULL);
+ authz->location = md_json_dups(p, json, MD_KEY_LOCATION, NULL);
+ authz->dir = md_json_dups(p, json, MD_KEY_DIR, NULL);
+ authz->state = (int)md_json_getl(json, MD_KEY_STATE, NULL);
+ return authz;
+ }
+ return NULL;
+}
+
+/**************************************************************************************************/
+/* authz_set conversion */
+
+#define MD_KEY_ACCOUNT "account"
+#define MD_KEY_AUTHZS "authorizations"
+
+static apr_status_t authz_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton)
+{
+ return md_json_setj(md_acme_authz_to_json(value, p), json, NULL);
+}
+
+static apr_status_t authz_from_json(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton)
+{
+ *pvalue = md_acme_authz_from_json(json, p);
+ return (*pvalue)? APR_SUCCESS : APR_EINVAL;
+}
+
+md_json_t *md_acme_authz_set_to_json(md_acme_authz_set_t *set, apr_pool_t *p)
+{
+ md_json_t *json = md_json_create(p);
+ if (json) {
+ md_json_seta(set->authzs, authz_to_json, NULL, json, MD_KEY_AUTHZS, NULL);
+ return json;
+ }
+ return NULL;
+}
+
+md_acme_authz_set_t *md_acme_authz_set_from_json(md_json_t *json, apr_pool_t *p)
+{
+ md_acme_authz_set_t *set = md_acme_authz_set_create(p, NULL);
+ if (set) {
+ md_json_geta(set->authzs, authz_from_json, NULL, json, MD_KEY_AUTHZS, NULL);
+ return set;
+ }
+ return NULL;
+}
+
+/**************************************************************************************************/
+/* persistence */
+
+apr_status_t md_acme_authz_set_load(struct md_store_t *store, md_store_group_t group,
+ const char *md_name, md_acme_authz_set_t **pauthz_set,
+ apr_pool_t *p)
+{
+ apr_status_t rv;
+ md_json_t *json;
+ md_acme_authz_set_t *authz_set;
+
+ rv = md_store_load_json(store, group, md_name, MD_FN_AUTHZ, &json, p);
+ if (APR_SUCCESS == rv) {
+ authz_set = md_acme_authz_set_from_json(json, p);
+ }
+ *pauthz_set = (APR_SUCCESS == rv)? authz_set : NULL;
+ return rv;
+}
+
+static apr_status_t p_save(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_t *store = baton;
+ md_json_t *json;
+ md_store_group_t group;
+ md_acme_authz_set_t *set;
+ const char *md_name;
+ int create;
+
+ group = va_arg(ap, int);
+ md_name = va_arg(ap, const char *);
+ set = va_arg(ap, md_acme_authz_set_t *);
+ create = va_arg(ap, int);
+
+ json = md_acme_authz_set_to_json(set, ptemp);
+ assert(json);
+ return md_store_save_json(store, ptemp, group, md_name, MD_FN_AUTHZ, json, create);
+}
+
+apr_status_t md_acme_authz_set_save(struct md_store_t *store, apr_pool_t *p,
+ md_store_group_t group, const char *md_name,
+ md_acme_authz_set_t *authz_set, int create)
+{
+ return md_util_pool_vdo(p_save, store, p, group, md_name, authz_set, create, NULL);
+}
+
+static apr_status_t p_purge(void *baton, apr_pool_t *p, apr_pool_t *ptemp, va_list ap)
+{
+ md_store_t *store = baton;
+ md_acme_authz_set_t *authz_set;
+ const md_acme_authz_t *authz;
+ md_store_group_t group;
+ const char *md_name;
+ int i;
+
+ group = va_arg(ap, int);
+ md_name = va_arg(ap, const char *);
+
+ if (APR_SUCCESS == md_acme_authz_set_load(store, group, md_name, &authz_set, p)) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz_set loaded for %s", md_name);
+ for (i = 0; i < authz_set->authzs->nelts; ++i) {
+ authz = APR_ARRAY_IDX(authz_set->authzs, i, const md_acme_authz_t*);
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz check %s", authz->domain);
+ if (authz->dir) {
+ md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "authz purge %s", authz->dir);
+ md_store_purge(store, p, MD_SG_CHALLENGES, authz->dir);
+ }
+ }
+ }
+ return md_store_remove(store, group, md_name, MD_FN_AUTHZ, ptemp, 1);
+}
+
+apr_status_t md_acme_authz_set_purge(md_store_t *store, md_store_group_t group,
+ apr_pool_t *p, const char *md_name)
+{
+ return md_util_pool_vdo(p_purge, store, p, group, md_name, NULL);
+}
+