diff options
Diffstat (limited to 'bounce.c')
-rw-r--r-- | bounce.c | 323 |
1 files changed, 101 insertions, 222 deletions
diff --git a/bounce.c b/bounce.c index d0bccfc..556c682 100644 --- a/bounce.c +++ b/bounce.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 C. McEnroe <june@causal.agency> +/* Copyright (C) 2019 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -47,15 +47,6 @@ #include <tls.h> #include <unistd.h> -#ifdef __FreeBSD__ -#include <sys/capsicum.h> -#endif - -// For getentropy(2): -#ifdef __APPLE__ -#include <sys/random.h> -#endif - #ifndef SIGINFO #define SIGINFO SIGUSR2 #endif @@ -126,51 +117,6 @@ static void saveLoad(const char *path) { atexit(saveSave); } -#ifdef __FreeBSD__ -static void capLimit(int fd, const cap_rights_t *rights) { - int error = cap_rights_limit(fd, rights); - if (error) err(EX_OSERR, "cap_rights_limit"); -} -#endif - -#ifdef __OpenBSD__ -static void unveilParent(const char *path, const char *mode) { - char buf[PATH_MAX]; - strlcpy(buf, path, sizeof(buf)); - char *base = strrchr(buf, '/'); - if (base) *base = '\0'; - int error = unveil((base ? buf : "."), mode); - if (error && errno != ENOENT) err(EX_NOINPUT, "%s", path); -} - -static void unveilTarget(const char *path, const char *mode) { - char buf[PATH_MAX]; - strlcpy(buf, path, sizeof(buf)); - char *base = strrchr(buf, '/'); - base = (base ? base + 1 : buf); - ssize_t len = readlink(path, base, sizeof(buf) - (base - buf) - 1); - if (len < 0) return; - base[len] = '\0'; - unveilParent(buf, mode); -} - -static void unveilConfig(const char *path) { - const char *dirs = NULL; - for (const char *abs; NULL != (abs = configPath(&dirs, path));) { - unveilParent(abs, "r"); - unveilTarget(abs, "r"); - } -} - -static void unveilData(const char *path) { - const char *dirs = NULL; - for (const char *abs; NULL != (abs = dataPath(&dirs, path));) { - int error = unveil(abs, "rwc"); - if (error && errno != ENOENT) err(EX_CANTCREAT, "%s", abs); - } -} -#endif /* __OpenBSD__ */ - static size_t parseSize(const char *str) { char *rest; size_t size = strtoull(str, &rest, 0); @@ -189,7 +135,7 @@ static struct timeval parseInterval(const char *str) { } static void hashPass(void); -static void genCert(const char *path, const char *ca); +static void genCert(const char *path); int main(int argc, char *argv[]) { int error; @@ -221,6 +167,7 @@ int main(int argc, char *argv[]) { const char *user = NULL; const char *real = NULL; + const char *mode = NULL; const char *join = NULL; const char *quit = "connection reset by purr"; @@ -247,6 +194,7 @@ int main(int argc, char *argv[]) { { .val = 'h', .name = "host", required_argument }, { .val = 'j', .name = "join", required_argument }, { .val = 'k', .name = "client-priv", required_argument }, + { .val = 'm', .name = "mode", required_argument }, { .val = 'n', .name = "nick", required_argument }, { .val = 'o', .name = "print-cert", no_argument }, { .val = 'p', .name = "port", required_argument }, @@ -291,44 +239,40 @@ int main(int argc, char *argv[]) { break; case 'h': host = optarg; break; case 'j': join = optarg; break; case 'k': clientPriv = optarg; + break; case 'm': mode = optarg; break; case 'n': nick = optarg; - break; case 'o': insecure = true; printCert = true; + break; case 'o': printCert = true; break; case 'p': port = optarg; break; case 'q': quit = optarg; break; case 'r': real = optarg; break; case 's': ringSize = parseSize(optarg); break; case 't': trust = optarg; break; case 'u': user = optarg; - break; case 'v': verbose = true; + break; case 'v': verbose = true; setlinebuf(stdout); break; case 'w': pass = optarg; break; case 'x': hashPass(); return EX_OK; break; case 'y': clientAway = optarg; break; default: return EX_USAGE; } } - if (blindReq & CapUnsupported) errx(EX_CONFIG, "unsupported capability"); - if (genPath) genCert(genPath, caPath); + if (blindReq & CapUnsupported) errx(EX_USAGE, "unsupported capability"); + if (genPath) genCert(genPath); if (bindPath[0]) { struct stat st; int error = stat(bindPath, &st); - if (error && errno != ENOENT) err(EX_CANTCREAT, "%s", bindPath); - if (S_ISDIR(st.st_mode)) { + if (error) { + if (errno != ENOENT) err(EX_CANTCREAT, "%s", bindPath); + } else if (S_ISDIR(st.st_mode)) { size_t len = strlen(bindPath); snprintf(&bindPath[len], sizeof(bindPath) - len, "/%s", bindHost); } } if (!certPath[0]) { - snprintf( - certPath, sizeof(certPath), CERTBOT_PATH "/live/%s/fullchain.pem", - bindHost - ); + snprintf(certPath, sizeof(certPath), "%s.pem", bindHost); } if (!privPath[0]) { - snprintf( - privPath, sizeof(privPath), CERTBOT_PATH "/live/%s/privkey.pem", - bindHost - ); + snprintf(privPath, sizeof(privPath), "%s.key", bindHost); } if (!host) errx(EX_USAGE, "host required"); @@ -340,30 +284,25 @@ int main(int argc, char *argv[]) { if (!real) real = nick; if (!clientAway) clientAway = "pounced :3"; if (clientPass && clientPass[0] != '$') { - errx(EX_CONFIG, "password must be hashed with -x"); + errx(EX_USAGE, "password must be hashed with -x"); + } + if (strchr(bindHost, '.')) { + clientOrigin = strdup(bindHost); + if (!clientOrigin) err(EX_OSERR, "strdup"); + } else { + int n = asprintf(&clientOrigin, "%s.", bindHost); + if (n < 0) err(EX_OSERR, "asprintf"); } + if (printCert) { #ifdef __OpenBSD__ - unveilConfig(certPath); - unveilConfig(privPath); - if (caPath) unveilConfig(caPath); - if (trust) unveilConfig(trust); - if (clientCert) unveilConfig(clientCert); - if (clientPriv) unveilConfig(clientPriv); - if (savePath) unveilData(savePath); - if (bindPath[0]) unveilParent(bindPath, "rwc"); - - error = unveil(tls_default_ca_cert_file(), "r"); - if (error) err(EX_OSFILE, "%s", tls_default_ca_cert_file()); - - error = pledge("stdio rpath wpath cpath flock inet dns unix recvfd", NULL); - if (error) err(EX_OSERR, "pledge"); + error = pledge("stdio inet dns", NULL); + if (error) err(EX_OSERR, "pledge"); #endif - - if (printCert) { - serverConfig(insecure, trust, clientCert, clientPriv); + serverConfig(true, NULL, NULL, NULL); serverConnect(serverBindHost, host, port); serverPrintCert(); + serverClose(); return EX_OK; } @@ -375,88 +314,47 @@ int main(int argc, char *argv[]) { ringAlloc(ringSize); if (savePath) saveLoad(savePath); + serverConfig(insecure, trust, clientCert, clientPriv); - struct Cert localCA = { -1, -1, "" }; - if (caPath) { - error = 0; - const char *dirs = NULL; - for (const char *path; NULL != (path = configPath(&dirs, caPath));) { - error = certOpen(&localCA, path); - if (!error) break; +#ifdef __OpenBSD__ + char buf[PATH_MAX]; + const char *paths[] = { certPath, privPath, caPath }; + for (size_t i = 0; i < ARRAY_LEN(paths); ++i) { + if (!paths[i]) continue; + for (int j = 0; configPath(buf, sizeof(buf), paths[i], j); ++j) { + error = unveil(buf, "r"); + if (error && errno != ENOENT) err(EX_NOINPUT, "%s", buf); } - if (error) err(EX_NOINPUT, "%s", caPath); } + error = unveil(tls_default_ca_cert_file(), "r"); + if (error) err(EX_OSFILE, "%s", tls_default_ca_cert_file()); - const char *dirs; - struct Cert cert; - struct Cert priv; - dirs = NULL; - for (const char *path; NULL != (path = configPath(&dirs, certPath));) { - error = certOpen(&cert, path); - if (!error) break; - } - if (error) err(EX_NOINPUT, "%s", certPath); - dirs = NULL; - for (const char *path; NULL != (path = configPath(&dirs, privPath));) { - error = certOpen(&priv, path); - if (!error) break; + if (bindPath[0]) { + error = unveil(bindPath, "c"); + if (error) err(EX_NOINPUT, "%s", bindPath); + error = pledge("stdio rpath inet dns cpath unix recvfd", NULL); + } else { + error = pledge("stdio rpath inet dns", NULL); } - if (error) err(EX_NOINPUT, "%s", privPath); - - FILE *certRead = certFile(&cert); - if (!certRead) err(EX_NOINPUT, "%s", certPath); - FILE *privRead = certFile(&priv); - if (!privRead) err(EX_NOINPUT, "%s", privPath); - FILE *caRead = (caPath ? certFile(&localCA) : NULL); - if (caPath && !caRead) err(EX_NOINPUT, "%s", caPath); + if (error) err(EX_OSERR, "pledge"); +#endif - localConfig(certRead, privRead, caRead, !clientPass); - fclose(certRead); - fclose(privRead); - if (caPath) fclose(caRead); + error = localConfig(certPath, privPath, caPath, !clientPass); + if (error) return EX_NOINPUT; int bind[8]; size_t binds = bindPath[0] ? localUnix(bind, ARRAY_LEN(bind), bindPath) : localBind(bind, ARRAY_LEN(bind), bindHost, bindPort); - - serverConfig(insecure, trust, clientCert, clientPriv); int server = serverConnect(serverBindHost, host, port); #ifdef __OpenBSD__ - char promises[64]; - snprintf( - promises, sizeof(promises), "stdio rpath inet%s", - (bindPath[0] ? " cpath unix recvfd" : "") - ); - error = pledge(promises, NULL); - if (error) err(EX_OSERR, "pledge"); -#endif - -#ifdef __FreeBSD__ - error = cap_enter(); - if (error) err(EX_OSERR, "cap_enter"); - - cap_rights_t saveRights, fileRights, sockRights, bindRights; - cap_rights_init(&saveRights, CAP_WRITE); - cap_rights_init(&fileRights, CAP_FCNTL, CAP_FSTAT, CAP_LOOKUP, CAP_PREAD); - cap_rights_init(&sockRights, CAP_EVENT, CAP_RECV, CAP_SEND, CAP_SETSOCKOPT); - cap_rights_init(&bindRights, CAP_LISTEN, CAP_ACCEPT); - cap_rights_merge(&bindRights, &sockRights); - - if (saveFile) capLimit(fileno(saveFile), &saveRights); - capLimit(cert.parent, &fileRights); - capLimit(cert.target, &fileRights); - capLimit(priv.parent, &fileRights); - capLimit(priv.target, &fileRights); - if (caPath) { - capLimit(localCA.parent, &fileRights); - capLimit(localCA.target, &fileRights); - } - for (size_t i = 0; i < binds; ++i) { - capLimit(bind[i], &bindRights); + if (bindPath[0]) { + error = pledge("stdio rpath cpath unix recvfd", NULL); + } else { + error = pledge("stdio rpath inet", NULL); } - capLimit(server, &sockRights); + if (error) err(EX_OSERR, "pledge"); #endif stateLogin(pass, blindReq, plain, nick, user, real); @@ -465,7 +363,8 @@ int main(int argc, char *argv[]) { while (!stateReady()) serverRecv(); serverFormat("AWAY :%s\r\n", clientAway); - if (join) serverFormat("JOIN :%s\r\n", join); + if (mode) serverFormat("MODE %s %s\r\n", stateNick(), mode); + if (join) serverFormat("JOIN %s\r\n", join); signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); @@ -482,18 +381,24 @@ int main(int argc, char *argv[]) { eventAdd(server, NULL); size_t clientIndex = event.len; + enum { + NeedTime = 10, + IdleTime = 15 * 60, + }; for (;;) { enum Need needs = 0; + time_t now = time(NULL); for (size_t i = clientIndex; i < event.len; ++i) { + struct Client *client = event.clients[i]; event.fds[i].events = POLLIN; - if (clientDiff(event.clients[i])) { + needs |= client->need; + if (client->need) continue; + if (ringDiff(client->consumer) || now - client->idle >= IdleTime) { event.fds[i].events |= POLLOUT; } - needs |= event.clients[i]->need; } - int timeout = 10000; - int ready = poll(event.fds, event.len, (needs ? timeout : -1)); + int ready = poll(event.fds, event.len, (needs ? NeedTime * 1000 : -1)); if (ready < 0 && errno != EINTR) err(EX_IOERR, "poll"); if (needs) { @@ -501,7 +406,7 @@ int main(int argc, char *argv[]) { for (size_t i = event.len - 1; i >= clientIndex; --i) { struct Client *client = event.clients[i]; if (!client->need) continue; - if (now - client->time < timeout / 1000) continue; + if (now - client->time < NeedTime) continue; clientFree(client); eventRemove(i); } @@ -513,9 +418,14 @@ int main(int argc, char *argv[]) { struct Client *client = event.clients[i]; if (client) { - if (revents & POLLOUT) clientConsume(client); + if (revents & POLLOUT) { + clientConsume(client); + if (now - client->idle >= IdleTime) { + clientFormat(client, "PING :%s\r\n", clientOrigin); + } + } if (revents & POLLIN) clientRecv(client); - if (client->error || revents & (POLLHUP | POLLERR)) { + if (client->remove || revents & (POLLHUP | POLLERR)) { clientFree(client); eventRemove(i); } @@ -532,7 +442,7 @@ int main(int argc, char *argv[]) { } } - if (signals[SIGINT] || signals[SIGTERM]) { + if (clientQuit || signals[SIGINT] || signals[SIGTERM]) { break; } if (signals[SIGALRM]) { @@ -545,99 +455,68 @@ int main(int argc, char *argv[]) { } if (signals[SIGUSR1]) { signals[SIGUSR1] = 0; - certRead = certFile(&cert); - privRead = certFile(&priv); - if (caPath) caRead = certFile(&localCA); - if (!certRead) warn("%s", certPath); - if (!privRead) warn("%s", privPath); - if (!caRead && caPath) warn("%s", caPath); - if (!certRead || !privRead || (!caRead && caPath)) continue; - localConfig(certRead, privRead, caRead, !clientPass); - fclose(certRead); - fclose(privRead); - if (caPath) fclose(caRead); + localConfig(certPath, privPath, caPath, !clientPass); } } + if (clientQuit && clientQuit[0]) quit = clientQuit; serverFormat("QUIT :%s\r\n", quit); + serverClose(); for (size_t i = clientIndex; i < event.len; ++i) { struct Client *client = event.clients[i]; if (!client->need) { - clientFormat( - client, ":%s QUIT :%s\r\nERROR :Disconnecting\r\n", - stateEcho(), quit - ); + clientFormat(client, ":%s QUIT :%s\r\n", stateEcho(), quit); + clientFormat(client, "ERROR :Disconnecting\r\n"); } clientFree(client); } + if (bindPath[0]) unlink(bindPath); } #ifdef __OpenBSD__ static void hashPass(void) { + int error = pledge("stdio tty", NULL); + if (error) err(EX_OSERR, "pledge"); char hash[_PASSWORD_LEN]; char *pass = getpass("Password: "); - int error = crypt_newhash(pass, "bcrypt,a", hash, sizeof(hash)); + error = crypt_newhash(pass, "bcrypt,a", hash, sizeof(hash)); if (error) err(EX_OSERR, "crypt_newhash"); printf("%s\n", hash); } #else static void hashPass(void) { byte rand[12]; - int error = getentropy(rand, sizeof(rand)); - if (error) err(EX_OSERR, "getentropy"); - + FILE *file = fopen("/dev/urandom", "r"); + if (!file) err(EX_OSFILE, "/dev/urandom"); + size_t n = fread(rand, sizeof(rand), 1, file); + if (!n) err(EX_IOERR, "/dev/urandom"); + fclose(file); char salt[3 + BASE64_SIZE(sizeof(rand))] = "$6$"; base64(&salt[3], rand, sizeof(rand)); - char *pass = getpass("Password: "); printf("%s\n", crypt(pass, salt)); } #endif -static void genReq(const char *path) { +static void genCert(const char *path) { + int error; + +#ifdef __OpenBSD__ + error = pledge("stdio proc exec", NULL); + if (error) err(EX_OSERR, "pledge"); +#endif + const char *name = strrchr(path, '/'); name = (name ? &name[1] : path); char subj[256]; snprintf(subj, sizeof(subj), "/CN=%.*s", (int)strcspn(name, "."), name); - execlp( - OPENSSL_BIN, "openssl", "req", - "-new", "-newkey", "rsa:4096", "-sha256", "-nodes", - "-subj", subj, "-keyout", path, - NULL - ); - err(EX_UNAVAILABLE, "openssl"); -} - -static void redir(int dst, int src) { - int fd = dup2(src, dst); - if (fd < 0) err(EX_OSERR, "dup2"); - close(src); -} - -static void genCert(const char *path, const char *ca) { - int out = open(path, O_WRONLY | O_APPEND | O_CREAT, 0600); - if (out < 0) err(EX_CANTCREAT, "%s", path); - - int rw[2]; - int error = pipe(rw); - if (error) err(EX_OSERR, "pipe"); - pid_t pid = fork(); - if (pid < 0) err(EX_OSERR, "fork"); - if (!pid) { - close(rw[0]); - redir(STDOUT_FILENO, rw[1]); - genReq(path); - } - - close(rw[1]); - redir(STDIN_FILENO, rw[0]); - redir(STDOUT_FILENO, out); + umask(0066); execlp( - OPENSSL_BIN, "openssl", "x509", - "-req", "-days", "3650", "-CAcreateserial", - (ca ? "-CA" : "-signkey"), (ca ? ca : path), + OPENSSL_BIN, "openssl", "req", + "-x509", "-new", "-newkey", "rsa:4096", "-sha256", "-days", "3650", + "-nodes", "-subj", subj, "-out", path, "-keyout", path, NULL ); - err(EX_UNAVAILABLE, "openssl"); + err(127, "openssl"); } |