summaryrefslogtreecommitdiff
path: root/usr.sbin/acme-client/netproc.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/acme-client/netproc.c')
-rw-r--r--usr.sbin/acme-client/netproc.c811
1 files changed, 811 insertions, 0 deletions
diff --git a/usr.sbin/acme-client/netproc.c b/usr.sbin/acme-client/netproc.c
new file mode 100644
index 00000000000..039f51a8205
--- /dev/null
+++ b/usr.sbin/acme-client/netproc.c
@@ -0,0 +1,811 @@
+/* $Id: netproc.c,v 1.1 2016/08/31 22:01:42 florian Exp $ */
+/*
+ * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "http.h"
+#include "extern.h"
+
+#define URL_REAL_CA "https://acme-v01.api.letsencrypt.org/directory"
+#define URL_STAGE_CA "https://acme-staging.api.letsencrypt.org/directory"
+
+#define RETRY_DELAY 5
+#define RETRY_MAX 10
+
+/*
+ * Buffer used when collecting the results of a CURL transfer.
+ */
+struct buf {
+ char *buf; /* binary buffer */
+ size_t sz; /* length of buffer */
+};
+
+/*
+ * Used for CURL communications.
+ */
+struct conn {
+ const char *na; /* nonce authority */
+ int fd; /* acctproc handle */
+ int dfd; /* dnsproc handle */
+ struct buf buf; /* transfer buffer */
+};
+
+/*
+ * If something goes wrong (or we're tracing output), we dump the
+ * current transfer's data as a debug message.
+ * Make sure that print all non-printable characters as question marks
+ * so that we don't spam the console.
+ * Also, consolidate white-space.
+ * This of course will ruin string literals, but the intent here is just
+ * to show the message, not to replicate it.
+ */
+static void
+buf_dump(const struct buf *buf)
+{
+ size_t i;
+ int j;
+ char *nbuf;
+
+ if (0 == buf->sz)
+ return;
+ if (NULL == (nbuf = malloc(buf->sz)))
+ err(EXIT_FAILURE, "malloc");
+
+ for (j = 0, i = 0; i < buf->sz; i++)
+ if (isspace((int)buf->buf[i])) {
+ nbuf[j++] = ' ';
+ while (isspace((int)buf->buf[i]))
+ i++;
+ i--;
+ } else
+ nbuf[j++] = isprint((int)buf->buf[i]) ?
+ buf->buf[i] : '?';
+ dodbg("transfer buffer: [%.*s] (%zu bytes)", j, nbuf, buf->sz);
+ free(nbuf);
+}
+
+/*
+ * Extract the domain and port from a URL.
+ * The url must be formatted as schema://address[/stuff].
+ * This returns NULL on failure.
+ */
+static char *
+url2host(const char *host, short *port, char **path)
+{
+ char *url, *ep;
+
+ /* We only understand HTTP and HTTPS. */
+
+ if (0 == strncmp(host, "https://", 8)) {
+ *port = 443;
+ if (NULL == (url = strdup(host + 8))) {
+ warn("strdup");
+ return(NULL);
+ }
+ } else if (0 == strncmp(host, "http://", 7)) {
+ *port = 80;
+ if (NULL == (url = strdup(host + 7))) {
+ warn("strdup");
+ return(NULL);
+ }
+ } else {
+ warnx("%s: unknown schema", host);
+ return(NULL);
+ }
+
+ /* Terminate path part. */
+
+ if (NULL != (ep = strchr(url, '/'))) {
+ *path = strdup(ep);
+ *ep = '\0';
+ } else
+ *path = strdup("");
+
+ if (NULL == *path) {
+ warn("strdup");
+ free(url);
+ return(NULL);
+ }
+
+ return(url);
+}
+
+/*
+ * Contact dnsproc and resolve a host.
+ * Place the answers in "v" and return the number of answers, which can
+ * be at most MAX_SERVERS_DNS.
+ * Return <0 on failure.
+ */
+static ssize_t
+urlresolve(int fd, const char *host, struct source *v)
+{
+ char *addr;
+ size_t i, sz;
+ long lval;
+
+ if (writeop(fd, COMM_DNS, DNS_LOOKUP) <= 0)
+ return(-1);
+ else if (writestr(fd, COMM_DNSQ, host) <= 0)
+ return(-1);
+ else if ((lval = readop(fd, COMM_DNSLEN)) < 0)
+ return(-1);
+
+ sz = lval;
+ assert(sz <= MAX_SERVERS_DNS);
+
+ for (i = 0; i < sz; i++) {
+ memset(&v[i], 0, sizeof(struct source));
+ if ((lval = readop(fd, COMM_DNSF)) < 0)
+ goto err;
+ else if (4 != lval && 6 != lval)
+ goto err;
+ else if (NULL == (addr = readstr(fd, COMM_DNSA)))
+ goto err;
+ v[i].family = lval;
+ v[i].ip = addr;
+ }
+
+ return(sz);
+err:
+ for (i = 0; i < sz; i++)
+ free(v[i].ip);
+ return(-1);
+}
+
+/*
+ * Send a "regular" HTTP GET message to "addr" and stuff the response
+ * into the connection buffer.
+ * Return the HTTP error code or <0 on failure.
+ */
+static long
+nreq(struct conn *c, const char *addr)
+{
+ struct httpget *g;
+ struct source src[MAX_SERVERS_DNS];
+ char *host, *path;
+ short port;
+ size_t srcsz;
+ ssize_t ssz;
+ long code;
+
+ if (NULL == (host = url2host(addr, &port, &path)))
+ return(-1);
+
+ if ((ssz = urlresolve(c->dfd, host, src)) < 0) {
+ free(host);
+ free(path);
+ return(-1);
+ }
+ srcsz = ssz;
+
+ g = http_get(src, srcsz, host, port, path, NULL, 0);
+ free(host);
+ free(path);
+ if (NULL == g)
+ return(-1);
+
+ code = g->code;
+
+ /* Copy the body part into our buffer. */
+
+ free(c->buf.buf);
+ c->buf.sz = g->bodypartsz;
+ c->buf.buf = malloc(c->buf.sz);
+ memcpy(c->buf.buf, g->bodypart, c->buf.sz);
+ http_get_free(g);
+ if (NULL == c->buf.buf) {
+ warn("malloc");
+ return(-1);
+ }
+ return(code);
+}
+
+/*
+ * Create and send a signed communication to the ACME server.
+ * Stuff the response into the communication buffer.
+ * Return <0 on failure on the HTTP error code otherwise.
+ */
+static long
+sreq(struct conn *c, const char *addr, const char *req)
+{
+ struct httpget *g;
+ struct source src[MAX_SERVERS_DNS];
+ char *host, *path, *nonce, *reqsn;
+ short port;
+ struct httphead *h;
+ ssize_t ssz;
+ long code;
+
+ if (NULL == (host = url2host(c->na, &port, &path)))
+ return(-1);
+
+ if ((ssz = urlresolve(c->dfd, host, src)) < 0) {
+ free(host);
+ free(path);
+ return(-1);
+ }
+
+ g = http_get(src, (size_t)ssz, host, port, path, NULL, 0);
+ free(host);
+ free(path);
+ if (NULL == g)
+ return(-1);
+
+ h = http_head_get("Replay-Nonce", g->head, g->headsz);
+ if (NULL == h) {
+ warnx("%s: no replay nonce", c->na);
+ http_get_free(g);
+ return(-1);
+ } else if (NULL == (nonce = strdup(h->val))) {
+ warn("strdup");
+ http_get_free(g);
+ return(-1);
+ }
+ http_get_free(g);
+
+ /*
+ * Send the nonce and request payload to the acctproc.
+ * This will create the proper JSON object we need.
+ */
+
+ if (writeop(c->fd, COMM_ACCT, ACCT_SIGN) <= 0) {
+ free(nonce);
+ return(-1);
+ } else if (writestr(c->fd, COMM_PAY, req) <= 0) {
+ free(nonce);
+ return(-1);
+ } else if (writestr(c->fd, COMM_NONCE, nonce) <= 0) {
+ free(nonce);
+ return(-1);
+ }
+ free(nonce);
+
+ /* Now read back the signed payload. */
+
+ if (NULL == (reqsn = readstr(c->fd, COMM_REQ)))
+ return(-1);
+
+ /* Now send the signed payload to the CA. */
+
+ if (NULL == (host = url2host(addr, &port, &path))) {
+ free(reqsn);
+ return(-1);
+ } else if ((ssz = urlresolve(c->dfd, host, src)) < 0) {
+ free(host);
+ free(path);
+ free(reqsn);
+ return(-1);
+ }
+
+ g = http_get(src, (size_t)ssz, host,
+ port, path, reqsn, strlen(reqsn));
+
+ free(host);
+ free(path);
+ free(reqsn);
+ if (NULL == g)
+ return(-1);
+
+ /* Stuff response into parse buffer. */
+
+ code = g->code;
+
+ free(c->buf.buf);
+ c->buf.sz = g->bodypartsz;
+ c->buf.buf = malloc(c->buf.sz);
+ memcpy(c->buf.buf, g->bodypart, c->buf.sz);
+ http_get_free(g);
+ if (NULL == c->buf.buf) {
+ warn("malloc");
+ return(-1);
+ }
+ return(code);
+}
+
+/*
+ * Send to the CA that we want to authorise a new account.
+ * This only happens once for a new account key.
+ * Returns non-zero on success.
+ */
+static int
+donewreg(struct conn *c, const char *agreement,
+ const struct capaths *p)
+{
+ int rc;
+ char *req;
+ long lc;
+
+ rc = 0;
+ dodbg("%s: new-reg", p->newreg);
+
+ if (NULL == (req = json_fmt_newreg(agreement)))
+ warnx("json_fmt_newreg");
+ else if ((lc = sreq(c, p->newreg, req)) < 0)
+ warnx("%s: bad comm", p->newreg);
+ else if (200 != lc && 201 != lc)
+ warnx("%s: bad HTTP: %ld", p->newreg, lc);
+ else if (NULL == c->buf.buf || 0 == c->buf.sz)
+ warnx("%s: empty response", p->newreg);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ free(req);
+ return(rc);
+}
+
+/*
+ * Request a challenge for the given domain name.
+ * This must happen for each name "alt".
+ * On non-zero exit, fills in "chng" with the challenge.
+ */
+static int
+dochngreq(struct conn *c, const char *alt,
+ struct chng *chng, const struct capaths *p)
+{
+ int rc;
+ char *req;
+ long lc;
+ struct jsmnn *j;
+
+ j = NULL;
+ rc = 0;
+ dodbg("%s: req-auth: %s", p->newauthz, alt);
+
+ if (NULL == (req = json_fmt_newauthz(alt)))
+ warnx("json_fmt_newauthz");
+ else if ((lc = sreq(c, p->newauthz, req)) < 0)
+ warnx("%s: bad comm", p->newauthz);
+ else if (200 != lc && 201 != lc)
+ warnx("%s: bad HTTP: %ld", p->newauthz, lc);
+ else if (NULL == (j = json_parse(c->buf.buf, c->buf.sz)))
+ warnx("%s: bad JSON object", p->newauthz);
+ else if ( ! json_parse_challenge(j, chng))
+ warnx("%s: bad challenge", p->newauthz);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ json_free(j);
+ free(req);
+ return(rc);
+}
+
+/*
+ * Note to the CA that a challenge response is in place.
+ */
+static int
+dochngresp(struct conn *c, const struct chng *chng, const char *th)
+{
+ int rc;
+ long lc;
+ char *req;
+
+ rc = 0;
+ dodbg("%s: challenge", chng->uri);
+
+ if (NULL == (req = json_fmt_challenge(chng->token, th)))
+ warnx("json_fmt_challenge");
+ else if ((lc = sreq(c, chng->uri, req)) < 0)
+ warnx("%s: bad comm", chng->uri);
+ else if (200 != lc && 201 != lc && 202 != lc)
+ warnx("%s: bad HTTP: %ld", chng->uri, lc);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ free(req);
+ return(rc);
+}
+
+/*
+ * Check with the CA whether a challenge has been processed.
+ * Note: we'll only do this a limited number of times, and pause for a
+ * time between checks, but this happens in the caller.
+ */
+static int
+dochngcheck(struct conn *c, struct chng *chng)
+{
+ int cc;
+ long lc;
+ struct jsmnn *j;
+
+ dodbg("%s: status", chng->uri);
+
+ if ((lc = nreq(c, chng->uri)) < 0) {
+ warnx("%s: bad comm", chng->uri);
+ return(0);
+ } else if (200 != lc && 201 != lc && 202 != lc) {
+ warnx("%s: bad HTTP: %ld", chng->uri, lc);
+ buf_dump(&c->buf);
+ return(0);
+ } else if (NULL == (j = json_parse(c->buf.buf, c->buf.sz))) {
+ warnx("%s: bad JSON object", chng->uri);
+ buf_dump(&c->buf);
+ return(0);
+ } else if (-1 == (cc = json_parse_response(j))) {
+ warnx("%s: bad response", chng->uri);
+ buf_dump(&c->buf);
+ json_free(j);
+ return(0);
+ } else if (cc > 0)
+ chng->status = 1;
+
+ json_free(j);
+ return(1);
+}
+
+static int
+dorevoke(struct conn *c, const char *addr, const char *cert)
+{
+ char *req;
+ int rc;
+ long lc;
+
+ lc = 0;
+ rc = 0;
+ dodbg("%s: revocation", addr);
+
+ if (NULL == (req = json_fmt_revokecert(cert)))
+ warnx("json_fmt_revokecert");
+ else if ((lc = sreq(c, addr, req)) < 0)
+ warnx("%s: bad comm", addr);
+ else if (200 != lc && 201 != lc && 409 != lc)
+ warnx("%s: bad HTTP: %ld", addr, lc);
+ else
+ rc = 1;
+
+ if (409 == lc)
+ warnx("%s: already revoked", addr);
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ free(req);
+ return(rc);
+}
+
+/*
+ * Submit our certificate to the CA.
+ * This, upon success, will return the signed CA.
+ */
+static int
+docert(struct conn *c, const char *addr, const char *cert)
+{
+ char *req;
+ int rc;
+ long lc;
+
+ rc = 0;
+ dodbg("%s: certificate", addr);
+
+ if (NULL == (req = json_fmt_newcert(cert)))
+ warnx("json_fmt_newcert");
+ else if ((lc = sreq(c, addr, req)) < 0)
+ warnx("%s: bad comm", addr);
+ else if (200 != lc && 201 != lc)
+ warnx("%s: bad HTTP: %ld", addr, lc);
+ else if (0 == c->buf.sz || NULL == c->buf.buf)
+ warnx("%s: empty response", addr);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ free(req);
+ return(rc);
+}
+
+/*
+ * Look up directories from the certificate authority.
+ */
+static int
+dodirs(struct conn *c, const char *addr, struct capaths *paths)
+{
+ struct jsmnn *j;
+ long lc;
+ int rc;
+
+ j = NULL;
+ rc = 0;
+ dodbg("%s: directories", addr);
+
+ if ((lc = nreq(c, addr)) < 0)
+ warnx("%s: bad comm", addr);
+ else if (200 != lc && 201 != lc)
+ warnx("%s: bad HTTP: %ld", addr, lc);
+ else if (NULL == (j = json_parse(c->buf.buf, c->buf.sz)))
+ warnx("json_parse");
+ else if ( ! json_parse_capaths(j, paths))
+ warnx("%s: bad CA paths", addr);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ json_free(j);
+ return(rc);
+}
+
+/*
+ * Request the full chain certificate.
+ */
+static int
+dofullchain(struct conn *c, const char *addr)
+{
+ int rc;
+ long lc;
+
+ rc = 0;
+ dodbg("%s: full chain", addr);
+
+ if ((lc = nreq(c, addr)) < 0)
+ warnx("%s: bad comm", addr);
+ else if (200 != lc && 201 != lc)
+ warnx("%s: bad HTTP: %ld", addr, lc);
+ else
+ rc = 1;
+
+ if (0 == rc || verbose > 1)
+ buf_dump(&c->buf);
+ return(rc);
+}
+
+/*
+ * Here we communicate with the letsencrypt server.
+ * For this, we'll need the certificate we want to upload and our
+ * account key information.
+ */
+int
+netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd,
+ int newacct, int revoke, int staging,
+ const char *const *alts, size_t altsz, const char *agreement)
+{
+ int rc;
+ size_t i;
+ char *cert, *thumb, *url;
+ struct conn c;
+ struct capaths paths;
+ struct chng *chngs;
+ long lval;
+
+ rc = 0;
+ memset(&paths, 0, sizeof(struct capaths));
+ memset(&c, 0, sizeof(struct conn));
+ url = cert = thumb = NULL;
+ chngs = NULL;
+
+ /* File-system, user, and sandbox jail. */
+
+ if ( ! sandbox_before())
+ goto out;
+ else if ( ! dropfs(PATH_VAR_EMPTY))
+ goto out;
+ else if ( ! dropprivs())
+ goto out;
+ else if ( ! sandbox_after())
+ goto out;
+
+ /*
+ * Wait until the acctproc, keyproc, and revokeproc have started
+ * up and are ready to serve us data.
+ * There's no point in running if these don't work.
+ * Then check whether revokeproc indicates that the certificate
+ * on file (if any) can be updated.
+ */
+
+ if (0 == (lval = readop(afd, COMM_ACCT_STAT))) {
+ rc = 1;
+ goto out;
+ } else if (ACCT_READY != lval) {
+ warnx("unknown operation from acctproc");
+ goto out;
+ }
+
+ if (0 == (lval = readop(kfd, COMM_KEY_STAT))) {
+ rc = 1;
+ goto out;
+ } else if (KEY_READY != lval) {
+ warnx("unknown operation from keyproc");
+ goto out;
+ }
+
+ if (0 == (lval = readop(rfd, COMM_REVOKE_RESP))) {
+ rc = 1;
+ goto out;
+ } else if (REVOKE_EXP != lval && REVOKE_OK != lval) {
+ warnx("unknown operation from revokeproc");
+ goto out;
+ }
+
+ /* If our certificate is up-to-date, return now. */
+
+ if (REVOKE_OK == lval) {
+ rc = 1;
+ goto out;
+ }
+
+ /* Allocate main state. */
+
+ chngs = calloc(altsz, sizeof(struct chng));
+ if (NULL == chngs) {
+ warn("calloc");
+ goto out;
+ }
+
+ c.dfd = dfd;
+ c.fd = afd;
+ c.na = staging ? URL_STAGE_CA : URL_REAL_CA;
+
+ /*
+ * Look up the domain of the ACME server.
+ * We'll use this ourselves instead of having libcurl do the DNS
+ * resolution itself.
+ */
+ if ( ! dodirs(&c, c.na, &paths))
+ goto out;
+
+ /*
+ * If we're meant to revoke, then wait for revokeproc to send us
+ * the certificate (if it's found at all).
+ * Following that, submit the request to the CA then notify the
+ * certproc, which will in turn notify the fileproc.
+ */
+
+ if (revoke) {
+ if (NULL == (cert = readstr(rfd, COMM_CSR)))
+ goto out;
+ if ( ! dorevoke(&c, paths.revokecert, cert))
+ goto out;
+ else if (writeop(cfd, COMM_CSR_OP, CERT_REVOKE) > 0)
+ rc = 1;
+ goto out;
+ }
+
+ /* If new, register with the CA server. */
+
+ if (newacct && ! donewreg(&c, agreement, &paths))
+ goto out;
+
+ /* Pre-authorise all domains with CA server. */
+
+ for (i = 0; i < altsz; i++)
+ if ( ! dochngreq(&c, alts[i], &chngs[i], &paths))
+ goto out;
+
+ /*
+ * We now have our challenges.
+ * We need to ask the acctproc for the thumbprint.
+ * We'll combine this to the challenge to create our response,
+ * which will be orchestrated by the chngproc.
+ */
+
+ if (writeop(afd, COMM_ACCT, ACCT_THUMBPRINT) <= 0)
+ goto out;
+ else if (NULL == (thumb = readstr(afd, COMM_THUMB)))
+ goto out;
+
+ /* We'll now ask chngproc to build the challenge. */
+
+ for (i = 0; i < altsz; i++) {
+ if (writeop(Cfd, COMM_CHNG_OP, CHNG_SYN) <= 0)
+ goto out;
+ else if (writestr(Cfd, COMM_THUMB, thumb) <= 0)
+ goto out;
+ else if (writestr(Cfd, COMM_TOK, chngs[i].token) <= 0)
+ goto out;
+
+ /* Read that the challenge has been made. */
+
+ if (CHNG_ACK != readop(Cfd, COMM_CHNG_ACK))
+ goto out;
+
+ /* Write to the CA that it's ready. */
+
+ if ( ! dochngresp(&c, &chngs[i], thumb))
+ goto out;
+ }
+
+ /*
+ * We now wait on the ACME server for each domain.
+ * Connect to the server (assume it's the same server) once
+ * every five seconds.
+ */
+
+ for (i = 0; i < altsz; i++) {
+ if (1 == chngs[i].status)
+ continue;
+
+ if (chngs[i].retry++ >= RETRY_MAX) {
+ warnx("%s: too many tries", chngs[i].uri);
+ goto out;
+ }
+
+ /* Sleep before every attempt. */
+ sleep(RETRY_DELAY);
+ if ( ! dochngcheck(&c, &chngs[i]))
+ goto out;
+ }
+
+ /*
+ * Write our acknowledgement that the challenges are over.
+ * The challenge process will remove all of the files.
+ */
+
+ if (writeop(Cfd, COMM_CHNG_OP, CHNG_STOP) <= 0)
+ goto out;
+
+ /* Wait to receive the certificate itself. */
+
+ if (NULL == (cert = readstr(kfd, COMM_CERT)))
+ goto out;
+
+ /*
+ * Otherwise, submit the CA for signing, download the signed
+ * copy, and ship that into the certificate process for copying.
+ */
+
+ if ( ! docert(&c, paths.newcert, cert))
+ goto out;
+ else if (writeop(cfd, COMM_CSR_OP, CERT_UPDATE) <= 0)
+ goto out;
+ else if (writebuf(cfd, COMM_CSR, c.buf.buf, c.buf.sz) <= 0)
+ goto out;
+
+ /*
+ * Read back the issuer from the certproc.
+ * Then contact the issuer to get the certificate chain.
+ * Write this chain directly back to the certproc.
+ */
+
+ if (NULL == (url = readstr(cfd, COMM_ISSUER)))
+ goto out;
+ else if ( ! dofullchain(&c, url))
+ goto out;
+ else if (writebuf(cfd, COMM_CHAIN, c.buf.buf, c.buf.sz) <= 0)
+ goto out;
+
+ rc = 1;
+out:
+ close(cfd);
+ close(kfd);
+ close(afd);
+ close(Cfd);
+ close(dfd);
+ close(rfd);
+ free(cert);
+ free(url);
+ free(thumb);
+ free(c.buf.buf);
+ if (NULL != chngs)
+ for (i = 0; i < altsz; i++)
+ json_free_challenge(&chngs[i]);
+ free(chngs);
+ json_free_capaths(&paths);
+ return(rc);
+}