/* * 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); }
static void rewind_tape(int fd) { struct stat sp; if(fstat(fd, &sp)) errx(12, "fstat in rewind"); /* * don't want to do tape ioctl on regular files: */ if( S_ISREG(sp.st_mode) ) { if( lseek(fd, 0, SEEK_SET) == -1 ) errx(13, "lseek"); } else /* assume its a tape */ writeop(fd, MTREW); }
int main(int argc, char *argv[]) { int lastnread, nread, nw, inp, outp; enum {READ, VERIFY, COPY, COPYVERIFY} op = READ; sig_t oldsig; int ch, needeof; char *buff; const char *inf; msg = stdout; guesslen = 1; outp = -1; while ((ch = getopt(argc, argv, "cs:vx")) != -1) switch((char)ch) { case 'c': op = COPYVERIFY; break; case 's': maxblk = atoi(optarg); if (maxblk <= 0) { warnx("illegal block size"); usage(); } guesslen = 0; break; case 'v': op = VERIFY; break; case 'x': msg = stderr; break; case '?': default: usage(); } argc -= optind; argv += optind; switch(argc) { case 0: if (op != READ) usage(); inf = _PATH_DEFTAPE; break; case 1: if (op != READ) usage(); inf = argv[0]; break; case 2: if (op == READ) op = COPY; inf = argv[0]; if ((outp = open(argv[1], op == VERIFY ? O_RDONLY : op == COPY ? O_WRONLY : O_RDWR, DEFFILEMODE)) < 0) err(3, "%s", argv[1]); break; default: usage(); } if ((inp = open(inf, O_RDONLY, 0)) < 0) err(1, "%s", inf); buff = getspace(maxblk); if (op == VERIFY) { verify(inp, outp, buff); exit(0); } if ((oldsig = signal(SIGINT, SIG_IGN)) != SIG_IGN) (void) signal(SIGINT, intr); needeof = 0; for (lastnread = NOCOUNT;;) { if ((nread = read(inp, buff, maxblk)) == -1) { while (errno == EINVAL && (maxblk -= 1024)) { nread = read(inp, buff, maxblk); if (nread >= 0) goto r1; } err(1, "read error, file %d, record %ju", filen, (intmax_t)record); } else if (nread != lastnread) { if (lastnread != 0 && lastnread != NOCOUNT) { if (lastrec == 0 && nread == 0) fprintf(msg, "%ju records\n", (intmax_t)record); else if (record - lastrec > 1) fprintf(msg, "records %ju to %ju\n", (intmax_t)lastrec, (intmax_t)record); else fprintf(msg, "record %ju\n", (intmax_t)lastrec); } if (nread != 0) fprintf(msg, "file %d: block size %d: ", filen, nread); (void) fflush(stdout); lastrec = record; } r1: guesslen = 0; if (nread > 0) { if (op == COPY || op == COPYVERIFY) { if (needeof) { writeop(outp, MTWEOF); needeof = 0; } nw = write(outp, buff, nread); if (nw != nread) { if (nw == -1) { warn("write error, file %d, record %ju", filen, (intmax_t)record); } else { warnx("write error, file %d, record %ju", filen, (intmax_t)record); warnx("write (%d) != read (%d)", nw, nread); } errx(5, "copy aborted"); } } size += nread; record++; } else { if (lastnread <= 0 && lastnread != NOCOUNT) { fprintf(msg, "eot\n"); break; } fprintf(msg, "file %d: eof after %ju records: %ju bytes\n", filen, (intmax_t)record, (intmax_t)size); needeof = 1; filen++; tsize += size; size = record = lastrec = 0; lastnread = 0; } lastnread = nread; } fprintf(msg, "total length: %ju bytes\n", (intmax_t)tsize); (void)signal(SIGINT, oldsig); if (op == COPY || op == COPYVERIFY) { writeop(outp, MTWEOF); writeop(outp, MTWEOF); if (op == COPYVERIFY) { rewind_tape(outp); rewind_tape(inp); verify(inp, outp, buff); } } exit(0); }
int dnsproc(int nfd) { int rc, cc; char *look, *last; struct addr v[MAX_SERVERS_DNS]; long lval; size_t i; ssize_t vsz; enum dnsop op; rc = 0; look = last = NULL; vsz = 0; /* * Why don't we chroot() here? * On OpenBSD, the pledge(2) takes care of our constraining the * environment to DNS resolution only, so the chroot(2) is * unnecessary. * On Mac OS X, we can't chroot(2): we'd need to have an mdns * responder thing in each jail. * On Linux, forget it. getaddrinfo(2) pulls on all sorts of * mystery meat. */ if ( ! sandbox_before()) goto out; else if ( ! dropprivs()) goto out; else if ( ! sandbox_after()) goto out; /* * This is simple: just loop on a request operation, and each * time we write back zero or more entries. * Also do a simple trick and cache the last lookup. */ for (;;) { op = DNS__MAX; if (0 == (lval = readop(nfd, COMM_DNS))) op = DNS_STOP; else if (DNS_LOOKUP == lval) op = lval; if (DNS__MAX == op) { warnx("unknown operation from netproc"); goto out; } else if (DNS_STOP == op) break; if (NULL == (look = readstr(nfd, COMM_DNSQ))) goto out; /* * Check if we're asked to repeat the lookup. * If not, request it from host_dns(). */ if (NULL == last || strcmp(look, last)) { if ((vsz = host_dns(look, v)) < 0) goto out; free(last); last = look; look = NULL; } else { doddbg("%s: cached", look); free(look); look = NULL; } if (0 == (cc = writeop(nfd, COMM_DNSLEN, vsz))) break; else if (cc < 0) goto out; for (i = 0; i < (size_t)vsz; i++) { if (writeop(nfd, COMM_DNSF, v[i].family) <= 0) goto out; if (writestr(nfd, COMM_DNSA, v[i].ip) <= 0) goto out; } } rc = 1; out: close(nfd); free(look); free(last); return(rc); }
int certproc(int netsock, int filesock) { char *csr, *chain, *url; unsigned char *csrcp, *chaincp; size_t csrsz, chainsz; int i, rc, idx, cc; enum certop op; long lval; X509 *x, *chainx; X509_EXTENSION *ext; X509V3_EXT_METHOD *method; void *entries; STACK_OF(CONF_VALUE) *val; CONF_VALUE *nval; ext = NULL; idx = -1; method = NULL; chain = csr = url = NULL; rc = 0; x = chainx = NULL; /* File-system and sandbox jailing. */ if ( ! sandbox_before()) goto out; ERR_load_crypto_strings(); if ( ! dropfs(PATH_VAR_EMPTY)) goto out; else if ( ! dropprivs()) goto out; else if ( ! sandbox_after()) goto out; /* Read what the netproc wants us to do. */ op = CERT__MAX; if (0 == (lval = readop(netsock, COMM_CSR_OP))) op = CERT_STOP; else if (CERT_REVOKE == lval || CERT_UPDATE == lval) op = lval; if (CERT_STOP == op) { rc = 1; goto out; } else if (CERT__MAX == op) { warnx("unknown operation from netproc"); goto out; } /* * Pass revocation right through to fileproc. * If the reader is terminated, ignore it. */ if (CERT_REVOKE == op) { if (writeop(filesock, COMM_CHAIN_OP, FILE_REMOVE) >= 0) rc = 1; goto out; } /* * Wait until we receive the DER encoded (signed) certificate * from the network process. * Then convert the DER encoding into an X509 certificate. */ if (NULL == (csr = readbuf(netsock, COMM_CSR, &csrsz))) goto out; csrcp = (u_char *)csr; x = d2i_X509(NULL, (const u_char **)&csrcp, csrsz); if (NULL == x) { warnx("d2i_X509"); goto out; } /* * Extract the CA Issuers from its NID. * TODO: I have no idea what I'm doing. */ idx = X509_get_ext_by_NID(x, NID_info_access, idx); if (idx >= 0 && NULL != (ext = X509_get_ext(x, idx))) method = (X509V3_EXT_METHOD *)X509V3_EXT_get(ext); entries = X509_get_ext_d2i(x, NID_info_access, 0, 0); if (NULL != method && NULL != entries) { val = method->i2v(method, entries, 0); for (i = 0; i < sk_CONF_VALUE_num(val); i++) { nval = sk_CONF_VALUE_value(val, i); if (strcmp(nval->name, "CA Issuers - URI")) continue; url = strdup(nval->value); if (NULL == url) { warn("strdup"); goto out; } break; } } if (NULL == url) { warnx("no CA issuer registered with certificate"); goto out; } /* Write the CA issuer to the netsock. */ if (writestr(netsock, COMM_ISSUER, url) <= 0) goto out; /* Read the full-chain back from the netsock. */ if (NULL == (chain = readbuf(netsock, COMM_CHAIN, &chainsz))) goto out; /* * Then check if the chain is PEM-encoded by looking to see if * it begins with the PEM marker. * If so, ship it as-is; otherwise, convert to a PEM encoded * buffer and ship that. * FIXME: if PEM, re-parse it. */ if (chainsz <= strlen(MARKER) || strncmp(chain, MARKER, strlen(MARKER))) { chaincp = (u_char *)chain; chainx = d2i_X509(NULL, (const u_char **)&chaincp, chainsz); if (NULL == chainx) { warnx("d2i_X509"); goto out; } free(chain); if (NULL == (chain = x509buf(chainx, &chainsz))) goto out; } /* Allow reader termination to just push us out. */ if (0 == (cc = writeop(filesock, COMM_CHAIN_OP, FILE_CREATE))) rc = 1; if (cc <= 0) goto out; if (0 == (cc = writebuf(filesock, COMM_CHAIN, chain, chainsz))) rc = 1; if (cc <= 0) goto out; /* * Next, convert the X509 to a buffer and send that. * Reader failure doesn't change anything. */ free(chain); if (NULL == (chain = x509buf(x, &chainsz))) goto out; if (writebuf(filesock, COMM_CSR, chain, chainsz) < 0) goto out; rc = 1; out: close(netsock); close(filesock); if (NULL != x) X509_free(x); if (NULL != chainx) X509_free(chainx); free(csr); free(url); free(chain); ERR_print_errors_fp(stderr); ERR_free_strings(); 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); }
/* * 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) { 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, &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); }
/* * 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); }