Hi tech@,
(super-slightly revised mail/patch compared to the one from October 1st,
mainly indent and line-wrap [1])
I'm using acme-client(1) to handle my certificates on a bunch of
mailservers (smtps, imaps, pops) and a dedicated syslogd(8) server with
tls. My daily cron on these machines contains a line like this:
rcctl -f start httpd >/dev/null && (acme-client foo.netsend.nl; rcctl
stop httpd >/dev/null)
I think it would be nicer if acme-client is able to start and stop
httpd(8) itself with the config mentioned in acme-client(5) so users on
non-webservers don't have to be bothered with setting up a web server
themselves.
The attached patch makes this possible by adding a "-l" switch to
acme-client and simplifies the aforementioned cron entry to the
following:
acme-client -l foo.netsend.nl
On first use, the code creates a new acme-client specific httpd.conf(5)
in /etc/httpd.acme.conf but I'm not entirely happy with that. I think it
would be nicer if /etc/ could be left untouched but I was not able to do
that. I would appreciate feedback on it. Other options could be to
extend httpd so that it can read configuration files from stdin, so
there is no fiddling with any config files at all. Or to create the
config file in a temporary location each time acme-client starts.
Another possible improvement might be to automatically and temporarily
open up port 80 in pf, again I'm not sure how desirable that would be.
Feedback and directions are very welcome. :)
Kind regards,
Tim
[1] https://marc.info/?l=openbsd-tech&m=150688390625866&w=2
Index: Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/Makefile,v
retrieving revision 1.8
diff -u -p -r1.8 Makefile
--- Makefile 3 Jul 2017 22:21:47 -0000 1.8
+++ Makefile 5 Dec 2017 11:16:44 -0000
@@ -2,7 +2,7 @@
PROG= acme-client
SRCS= acctproc.c base64.c certproc.c chngproc.c dbg.c dnsproc.c
SRCS+= fileproc.c http.c jsmn.c json.c keyproc.c main.c netproc.c
-SRCS+= parse.y revokeproc.c rsa.c util.c
+SRCS+= parse.y revokeproc.c rsa.c servproc.c util.c
MAN= acme-client.1 acme-client.conf.5
Index: acme-client.1
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/acme-client.1,v
retrieving revision 1.23
diff -u -p -r1.23 acme-client.1
--- acme-client.1 17 Oct 2017 22:47:58 -0000 1.23
+++ acme-client.1 5 Dec 2017 11:16:44 -0000
@@ -41,6 +41,8 @@ Create a new RSA domain key if one does
Force updating the certificate signature even if it's too soon.
.It Fl f Ar configfile
Specify an alternative configuration file.
+.It Fl l
+Listen: let acme-client start a web server internally.
.It Fl n
No operation: check and print configuration.
.It Fl r
Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/extern.h,v
retrieving revision 1.9
diff -u -p -r1.9 extern.h
--- extern.h 27 Nov 2017 01:58:52 -0000 1.9
+++ extern.h 5 Dec 2017 11:16:44 -0000
@@ -37,6 +37,14 @@ enum acctop {
};
/*
+ * Requests to servproc.
+ */
+enum servop {
+ SERV_STOP = 0,
+ SERV__MAX
+};
+
+/*
* Requests to and from chngproc.
*/
enum chngop {
@@ -105,6 +113,7 @@ enum comp {
COMP_FILE, /* handles writing certs */
COMP_DNS, /* handles DNS lookups */
COMP_REVOKE, /* checks X509 expiration */
+ COMP_SERV, /* httpd */
COMP__MAX
};
@@ -119,6 +128,7 @@ enum comm {
COMM_PAY,
COMM_NONCE,
COMM_TOK,
+ COMM_SERV_OP,
COMM_CHNG_OP,
COMM_CHNG_ACK,
COMM_ACCT,
@@ -176,6 +186,7 @@ __BEGIN_DECLS
int acctproc(int, const char *, int);
int certproc(int, int);
int chngproc(int, const char *);
+int servproc(int);
int dnsproc(int);
int revokeproc(int, const char *, const char *,
int, int, const char *const *, size_t);
@@ -183,7 +194,7 @@ int fileproc(int, const char *, const
const char *);
int keyproc(int, const char *,
const char **, size_t, int);
-int netproc(int, int, int, int, int, int, int, int,
+int netproc(int, int, int, int, int, int, int, int, int,
struct authority_c *, const char *const *,
size_t);
Index: main.c
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/main.c,v
retrieving revision 1.36
diff -u -p -r1.36 main.c
--- main.c 27 Nov 2017 01:58:52 -0000 1.36
+++ main.c 5 Dec 2017 11:16:44 -0000
@@ -42,7 +42,7 @@ main(int argc, char *argv[])
char *chngdir = NULL, *auth = NULL;
char *conffile = CONF_FILE;
int key_fds[2], acct_fds[2], chng_fds[2], cert_fds[2];
- int file_fds[2], dns_fds[2], rvk_fds[2];
+ int file_fds[2], dns_fds[2], rvk_fds[2], serv_fds[2];
int force = 0;
int c, rc, revocate = 0;
int popts = 0;
@@ -56,7 +56,7 @@ main(int argc, char *argv[])
struct domain_c *domain = NULL;
struct altname_c *ac;
- while ((c = getopt(argc, argv, "FADrvnf:")) != -1)
+ while ((c = getopt(argc, argv, "FADlrvnf:")) != -1)
switch (c) {
case 'f':
if ((conffile = strdup(optarg)) == NULL)
@@ -71,6 +71,9 @@ main(int argc, char *argv[])
case 'D':
popts |= ACME_OPT_NEWDKEY;
break;
+ case 'l':
+ popts |= ACME_OPT_LISTEN;
+ break;
case 'r':
revocate = 1;
break;
@@ -238,6 +241,8 @@ main(int argc, char *argv[])
err(EXIT_FAILURE, "socketpair");
if (socketpair(AF_UNIX, SOCK_STREAM, 0, rvk_fds) == -1)
err(EXIT_FAILURE, "socketpair");
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, serv_fds) == -1)
+ err(EXIT_FAILURE, "socketpair");
/* Start with the network-touching process. */
@@ -254,9 +259,14 @@ main(int argc, char *argv[])
close(file_fds[1]);
close(dns_fds[0]);
close(rvk_fds[0]);
+ close(serv_fds[0]);
+ if ((popts & ACME_OPT_LISTEN) == 0) {
+ close(serv_fds[1]);
+ serv_fds[1] = -1;
+ }
c = netproc(key_fds[1], acct_fds[1],
chng_fds[1], cert_fds[1],
- dns_fds[1], rvk_fds[1],
+ dns_fds[1], rvk_fds[1], serv_fds[1],
(popts & ACME_OPT_NEWACCT), revocate, authority,
(const char *const *)alts, altsz);
free(alts);
@@ -269,6 +279,7 @@ main(int argc, char *argv[])
close(cert_fds[1]);
close(dns_fds[1]);
close(rvk_fds[1]);
+ close(serv_fds[1]);
/* Now the key-touching component. */
@@ -284,6 +295,7 @@ main(int argc, char *argv[])
close(chng_fds[0]);
close(file_fds[0]);
close(file_fds[1]);
+ close(serv_fds[0]);
c = keyproc(key_fds[0], domain->key,
(const char **)alts, altsz, (popts & ACME_OPT_NEWDKEY));
free(alts);
@@ -306,6 +318,7 @@ main(int argc, char *argv[])
close(chng_fds[0]);
close(file_fds[0]);
close(file_fds[1]);
+ close(serv_fds[0]);
c = acctproc(acct_fds[0], acctkey, (popts & ACME_OPT_NEWACCT));
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
@@ -325,6 +338,7 @@ main(int argc, char *argv[])
close(rvk_fds[0]);
close(file_fds[0]);
close(file_fds[1]);
+ close(serv_fds[0]);
c = chngproc(chng_fds[0], chngdir);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
@@ -342,6 +356,7 @@ main(int argc, char *argv[])
close(dns_fds[0]);
close(rvk_fds[0]);
close(file_fds[1]);
+ close(serv_fds[0]);
c = certproc(cert_fds[0], file_fds[0]);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
@@ -359,6 +374,7 @@ main(int argc, char *argv[])
free(alts);
close(dns_fds[0]);
close(rvk_fds[0]);
+ close(serv_fds[0]);
c = fileproc(file_fds[1], certdir, certfile, chainfile,
fullchainfile);
/*
@@ -379,6 +395,7 @@ main(int argc, char *argv[])
proccomp = COMP_DNS;
free(alts);
close(rvk_fds[0]);
+ close(serv_fds[0]);
c = dnsproc(dns_fds[0]);
exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
}
@@ -392,6 +409,7 @@ main(int argc, char *argv[])
if (pids[COMP_REVOKE] == 0) {
proccomp = COMP_REVOKE;
+ close(serv_fds[0]);
c = revokeproc(rvk_fds[0], certdir,
certfile != NULL ? certfile : fullchainfile,
force, revocate,
@@ -402,6 +420,27 @@ main(int argc, char *argv[])
close(rvk_fds[0]);
+ /* Optional server component. */
+
+ pids[COMP_SERV] = -1;
+
+ if (popts & ACME_OPT_LISTEN) {
+
+ /* Start a web server ourselves. */
+
+ if ((pids[COMP_SERV] = fork()) == -1)
+ err(EXIT_FAILURE, "fork");
+
+ if (pids[COMP_SERV] == 0) {
+ proccomp = COMP_SERV;
+ free(alts);
+ c = servproc(serv_fds[0]);
+ exit(c ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+ }
+
+ close(serv_fds[0]);
+
/* Jail: sandbox, file-system, user. */
if (pledge("stdio", NULL) == -1) {
@@ -423,10 +462,15 @@ main(int argc, char *argv[])
checkexit(pids[COMP_DNS], COMP_DNS) +
checkexit(pids[COMP_REVOKE], COMP_REVOKE);
+ if (pids[COMP_SERV] != -1)
+ rc += checkexit(pids[COMP_SERV], COMP_SERV);
+ else
+ rc++;
+
free(alts);
return rc != COMP__MAX ? EXIT_FAILURE : (c == 2 ? EXIT_SUCCESS : 2);
usage:
fprintf(stderr,
- "usage: acme-client [-ADFnrv] [-f configfile] domain\n");
+ "usage: acme-client [-ADFlnrv] [-f configfile] domain\n");
return EXIT_FAILURE;
}
Index: netproc.c
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/netproc.c,v
retrieving revision 1.14
diff -u -p -r1.14 netproc.c
--- netproc.c 27 Nov 2017 01:58:52 -0000 1.14
+++ netproc.c 5 Dec 2017 11:16:44 -0000
@@ -565,7 +565,7 @@ dofullchain(struct conn *c, const char *
* account key information.
*/
int
-netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd,
+netproc(int kfd, int afd, int Cfd, int cfd, int dfd, int rfd, int sfd,
int newacct, int revocate, struct authority_c *authority,
const char *const *alts,size_t altsz)
{
@@ -737,13 +737,19 @@ netproc(int kfd, int afd, int Cfd, int c
}
/*
- * Write our acknowledgement that the challenges are over.
- * The challenge process will remove all of the files.
+ * Write our acknowledgement that the challenges are over to both the
+ * challenge process and the server process, if started.
+ * The challenge process will remove all of the files and the server
+ * process will stop the http server.
*/
if (writeop(Cfd, COMM_CHNG_OP, CHNG_STOP) <= 0)
goto out;
+ if (sfd != -1)
+ if (writeop(sfd, COMM_SERV_OP, SERV_STOP) <= 0)
+ goto out;
+
/* Wait to receive the certificate itself. */
if ((cert = readstr(kfd, COMM_CERT)) == NULL)
@@ -782,6 +788,8 @@ out:
close(Cfd);
close(dfd);
close(rfd);
+ if (sfd != -1)
+ close(sfd);
free(cert);
free(url);
free(thumb);
Index: parse.h
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/parse.h,v
retrieving revision 1.9
diff -u -p -r1.9 parse.h
--- parse.h 27 Nov 2017 16:53:04 -0000 1.9
+++ parse.h 5 Dec 2017 11:16:44 -0000
@@ -61,6 +61,7 @@ struct keyfile {
#define ACME_OPT_NEWACCT 0x00000002
#define ACME_OPT_NEWDKEY 0x00000004
#define ACME_OPT_CHECK 0x00000008
+#define ACME_OPT_LISTEN 0x00000016
struct acme_conf {
int opts;
Index: servproc.c
===================================================================
RCS file: servproc.c
diff -N servproc.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ servproc.c 5 Dec 2017 11:16:44 -0000
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2017 Tim Kuijsten <[email protected]>
+ *
+ * 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.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "extern.h"
+
+#define HTTPDCONF "/etc/httpd.acme.conf"
+#define HTTPDBIN "/usr/sbin/httpd"
+
+int
+servproc(int netsock)
+{
+ int rc = 0, s, fd = -1;
+ pid_t pid;
+ char cfg[] = "server \"acmeclient\" {\n"
+ " listen on egress port 80\n"
+ " location \"/.well-known/acme-challenge/*\" {\n"
+ " root \"/acme\"\n"
+ " root strip 2\n"
+ " }\n"
+ "}\n";
+
+ if (pledge("stdio wpath cpath proc exec", NULL) == -1)
+ err(EXIT_FAILURE, "%s: pledge", __func__);
+
+ /*
+ * Create a minimal acme config for httpd if one does not yet exist.
+ */
+
+ if ((fd = open(HTTPDCONF, O_WRONLY | O_CREAT | O_EXCL, 0644)) == -1) {
+ if (errno != EEXIST)
+ err(EXIT_FAILURE, "open");
+ } else {
+ s = write(fd, cfg, strlen(cfg));
+ if (s != (ssize_t)strlen(cfg))
+ err(EXIT_FAILURE, "%s: write", __func__);
+ if (close(fd) == -1)
+ err(EXIT_FAILURE, "%s: close", __func__);
+ }
+
+ /* Fork+exec httpd. */
+
+ if ((pid = fork()) == -1)
+ err(EXIT_FAILURE, "%s: fork", __func__);
+
+ if (pid == 0) {
+ if (verbose < 2)
+ close(STDOUT_FILENO);
+ if (verbose < 1)
+ close(STDERR_FILENO);
+
+ if (execle(HTTPDBIN, "httpd", "-df", HTTPDCONF, NULL,
+ NULL) == -1)
+ err(EXIT_FAILURE, "%s: execle", __func__);
+ }
+
+ if (pledge("stdio proc", NULL) == -1)
+ err(EXIT_FAILURE, "%s: pledge", __func__);
+
+ /*
+ * Wait until we get a request to stop.
+ */
+
+ if (readop(netsock, COMM_SERV_OP) != SERV_STOP) {
+ warnx("unknown operation from netproc");
+ goto out;
+ }
+
+ if (kill(pid, SIGINT) == -1) {
+ warnx("%s: kill", __func__);
+ goto out;
+ }
+
+ rc = checkexit(pid, COMP_SERV);
+out:
+ if (rc == 0) {
+ warnx("%s: killing httpd, SIGKILL", __func__);
+ if (kill(pid, SIGKILL) == -1) {
+ if (errno != ESRCH) {
+ warn("%s: kill", __func__);
+ rc = checkexit(pid, COMP_SERV);
+ }
+ }
+ }
+
+ close(netsock);
+ return rc;
+}
Index: util.c
===================================================================
RCS file: /cvs/src/usr.sbin/acme-client/util.c,v
retrieving revision 1.10
diff -u -p -r1.10 util.c
--- util.c 27 Nov 2017 16:53:04 -0000 1.10
+++ util.c 5 Dec 2017 11:16:44 -0000
@@ -42,6 +42,7 @@ static const char *const comps[COMP__MAX
"fileproc", /* COMP_FILE */
"dnsproc", /* COMP_DNS */
"revokeproc", /* COMP_REVOKE */
+ "servproc", /* COMP_SERV */
};
static const char *const comms[COMM__MAX] = {
@@ -51,6 +52,7 @@ static const char *const comms[COMM__MAX
"payload", /* COMM_PAY */
"nonce", /* COMM_NONCE */
"token", /* COMM_TOK */
+ "serv-op", /* COMM_SERV_OP */
"challenge-op", /* COMM_CHNG_OP */
"challenge-ack", /* COMM_CHNG_ACK */
"account", /* COMM_ACCT */