| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <assert.h> |
| #include <stdio.h> |
| |
| #include <apr_lib.h> |
| #include <apr_buckets.h> |
| #include <apr_file_info.h> |
| #include <apr_file_io.h> |
| #include <apr_fnmatch.h> |
| #include <apr_hash.h> |
| #include <apr_strings.h> |
| #include <apr_tables.h> |
| |
| #include "md.h" |
| #include "md_crypt.h" |
| #include "md_json.h" |
| #include "md_http.h" |
| #include "md_log.h" |
| #include "md_jws.h" |
| #include "md_result.h" |
| #include "md_store.h" |
| #include "md_util.h" |
| |
| #include "md_acme.h" |
| #include "md_acme_authz.h" |
| |
| 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; |
| } |
| |
| /**************************************************************************************************/ |
| /* 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; |
| |
| (void)acme; |
| (void)p; |
| if (location) { |
| ctx->authz = md_acme_authz_create(ctx->p); |
| ctx->authz->domain = apr_pstrdup(ctx->p, ctx->domain); |
| ctx->authz->url = apr_pstrdup(ctx->p, location); |
| ctx->authz->resource = md_json_clone(ctx->p, body); |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, rv, ctx->p, "authz_new at %s", location); |
| } |
| 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, |
| 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->api.v1.new_authz, on_init_authz, authz_created, NULL, NULL, &ctx); |
| |
| *pauthz = (APR_SUCCESS == rv)? ctx.authz : NULL; |
| return rv; |
| } |
| |
| /**************************************************************************************************/ |
| /* Update an existing authorization */ |
| |
| apr_status_t md_acme_authz_retrieve(md_acme_t *acme, apr_pool_t *p, const char *url, |
| md_acme_authz_t **pauthz) |
| { |
| md_acme_authz_t *authz; |
| apr_status_t rv; |
| |
| authz = apr_pcalloc(p, sizeof(*authz)); |
| authz->url = apr_pstrdup(p, url); |
| rv = md_acme_authz_update(authz, acme, p); |
| |
| *pauthz = (APR_SUCCESS == rv)? authz : NULL; |
| return rv; |
| } |
| |
| 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); |
| assert(authz); |
| assert(authz->url); |
| |
| 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; |
| |
| if (APR_SUCCESS == (rv = md_acme_get_json(&json, acme, authz->url, p)) |
| && (s = md_json_gets(json, MD_KEY_STATUS, NULL))) { |
| |
| authz->domain = md_json_gets(json, MD_KEY_IDENTIFIER, MD_KEY_VALUE, NULL); |
| authz->resource = json; |
| if (!strcmp(s, "pending")) { |
| authz->state = MD_ACME_AUTHZ_S_PENDING; |
| err = "challenge 'pending'"; |
| log_level = MD_LOG_DEBUG; |
| } |
| else if (!strcmp(s, "valid")) { |
| authz->state = MD_ACME_AUTHZ_S_VALID; |
| err = "challenge 'valid'"; |
| 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'"; |
| } |
| } |
| |
| if (json && authz->state == MD_ACME_AUTHZ_S_UNKNOWN) { |
| err = "unable to understand response"; |
| rv = APR_EINVAL; |
| } |
| |
| 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, authz->domain, authz->url, |
| json? md_json_writep(json, p, MD_JSON_FMT_COMPACT) : "not available"); |
| } |
| |
| 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); |
| if (md_json_has_key(json, MD_KEY_URL, NULL)) { /* ACMEv2 */ |
| cha->uri = md_json_dups(p, json, MD_KEY_URL, NULL); |
| } |
| else { /* ACMEv1 */ |
| cha->uri = md_json_dups(p, json, MD_KEY_URI, NULL); |
| } |
| cha->token = md_json_dups(p, json, MD_KEY_TOKEN, NULL); |
| cha->key_authz = md_json_dups(p, json, MD_KEY_KEYAUTHZ, NULL); |
| |
| 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); |
| if (MD_ACME_VERSION_MAJOR(req->acme->version) <= 1) { |
| md_json_sets(MD_KEY_CHALLENGE, jpayload, MD_KEY_RESOURCE, NULL); |
| } |
| if (ctx->challenge->key_authz) { |
| md_json_sets(ctx->challenge->key_authz, jpayload, MD_KEY_KEYAUTHZ, NULL); |
| } |
| |
| return md_acme_req_body_init(req, jpayload); |
| } |
| |
| 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; |
| |
| (void)acme; |
| (void)p; |
| (void)hdrs; |
| (void)body; |
| md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, ctx->p, "updated authz %s", ctx->authz->url); |
| 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; |
| |
| (void)authz; |
| 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, |
| md_pkey_spec_t *key_spec, |
| apr_array_header_t *acme_tls_1_domains, |
| apr_table_t *env, apr_pool_t *p) |
| { |
| const char *data; |
| apr_status_t rv; |
| int notify_server; |
| |
| (void)key_spec; |
| (void)env; |
| (void)acme_tls_1_domains; |
| if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, ¬ify_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); |
| 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, NULL, &ctx); |
| } |
| out: |
| return rv; |
| } |
| |
| static apr_status_t cha_tls_alpn_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, |
| md_acme_t *acme, md_store_t *store, |
| md_pkey_spec_t *key_spec, |
| apr_array_header_t *acme_tls_1_domains, |
| apr_table_t *env, apr_pool_t *p) |
| { |
| md_cert_t *cha_cert; |
| md_pkey_t *cha_key; |
| const char *acme_id, *token; |
| apr_status_t rv; |
| int notify_server; |
| md_data_t data; |
| |
| (void)env; |
| if (md_array_str_index(acme_tls_1_domains, authz->domain, 0, 0) < 0) { |
| rv = APR_ENOTIMPL; |
| md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, |
| "%s: protocol 'acme-tls/1' not enabled for this domain.", |
| authz->domain); |
| goto out; |
| } |
| if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, ¬ify_server))) { |
| goto out; |
| } |
| rv = md_store_load(store, MD_SG_CHALLENGES, authz->domain, MD_FN_TLSALPN01_CERT, |
| MD_SV_CERT, (void**)&cha_cert, p); |
| if ((APR_SUCCESS == rv && !md_cert_covers_domain(cha_cert, authz->domain)) |
| || APR_STATUS_IS_ENOENT(rv)) { |
| |
| if (APR_SUCCESS != (rv = md_pkey_gen(&cha_key, p, key_spec))) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 challenge key", |
| authz->domain); |
| goto out; |
| } |
| |
| /* Create a "tls-alpn-01" certificate for the domain we want to authenticate. |
| * The server will need to answer a TLS connection with SNI == authz->domain |
| * and ALPN procotol "acme-tls/1" with this certificate. |
| */ |
| MD_DATA_SET_STR(&data, cha->key_authz); |
| rv = md_crypt_sha256_digest_hex(&token, p, &data); |
| if (APR_SUCCESS != rv) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 cert", |
| authz->domain); |
| goto out; |
| } |
| |
| acme_id = apr_psprintf(p, "critical,DER:04:20:%s", token); |
| if (APR_SUCCESS != (rv = md_cert_make_tls_alpn_01(&cha_cert, authz->domain, acme_id, cha_key, |
| apr_time_from_sec(7 * MD_SECS_PER_DAY), p))) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create tls-alpn-01 cert", |
| authz->domain); |
| goto out; |
| } |
| |
| if (APR_SUCCESS == (rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_TLSALPN01_PKEY, |
| MD_SV_PKEY, (void*)cha_key, 0))) { |
| rv = md_store_save(store, p, MD_SG_CHALLENGES, authz->domain, MD_FN_TLSALPN01_CERT, |
| MD_SV_CERT, (void*)cha_cert, 0); |
| } |
| 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, NULL, &ctx); |
| } |
| out: |
| return rv; |
| } |
| |
| static apr_status_t cha_dns_01_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, |
| md_acme_t *acme, md_store_t *store, |
| md_pkey_spec_t *key_spec, |
| apr_array_header_t *acme_tls_1_domains, |
| apr_table_t *env, apr_pool_t *p) |
| { |
| const char *token; |
| const char * const *argv; |
| const char *cmdline, *dns01_cmd; |
| apr_status_t rv; |
| int exit_code, notify_server; |
| authz_req_ctx ctx; |
| md_data_t data; |
| |
| (void)store; |
| (void)key_spec; |
| (void)acme_tls_1_domains; |
| |
| dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01); |
| if (!dns01_cmd) { |
| rv = APR_ENOTIMPL; |
| md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 command not set", |
| authz->domain); |
| goto out; |
| } |
| |
| if (APR_SUCCESS != (rv = setup_key_authz(cha, authz, acme, p, ¬ify_server))) { |
| goto out; |
| } |
| |
| MD_DATA_SET_STR(&data, cha->key_authz); |
| rv = md_crypt_sha256_digest64(&token, p, &data); |
| if (APR_SUCCESS != rv) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: create dns-01 token", |
| authz->domain); |
| goto out; |
| } |
| |
| cmdline = apr_psprintf(p, "%s setup %s %s", dns01_cmd, authz->domain, token); |
| md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, |
| "%s: dns-01 setup command: %s", authz->domain, cmdline); |
| apr_tokenize_to_argv(cmdline, (char***)&argv, p); |
| if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, &exit_code))) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, |
| "%s: dns-01 setup command failed to execute", authz->domain); |
| goto out; |
| } |
| if (exit_code) { |
| rv = APR_EGENERAL; |
| md_log_perror(MD_LOG_MARK, MD_LOG_INFO, rv, p, |
| "%s: dns-01 setup command returns %d", authz->domain, exit_code); |
| goto out; |
| } |
| |
| /* challenge is setup, tell ACME server so it may (re)try verification */ |
| md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, "%s: dns-01 setup succeeded", authz->domain); |
| authz_req_ctx_init(&ctx, acme, NULL, authz, p); |
| ctx.challenge = cha; |
| rv = md_acme_POST(acme, cha->uri, on_init_authz_resp, authz_http_set, NULL, NULL, &ctx); |
| |
| out: |
| return rv; |
| } |
| |
| static apr_status_t cha_dns_01_teardown(md_store_t *store, const char *domain, |
| apr_table_t *env, apr_pool_t *p) |
| { |
| const char * const *argv; |
| const char *cmdline, *dns01_cmd; |
| apr_status_t rv; |
| int exit_code; |
| |
| (void)store; |
| |
| dns01_cmd = apr_table_get(env, MD_KEY_CMD_DNS01); |
| if (!dns01_cmd) { |
| rv = APR_ENOTIMPL; |
| md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, "%s: dns-01 command not set", domain); |
| goto out; |
| } |
| |
| cmdline = apr_psprintf(p, "%s teardown %s", dns01_cmd, domain); |
| apr_tokenize_to_argv(cmdline, (char***)&argv, p); |
| if (APR_SUCCESS != (rv = md_util_exec(p, argv[0], argv, &exit_code)) || exit_code) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, |
| "%s: dns-01 teardown command failed (exit code=%d)", |
| domain, exit_code); |
| } |
| out: |
| return rv; |
| } |
| |
| static apr_status_t cha_teardown_dir(md_store_t *store, const char *domain, |
| apr_table_t *env, apr_pool_t *p) |
| { |
| (void)env; |
| return md_store_purge(store, p, MD_SG_CHALLENGES, domain); |
| } |
| |
| typedef apr_status_t cha_setup(md_acme_authz_cha_t *cha, md_acme_authz_t *authz, |
| md_acme_t *acme, md_store_t *store, |
| md_pkey_spec_t *key_spec, |
| apr_array_header_t *acme_tls_1_domains, |
| apr_table_t *env, apr_pool_t *p); |
| |
| typedef apr_status_t cha_teardown(md_store_t *store, const char *domain, |
| apr_table_t *env, apr_pool_t *p); |
| |
| typedef struct { |
| const char *name; |
| cha_setup *setup; |
| cha_teardown *teardown; |
| } cha_type; |
| |
| static const cha_type CHA_TYPES[] = { |
| { MD_AUTHZ_TYPE_HTTP01, cha_http_01_setup, cha_teardown_dir }, |
| { MD_AUTHZ_TYPE_TLSALPN01, cha_tls_alpn_01_setup, cha_teardown_dir }, |
| { MD_AUTHZ_TYPE_DNS01, cha_dns_01_setup, cha_dns_01_teardown }, |
| }; |
| static const apr_size_t CHA_TYPES_LEN = (sizeof(CHA_TYPES)/sizeof(CHA_TYPES[0])); |
| |
| 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; |
| |
| (void)index; |
| if ((ctype = md_json_gets(json, MD_KEY_TYPE, NULL))) { |
| 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, md_pkey_spec_t *key_spec, |
| apr_array_header_t *acme_tls_1_domains, |
| apr_table_t *env, apr_pool_t *p, const char **psetup_token, |
| md_result_t *result) |
| { |
| apr_status_t rv; |
| int i; |
| cha_find_ctx fctx; |
| const char *challenge_setup; |
| |
| assert(acme); |
| assert(authz); |
| assert(authz->resource); |
| |
| fctx.p = p; |
| fctx.accepted = NULL; |
| |
| /* Look in the order challenge types are defined: |
| * - if they are offered by the CA, try to set it up |
| * - if setup was successful, we are done and the CA will evaluate us |
| * - if setup failed, continue to look for another supported challenge type |
| * - if there is no overlap in types, tell the user that she has to configure |
| * either more types (dns, tls-alpn-01), make ports available or refrain |
| * from using wildcard domains when dns is not available. etc. |
| * - if there was an overlap, but no setup was successful, report that. We |
| * will retry this, maybe the failure is temporary (e.g. command to setup DNS |
| */ |
| rv = APR_ENOTIMPL; |
| challenge_setup = NULL; |
| for (i = 0; i < challenges->nelts && !fctx.accepted; ++i) { |
| fctx.type = APR_ARRAY_IDX(challenges, i, const char *); |
| md_json_itera(find_type, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL); |
| |
| if (fctx.accepted) { |
| for (i = 0; i < (int)CHA_TYPES_LEN; ++i) { |
| if (!apr_strnatcasecmp(CHA_TYPES[i].name, fctx.accepted->type)) { |
| md_result_activity_printf(result, "Setting up challenge '%s' for domain %s", |
| fctx.accepted->type, authz->domain); |
| rv = CHA_TYPES[i].setup(fctx.accepted, authz, acme, store, key_spec, |
| acme_tls_1_domains, env, p); |
| if (APR_SUCCESS == rv) { |
| md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, rv, p, |
| "%s: set up challenge '%s'", |
| authz->domain, fctx.accepted->type); |
| challenge_setup = CHA_TYPES[i].name; |
| goto out; |
| } |
| md_result_printf(result, rv, "error setting up challenge '%s', " |
| "for domain %s, looking for other option", |
| fctx.accepted->type, authz->domain); |
| md_result_log(result, MD_LOG_INFO); |
| } |
| } |
| } |
| } |
| |
| out: |
| *psetup_token = (APR_SUCCESS == rv)? apr_psprintf(p, "%s:%s", challenge_setup, authz->domain) : NULL; |
| if (!fctx.accepted || APR_ENOTIMPL == rv) { |
| rv = APR_EINVAL; |
| fctx.offered = apr_array_make(p, 5, sizeof(const char*)); |
| md_json_itera(collect_offered, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL); |
| md_result_printf(result, rv, "None of offered challenge types for domain %s are supported. " |
| "The server offered '%s' and available are: '%s'.", |
| authz->domain, |
| apr_array_pstrcat(p, fctx.offered, ' '), |
| apr_array_pstrcat(p, challenges, ' ')); |
| result->problem = "challenge-mismatch"; |
| md_result_log(result, MD_LOG_ERR); |
| } |
| else if (APR_SUCCESS != rv) { |
| fctx.offered = apr_array_make(p, 5, sizeof(const char*)); |
| md_json_itera(collect_offered, &fctx, authz->resource, MD_KEY_CHALLENGES, NULL); |
| md_result_printf(result, rv, "None of the offered challenge types %s offered " |
| "for domain %s could be setup successfully. Please check the " |
| "log for errors.", authz->domain, |
| apr_array_pstrcat(p, fctx.offered, ' ')); |
| result->problem = "challenge-setup-failure"; |
| md_result_log(result, MD_LOG_ERR); |
| } |
| return rv; |
| } |
| |
| apr_status_t md_acme_authz_teardown(struct md_store_t *store, |
| const char *token, apr_table_t *env, apr_pool_t *p) |
| { |
| char *challenge, *domain; |
| int i; |
| |
| if (strchr(token, ':')) { |
| challenge = apr_pstrdup(p, token); |
| domain = strchr(challenge, ':'); |
| *domain = '\0'; domain++; |
| for (i = 0; i < (int)CHA_TYPES_LEN; ++i) { |
| if (!apr_strnatcasecmp(CHA_TYPES[i].name, challenge)) { |
| if (CHA_TYPES[i].teardown) { |
| return CHA_TYPES[i].teardown(store, domain, env, p); |
| } |
| break; |
| } |
| } |
| } |
| return APR_SUCCESS; |
| } |
| |