commit 15a29e196cf8195c24b8de5de71044af2b61abc2
Author: aabacchus <ben@bvnf.space>
Date: Sat, 23 Jul 2022 23:32:33 +0100
init
Diffstat:
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;
+}