diff options
Diffstat (limited to 'bounce.c')
-rw-r--r-- | bounce.c | 349 |
1 files changed, 139 insertions, 210 deletions
diff --git a/bounce.c b/bounce.c index 381e334..9ab0f1d 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 @@ -25,7 +25,6 @@ * covered work. */ -#include <assert.h> #include <err.h> #include <errno.h> #include <fcntl.h> @@ -44,18 +43,10 @@ #include <sys/stat.h> #include <sys/time.h> #include <sysexits.h> +#include <time.h> #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_OSERR, "unveil"); -} - -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_OSERR, "unveil"); - } -} -#endif /* __OpenBSD__ */ - static size_t parseSize(const char *str) { char *rest; size_t size = strtoull(str, &rest, 0); @@ -206,6 +152,8 @@ int main(int argc, char *argv[]) { const char *genPath = NULL; bool insecure = false; + bool printCert = false; + const char *trust = NULL; const char *clientCert = NULL; const char *clientPriv = NULL; const char *serverBindHost = NULL; @@ -219,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"; @@ -245,11 +194,14 @@ 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 }, { .val = 'q', .name = "quit", required_argument }, { .val = 'r', .name = "real", required_argument }, { .val = 's', .name = "size", required_argument }, + { .val = 't', .name = "trust", required_argument }, { .val = 'u', .name = "user", required_argument }, { .val = 'v', .name = "verbose", no_argument }, { .val = 'w', .name = "pass", required_argument }, @@ -287,42 +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': 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 (blindReq & CapUnsupported) errx(EX_USAGE, "unsupported capability"); if (genPath) genCert(genPath, caPath); 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"); @@ -334,24 +284,27 @@ 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 (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 inet flock unix dns recvfd", NULL); - if (error) err(EX_OSERR, "pledge"); + error = pledge("stdio inet dns", NULL); + if (error) err(EX_OSERR, "pledge"); #endif + serverConfig(true, NULL, NULL, NULL); + serverConnect(serverBindHost, host, port); + serverPrintCert(); + serverClose(); + return EX_OK; + } // Either exit with cleanup or ignore signals until entering the main loop. signal(SIGINT, justExit); @@ -361,78 +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, clientCert, clientPriv); int server = serverConnect(serverBindHost, host, port); -#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); +#ifdef __OpenBSD__ + 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); @@ -441,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); @@ -456,120 +379,120 @@ int main(int argc, char *argv[]) { eventAdd(bind[i], NULL); } eventAdd(server, NULL); + size_t clientIndex = event.len; + enum { + NeedTime = 10, + IdleTime = 15 * 60, + }; for (;;) { - for (size_t i = binds + 1; i < event.len; ++i) { - assert(event.clients[i]); - if (clientDiff(event.clients[i])) { + 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; + needs |= client->need; + if (client->need) continue; + if (ringDiff(client->consumer) || now - client->idle >= IdleTime) { event.fds[i].events |= POLLOUT; - } else { - event.fds[i].events &= ~POLLOUT; } } - int nfds = poll(event.fds, event.len, -1); - if (nfds < 0 && errno != EINTR) err(EX_IOERR, "poll"); + int ready = poll(event.fds, event.len, (needs ? NeedTime * 1000 : -1)); + if (ready < 0 && errno != EINTR) err(EX_IOERR, "poll"); + + if (needs) { + time_t now = time(NULL); + for (size_t i = event.len - 1; i >= clientIndex; --i) { + struct Client *client = event.clients[i]; + if (!client->need) continue; + if (now - client->time < NeedTime) continue; + clientFree(client); + eventRemove(i); + } + } - for (size_t i = event.len - 1; nfds > 0 && i < event.len; --i) { + for (size_t i = event.len - 1; ready > 0 && i < event.len; --i) { short revents = event.fds[i].revents; if (!revents) continue; - if (event.fds[i].fd == server) { + struct Client *client = event.clients[i]; + if (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->remove || revents & (POLLHUP | POLLERR)) { + clientFree(client); + eventRemove(i); + } + } else if (event.fds[i].fd == server) { serverRecv(); - continue; - } - - if (!event.clients[i]) { - int fd; - struct tls *tls = localAccept(&fd, event.fds[i].fd); - if (!tls) { + } else { + struct tls *tls = NULL; + int sock = localAccept(&tls, event.fds[i].fd); + if (sock < 0) { warn("accept"); continue; } - - error = tls_handshake(tls); - if (error) { - warnx("tls_handshake: %s", tls_error(tls)); - tls_free(tls); - close(fd); - } else { - eventAdd(fd, clientAlloc(tls)); - } - continue; - } - - struct Client *client = event.clients[i]; - if (revents & POLLOUT) clientConsume(client); - if (revents & POLLIN) clientRecv(client); - if (clientError(client) || revents & (POLLHUP | POLLERR)) { - clientFree(client); - eventRemove(i); + eventAdd(sock, clientAlloc(sock, tls)); } } - if (signals[SIGINT] || signals[SIGTERM]) break; - + if (clientQuit || signals[SIGINT] || signals[SIGTERM]) { + break; + } if (signals[SIGALRM]) { signals[SIGALRM] = 0; serverDequeue(); } - if (signals[SIGINFO]) { signals[SIGINFO] = 0; ringInfo(); } - if (signals[SIGUSR1]) { signals[SIGUSR1] = 0; - certRead = certFile(&cert); - if (!certRead) { - warn("%s", certPath); - continue; - } - privRead = certFile(&priv); - if (!privRead) { - warn("%s", privPath); - continue; - } - caRead = (caPath ? certFile(&localCA) : NULL); - if (caPath && !caRead) { - warn("%s", 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); - for (size_t i = binds + 1; i < event.len; ++i) { - assert(event.clients[i]); - clientFormat(event.clients[i], ":%s QUIT :%s\r\n", stateEcho(), quit); - clientFormat(event.clients[i], "ERROR :Disconnecting\r\n"); - clientFree(event.clients[i]); - close(event.fds[i].fd); + 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\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)); } @@ -599,8 +522,14 @@ 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 error; +#ifdef __OpenBSD__ + error = pledge("stdio proc exec", NULL); + if (error) err(EX_OSERR, "pledge"); +#endif + int rw[2]; - int error = pipe(rw); + error = pipe(rw); if (error) err(EX_OSERR, "pipe"); pid_t pid = fork(); |