irced

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 15a29e196cf8195c24b8de5de71044af2b61abc2
Author: aabacchus <ben@bvnf.space>
Date:   Sat, 23 Jul 2022 23:32:33 +0100

init

Diffstat:
A.gitignore | 1+
AMakefile | 9+++++++++
AREADME | 1+
Airced.c | 252+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 263 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +irced diff --git a/Makefile b/Makefile @@ -0,0 +1,9 @@ +.POSIX: + +CFLAGS = -Wall -Wextra -pedantic -Og -g -D_POSIX_C_SOURCE=200809L +LDFLAGS = -static + +all: irced + +clean: + rm -f irced diff --git a/README b/README @@ -0,0 +1 @@ +ed, but like irc diff --git a/irced.c b/irced.c @@ -0,0 +1,252 @@ +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <poll.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#define NUM_CONNS 3 +#define PROGRAM "/bin/ed" + +void +fail(char *s, ...) { + char e[0x400] = {'\0'}; + va_list ap; + fflush(stdout); + + va_start(ap, s); + vsnprintf(e, sizeof(e), s, ap); + va_end(ap); + + syslog(LOG_DAEMON | LOG_ERR, "%s", e); + fprintf(stderr, "irced: %s\n", e); + exit(1); +} + +void +drop_privileges(char *path) { + if (chdir(path) != 0) + fail("chdir(%s) failed: %s", path, strerror(errno)); +#ifdef __OpenBSD__ + if (unveil(path, "rwxc") != 0) + fail("unveil failed: %s", strerror(errno)); +#endif +} + +void sigchld_handler(int s) { + int e = errno; + while (waitpid(-1, NULL, WNOHANG) > 0); + errno = e; +} + +void +usage(char *argv0) { + fprintf(stderr, "usage: %s [-p port] dir\n", argv0); +} + +int +start_listening(char *port) { + int sfd = -1; + struct addrinfo hints = {0}, *result, *rp; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = 0; + + int s = getaddrinfo(NULL, port, &hints, &result); + if (s != 0) { + fail("getaddrinfo: %s", gai_strerror(s)); + return -1; + } + + for (rp = result; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sfd == -1) + continue; + + if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) + break; + + if (close(sfd) == -1) { + fail("close: %s", strerror(errno)); + freeaddrinfo(result); + return -1; + } + } + + freeaddrinfo(result); + + if (rp == NULL) { + fail("localhost:%s: connection refused", port); + return -1; + } + + /* can have up to SOMAXCONN backlogged. */ + if (listen(sfd, 10) == -1) { + fail("listen: %s", strerror(errno)); + close(sfd); + return -1; + } + + return sfd; +} + +int +accept_one(int sfd) { + int newfd; + struct sockaddr their_addr; + socklen_t addr_size = sizeof their_addr; + newfd = accept(sfd, &their_addr, &addr_size); + fprintf(stderr, "accepted %d\n", newfd); + if (newfd == -1) + fail("accept: %s", strerror(errno)); + return newfd; +} + +void +fork_rw(char *cmd, int *wr, int *rd) { + int to[2], from[2]; + pipe(to); + pipe(from); + *wr = to[1]; + *rd = from[0]; + + switch (fork()) { + case -1: + fail("fork failed: %s", strerror(errno)); + break; + case 0: + /* child */ + drop_privileges("."); + close(to[1]); + close(from[0]); + dup2(to[0], 0); + dup2(from[1], 1); + execl(cmd, cmd, 0); + fail("exec %s failed: %s", strerror(errno)); + break; + default: + close(to[0]); + close(from[1]); + break; + } +} + +int +loop(int sfd) { + int pollret, to_ed, from_ed, n; + struct pollfd fds[NUM_CONNS + 2]; /* NUM_CONNS clients + server listening + program. */ + char buf[0x100] = {'\0'}; + ssize_t buf_used; + n = 0; + fds[0].fd = sfd; n++; + fds[0].events = POLLIN; + fork_rw(PROGRAM, &to_ed, &from_ed); + fds[1].fd = from_ed; n++; + fds[1].events = POLLIN; + + while (1) { + for (int i = 0; i < n; i++) + fds[i].revents = 0; + + pollret = poll(fds, n, -1); + if (pollret < 0) { + if (errno == EAGAIN) + continue; + fail("poll: %s", strerror(errno)); + return 1; + } + if (fds[0].revents & POLLIN) { + int newfd = accept_one(sfd); + if (newfd == -1) + continue; + if (n + 1 > NUM_CONNS + 2) { + dprintf(newfd, "sorry, my hands are full!\n(no more connections allowed.)\n"); + shutdown(newfd, SHUT_RDWR); + close(newfd); + continue; + } + + fds[n].fd = newfd; + fds[n].events = POLLIN; + n++; + } + for (int i = 1; i < n; i++) { + if (fds[i].revents & POLLIN) { + buf_used = read(fds[i].fd, buf, sizeof buf); + if (buf_used <= 0) { + /* shutdown */ + close(fds[i].fd); + fds[i].fd = -1; + } else { + fprintf(stderr, "got '"); + write(2, buf, buf_used); + fprintf(stderr, "'\n"); + /* send to ed */ + if (i != 1 && write(to_ed, buf, buf_used) == -1) + fail("couldn't write to ed: %s", strerror(errno)); + /* send to others */ + for (int j = 2; j < n; j++) { + if (i == j) /* don't send to self */ + continue; + /* TODO: prefix messages with user id/name? */ + if (send(fds[j].fd, buf, buf_used, 0) == -1) { + fprintf(stderr, "couldn't write to fd %d: %s\n", fds[j].fd, strerror(errno)); + continue; + } + } + } + } + } + } + return 0; +} + +int +main(int argc, char **argv) { + int c, ret, sfd; + char *path, *port; + ret = 0; + port = "1314"; + + openlog("irced", LOG_NDELAY, LOG_DAEMON); + + while ((c = getopt(argc, argv, "p:")) != -1) { + switch(c) { + case 'p': + port = strdup(optarg); + break; + case '?': + usage(argv[0]); + return 1; + } + } + if (argc - optind != 1) { + usage(argv[0]); + return 1; + } + + path = argv[optind]; + drop_privileges(path); + + sfd = start_listening(port); + if (sfd == -1) { + return 1; + } + fprintf(stderr, "bound to port %s\n", port); + + if (loop(sfd) != 0) + ret = 1; + + close(sfd); + free(port); + return ret; +}