From 5e6094e437a5437ceb6b083d16995ea629a4d720 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Sun, 12 Jan 2020 18:07:54 -0500 Subject: Add option to set local client CA This is a little bit messy. Allows setting either -A or -W or both. Implements SASL EXTERNAL for clients that expect that when connecting with a client certificate. Need to test that reloading still works inside capsicum, since I suspect that rewind call may be blocked. --- bounce.c | 16 +++++++++++++--- bounce.h | 3 ++- client.c | 28 ++++++++++++++++++++++++++++ local.c | 20 +++++++++++++++++++- pounce.1 | 22 +++++++++++++++++++++- 5 files changed, 83 insertions(+), 6 deletions(-) diff --git a/bounce.c b/bounce.c index 89b2ff1..7ea1c3f 100644 --- a/bounce.c +++ b/bounce.c @@ -215,6 +215,7 @@ int main(int argc, char *argv[]) { char bindPath[PATH_MAX] = ""; char certPath[PATH_MAX] = ""; char privPath[PATH_MAX] = ""; + const char *caPath = NULL; bool insecure = false; const char *clientCert = NULL; @@ -232,9 +233,10 @@ int main(int argc, char *argv[]) { const char *join = NULL; const char *quit = "connection reset by purr"; - const char *Opts = "!C:H:K:NP:U:W:a:c:ef:g:h:j:k:n:p:q:r:s:u:vw:xy:"; + const char *Opts = "!A:C:H:K:NP:U:W:a:c:ef:g:h:j:k:n:p:q:r:s:u:vw:xy:"; const struct option LongOpts[] = { { "insecure", no_argument, NULL, '!' }, + { "client-ca", required_argument, NULL, 'A' }, { "cert", required_argument, NULL, 'C' }, { "bind-host", required_argument, NULL, 'H' }, { "priv", required_argument, NULL, 'K' }, @@ -265,6 +267,7 @@ int main(int argc, char *argv[]) { while (0 < (opt = getopt_config(argc, argv, Opts, LongOpts, NULL))) { switch (opt) { break; case '!': insecure = true; + break; case 'A': clientCA = true; caPath = optarg; break; case 'C': strlcpy(certPath, optarg, sizeof(certPath)); break; case 'H': bindHost = optarg; break; case 'K': strlcpy(privPath, optarg, sizeof(privPath)); @@ -331,11 +334,17 @@ int main(int argc, char *argv[]) { ringAlloc(ringSize); if (savePath) saveLoad(savePath); + FILE *localCA = NULL; + if (caPath) { + localCA = fopen(caPath, "r"); + if (!localCA) err(EX_NOINPUT, "%s", caPath); + } + struct SplitPath certSplit = splitPath(certPath); struct SplitPath privSplit = splitPath(privPath); FILE *cert = splitOpen(certSplit); FILE *priv = splitOpen(privSplit); - localConfig(cert, priv); + localConfig(cert, priv, localCA, !clientPass); fclose(cert); fclose(priv); @@ -359,6 +368,7 @@ int main(int argc, char *argv[]) { cap_rights_merge(&bindRights, &sockRights); if (saveFile) capLimit(fileno(saveFile), &saveRights); + if (localCA) capLimit(fileno(localCA), &fileRights); capLimitSplit(certSplit, &fileRights); capLimitSplit(privSplit, &fileRights); for (size_t i = 0; i < binds; ++i) { @@ -401,7 +411,7 @@ int main(int argc, char *argv[]) { if (signals[SIGUSR1]) { cert = splitOpen(certSplit); priv = splitOpen(privSplit); - localConfig(cert, priv); + localConfig(cert, priv, localCA, !clientPass); fclose(cert); fclose(priv); signals[SIGUSR1] = 0; diff --git a/bounce.h b/bounce.h index 5bff619..a0f9160 100644 --- a/bounce.h +++ b/bounce.h @@ -127,7 +127,7 @@ void ringInfo(void); int ringSave(FILE *file); void ringLoad(FILE *file); -void localConfig(FILE *cert, FILE *priv); +void localConfig(FILE *cert, FILE *priv, FILE *ca, bool require); size_t localBind(int fds[], size_t cap, const char *host, const char *port); size_t localUnix(int fds[], size_t cap, const char *path); struct tls *localAccept(int *fd, int bind); @@ -139,6 +139,7 @@ void serverSend(const char *ptr, size_t len); void serverFormat(const char *format, ...) __attribute__((format(printf, 1, 2))); +extern bool clientCA; extern char *clientPass; extern char *clientAway; struct Client *clientAlloc(struct tls *tls); diff --git a/client.c b/client.c index a598ec0..fe5eceb 100644 --- a/client.c +++ b/client.c @@ -30,6 +30,7 @@ #include "bounce.h" +bool clientCA; char *clientPass; char *clientAway; @@ -57,6 +58,9 @@ struct Client *clientAlloc(struct tls *tls) { if (!client) err(EX_OSERR, "calloc"); client->tls = tls; client->need = NeedNick | NeedUser | (clientPass ? NeedPass : 0); + if (clientCA && tls_peer_cert_provided(tls)) { + client->need &= ~NeedPass; + } return client; } @@ -160,7 +164,9 @@ static void handlePass(struct Client *client, struct Message *msg) { static void handleCap(struct Client *client, struct Message *msg) { if (!msg->params[0]) msg->params[0] = ""; + enum Cap avail = CapServerTime | CapPassive | (stateCaps & ~CapSASL); + if (clientCA) avail |= CapSASL; if (!strcmp(msg->params[0], "END")) { if (!client->need) return; @@ -192,6 +198,27 @@ static void handleCap(struct Client *client, struct Message *msg) { } } +static void handleAuthenticate(struct Client *client, struct Message *msg) { + if (!msg->params[0]) msg->params[0] = ""; + if (!strcmp(msg->params[0], "EXTERNAL")) { + clientFormat(client, "AUTHENTICATE +\r\n"); + } else if (!strcmp(msg->params[0], "+")) { + clientFormat( + client, ":%s 900 * %s * :You are now logged in as *\r\n", + ORIGIN, stateEcho() + ); + clientFormat( + client, ":%s 903 * :SASL authentication successful\r\n", + ORIGIN + ); + } else { + clientFormat( + client, ":%s 904 * :SASL authentication failed\r\n", + ORIGIN + ); + } +} + static void handleQuit(struct Client *client, struct Message *msg) { (void)msg; clientFormat(client, "ERROR :Detaching\r\n"); @@ -218,6 +245,7 @@ static const struct { Handler *fn; bool need; } Handlers[] = { + { "AUTHENTICATE", handleAuthenticate, false }, { "CAP", handleCap, false }, { "NICK", handleNick, false }, { "NOTICE", handlePrivmsg, true }, diff --git a/local.c b/local.c index c147259..a4de1bc 100644 --- a/local.c +++ b/local.c @@ -47,13 +47,14 @@ static byte *readFile(size_t *len, FILE *file) { byte *buf = malloc(stat.st_size); if (!buf) err(EX_OSERR, "malloc"); + rewind(file); *len = fread(buf, 1, stat.st_size, file); if (ferror(file)) err(EX_IOERR, "fread"); return buf; } -void localConfig(FILE *cert, FILE *priv) { +void localConfig(FILE *cert, FILE *priv, FILE *ca, bool require) { tls_free(server); server = tls_server(); if (!server) errx(EX_SOFTWARE, "tls_server"); @@ -76,6 +77,23 @@ void localConfig(FILE *cert, FILE *priv) { } free(buf); + if (ca) { + buf = readFile(&len, ca); + error = tls_config_set_ca_mem(config, buf, len); + if (error) { + errx( + EX_CONFIG, "tls_config_set_ca_mem: %s", + tls_config_error(config) + ); + } + free(buf); + if (require) { + tls_config_verify_client(config); + } else { + tls_config_verify_client_optional(config); + } + } + error = tls_configure(server, config); if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(server)); tls_config_free(config); diff --git a/pounce.1 b/pounce.1 index 3b7f8e1..3aed409 100644 --- a/pounce.1 +++ b/pounce.1 @@ -1,4 +1,4 @@ -.Dd January 10, 2020 +.Dd January 12, 2020 .Dt POUNCE 1 .Os . @@ -9,6 +9,7 @@ .Sh SYNOPSIS .Nm .Op Fl Nev +.Op Fl A Ar cert .Op Fl C Ar cert .Op Fl H Ar host .Op Fl K Ar priv @@ -68,6 +69,20 @@ following their corresponding flags. The arguments are as follows: . .Bl -tag -width Ds +.It Fl A Ar path , Cm client-ca = Ar path +Load the TLS client certificate authority (CA) from +.Ar path . +If +.Fl W +is unset, +clients must present a certificate signed by the CA +to connect. +If +.Fl W +is also set, +clients may either connect using the password +or a client certificate. +. .It Fl C Ar path , Cm cert = Ar path Load TLS certificate from .Ar path . @@ -132,6 +147,11 @@ The .Ar pass string must be hashed using .Fl x . +If +.Fl A +is also set, +clients may instead connect +using a client certificate. . .It Fl a Ar user : Ns Ar pass , Cm sasl-plain = Ar user : Ns Ar pass Authenticate as -- cgit 1.4.1