summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorphoebos <phoebos@tilde.institute>2023-05-27 16:29:03 +0000
committerphoebos <phoebos@tilde.institute>2023-05-27 16:29:03 +0000
commit191856c5a614e9765dfe0308125bf395557bea21 (patch)
tree5809d9553dbb9e886c7061993c822005670eb179
parent6139188d408ef5e107c1afaec1495c42d942d113 (diff)
downloadcbot-191856c5a614e9765dfe0308125bf395557bea21.tar.gz
do irc
and other misc (test, sig handler)
-rw-r--r--receive.c174
1 files changed, 168 insertions, 6 deletions
diff --git a/receive.c b/receive.c
index d1568d3..d85a1ac 100644
--- a/receive.c
+++ b/receive.c
@@ -1,19 +1,30 @@
 #include <err.h>
 #include <errno.h>
+#include <netdb.h>
 #include <poll.h>
+#include <signal.h>
 #include <stdio.h>
 #include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
 #include <sys/socket.h>
+#include <sys/types.h>
 #include <sys/un.h>
 #include <unistd.h>
 
 #define SOCK_PATH "/tmp/kissbot"
+#define IRC_HOST  "irc.libera.chat"
+#define IRC_PORT  "6667"
+#define IRC_NICK  "_kissbot_"
+
+int sock_write(int s, char *msg, ...);
 
 int
 read_print(int s) {
-	char buf[100];
+	char buf[0x1000];
 	ssize_t n;
-	while ((n = recv(s, buf, sizeof buf, 0)) > 0)
+	n = recv(s, buf, sizeof buf, 0);
+	if (n > 0)
 		write(1, buf, n);
 	if (n < 0)
 		perror("recv");
@@ -21,6 +32,52 @@ read_print(int s) {
 }
 
 int
+parse_irc(int s, char *p, size_t len) {
+	/* look for the beginning of lines and handle some commands */
+	fprintf(stderr, "parse_irc got '%s'\n", p);
+	while (1) {
+		char *e = memchr(p, '\n', len);
+		size_t off = len;
+		/* If found a '\n', great; otherwise off = len is the whole rest of
+		 * the line. */
+		if (e != NULL)
+			off = e - p;
+		if (off <= 6)
+			break;
+		if (memcmp(p, "PING", 4) == 0) {
+			p[1] = 'O';
+			char *r = p;
+			if (e == NULL) {
+				r = malloc(len+1);
+				if (r == NULL)
+					err(1, "malloc");
+				strlcpy(r, p, len+1);
+			} else
+				*e = '\0';
+
+			sock_write(s, "%s\n", r);
+
+			if (!e)
+				free(r);
+		}
+		if (e == NULL || len < off + 1)
+			break;
+		len -= off + 1;
+		p = e + 1;
+	}
+	return 0;
+}
+
+int
+handle_irc(int s) {
+	char buf[0x1000];
+	ssize_t n = recv(s, buf, sizeof buf, 0);
+	if (n <= 0)
+		return 1;
+	return parse_irc(s, buf, n);
+}
+
+int
 start_listening(void) {
 	struct sockaddr_un my_addr = {.sun_family = AF_UNIX};
 	int s = socket(AF_UNIX, SOCK_STREAM, 0);
@@ -45,11 +102,75 @@ accept_one(int s) {
 }
 
 int
+sock_write(int s, char *msg, ...) {
+	va_list ap;
+	va_start(ap, msg);
+	if (vdprintf(s, msg, ap) < 0) {
+		perror("sock_write");
+		va_end(ap);
+		return -1;
+	}
+	va_end(ap);
+	return 0;
+}
+
+int
+irc_identify(int s) {
+	return sock_write(s, "NICK %s\r\nUSER %s 8 x :%s\r\n", IRC_NICK, IRC_NICK, IRC_NICK);
+}
+
+int
+connect_irc(void) {
+	int s = -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 e = getaddrinfo(IRC_HOST, IRC_PORT, &hints, &result);
+	if (e != 0)
+		errx(1, "%s", gai_strerror(e));
+
+	for (rp = result; rp != NULL; rp = rp->ai_next) {
+		s = socket(AF_INET, SOCK_STREAM, 0);
+		if (s == -1)
+			continue;
+		if (connect(s, rp->ai_addr, rp->ai_addrlen) == 0)
+			break;
+
+		close(s);
+		s = -1;
+	}
+	freeaddrinfo(result);
+
+	if (s == -1 || irc_identify(s) != 0)
+		errx(1, "couldn't connect to %s:%s", IRC_HOST, IRC_PORT);
+	return s;
+}
+
+void
+cleanup(int sig) {
+	unlink(SOCK_PATH);
+	_exit(1);
+}
+
+#ifndef TEST
+int
 main(void) {
+	struct sigaction act = {
+		.sa_handler = cleanup,
+		.sa_flags = 0,
+		.sa_mask = 0,
+	};
+	if (sigaction(SIGINT, &act, NULL) == -1) {
+		err(1, "sigaction");
+	}
 	struct pollfd fds[3]; /* UNIX socket, IRC socket, client on UNIX socket */
 	fds[0].fd = start_listening();
 	fds[0].events = POLLIN;
-	fds[1].fd = -1;
+	fds[1].fd = connect_irc();
+	fds[1].events = POLLIN ;
 	fds[2].fd = -1;
 
 	while (1) {
@@ -72,17 +193,58 @@ main(void) {
 				fds[2].events = POLLIN;
 			}
 		}
+		if (fds[0].revents & POLLHUP) {
+			close(fds[1].fd);
+			if (fds[2].fd >= 0)
+				close(fds[2].fd);
+			errx(1, "unexpected error, quitting\n");
+		}
 		if (fds[2].revents & POLLIN) {
 			read_print(fds[2].fd);
-			close(fds[2].fd);
-			fds[2].fd = -1;
-			fds[0].events = POLLIN;
 		}
 		if (fds[2].revents & POLLHUP) {
 			close(fds[2].fd);
 			fds[2].fd = -1;
 			fds[0].events = POLLIN;
+			fds[1].events = POLLIN;
+		}
+		if (fds[1].revents & POLLIN) {
+			handle_irc(fds[1].fd);
+		}
+		if (fds[1].revents & POLLHUP) {
+			fprintf(stderr, "IRC disconnected; trying to reconnect\n");
+			close(fds[1].fd);
+			fds[1].fd = connect_irc();
+			/* could exit(3) in connect_irc */
 		}
 	}
 	close(fds[0].fd);
 }
+#else
+int main(void) {
+	struct slen {
+		char *s; size_t len;
+	};
+	struct slen s[] = {
+		{strdup("PING :hi\n"), 9},
+		{strdup("PING :ih"), 8},
+		{strdup(""), 0},
+		{strdup("PING no colon\n"), 14},
+		{strdup("PING :too short len"), 10},
+		{strdup("PING :too long len"), 20},
+	};
+	printf("%s", s[0].s);
+	for (size_t i = 0; i < sizeof(s)/sizeof(s[0]); i++) {
+		parse_irc(1, s[i].s, s[i].len);
+		free(s[i].s);
+		fflush(stdout);
+	}
+
+	char str[100];
+	while (fgets(str, sizeof str, stdin) != NULL) {
+		parse_irc(1, str, strlen(str));
+		fflush(stdout);
+	}
+	return 0;
+}
+#endif