aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNatanael Copa <ncopa@alpinelinux.org>2009-12-29 14:41:25 +0000
committerNatanael Copa <ncopa@alpinelinux.org>2009-12-29 14:41:25 +0000
commitb897894d81184417f5d88b5bea8ef7f2b7da728d (patch)
tree0803f4d3437ad0e439cf5af882b0b49aaad65e20
downloadsircbot-b897894d81184417f5d88b5bea8ef7f2b7da728d.tar.bz2
sircbot-b897894d81184417f5d88b5bea8ef7f2b7da728d.tar.xz
initial commit
-rw-r--r--Makefile14
-rw-r--r--irc.c85
-rw-r--r--irc.h24
-rw-r--r--sircbot.c378
4 files changed, 501 insertions, 0 deletions
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 <sys/socket.h>
+#include <sys/types.h>
+
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <sys/poll.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#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;
+}
+