OPTIONS_OBJS += src/ssl_sock.o src/ssl_ckch.o src/ssl_ocsp.o src/ssl_crtlist.o \
src/ssl_sample.o src/cfgparse-ssl.o src/ssl_gencert.o \
src/ssl_utils.o src/jwt.o src/ssl_clienthello.o src/jws.o src/acme.o \
- src/ssl_trace.o src/jwe.o
+ src/acme_resolvers.o src/ssl_trace.o src/jwe.o
endif
ifneq ($(USE_ENGINE:0=),)
#ifndef _ACME_T_H_
#define _ACME_T_H_
+#include <haproxy/acme_resolvers-t.h>
#include <haproxy/istbuf.h>
#include <haproxy/openssl-compat.h>
+#if defined(HAVE_ACME)
+
#define ACME_RETRY 5
/* acme section configuration */
int linenum; /* config linenum */
char *name; /* section name */
int reuse_key; /* do we need to renew the private key */
+ int dns_check; /* enable DNS resolution to verify TXT record before challenge */
+ unsigned int dns_delay; /* delay in seconds before re-triggering DNS resolution (default: 300) */
char *directory; /* directory URL */
char *map; /* storage for tokens + thumbprint */
struct {
ACME_NEWACCOUNT,
ACME_NEWORDER,
ACME_AUTH,
+ ACME_RSLV_WAIT,
+ ACME_RSLV_TRIGGER,
+ ACME_RSLV_READY,
ACME_CHALLENGE,
ACME_CHKCHALLENGE,
ACME_FINALIZE,
struct ist chall; /* challenge URI */
struct ist token; /* token */
int validated; /* already validated */
+ struct acme_rslv *rslv; /* acme dns-01 resolver */
int ready; /* is the challenge ready ? */
void *next;
};
X509_REQ *req;
struct ist finalize;
struct ist certificate;
+ unsigned int dnstasks; /* number of DNS tasks running for this ctx */
struct task *task;
struct ebmb_node node;
char name[VAR_ARRAY];
#define ACME_VERB_ADVANCED 4
#define ACME_VERB_COMPLETE 5
+#endif /* ! HAVE_ACME */
+
#endif
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef _HAPROXY_ACME_RESOLVERS_T_H
+#define _HAPROXY_ACME_RESOLVERS_T_H
+
+#include <haproxy/obj_type-t.h>
+#include <haproxy/resolvers-t.h>
+
+struct dns_counters;
+
+/* TXT records for dns-01 */
+
+struct acme_rslv {
+ enum obj_type obj_type; /* OBJ_TYPE_ACME_RSLV */
+ unsigned int *dnstasks; /* number of running DNS resolution for the same acme_task */
+ char *hostname_dn;
+ int hostname_dn_len;
+ struct resolvers *resolvers;
+ struct resolv_requester *requester;
+ int result; /* RSLV_STATUS_* — NONE until done */
+ int error_code; /* RSLV_RESP_* from the error callback */
+ struct task *acme_task; /* ACME task to wake on completion, or NULL */
+ struct ist txt; /* first TXT record found */
+ int (*success_cb)(struct resolv_requester *, struct dns_counters *);
+ int (*error_cb)(struct resolv_requester *, int);
+};
+
+#endif /* _HAPROXY_ACME_RESOLVERS_T_H */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef _HAPROXY_ACME_RESOLVERS_H
+#define _HAPROXY_ACME_RESOLVERS_H
+
+#include <haproxy/openssl-compat.h>
+
+#if defined(HAVE_ACME)
+
+#include <haproxy/acme_resolvers-t.h>
+#include <haproxy/acme-t.h>
+#include <haproxy/resolvers-t.h>
+
+struct acme_rslv *acme_rslv_start(struct acme_auth *auth, unsigned int *dnstasks, char **errmsg);
+void acme_rslv_free(struct acme_rslv *rslv);
+
+#endif
+
+#endif /* _HAPROXY_ACME_RESOLVERS_H */
OBJ_TYPE_DGRAM, /* object is a struct quic_dgram */
#endif
OBJ_TYPE_HATERM, /* object is a struct hstream */
+ OBJ_TYPE_ACME_RSLV, /* object is a struct acme_rslv */
OBJ_TYPE_ENTRIES /* last one : number of entries */
} __attribute__((packed)) ;
#ifndef _HAPROXY_OBJ_TYPE_H
#define _HAPROXY_OBJ_TYPE_H
+#include <haproxy/acme_resolvers-t.h>
#include <haproxy/api.h>
#include <haproxy/applet-t.h>
#include <haproxy/check-t.h>
static inline const char *obj_type_name(const enum obj_type *t)
{
switch (obj_type(t)) {
- case OBJ_TYPE_NONE: return "NONE";
- case OBJ_TYPE_LISTENER: return "LISTENER";
- case OBJ_TYPE_PROXY: return "PROXY";
- case OBJ_TYPE_SERVER: return "SERVER";
- case OBJ_TYPE_APPLET: return "APPLET";
- case OBJ_TYPE_APPCTX: return "APPCTX";
- case OBJ_TYPE_CONN: return "CONN";
- case OBJ_TYPE_SRVRQ: return "SRVRQ";
- case OBJ_TYPE_SC: return "SC";
- case OBJ_TYPE_STREAM: return "STREAM";
- case OBJ_TYPE_CHECK: return "CHECK";
+ case OBJ_TYPE_NONE: return "NONE";
+ case OBJ_TYPE_LISTENER: return "LISTENER";
+ case OBJ_TYPE_PROXY: return "PROXY";
+ case OBJ_TYPE_SERVER: return "SERVER";
+ case OBJ_TYPE_APPLET: return "APPLET";
+ case OBJ_TYPE_APPCTX: return "APPCTX";
+ case OBJ_TYPE_CONN: return "CONN";
+ case OBJ_TYPE_SRVRQ: return "SRVRQ";
+ case OBJ_TYPE_SC: return "SC";
+ case OBJ_TYPE_STREAM: return "STREAM";
+ case OBJ_TYPE_CHECK: return "CHECK";
+ case OBJ_TYPE_ACME_RSLV: return "ACME_RSLV";
#ifdef USE_QUIC
case OBJ_TYPE_DGRAM: return "DGRAM";
#endif
return __objt_hstream(t);
}
+static inline struct acme_rslv *__objt_acme_rslv(enum obj_type *t)
+{
+ return container_of(t, struct acme_rslv, obj_type);
+}
+
+static inline struct acme_rslv *objt_acme_rslv(enum obj_type *t)
+{
+ if (!t || *t != OBJ_TYPE_ACME_RSLV)
+ return NULL;
+ return __objt_acme_rslv(t);
+}
+
#ifdef USE_QUIC
static inline struct quic_dgram *__objt_dgram(enum obj_type *t)
{
static inline void *obj_base_ptr(enum obj_type *t)
{
switch (obj_type(t)) {
- case OBJ_TYPE_NONE: return NULL;
- case OBJ_TYPE_LISTENER: return __objt_listener(t);
- case OBJ_TYPE_PROXY: return __objt_proxy(t);
- case OBJ_TYPE_SERVER: return __objt_server(t);
- case OBJ_TYPE_APPLET: return __objt_applet(t);
- case OBJ_TYPE_APPCTX: return __objt_appctx(t);
- case OBJ_TYPE_CONN: return __objt_conn(t);
- case OBJ_TYPE_SRVRQ: return __objt_resolv_srvrq(t);
- case OBJ_TYPE_SC: return __objt_sc(t);
- case OBJ_TYPE_STREAM: return __objt_stream(t);
- case OBJ_TYPE_CHECK: return __objt_check(t);
+ case OBJ_TYPE_NONE: return NULL;
+ case OBJ_TYPE_LISTENER: return __objt_listener(t);
+ case OBJ_TYPE_PROXY: return __objt_proxy(t);
+ case OBJ_TYPE_SERVER: return __objt_server(t);
+ case OBJ_TYPE_APPLET: return __objt_applet(t);
+ case OBJ_TYPE_APPCTX: return __objt_appctx(t);
+ case OBJ_TYPE_CONN: return __objt_conn(t);
+ case OBJ_TYPE_SRVRQ: return __objt_resolv_srvrq(t);
+ case OBJ_TYPE_SC: return __objt_sc(t);
+ case OBJ_TYPE_STREAM: return __objt_stream(t);
+ case OBJ_TYPE_CHECK: return __objt_check(t);
+ case OBJ_TYPE_ACME_RSLV: return __objt_acme_rslv(t);
#ifdef USE_QUIC
case OBJ_TYPE_DGRAM: return __objt_dgram(t);
#endif
* Implements the ACMEv2 RFC 8555 protocol
*/
+#include "haproxy/ticks.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <haproxy/acme-t.h>
+#include <haproxy/acme_resolvers.h>
#include <haproxy/base64.h>
#include <haproxy/intops.h>
#include <haproxy/cfgparse.h>
#include <haproxy/list.h>
#include <haproxy/log.h>
#include <haproxy/pattern.h>
+#include <haproxy/resolvers.h>
#include <haproxy/sink.h>
#include <haproxy/ssl_ckch.h>
#include <haproxy/ssl_gencert.h>
case ACME_NEWACCOUNT: chunk_appendf(&trace_buf, "ACME_NEWACCOUNT"); break;
case ACME_NEWORDER: chunk_appendf(&trace_buf, "ACME_NEWORDER"); break;
case ACME_AUTH: chunk_appendf(&trace_buf, "ACME_AUTH"); break;
+ case ACME_RSLV_WAIT: chunk_appendf(&trace_buf, "ACME_RSLV_WAIT"); break;
+ case ACME_RSLV_TRIGGER: chunk_appendf(&trace_buf, "ACME_RSLV_TRIGGER"); break;
+ case ACME_RSLV_READY: chunk_appendf(&trace_buf, "ACME_RSLV_READY"); break;
case ACME_CHALLENGE: chunk_appendf(&trace_buf, "ACME_CHALLENGE"); break;
case ACME_CHKCHALLENGE: chunk_appendf(&trace_buf, "ACME_CHKCHALLENGE"); break;
case ACME_FINALIZE: chunk_appendf(&trace_buf, "ACME_FINALIZE"); break;
ret->linenum = 0;
ret->challenge = strdup("http-01"); /* default value */
+ ret->dns_delay = 300; /* default DNS re-trigger delay in seconds */
/* The default generated keys are EC-384 */
ret->key.type = EVP_PKEY_EC;
ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
goto out;
}
+ } else if (strcmp(args[0], "dns-check") == 0) {
+ if (!*args[1]) {
+ ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires an argument\n", file, linenum, args[0], cursection);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ if (alertif_too_many_args(1, file, linenum, args, &err_code))
+ goto out;
+
+ if (strcmp(args[1], "on") == 0) {
+ cur_acme->dns_check = 1;
+ } else if (strcmp(args[1], "off") == 0) {
+ cur_acme->dns_check = 0;
+ } else {
+ err_code |= ERR_ALERT | ERR_FATAL;
+ ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires either the 'on' or 'off' parameter\n", file, linenum, args[0], cursection);
+ goto out;
+ }
+ } else if (strcmp(args[0], "dns-delay") == 0) {
+ const char *res;
+
+ if (!*args[1]) {
+ ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires an argument\n", file, linenum, args[0], cursection);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+ if (alertif_too_many_args(1, file, linenum, args, &err_code))
+ goto out;
+
+ res = parse_time_err(args[1], &cur_acme->dns_delay, TIME_UNIT_S);
+ if (res == PARSE_TIME_OVER) {
+ ha_alert("parsing [%s:%d]: timer overflow in argument <%s> to '%s'\n", file, linenum, args[1], args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ } else if (res == PARSE_TIME_UNDER) {
+ ha_alert("parsing [%s:%d]: timer underflow in argument <%s> to '%s'\n", file, linenum, args[1], args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ } else if (res) {
+ ha_alert("parsing [%s:%d]: unexpected character '%c' in argument to '%s'\n", file, linenum, *res, args[0]);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
} else if (strcmp(args[0], "reuse-key") == 0) {
if (!*args[1]) {
ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires an argument\n", file, linenum, args[0], cursection);
{ CFG_ACME, "curves", cfg_parse_acme_cfg_key },
{ CFG_ACME, "map", cfg_parse_acme_kws },
{ CFG_ACME, "reuse-key", cfg_parse_acme_kws },
+ { CFG_ACME, "dns-check", cfg_parse_acme_kws },
+ { CFG_ACME, "dns-delay", cfg_parse_acme_kws },
{ CFG_ACME, "acme-vars", cfg_parse_acme_vars_provider },
{ CFG_ACME, "provider-name", cfg_parse_acme_vars_provider },
{ CFG_GLOBAL, "acme.scheduler", cfg_parse_global_acme_sched },
istfree(&auth->chall);
istfree(&auth->token);
istfree(&auth->dns);
+ acme_rslv_free(auth->rslv);
next = auth->next;
free(auth);
auth = next;
goto retry;
}
if ((ctx->next_auth = ctx->next_auth->next) == NULL) {
- st = ACME_CHALLENGE;
+ if (strcasecmp(ctx->cfg->challenge, "dns-01") == 0 && ctx->cfg->dns_check)
+ st = ACME_RSLV_WAIT;
+ else
+ st = ACME_CHALLENGE;
ctx->next_auth = ctx->auths;
}
/* call with next auth or do the challenge step */
goto nextreq;
}
break;
+ case ACME_RSLV_WAIT: {
+ /* wait dns-delay */
+ st = ACME_RSLV_TRIGGER;
+ ctx->http_state = ACME_HTTP_REQ;
+ ctx->state = st;
+ send_log(NULL, LOG_NOTICE, "acme: %s: dns-01: triggering the resolution in %ds\n",
+ ctx->store->path, ctx->cfg->dns_delay);
+
+ task->expire = tick_add(now_ms, ctx->cfg->dns_delay * 1000);
+ return task;
+ }
+ break;
+ case ACME_RSLV_TRIGGER: {
+ struct acme_auth *auth;
+
+ /* if it was trigger by the CLI, still wait dns_delay if
+ * not everything is ready, or skip and to to
+ * ACME_CHALLENGE */
+ if (!(state & TASK_WOKEN_TIMER)) {
+ int all_ready = 1;
+
+ for (auth = ctx->auths; auth != NULL; auth = auth->next) {
+ if (auth->ready)
+ continue;
+ all_ready = 0;
+ }
+ if (all_ready) {
+ st = ACME_CHALLENGE;
+ ctx->http_state = ACME_HTTP_REQ;
+ ctx->state = st;
+ goto nextreq;
+ } else {
+ return task;
+ }
+ }
+
+ /* on timer expiry, re-trigger resolution for non-ready auths */
+ for (auth = ctx->auths; auth != NULL; auth = auth->next) {
+ if (auth->ready)
+ continue;
+
+ HA_ATOMIC_INC(&ctx->dnstasks);
+
+ auth->rslv = acme_rslv_start(auth, &ctx->dnstasks, &errmsg);
+ if (!auth->rslv)
+ goto abort;
+ auth->rslv->acme_task = task;
+ }
+ st = ACME_RSLV_READY;
+ goto wait;
+ }
+ break;
+ case ACME_RSLV_READY: {
+ struct acme_auth *auth;
+ int all_ready = 1;
+
+ /* if triggered by the CLI, wait for the DNS tasks to
+ * finish
+ */
+ if (HA_ATOMIC_LOAD(&ctx->dnstasks) != 0)
+ goto wait;
+
+ /* triggered by the latest DNS task */
+ for (auth = ctx->auths; auth != NULL; auth = auth->next) {
+ if (auth->ready)
+ continue;
+ if (auth->rslv->result != RSLV_STATUS_VALID) {
+ send_log(NULL, LOG_NOTICE, "acme: %s: dns-01: Couldn't get the TXT record for \"_acme-challenge.%.*s\", expected \"%.*s\" (status=%d)\n",
+ ctx->store->path, (int)auth->dns.len, auth->dns.ptr,
+ (int)auth->token.len, auth->token.ptr,
+ auth->rslv->result);
+ all_ready = 0;
+ } else {
+ if (isteq(auth->rslv->txt, auth->token)) {
+ auth->ready = 1;
+ } else {
+ send_log(NULL, LOG_NOTICE, "acme: %s: dns-01: TXT record mismatch for \"_acme-challenge.%.*s\": expected \"%.*s\", got \"%.*s\"\n",
+ ctx->store->path, (int)auth->dns.len, auth->dns.ptr,
+ (int)auth->token.len, auth->token.ptr,
+ (int)auth->rslv->txt.len, auth->rslv->txt.ptr);
+ all_ready = 0;
+ }
+ }
+ acme_rslv_free(auth->rslv);
+ auth->rslv = NULL;
+ }
+ if (all_ready) {
+ st = ACME_CHALLENGE;
+ ctx->next_auth = ctx->auths;
+ goto nextreq;
+ }
+
+ /* not all ready yet, retry after dns-delay */
+ st = ACME_RSLV_WAIT;
+ ctx->http_state = ACME_HTTP_REQ;
+ ctx->state = st;
+ goto nextreq;
+ }
+ break;
case ACME_CHALLENGE:
if (http_st == ACME_HTTP_REQ) {
/* if challenge is already validated we skip this stage */
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/*
+ * Implements the DNS resolution pre-check for dns-01
+ */
+
+#include <haproxy/openssl-compat.h>
+
+#if defined(HAVE_ACME)
+
+#include <stdlib.h>
+#include <string.h>
+
+
+#include <haproxy/acme_resolvers.h>
+#include <haproxy/applet.h>
+#include <haproxy/obj_type.h>
+#include <haproxy/resolvers.h>
+#include <haproxy/tools.h>
+
+/* success callback, copy the TXT string to rslv->txt */
+static int acme_rslv_success_cb(struct resolv_requester *req, struct dns_counters *counters)
+{
+ struct acme_rslv *rslv = objt_acme_rslv(req->owner);
+ struct resolv_resolution *res;
+ struct eb32_node *eb32;
+ struct resolv_answer_item *item;
+
+ if (!rslv)
+ return 1;
+
+ rslv->result = RSLV_STATUS_INVALID;
+
+ res = req->resolution;
+ if (!res)
+ goto done;
+
+ /* XXX: must fail on multiple TXT entries for the same dn */
+
+ /* copy the data from the response tree */
+ for (eb32 = eb32_first(&res->response.answer_tree); eb32 != NULL; eb32 = eb32_next(eb32)) {
+ item = eb32_entry(eb32, typeof(*item), link);
+ /* only handle 1 entry */
+ if (item->type == DNS_RTYPE_TXT) {
+ int len = item->data_len;
+
+ if (len > DNS_MAX_NAME_SIZE)
+ len = DNS_MAX_NAME_SIZE;
+ rslv->txt = istdup(ist2(item->data.target, len));
+ break;
+ }
+ }
+
+ rslv->result = RSLV_STATUS_VALID;
+done:
+ /* if there's no other DNS task for this acme task, wake up acme_task */
+ if (HA_ATOMIC_SUB_FETCH(rslv->dnstasks, 1) == 0) {
+ if (rslv->acme_task)
+ task_wakeup(rslv->acme_task, TASK_WOKEN_MSG);
+ }
+ return 1;
+}
+
+/* error callback, set the error code to rslv->result */
+static int acme_rslv_error_cb(struct resolv_requester *req, int error_code)
+{
+ struct acme_rslv *rslv = objt_acme_rslv(req->owner);
+
+ if (!rslv)
+ return 0;
+
+ rslv->result = error_code;
+ if (HA_ATOMIC_SUB_FETCH(rslv->dnstasks, 1) == 0) {
+ if (rslv->acme_task)
+ task_wakeup(rslv->acme_task, TASK_WOKEN_MSG);
+ }
+
+ return 0;
+}
+
+/* unlink from the resolver and free the acme_rslv */
+void acme_rslv_free(struct acme_rslv *rslv)
+{
+ if (!rslv)
+ return;
+ if (rslv->requester)
+ resolv_unlink_resolution(rslv->requester);
+ free(rslv->hostname_dn);
+ istfree(&rslv->txt);
+ free(rslv);
+}
+
+struct acme_rslv *acme_rslv_start(struct acme_auth *auth, unsigned int *dnstasks, char **errmsg)
+{
+ struct acme_rslv *rslv = NULL;
+ struct resolvers *resolvers;
+ char hostname[DNS_MAX_NAME_SIZE + 1];
+ char dn[DNS_MAX_NAME_SIZE + 1];
+ int hostname_len;
+ int dn_len;
+
+ /* XXX: allow to change the resolvers section to use */
+ resolvers = find_resolvers_by_id("default");
+ if (!resolvers) {
+ memprintf(errmsg, "couldn't find the \"default\" resolvers section!\n");
+ goto error;
+ }
+
+ /* dns-01 TXT record lives at _acme-challenge.<domain> */
+ hostname_len = snprintf(hostname, sizeof(hostname), "_acme-challenge.%.*s",
+ (int)auth->dns.len, auth->dns.ptr);
+ if (hostname_len < 0 || hostname_len >= (int)sizeof(hostname)) {
+ memprintf(errmsg, "hostname \"_acme-challenge.%.*s\" too long!\n", (int)auth->dns.len, auth->dns.ptr);
+ goto error;
+ }
+
+ dn_len = resolv_str_to_dn_label(hostname, hostname_len, dn, sizeof(dn));
+ if (dn_len <= 0) {
+ memprintf(errmsg, "couldn't convert hostname \"_acme-challenge.%.*s\" into dn label\n", (int)auth->dns.len, auth->dns.ptr);
+ goto error;
+ }
+
+ rslv = calloc(1, sizeof(*rslv));
+ if (!rslv) {
+ memprintf(errmsg, "Could not allocate memory\n");
+ goto error;
+ }
+
+ rslv->obj_type = OBJ_TYPE_ACME_RSLV;
+ rslv->resolvers = resolvers;
+ rslv->hostname_dn = strdup(dn);
+ rslv->hostname_dn_len = dn_len;
+ rslv->result = RSLV_STATUS_NONE;
+ rslv->success_cb = acme_rslv_success_cb;
+ rslv->error_cb = acme_rslv_error_cb;
+ rslv->dnstasks = dnstasks;
+
+ if (!rslv->hostname_dn) {
+ memprintf(errmsg, "Could not allocate memory\n");
+ goto error;
+ }
+
+ if (resolv_link_resolution(rslv, OBJ_TYPE_ACME_RSLV, 0) < 0) {
+ memprintf(errmsg, "Could not create resolution task for \"%.*s\"\n", hostname_len, hostname);
+ goto error;
+ }
+
+ resolv_trigger_resolution(rslv->requester);
+
+ return rslv;
+
+error:
+ if (rslv)
+ free(rslv->hostname_dn);
+ free(rslv);
+ return NULL;
+}
+
+#endif /* HAVE_ACME */
+
+/*
+ * Local variables:
+ * c-indent-level: 8
+ * c-basic-offset: 8
+ * End:
+ */
#include <import/cebis_tree.h>
#include <haproxy/action.h>
+#include <haproxy/acme_resolvers.h>
#include <haproxy/api.h>
#include <haproxy/applet.h>
#include <haproxy/cfgparse.h>
? DNS_RTYPE_A
: DNS_RTYPE_AAAA;
break;
+#if defined(HAVE_ACME)
+ case OBJ_TYPE_ACME_RSLV: {
+ struct acme_rslv *acme_rslv = (struct acme_rslv *)requester;
+
+ req = resolv_get_requester(&acme_rslv->requester,
+ &acme_rslv->obj_type,
+ acme_rslv->success_cb,
+ acme_rslv->error_cb);
+ if (!req)
+ goto err;
+
+ hostname_dn = &acme_rslv->hostname_dn;
+ hostname_dn_len = acme_rslv->hostname_dn_len;
+ resolvers = acme_rslv->resolvers;
+
+ query_type = DNS_RTYPE_TXT;
+ break;
+ }
+#endif
default:
goto err;
}
/* Always perform the resolution */
must_run = 1;
break;
+ case OBJ_TYPE_ACME_RSLV:
+ /* Always perform the resolution */
+ must_run = 1;
+ break;
+
default:
break;
}