Hi,
this diff adds a challenge hook to acme-client. This hook can be used to
fulfill challenges. For example by putting the requested files onto a
remote http server (http-01 challenge) or by modifying dns records
(dns-01 challenge). The latter are needed to obtain wildcard
certificates.
Is this diff ok? Is the design of the hook interface sane? Any feedback
is welcome.
Christopher
Index: etc/examples/acme-client.conf
===================================================================
RCS file: /cvs/src/etc/examples/acme-client.conf,v
retrieving revision 1.5
diff -u -p -r1.5 acme-client.conf
--- etc/examples/acme-client.conf 10 May 2023 07:34:57 -0000 1.5
+++ etc/examples/acme-client.conf 20 Feb 2024 21:20:26 -0000
@@ -25,6 +25,12 @@ authority buypass-test {
domain example.com {
alternative names { secure.example.com }
+ # For wildcard certificates dns-01 challenges need
+ # to be handled by a hook script.
+ # An example script can be found in /etc/examples/acme-hook.sh
+ #alternative names { *.example.com }
+ #challengehook "/etc/acme/acme-hook.sh"
+ #delay 310
domain key "/etc/ssl/private/example.com.key"
domain full chain certificate "/etc/ssl/example.com.fullchain.pem"
# Test with the staging server to avoid aggressive rate-limiting.
Index: etc/examples/acme-hook.sh
===================================================================
RCS file: etc/examples/acme-hook.sh
diff -N etc/examples/acme-hook.sh
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ etc/examples/acme-hook.sh 20 Feb 2024 21:20:26 -0000
@@ -0,0 +1,40 @@
+#!/bin/ksh
+#
+# $OpenBSD: $
+#
+
+password=XXXXXXXX
+
+update() {
+ doas -u nobody curl -K - <<- END
+ no-progress-meter
+ retry = 7
+ retry-connrefused
+ retry-delay = 20
+ max-time = 5
+ url = dyn.dns.he.net
+ user = $1:$password
+ data = txt=$2
+ END
+}
+
+while read type domain token thumb _reserved
+do
+ if [ "$type" = "dns-01" ]
+ then
+ txt=`echo -n "$token.$thumb" |sha256 -b |tr '+/' '-_' |tr -d '='`
+ domain="_acme-challenge.$domain"
+ result=`update "$domain" "$txt"`
+ echo "$0: Setting $domain to $txt: $result" >&2
+ reset[${#reset[@]}]="$domain"
+ echo "HANDLED"
+ else
+ echo "UNHANDLED"
+ fi
+done
+
+for domain in "${reset[@]}"
+do
+ result=`update "$domain" "X"`
+ echo "$0: Resetting $domain: $result" >&2
+done
Index: usr.sbin/acme-client/acme-client.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/acme-client.conf.5,v
retrieving revision 1.29
diff -u -p -r1.29 acme-client.conf.5
--- usr.sbin/acme-client/acme-client.conf.5 11 Jan 2021 07:23:42 -0000
1.29
+++ usr.sbin/acme-client/acme-client.conf.5 20 Feb 2024 21:20:26 -0000
@@ -193,10 +193,63 @@ The certificate authority (as declared a
section) to use.
If this setting is absent, the first authority specified is used.
.It Ic challengedir Ar path
-The directory in which the challenge file will be stored.
+The directory in which the challenge file for
+.Dv http-01
+challenges will be stored if
+.Ar challengehook
+did not handle them.
If it is not specified, a default of
.Pa /var/www/acme
will be used.
+.It Ic challengehook Ar command
+.Ar command
+receives challenges, one per line, on its
+.Va stdin
+and responds on
+.Va stdout
+with
+.Dv HANDLED
+when the challenge was handled or
+.Dv UNHANDLED
+when the challenge was not accepted.
+.Ic challengehook
+is meant primarily for
+.Dv dns-01
+challeges, but can be used handle other types of challenges, too.
+.Pp
+The challenges are presented on stdin in this format:
+
+.Ar type
+.Ar identifier
+.Ar token
+.Ar thumb
+.Ar reserved
+
+The most interesting
+.Ar type
+is
+.Dv dns-01.
+.Ar identifier
+is the domain name to which a _acme-challenge. TXT subdomain record
+needs to be installed.
+.Ar token
+and
+.Ar thumb
+are the token and thumb which are needed to construct the TXT record.
+.Ar reserved
+is reserved for future use.
+
+An example hook script can be found in
+.Pa /etc/examples/acme-hook.sh
+
+.It Ic delay Ar seconds
+After challenges are handled, delay for
+.Ar seconds
+before asking the
+.Ar authority
+to check challenges. A generous delay may be needed to wait for changes
+to DNS to propagate to all servers checked by the
+.Ar authority.
.El
.Sh FILES
.Bl -tag -width /etc/examples/acme-client.conf -compact
Index: usr.sbin/acme-client/chngproc.c
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/chngproc.c,v
retrieving revision 1.17
diff -u -p -r1.17 chngproc.c
--- usr.sbin/acme-client/chngproc.c 5 May 2022 19:51:35 -0000 1.17
+++ usr.sbin/acme-client/chngproc.c 20 Feb 2024 21:20:26 -0000
@@ -24,20 +24,63 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include <sys/socket.h>
#include "extern.h"
int
-chngproc(int netsock, const char *root)
+chngproc(int netsock, const char *root, const char *hook)
{
char *tok = NULL, *th = NULL, *fmt = NULL, **fs = NULL;
+ char *id = NULL, *type = NULL;
size_t i, fsz = 0;
int rc = 0, fd = -1, cc;
+ int hook_fds[2], hook_pid;
+ char buf[16];
long lval;
enum chngop op;
void *pp;
+ if (hook != NULL) {
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, hook_fds) == -1) {
+ warn("socketpair");
+ goto out;
+ }
+
+ if ((hook_pid = fork()) == -1) {
+ warn("fork");
+ goto out;
+ }
+
+ if (hook_pid == 0) {
+ char *hook_buf;
+ char *argv[32];
+ const char *ifs = " \t\n";
+
+ close(hook_fds[0]);
+ if (dup2(hook_fds[1], STDIN_FILENO) != STDIN_FILENO ||
+ dup2(hook_fds[1], STDOUT_FILENO) != STDOUT_FILENO) {
+ warn("dup");
+ goto out;
+ }
+
+ hook_buf = strdup(hook);
+ i = 0;
+ argv[i] = strtok(hook_buf, ifs);
+ while (argv[i] != NULL &&
+ i + 1 < sizeof(argv) / sizeof(argv[0]))
+ argv[++i] = strtok(NULL, ifs);
+
+ if (i == 0 || argv[i] != NULL)
+ errx(1, "Empty challengehook or too many
arguments");
+
+ execv(argv[0], argv);
+ err(1, "execv failed");
+ }
+ close(hook_fds[1]);
+ }
+
if (unveil(root, "wc") == -1) {
warn("unveil %s", root);
goto out;
@@ -60,7 +103,7 @@ chngproc(int netsock, const char *root)
else if (lval == CHNG_SYN)
op = lval;
- if (op == CHNG__MAX) {
+ if (op >= CHNG__MAX) {
warnx("unknown operation from netproc");
goto out;
} else if (op == CHNG_STOP)
@@ -74,11 +117,15 @@ chngproc(int netsock, const char *root)
* of tokens that we'll later clean up.
*/
+ if ((id = readstr(netsock, COMM_ID)) == NULL)
+ goto out;
+ if ((type = readstr(netsock, COMM_TYPE)) == NULL)
+ goto out;
if ((th = readstr(netsock, COMM_THUMB)) == NULL)
goto out;
- else if ((tok = readstr(netsock, COMM_TOK)) == NULL)
+ if ((tok = readstr(netsock, COMM_TOK)) == NULL)
goto out;
- else if (strlen(tok) < 1) {
+ if (strlen(tok) < 1) {
warnx("token is too short");
goto out;
}
@@ -91,67 +138,103 @@ chngproc(int netsock, const char *root)
}
}
- if (asprintf(&fmt, "%s.%s", tok, th) == -1) {
- warn("asprintf");
- goto out;
- }
+ if (hook != NULL) {
+ write(hook_fds[0], type, strlen(type));
+ write(hook_fds[0], "\t", 1);
+ write(hook_fds[0], id, strlen(id));
+ write(hook_fds[0], "\t", 1);
+ write(hook_fds[0], tok, strlen(tok));
+ write(hook_fds[0], "\t", 1);
+ write(hook_fds[0], th, strlen(th));
+ write(hook_fds[0], "\t", 1);
+ write(hook_fds[0], "reserved\n", 9);
+ cc = read(hook_fds[0], buf, sizeof(buf));
+ if (cc <= 0)
+ err(1, "reading from challengehook failed");
+ }
+ else
+ strcpy(buf, "UNHANDLED");
+
+ if (strncmp(buf, "HANDLED", 7) == 0) {
+ op = CHNG_ACK;
+ }
+ else if (strncmp(buf, "UNHANDLED", 9) == 0
+ && strcmp(type, "http-01") == 0) {
+ /* Vector appending... */
+
+ pp = reallocarray(fs, (fsz + 1), sizeof(char *));
+ if (pp == NULL) {
+ warn("realloc");
+ goto out;
+ }
+ fs = pp;
+ if (asprintf(&fs[fsz], "%s/%s", root, tok) == -1) {
+ warn("asprintf");
+ goto out;
+ }
+ fsz++;
- /* Vector appending... */
+ /*
+ * Create and write to our challenge file.
+ * Note: we use file descriptors instead of FILE
+ * because we want to minimise our pledges.
+ */
+ fd = open(fs[fsz - 1], O_WRONLY|O_CREAT|O_TRUNC, 0444);
+ if (fd == -1) {
+ warn("%s", fs[fsz - 1]);
+ goto out;
+ }
+ if (asprintf(&fmt, "%s.%s", tok, th) == -1) {
+ warn("asprintf");
+ goto out;
+ }
+ if (write(fd, fmt, strlen(fmt)) == -1) {
+ warn("%s", fs[fsz - 1]);
+ goto out;
+ }
+ free(fmt);
+ if (close(fd) == -1) {
+ warn("%s", fs[fsz - 1]);
+ goto out;
+ }
+ fd = -1;
- pp = reallocarray(fs, (fsz + 1), sizeof(char *));
- if (pp == NULL) {
- warn("realloc");
- goto out;
- }
- fs = pp;
- if (asprintf(&fs[fsz], "%s/%s", root, tok) == -1) {
- warn("asprintf");
- goto out;
- }
- fsz++;
- free(tok);
- tok = NULL;
+ dodbg("%s: created", fs[fsz - 1]);
+ op = CHNG_ACK;
- /*
- * Create and write to our challenge file.
- * Note: we use file descriptors instead of FILE
- * because we want to minimise our pledges.
- */
- fd = open(fs[fsz - 1], O_WRONLY|O_CREAT|O_TRUNC, 0444);
- if (fd == -1) {
- warn("%s", fs[fsz - 1]);
- goto out;
}
- if (write(fd, fmt, strlen(fmt)) == -1) {
- warn("%s", fs[fsz - 1]);
- goto out;
+ else if (strncmp(buf, "UNHANDLED", 4) == 0) {
+ op = CHNG_FAIL;
}
- if (close(fd) == -1) {
- warn("%s", fs[fsz - 1]);
- goto out;
+ else {
+ warnx("got unknown reply from hook: <%.*s>\n", cc, buf);
+ op = CHNG_FAIL;
}
- fd = -1;
-
- free(th);
- free(fmt);
- th = fmt = NULL;
-
- dodbg("%s: created", fs[fsz - 1]);
- /*
- * Write our acknowledgement.
- * Ignore reader failure.
- */
-
- cc = writeop(netsock, COMM_CHNG_ACK, CHNG_ACK);
- if (cc == 0)
+ if (writeop(netsock, COMM_CHNG_ACK, op) <= 0)
break;
- if (cc < 0)
- goto out;
+
+ free(type);
+ free(id);
+ free(th);
+ free(tok);
+ type = id = th = tok = fmt = NULL;
}
rc = 1;
+
out:
+ if (hook != NULL) {
+ if (shutdown(hook_fds[0], SHUT_WR))
+ err(1, "shutdown challengehook failed");
+ cc = read(hook_fds[0], buf, sizeof(buf));
+ if (cc == 0) ; /* EOF */
+ else if (cc < 0)
+ warn("reading from challengehook failed");
+ else if (cc > 0)
+ warn("unexpected read from challengehook");
+ close(hook_fds[0]);
+ }
close(netsock);
if (fd != -1)
close(fd);
@@ -160,9 +243,18 @@ out:
warn("%s", fs[i]);
free(fs[i]);
}
- free(fs);
- free(fmt);
- free(th);
- free(tok);
+ if(type)
+ free(type);
+ if(id)
+ free(id);
+ if(fs)
+ free(fs);
+ if(fmt)
+ free(fmt);
+ if(th)
+ free(th);
+ if(tok)
+ free(tok);
+
return rc;
}
Index: usr.sbin/acme-client/extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/extern.h,v
retrieving revision 1.20
diff -u -p -r1.20 extern.h
--- usr.sbin/acme-client/extern.h 14 Sep 2020 16:00:17 -0000 1.20
+++ usr.sbin/acme-client/extern.h 20 Feb 2024 21:20:26 -0000
@@ -44,6 +44,7 @@ enum chngop {
CHNG_STOP = 0,
CHNG_SYN,
CHNG_ACK,
+ CHNG_FAIL,
CHNG__MAX
};
@@ -116,6 +117,8 @@ enum comp {
enum comm {
COMM_REQ,
COMM_THUMB,
+ COMM_ID,
+ COMM_TYPE,
COMM_CERT,
COMM_PAY,
COMM_NONCE,
@@ -157,6 +160,9 @@ enum chngstatus {
};
struct chng {
+ STAILQ_ENTRY(chng) next;
+ char *type; /* type of challenge */
+ char *identifier; /* domain to be authenticated */
char *uri; /* uri on ACME server */
char *token; /* token we must offer */
char *error; /* "detail" field in case of error */
@@ -164,6 +170,8 @@ struct chng {
enum chngstatus status; /* challenge accepted? */
};
+STAILQ_HEAD(chng_queue, chng);
+
enum orderstatus {
ORDER_INVALID = -1,
ORDER_PENDING = 0,
@@ -202,7 +210,7 @@ __BEGIN_DECLS
*/
int acctproc(int, const char *, enum keytype);
int certproc(int, int);
-int chngproc(int, const char *);
+int chngproc(int, const char *, const char *);
int dnsproc(int);
int revokeproc(int, const char *, int, int, const char *const *,
size_t);
@@ -211,7 +219,7 @@ int fileproc(int, const char *, const
int keyproc(int, const char *, const char **, size_t,
enum keytype);
int netproc(int, int, int, int, int, int, int,
- struct authority_c *, const char *const *,
+ struct authority_c *, int, const char *const *,
size_t);
/*
@@ -253,7 +261,7 @@ struct jsmnn *json_parse(const char *, s
void json_free(struct jsmnn *);
int json_parse_response(struct jsmnn *);
void json_free_challenge(struct chng *);
-int json_parse_challenge(struct jsmnn *, struct chng *);
+struct chng_queue json_parse_challenge(struct jsmnn *);
void json_free_order(struct order *);
int json_parse_order(struct jsmnn *, struct order *);
int json_parse_upd_order(struct jsmnn *, struct order *);
Index: usr.sbin/acme-client/json.c
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/json.c,v
retrieving revision 1.21
diff -u -p -r1.21 json.c
--- usr.sbin/acme-client/json.c 14 Sep 2020 16:00:17 -0000 1.21
+++ usr.sbin/acme-client/json.c 20 Feb 2024 21:20:26 -0000
@@ -22,6 +22,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include <sys/queue.h>
#include "jsmn.h"
#include "extern.h"
@@ -254,9 +255,8 @@ json_getarray(struct jsmnn *n, const cha
if (n->d.obj[i].lhs->type != JSMN_STRING &&
n->d.obj[i].lhs->type != JSMN_PRIMITIVE)
continue;
- else if (strcmp(name, n->d.obj[i].lhs->d.str))
- continue;
- break;
+ if (strcmp(name, n->d.obj[i].lhs->d.str) == 0)
+ break;
}
if (i == n->fields)
return NULL;
@@ -331,10 +331,12 @@ json_getstr(struct jsmnn *n, const char
void
json_free_challenge(struct chng *p)
{
-
+ free(p->type);
+ free(p->identifier);
free(p->uri);
free(p->token);
- p->uri = p->token = NULL;
+ free(p->error);
+ free(p);
}
/*
@@ -370,43 +372,64 @@ json_parse_response(struct jsmnn *n)
* information, into a structure.
* We only care about the HTTP-01 response.
*/
-int
-json_parse_challenge(struct jsmnn *n, struct chng *p)
+struct chng_queue
+json_parse_challenge(struct jsmnn *n)
{
- struct jsmnn *array, *obj, *error;
+ struct jsmnn *array, *obj, *identifier, *error;
+ struct chng_queue chngs = STAILQ_HEAD_INITIALIZER(chngs);
+ struct chng *p;
size_t i;
- int rc;
- char *type;
if (n == NULL)
- return 0;
+ return chngs;
+
+ identifier = json_getobj(n, "identifier");
+ if (identifier == NULL)
+ return chngs;
array = json_getarray(n, "challenges");
if (array == NULL)
- return 0;
+ return chngs;
for (i = 0; i < array->fields; i++) {
obj = json_getarrayobj(array->d.array[i]);
if (obj == NULL)
continue;
- type = json_getstr(obj, "type");
- if (type == NULL)
- continue;
- rc = strcmp(type, "http-01");
- free(type);
- if (rc)
- continue;
+ p = malloc(sizeof(struct chng));
+ if (p == NULL) {
+ warn("malloc");
+ goto fail;
+ }
+ p->identifier = json_getstr(identifier, "value");
+ p->type = json_getstr(obj, "type");
p->uri = json_getstr(obj, "url");
p->token = json_getstr(obj, "token");
+ if (p->identifier == NULL || p->type == NULL || p->uri == NULL
+ || p->token == NULL) {
+ warnx("malformed challenge");
+ goto fail;
+ }
p->status = json_parse_response(obj);
+ p->retry = 0;
if (p->status == CHNG_INVALID) {
error = json_getobj(obj, "error");
p->error = json_getstr(error, "detail");
}
- return p->uri != NULL && p->token != NULL;
+ else
+ p->error = NULL;
+ STAILQ_INSERT_TAIL(&chngs, p, next);
+ }
+
+ return chngs;
+
+fail:
+ while (!STAILQ_EMPTY(&chngs)) {
+ p = STAILQ_FIRST(&chngs);
+ STAILQ_REMOVE_HEAD(&chngs, next);
+ json_free_challenge(p);
}
- return 0;
+ return chngs;
}
static enum orderstatus
Index: usr.sbin/acme-client/main.c
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/main.c,v
retrieving revision 1.55
diff -u -p -r1.55 main.c
--- usr.sbin/acme-client/main.c 5 May 2022 19:51:35 -0000 1.55
+++ usr.sbin/acme-client/main.c 20 Feb 2024 21:20:26 -0000
@@ -220,7 +220,7 @@ main(int argc, char *argv[])
c = netproc(key_fds[1], acct_fds[1],
chng_fds[1], cert_fds[1],
dns_fds[1], rvk_fds[1],
- revocate, authority,
+ revocate, authority, domain->delay,
(const char *const *)alts, altsz);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
@@ -286,7 +286,7 @@ main(int argc, char *argv[])
close(rvk_fds[0]);
close(file_fds[0]);
close(file_fds[1]);
- c = chngproc(chng_fds[0], chngdir);
+ c = chngproc(chng_fds[0], chngdir, domain->challengehook);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
Index: usr.sbin/acme-client/netproc.c
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/netproc.c,v
retrieving revision 1.33
diff -u -p -r1.33 netproc.c
--- usr.sbin/acme-client/netproc.c 14 Dec 2022 18:32:26 -0000 1.33
+++ usr.sbin/acme-client/netproc.c 20 Feb 2024 21:20:26 -0000
@@ -15,6 +15,7 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <err.h>
@@ -505,14 +506,13 @@ doupdorder(struct conn *c, struct order
/*
* Request a challenge for the given domain name.
* This must be called for each name "alt".
- * On non-zero exit, fills in "chng" with the challenge.
*/
-static int
-dochngreq(struct conn *c, const char *auth, struct chng *chng)
+static struct chng_queue
+dochngreq(struct conn *c, const char *auth)
{
- int rc = 0;
long lc;
struct jsmnn *j = NULL;
+ struct chng_queue chngs = STAILQ_HEAD_INITIALIZER(chngs);
dodbg("%s: %s", __func__, auth);
@@ -522,15 +522,13 @@ dochngreq(struct conn *c, const char *au
warnx("%s: bad HTTP: %ld", auth, lc);
else if ((j = json_parse(c->buf.buf, c->buf.sz)) == NULL)
warnx("%s: bad JSON object", auth);
- else if (!json_parse_challenge(j, chng))
- warnx("%s: bad challenge", auth);
else
- rc = 1;
+ chngs = json_parse_challenge(j);
- if (rc == 0 || verbose > 1)
+ if (STAILQ_EMPTY(&chngs) || verbose > 1)
buf_dump(&c->buf);
json_free(j);
- return rc;
+ return chngs;
}
/*
@@ -673,7 +671,7 @@ dodirs(struct conn *c, const char *addr,
*/
int
netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd,
- int revocate, struct authority_c *authority,
+ int revocate, struct authority_c *authority, int delay,
const char *const *alts, size_t altsz)
{
int rc = 0;
@@ -682,7 +680,8 @@ netproc(int kfd, int afd, int Cfd, int c
struct conn c;
struct capaths paths;
struct order order;
- struct chng *chngs = NULL;
+ struct chng_queue chngs = STAILQ_HEAD_INITIALIZER(chngs);
+ struct chng *chng;
long lval;
memset(&paths, 0, sizeof(struct capaths));
@@ -782,12 +781,6 @@ netproc(int kfd, int afd, int Cfd, int c
if (!doneworder(&c, alts, altsz, &order, &paths))
goto out;
- chngs = calloc(order.authsz, sizeof(struct chng));
- if (chngs == NULL) {
- warn("calloc");
- goto out;
- }
-
/*
* Get thumbprint from acctproc. We will need it to construct
* a response to the challenge
@@ -812,42 +805,57 @@ netproc(int kfd, int afd, int Cfd, int c
goto out;
}
for (i = 0; i < order.authsz; i++) {
- if (!dochngreq(&c, order.auths[i], &chngs[i]))
+ struct chng_queue newchngs;
+
+ newchngs = dochngreq(&c, order.auths[i]);
+ if (STAILQ_EMPTY(&newchngs))
goto out;
+ STAILQ_CONCAT(&chngs, &newchngs);
+ }
+
+ STAILQ_FOREACH(chng, &chngs, next) {
dodbg("challenge, token: %s, uri: %s, status: "
- "%d", chngs[i].token, chngs[i].uri,
- chngs[i].status);
+ "%d", chng->token, chng->uri, chng->status);
- if (chngs[i].status == CHNG_VALID ||
- chngs[i].status == CHNG_INVALID)
+ if (chng->status == CHNG_VALID ||
+ chng->status == CHNG_INVALID)
continue;
- if (chngs[i].retry++ >= RETRY_MAX) {
+ if (chng->retry++ >= RETRY_MAX) {
warnx("%s: too many tries",
- chngs[i].uri);
+ chng->uri);
goto out;
}
if (writeop(Cfd, COMM_CHNG_OP, CHNG_SYN) <= 0)
goto out;
- else if (writestr(Cfd, COMM_THUMB, thumb) <= 0)
+ if (writestr(Cfd, COMM_ID, chng->identifier) <=
0)
goto out;
- else if (writestr(Cfd, COMM_TOK,
- chngs[i].token) <= 0)
+ if (writestr(Cfd, COMM_TYPE, chng->type) <= 0)
+ goto out;
+ if (writestr(Cfd, COMM_THUMB, thumb) <= 0)
+ goto out;
+ if (writestr(Cfd, COMM_TOK, chng->token) <= 0)
goto out;
/* Read that the challenge has been made. */
if (readop(Cfd, COMM_CHNG_ACK) != CHNG_ACK)
- goto out;
+ chng->status = CHNG_INVALID;
}
+
+ if (delay >= 0) {
+ dodbg("delay for %ds\n", delay);
+ sleep(delay);
+ }
+
/* Write to the CA that it's ready. */
- for (i = 0; i < order.authsz; i++) {
- if (chngs[i].status == CHNG_VALID ||
- chngs[i].status == CHNG_INVALID)
+ STAILQ_FOREACH(chng, &chngs, next) {
+ if (chng->status == CHNG_VALID ||
+ chng->status == CHNG_INVALID)
continue;
- if (!dochngresp(&c, &chngs[i]))
+ if (!dochngresp(&c, chng))
goto out;
}
break;
@@ -880,15 +888,18 @@ netproc(int kfd, int afd, int Cfd, int c
if (order.status != ORDER_VALID) {
for (i = 0; i < order.authsz; i++) {
- dochngreq(&c, order.auths[i], &chngs[i]);
- if (chngs[i].error != NULL) {
- if (stravis(&error, chngs[i].error, VIS_SAFE)
- != -1) {
+ struct chng_queue newchngs;
+
+ newchngs = dochngreq(&c, order.auths[i]);
+
+ STAILQ_FOREACH(chng, &newchngs, next)
+ if (chng->error != NULL
+ && stravis(&error, chng->error,
+ VIS_SAFE) != -1) {
warnx("%s", error);
free(error);
error = NULL;
}
- }
}
goto out;
}
@@ -917,10 +928,11 @@ out:
free(thumb);
free(c.kid);
free(c.buf.buf);
- if (chngs != NULL)
- for (i = 0; i < order.authsz; i++)
- json_free_challenge(&chngs[i]);
- free(chngs);
+ while (!STAILQ_EMPTY(&chngs)) {
+ chng = STAILQ_FIRST(&chngs);
+ STAILQ_REMOVE_HEAD(&chngs, next);
+ json_free_challenge(chng);
+ }
json_free_capaths(&paths);
return rc;
}
Index: usr.sbin/acme-client/parse.h
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/parse.h,v
retrieving revision 1.15
diff -u -p -r1.15 parse.h
--- usr.sbin/acme-client/parse.h 14 Sep 2020 16:00:17 -0000 1.15
+++ usr.sbin/acme-client/parse.h 20 Feb 2024 21:20:26 -0000
@@ -45,6 +45,7 @@ struct domain_c {
TAILQ_ENTRY(domain_c) entry;
TAILQ_HEAD(, altname_c) altname_list;
int altname_count;
+ int delay;
enum keytype keytype;
char *handle;
char *domain;
@@ -53,6 +54,7 @@ struct domain_c {
char *chain;
char *fullchain;
char *auth;
+ char *challengehook;
char *challengedir;
};
Index: usr.sbin/acme-client/parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/parse.y,v
retrieving revision 1.45
diff -u -p -r1.45 parse.y
--- usr.sbin/acme-client/parse.y 15 Dec 2022 08:06:13 -0000 1.45
+++ usr.sbin/acme-client/parse.y 20 Feb 2024 21:20:26 -0000
@@ -102,6 +102,7 @@ typedef struct {
%token AUTHORITY URL API ACCOUNT CONTACT
%token DOMAIN ALTERNATIVE NAME NAMES CERT FULL CHAIN KEY SIGN WITH CHALLENGEDIR
+%token CHALLENGEHOOK DELAY
%token YES NO
%token INCLUDE
%token ERROR
@@ -393,6 +394,28 @@ domainoptsl : ALTERNATIVE NAMES '{' optn
err(EXIT_FAILURE, "strdup");
domain->challengedir = s;
}
+ | CHALLENGEHOOK STRING {
+ char *s;
+ if (domain->challengehook != NULL) {
+ yyerror("duplicate challengehook");
+ YYERROR;
+ }
+ if ((s = strdup($2)) == NULL)
+ err(EXIT_FAILURE, "strdup");
+ domain->challengehook = s;
+ }
+ | DELAY NUMBER {
+ if (domain->delay >= 0) {
+ yyerror("duplicate delay");
+ YYERROR;
+ }
+ if ($2 < 0) {
+ yyerror("invalid delay: %lld ", $2);
+ YYERROR;
+ }
+ domain->delay = $2;
+
+ }
;
altname_l : altname optcommanl altname_l
@@ -462,7 +485,9 @@ lookup(char *s)
{"certificate", CERT},
{"chain", CHAIN},
{"challengedir", CHALLENGEDIR},
+ {"challengehook", CHALLENGEHOOK},
{"contact", CONTACT},
+ {"delay", DELAY},
{"domain", DOMAIN},
{"ecdsa", ECDSA},
{"full", FULL},
@@ -964,6 +989,7 @@ conf_new_domain(struct acme_conf *c, cha
return (NULL);
if ((d = calloc(1, sizeof(struct domain_c))) == NULL)
err(EXIT_FAILURE, "%s", __func__);
+ d->delay = -1;
TAILQ_INSERT_TAIL(&c->domain_list, d, entry);
d->handle = s;
@@ -1085,6 +1111,10 @@ print_config(struct acme_conf *xconf)
printf("\tsign with \"%s\"\n", d->auth);
if (d->challengedir != NULL)
printf("\tchallengedir \"%s\"\n", d->challengedir);
+ if (d->challengehook != NULL)
+ printf("\tchallengehook \"%s\"\n", d->challengehook);
+ if (d->delay >= 0)
+ printf("\tdelay \"%d\"\n", d->delay);
printf("}\n\n");
}
}
@@ -1100,7 +1130,7 @@ domain_valid(const char *cp)
{
for ( ; *cp != '\0'; cp++)
- if (!(*cp == '.' || *cp == '-' ||
+ if (!(*cp == '.' || *cp == '-' || *cp == '*' ||
*cp == '_' || isalnum((unsigned char)*cp)))
return 0;
return 1;