#define _POSIX_C_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SOCK_PATH "/tmp/kissbot" #define IRC_HOST "irc.libera.chat" #define IRC_PORT "6667" #define IRC_NICK "kissbot_" #define IRC_CHANNEL "#test" int sock_write(int s, char *msg, ...); int handle_client(int client, int irc) { char buf[0x1000]; ssize_t n; n = recv(client, buf, (sizeof buf) - 1, 0); if (n < 0) { perror("recv"); return 1; } assert((size_t)n < sizeof buf); char *b = buf; while (1) { char *l = memchr(b, '\n', n - (b - buf)); if (l == NULL) buf[n+1] = '\0'; else *l = '\0'; sock_write(irc, "PRIVMSG " IRC_CHANNEL " :%s\n", b); if (l == NULL) break; else b = l + 1; if (l - buf >= n - 1) /* -1 because the end of the line became \0 */ break; } return 0; } 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 %zu bytes '%s'\n", len, 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[0x1001]; ssize_t n = recv(s, buf, (sizeof buf)-1, 0); if (n <= 0) return 1; buf[n] = '\0'; 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); if (s == -1) err(1, "socket"); unlink(SOCK_PATH); strlcpy(my_addr.sun_path, SOCK_PATH, sizeof(my_addr.sun_path)-1); if (bind(s, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_un)) == -1) err(1, "bind"); if (listen(s, 10) == -1) err(1, "listen"); return s; } int accept_one(int s) { struct sockaddr_un peer_addr; socklen_t slen = sizeof(peer_addr); return accept(s, (struct sockaddr *)&peer_addr, &slen); } 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\nJOIN :%s\r\n", IRC_NICK, IRC_NICK, IRC_NICK, IRC_CHANNEL); } 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 = connect_irc(); fds[1].events = POLLIN ; fds[2].fd = -1; while (1) { fds[0].revents = 0; fds[1].revents = 0; fds[2].revents = 0; if (poll(fds, 3, -1) < 0) { if (errno == EAGAIN) continue; err(1, "poll"); } if ((fds[0].revents & POLLIN) && fds[2].fd == -1) { int s_recv = accept_one(fds[0].fd); if (s_recv != -1) { fds[0].events = 0; /* don't want to accept any more currently */ fds[2].fd = s_recv; 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) { handle_client(fds[2].fd, fds[1].fd); } 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) { close(fds[1].fd); if (fds[2].fd >= 0) close(fds[2].fd); close(fds[0].fd); errx(1, "IRC disconnected; exiting"); } } close(fds[0].fd); return 0; } #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 :first line\nPING :second\nASDF :not a ping\nPING :3rd ping\n"), 62}, }; 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