From b897894d81184417f5d88b5bea8ef7f2b7da728d Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Tue, 29 Dec 2009 14:41:25 +0000 Subject: initial commit --- Makefile | 14 +++ irc.c | 85 ++++++++++++++ irc.h | 24 ++++ sircbot.c | 378 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 501 insertions(+) create mode 100644 Makefile create mode 100644 irc.c create mode 100644 irc.h create mode 100644 sircbot.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a6c516a --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ + +CFLAGS ?= -g + +all: sircbot + +sircbot_OBJ = sircbot.o irc.o + +sircbot: $(sircbot_OBJ) + $(CC) $(LDFLAGS) -o $@ $^ + +.c.o: + $(CC) $(CFLAGS) -c $^ + + diff --git a/irc.c b/irc.c new file mode 100644 index 0000000..92a925e --- /dev/null +++ b/irc.c @@ -0,0 +1,85 @@ + +#include +#include + +#include +#include +#include +#include + +#include "irc.h" + +static int tcp_connect(const char *host, int port) +{ + struct sockaddr_in addr; + struct hostent *h; + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) + return sock; + + h = gethostbyname(host); + if (h == NULL) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + memcpy(&addr.sin_addr, h->h_addr, h->h_length); + if (connect(sock, (struct sockaddr *) &addr, + sizeof(struct sockaddr_in))) { + close(sock); + sock = -1; + } + return sock; +} + +struct irc_session *irc_connect(const char* server, int port, const char *nick, + const char *pass) +{ + char buf[256]; + struct irc_session *sess; + + sess = malloc(sizeof(struct irc_session)); + if (sess == NULL) + return NULL; + + sess->nick = nick; + sess->server = server; + sess->fd = tcp_connect(server, port); + if (sess->fd < 0) + return NULL; + + /* login */ + if (pass) + irc_send(sess, "PASS", pass); + irc_send(sess, "NICK", nick); + snprintf(buf, sizeof(buf), "%s localhost %s :%s", nick, server, nick); + irc_send(sess, "USER", buf); + return sess; +} + +int irc_send(struct irc_session *s, const char *command, const char *args) +{ + char buf[4096]; + snprintf(buf, sizeof(buf), "%s %s\r\n", command, args); + return write(s->fd, buf, strlen(buf)); +} + +int irc_send_chan(struct irc_session *s, const char *chan, const char *msg) +{ + char buf[4096]; + snprintf(buf, sizeof(buf), "%s :%s", chan, msg); + return irc_send(s, "PRIVMSG", buf); +} + +int irc_send_ping(struct irc_session *s) +{ + return irc_send(s, "PING", s->server); +} + +int irc_close(struct irc_session *s, const char *msg) +{ + irc_send(s, "QUIT", msg ? msg : ""); + close(s->fd); + free(s); +} diff --git a/irc.h b/irc.h new file mode 100644 index 0000000..e5dd2c8 --- /dev/null +++ b/irc.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) Natanael Copa 2009, 2010 + * GPL-2 + * + */ + +#ifndef IRC_H +#define IRC_H + +struct irc_session { + int fd; + const char *server; + const char *nick; + int last_pong; +}; + +struct irc_session *irc_connect(const char* server, int port, const char *nick, + const char *pass); +int irc_send(struct irc_session *s, const char *command, const char *args); +int irc_send_chan(struct irc_session *s, const char *chan, const char *msg); +int irc_send_ping(struct irc_session *s); +int irc_close(struct irc_session *s, const char *msg); + +#endif /* IRC_H */ diff --git a/sircbot.c b/sircbot.c new file mode 100644 index 0000000..f2d9d76 --- /dev/null +++ b/sircbot.c @@ -0,0 +1,378 @@ +/* Small daemon that connects to IRC server and joins given channel +and sends whatever is written to the FIFO + +Intended usage is git hook that sends commits, etc. +*/ + +#define _GNU_SOURCE + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "irc.h" + +#define PROGNAME "sircbot" + +#if !defined(DEFAULT_PIDFILE) +#define DEFAULT_PIDFILE "/var/run/" PROGNAME "/" PROGNAME ".pid" +#endif + +struct sircbot_channel { + char *name; + char *fifo; + int fd; +}; + +static int foreground = 0; +static int sigterm = 0; + + +static int write_pid(const char *file) +{ + int fd, n; + char tmp[16]; + fd = open(file, O_CREAT | O_WRONLY, 0660); + if (fd < 0) { + perror(file); + return -1; + } + n = snprintf(tmp, sizeof(tmp), "%d\n", getpid()); + if (write(fd, tmp, n) == n) + n = 0; + close(fd); + return n; +} + +int daemonize(const char *pidfile) +{ + int devnull, f; + int pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + /* exit parent */ + if (pid > 0) + exit(0); + + /* detatch to controling terminal */ + setsid(); + + pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + /* exit parent */ + if (pid > 0) + exit(0); + + if (write_pid(pidfile) < 0) + return -1; + /* redirect stdin/stdout/stderr to /dev/null */ + freopen("/dev/null", "r", stdin); + freopen("/dev/null", "w", stdout); + freopen("/dev/null", "w", stderr); + return 0; +} + +static void log_err(const char *msg) +{ + syslog(LOG_ERR, "%s: %s", msg, strerror(errno)); +} + +int init_fifo(struct sircbot_channel *chan) +{ + unlink(chan->fifo); + if (mkfifo(chan->fifo, 0666) < 0) + return -1; + chan->fd = open(chan->fifo, O_RDONLY | O_NONBLOCK); + if (chan->fd < 0) + return -1; + return 0; +} + +int find_channel(struct sircbot_channel *chan, int numchan, char *name) +{ + int i; + for (i = 0; i < numchan; i++) { + if (strcmp(chan[i].name, name) == 0) + return init_fifo(&chan[i]); + } + return 0; +} + +int run_hooks(char *user, char *rcpt, char* data) +{ + int pid = fork(); + int status; + char dir[PATH_MAX]; + if (pid < 0) { + log_err("fork"); + return -1; + } + if (pid == 0) { + /* detatch to controling terminal */ + setsid(); + + pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + /* exit parent */ + if (pid > 0) + exit(0); + + snprintf(dir, sizeof(dir), "/etc/" PROGNAME ".d/%s", rcpt); + printf("DEBUG: running scripts in %s\n", dir); + execlp("/bin/run-parts", "/bin/run-parts", "-a", user, + "-a", data, "-a", rcpt, dir, NULL); + log_err("run-parts"); + exit(1); + } + wait(&status); + return 0; +} + +int handle_response(struct irc_session *sess, char *user, char *cmd, char *data, + struct sircbot_channel *chan, int numchan) +{ + printf("DEBUG: handling response: user=%s, cmd=%s, data=%s\n", + user, cmd, data); + + if (strncmp(cmd, "PING", 4) == 0) { + return irc_send(sess, "PONG", data); + } else if (strncmp(cmd, "PONG", 4) == 0) { + sess->last_pong = time(NULL); + } else if (strncmp(cmd, "JOIN", 4) == 0) { + return find_channel(chan, numchan, data); + } else if (strncmp(cmd, "PRIVMSG", 7) == 0) { + char *p = strchr(data, ' '); + if (p) { + *p++ = '\0'; + if (*p == ':') + p++; + } + return run_hooks(user, data, p); + } + return 0; +} + +int parse_line(struct irc_session *sess, char *line, + struct sircbot_channel *chan, int numchan) +{ + char *user = NULL, *p, *cmd, *data = NULL; + printf("DEBUG: parsing: '%s'\n", line); + + if (line[0] == ':') { + user = &line[1]; + p = strchr(user, ' '); + if (p == NULL) + return -1; + *p++ = '\0'; + cmd = p; + if ((p = strchr(user, '!'))) + *p = '\0'; + } else + cmd = line; + p = strchr(cmd, ' '); + if (p) { + *p++ = '\0'; + data = p; + if (*data == ':') + data++; + } +// printf("DEBUG: user='%s', cmd='%s', data='%s'\n", user, cmd, data); + handle_response(sess, user, cmd, data, chan, numchan); + return 0; +} + +int parse_irc_data(char *buf, struct sircbot_channel *chan, int numchan, + struct irc_session *sess) +{ + char *p = buf; + while ((p = strsep(&buf, "\n")) != NULL) { + char *c = strchr(p, '\r'); + if (c != NULL) + *c = '\0'; + if (*p != '\0') + parse_line(sess, p, chan, numchan); + } + return 0; +} + +static int irc_loop(struct irc_session *sess, struct sircbot_channel *chan, + int numchan) +{ + int i, r, joined = 0; + char buf[1024]; + struct pollfd fds[numchan + 1]; + sigset_t sigmask; + struct timespec tv; + + for (i = 0; i < numchan; i++) + irc_send(sess, "JOIN", chan[i].name); + + sigemptyset(&sigmask); + sigaddset(&sigmask, SIGTERM); + tv.tv_sec = 120; + tv.tv_nsec = 0; + while (!sigterm) { + /* init pollfd strucs */ + fds[0].fd = sess->fd; + fds[0].events = POLLIN; + fds[0].revents = 0; + for (i = 1; i < numchan + 1; i++) { + fds[i].fd = chan[i-1].fd; + fds[i].events = POLLIN; + fds[i].revents = 0; + } + //r = ppoll(fds, numchan+1, &tv, &sigmask); + r = poll(fds, numchan+1, 120000); + if (r < 0) { + log_err("poll"); + return -1; + } else if (r == 0) { + /* timeout */ + irc_send_ping(sess); + continue; + } + for (i = 0; i < numchan + 1; i++) { + int j; + if (!(fds[i].revents & POLLIN)) + continue; + + printf("DEBUG: data available from fds[%i]\n", i); + r = read(fds[i].fd, buf, sizeof(buf)-1); + if (r < 0) + goto ret_err; + + printf("DEBUG: read %i bytes\n", r); + + buf[r] = '\0'; + if (i == 0) { + /* data was from IRC server */ + printf("DEBUG: data from server: %s\n", buf); + parse_irc_data(buf, chan, numchan, sess); + continue; + } + + /* data was from fifos */ + printf("DEBUG: data from fifo %s: %s\n", chan[i-1].name, buf); + r = irc_send_chan(sess, chan[i-1].name, buf); + if (r < 0) + goto ret_err; + } + } + return 0; + +ret_err: + log_err(sess->server); + return -1; +} + +void sighandler(int signal) +{ + switch (signal) { + case SIGTERM: + sigterm = 1; + break; + } +} + +static void usage_exit(int exitcode) +{ + printf("usage: sircbot [-f] [-n nick] [-s server] [-p port] CHANNEL\n"); + exit(exitcode); +} + +int main(int argc, char *argv[]) +{ + const char *pidfile = DEFAULT_PIDFILE; + const char *server = "irc.freenode.org"; + const char *nick = "sircbot"; + const char *user = "sircbot"; + const char *group = "sircbot"; + const char *pass = NULL; + struct sircbot_channel *chan; + int i, c, port = 6667; + + while ((c = getopt(argc, argv, "fn:p:P:s:")) != -1) { + switch (c) { + case 'f': + foreground = 1; + break; + case 'n': + nick = optarg; + break; + case 'P': + pass = optarg; + break; + case 'p': + port = atoi(optarg); + break; + case 's': + server = optarg; + break; + default: + usage_exit(1); + } + } + + argv += optind; + argc -= optind; + if (argc <= 0) + usage_exit(1); + + /* init channel strucs */ + chan = malloc(argc * sizeof(struct sircbot_channel)); + if (chan == NULL) + err(1, "malloc"); + for (i = 0; i < argc; i++) { + chan[i].fd = -1; + chan[i].name = strdup(argv[i]); + asprintf(&chan[i].fifo, "/var/run/sircbot/%s", argv[i]); + unlink(chan[i].fifo); + } + + /* daemonize */ + if (!foreground) { + if (daemonize(pidfile) < 0) + return 1; + } + signal(SIGTERM, sighandler); + openlog("sircbot",0, LOG_DAEMON); + + while (!sigterm) { + struct irc_session *s = irc_connect(server, port, nick, pass); + char buf[256]; + + if (s == NULL) { + log_err(server); + sleep(5); + continue; + } + + irc_loop(s, chan, argc); + irc_close(s, "bye"); + /* reset fifos */ + for (i = 0; i < argc; i++) { + close(chan[i].fd); + chan[i].fd = -1; + unlink(chan[i].fifo); + } + } + unlink(pidfile); + return 0; +} + -- cgit v1.2.3