/* * 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); }
/* * Make sure that the given process exits properly, i.e., properly * exiting with EXIT_SUCCESS *or* 2. * Returns non-zero on success and zero on failure and sets the "rc" * value to be the exit status. */ int checkexit_ext(int *rc, pid_t pid, enum comp comp) { int c; const char *cp; *rc = EXIT_FAILURE; if (-1 == waitpid(pid, &c, 0)) { warn("waitpid"); return (0); } if ( ! WIFEXITED(c) && WIFSIGNALED(c)) { cp = strsignal(WTERMSIG(c)); warnx("signal: %s(%u): %s", comps[comp], pid, cp); return (0); } else if ( ! WIFEXITED(c)) { warnx("did not exit: %s(%u)", comps[comp], pid); return (0); } /* Now check extended status. */ if (EXIT_SUCCESS != (*rc = WEXITSTATUS(c)) && 2 != *rc) { dodbg("bad exit: %s(%u): %d", comps[comp], pid, *rc); return (0); } return (1); }
/* * 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); }
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); }
/* * 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); }
/* * 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); }
/* * 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 struct capaths *p) { int rc; char *req; long lc; rc = 0; dodbg("%s: new-reg", p->newreg); if (NULL == (req = json_fmt_newreg(URL_LICENSE))) 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); }
/* * Copyright (c) 2003, 2004 Henning Brauer <*****@*****.**> * * 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 AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. */ static ssize_t host_dns(const char *s, struct addr *vec) { struct addrinfo hints, *res0, *res; int error; ssize_t vecsz; struct sockaddr *sa; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; /* DUMMY */ /* ntpd MUST NOT use AI_ADDRCONFIG here */ error = getaddrinfo(s, NULL, &hints, &res0); if (error == EAI_AGAIN || /* FIXME */ #ifndef __FreeBSD__ error == EAI_NODATA || #endif error == EAI_NONAME) return(0); if (error) { warnx("%s: parse error: %s", s, gai_strerror(error)); return(-1); } for (vecsz = 0, res = res0; NULL != res && vecsz < MAX_SERVERS_DNS; res = res->ai_next) { if (res->ai_family != AF_INET && res->ai_family != AF_INET6) continue; sa = res->ai_addr; if (AF_INET == res->ai_family) { vec[vecsz].family = 4; inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr), vec[vecsz].ip, INET6_ADDRSTRLEN); } else { vec[vecsz].family = 6; inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), vec[vecsz].ip, INET6_ADDRSTRLEN); } dodbg("%s: DNS: %s", s, vec[vecsz].ip); vecsz++; break; } freeaddrinfo(res0); return(vecsz); }
/* * 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); }
/* * Make sure that the given process exits properly, i.e., properly * exiting with EXIT_SUCCESS. * Returns non-zero on success and zero on failure. */ int checkexit(pid_t pid, enum comp comp) { int c, cc; const char *cp; if (-1 == waitpid(pid, &c, 0)) { warn("waitpid"); return (0); } else if ( ! WIFEXITED(c) && WIFSIGNALED(c)) { cp = strsignal(WTERMSIG(c)); warnx("signal: %s(%u): %s", comps[comp], pid, cp); return (0); } else if ( ! WIFEXITED(c)) { warnx("did not exit: %s(%u)", comps[comp], pid); return (0); } else if (EXIT_SUCCESS != WEXITSTATUS(c)) { cc = WEXITSTATUS(c); dodbg("bad exit: %s(%u): %d", comps[comp], pid, cc); return (0); } return (1); }
/* * 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); }
/* * 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); }
int fileproc(int certsock, const char *certdir, const struct config *cfg) { char *csr = NULL, *ch = NULL; char file[PATH_MAX]; size_t chsz, csz; int rc = 0; long lval; enum fileop op; time_t t; /* File-system and sandbox jailing. */ if ( ! sandbox_before()) goto out; else if ( ! dropfs(certdir)) goto out; else if ( ! sandbox_after(0)) goto out; /* Read our operation. */ op = FILE__MAX; if (0 == (lval = readop(certsock, COMM_CHAIN_OP))) op = FILE_STOP; else if (FILE_CREATE == lval || FILE_REMOVE == lval) op = lval; if (FILE_STOP == op) { rc = 1; goto out; } else if (FILE__MAX == op) { warnx("unknown operation from certproc"); goto out; } /* * If we're backing up, then copy all files (found) by linking * them to the file followed by the epoch in seconds. * If we're going to remove, the unlink(2) will cause the * original to go away. * If we're going to update, the rename(2) will replace the * certificate, leaving the backup as the only one. */ if (cfg->backup) { t = time(NULL); snprintf(file, sizeof(file), "cert-%llu.pem", (unsigned long long)t); if (-1 == link(CERT_PEM, file) && ENOENT != errno) { warnx("%s/%s", certdir, CERT_PEM); goto out; } else dodbg("%s/%s: linked to %s", certdir, CERT_PEM, file); snprintf(file, sizeof(file), "chain-%llu.pem", (unsigned long long)t); if (-1 == link(CHAIN_PEM, file) && ENOENT != errno) { warnx("%s/%s", certdir, CHAIN_PEM); goto out; } else dodbg("%s/%s: linked to %s", certdir, CHAIN_PEM, file); snprintf(file, sizeof(file), "fullchain-%llu.pem", (unsigned long long)t); if (-1 == link(FCHAIN_PEM, file) && ENOENT != errno) { warnx("%s/%s", certdir, FCHAIN_PEM); goto out; } else dodbg("%s/%s: linked to %s", certdir, FCHAIN_PEM, file); } /* * If revoking certificates, just unlink the files. * We return the special error code of 2 to indicate that the * certificates were removed. */ if (FILE_REMOVE == op) { if (-1 == unlink(CERT_PEM) && ENOENT != errno) { warn("%s/%s", certdir, CERT_PEM); goto out; } else dodbg("%s/%s: unlinked", certdir, CERT_PEM); if (-1 == unlink(CHAIN_PEM) && ENOENT != errno) { warn("%s/%s", certdir, CHAIN_PEM); goto out; } else dodbg("%s/%s: unlinked", certdir, CHAIN_PEM); if (-1 == unlink(FCHAIN_PEM) && ENOENT != errno) { warn("%s/%s", certdir, FCHAIN_PEM); goto out; } else dodbg("%s/%s: unlinked", certdir, FCHAIN_PEM); rc = 2; goto out; } /* * Start by downloading the chain PEM as a buffer. * This is not nil-terminated, but we're just going to guess * that it's well-formed and not actually touch the data. * Once downloaded, dump it into CHAIN_BAK. */ if (NULL == (ch = readbuf(certsock, COMM_CHAIN, &chsz))) goto out; if ( ! serialise(CHAIN_BAK, CHAIN_PEM, ch, chsz, NULL, 0)) goto out; dodbg("%s/%s: created", certdir, CHAIN_PEM); /* * Next, wait until we receive the DER encoded (signed) * certificate from the network process. * This comes as a stream of bytes: we don't know how many, so * just keep downloading. */ if (NULL == (csr = readbuf(certsock, COMM_CSR, &csz))) goto out; if ( ! serialise(CERT_BAK, CERT_PEM, csr, csz, NULL, 0)) goto out; dodbg("%s/%s: created", certdir, CERT_PEM); /* * Finally, create the full-chain file. * This is just the concatenation of the certificate and chain. * We return the special error code 2 to indicate that the * on-file certificates were changed. */ if ( ! serialise(FCHAIN_BAK, FCHAIN_PEM, csr, csz, ch, chsz)) goto out; dodbg("%s/%s: created", certdir, FCHAIN_PEM); rc = 2; out: close(certsock); free(csr); free(ch); return (rc); }
int chngproc(int netsock, const char *root, int remote) { int rc; long lval; enum chngop op; char *tok, *th, *fmt; char **fs; size_t i, fsz; void *pp; int fd, cc; rc = 0; th = tok = fmt = NULL; fd = -1; fs = NULL; fsz = 0; /* File-system and sandbox jailing. */ if ( ! sandbox_before()) goto out; else if ( ! dropfs(root)) goto out; else if ( ! sandbox_after()) goto out; /* * Loop while we wait to get a thumbprint and token. * We'll get this for each SAN request. */ for (;;) { op = CHNG__MAX; if (0 == (lval = readop(netsock, COMM_CHNG_OP))) op = CHNG_STOP; else if (CHNG_SYN == lval) op = lval; if (CHNG__MAX == op) { warnx("unknown operation from netproc"); goto out; } else if (CHNG_STOP == op) break; assert(CHNG_SYN == op); /* * Read the thumbprint and token. * The token is the filename, so store that in a vector * of tokens that we'll later clean up. */ if (NULL == (th = readstr(netsock, COMM_THUMB))) goto out; else if (NULL == (tok = readstr(netsock, COMM_TOK))) goto out; /* Vector appending... */ pp = realloc(fs, (fsz + 1) * sizeof(char *)); if (NULL == pp) { warn("realloc"); goto out; } fs = pp; fs[fsz] = tok; tok = NULL; fsz++; if (-1 == asprintf(&fmt, "%s.%s", fs[fsz - 1], th)) { warn("asprintf"); goto out; } /* * I use this for testing when letskencrypt is being run * on machines apart from where I'm hosting the * challenge directory. * DON'T DEPEND ON THIS FEATURE. */ if (remote) { puts("RUN THIS IN THE CHALLENGE DIRECTORY"); puts("YOU HAVE 20 SECONDS..."); printf("doas sh -c \"echo %s > %s\"\n", fmt, fs[fsz - 1]); sleep(20); puts("TIME'S UP."); } else { /* * 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_EXCL|O_CREAT, 0444); if (-1 == fd) { warn("%s", fs[fsz - 1]); goto out; } if (-1 == write(fd, fmt, strlen(fmt))) { warn("%s", fs[fsz - 1]); goto out; } else if (-1 == close(fd)) { warn("%s", fs[fsz - 1]); goto out; } fd = -1; } free(th); free(fmt); th = fmt = NULL; dodbg("%s/%s: created", root, fs[fsz - 1]); /* * Write our acknowledgement. * Ignore reader failure. */ cc = writeop(netsock, COMM_CHNG_ACK, CHNG_ACK); if (0 == cc) break; if (cc < 0) goto out; } rc = 1; out: close(netsock); if (-1 != fd) close(fd); for (i = 0; i < fsz; i++) { if (-1 == unlink(fs[i]) && ENOENT != errno) warn("%s", fs[i]); free(fs[i]); } free(fs); free(fmt); free(th); free(tok); return(rc); }