IRCWebHooks
Barry Adding files 3f04e22 (3 years, 9 months ago)diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..699ecea
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.swp
+*.o
+*.pem
+bot
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..2dc44ad
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,24 @@
+PRODUCT=bot
+
+CC=gcc
+CFLAGS=-lssl -lcrypto -lpthread
+LFLAGS=-lssl -lcrypto -lpthread
+
+SOURCES := $(shell find src/ -name '*.c')
+OBJS = $(sort $(subst src/,build/,$(subst .c,.o,$(SOURCES))))
+
+install: $(PRODUCT)
+ @echo "DONE!"
+
+$(PRODUCT): $(OBJS)
+ @echo "LINKING $(PRODUCT)..."
+ @$(CC) -o $@ $^ $(LFLAGS)
+
+clean:
+ @echo "REMOVING OBJECT FILES..."
+ @touch $(OBJS)
+ @rm $(OBJS)
+
+build/%.o: src/%.c
+ @echo "COMPILING $<..."
+ @$(CC) -c $< -o $@ $(CFLAGS)
diff --git a/README b/README
new file mode 100644
index 0000000..4c1b0b4
--- /dev/null
+++ b/README
@@ -0,0 +1,37 @@
+# IRC Web Hook Bot
+This is a simple IRC bot that posts the content of POST requests it receives.
+
+It runs a small webserver, and connects to an IRC server.
+
+## How to install
+Download the code, then run `make`, `sudo ./webircbot`.
+
+Root access is required to make use of low port numbers, but you can run the webserver on any port.
+
+If you want to change any settings, edit `src/config.h`, then run `make clean install`
+
+## How to use
+By default the bot will join the channels specified in the config.
+To get it to join more channels, private message the bot in the server and send `JOIN #channel`.
+
+You can also use `LEAVE #channel` to get it to leave.
+
+It will by default send notifications to all channels it is joined to.
+
+Only allowed users can control the bot from IRC.
+You can specify users by an ident or a vhost in the config.
+You may specify as many as you like by separating them with a comma
+
+
+
+Only want it to message one channel, or a specific user?
+
+Send the webhook to the channel name, e.g. `https://webhook.barryserver.net/channel`, or `https://webhook.barryserver.net/@Barry`
+
+`@` specified that you want to message a user.
+
+## How to call a webhook
+The bot reads from POST data, specifically the data sent in `content`.
+This content is plaintext, not URL encoded.
+
+So `curl -k -X POST -d 'content=Hello, World!' https://webhook.barryserver.net/@Barry` will send `Hello, World!` directly to the user with the nick `Barry`.
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 0000000..fb8f9ac
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,21 @@
+#ifndef CONFIG_H
+#define CONFIG_H
+
+//#define VERBOSE 1
+
+/* IRC settings */
+#define NICK "webhook"
+#define PASS "password" // Leave undefined if no password
+#define CHAN "#webhooks" // Comma separated list
+#define HOST "irc.rizon.net"
+#define PORT "6697"
+
+/* Admin settings */
+#define ADMIN_IDENT "~Barry" // Comma separated list
+#define ADMIN_VHOST "barryserver.net" // Comma separated list
+
+/* Web server settings */
+#define WEB_PORT 8080
+#define SSL_SERVER 1 // Leave undefined for HTTP
+
+#endif
diff --git a/src/irc.c b/src/irc.c
new file mode 100644
index 0000000..5ab201b
--- /dev/null
+++ b/src/irc.c
@@ -0,0 +1,240 @@
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include "config.h"
+#include "list.h"
+#include "irc.h"
+
+SSL_CTX *ctx;
+SSL *ssl;
+int conn;
+char sbuf[512];
+struct Node *head = NULL;
+struct Node *current = NULL;
+
+/* Auth a user */
+int
+auth(char *ident, char *vhost)
+{
+ char *pt;
+ char *identlist = strdup(ADMIN_IDENT);
+ char *vhostlist = strdup(ADMIN_VHOST);
+
+ pt = strtok(identlist,",");
+ while (pt != NULL) {
+ if (!strncmp(ident, pt, strlen(pt))) return 1;
+ pt = strtok(NULL, ",");
+ }
+
+ pt = strtok(vhostlist,",");
+ while (pt != NULL) {
+ if (!strncmp(vhost, pt, strlen(pt))) return 1;
+ pt = strtok(NULL, ",");
+ }
+
+ return 0;
+}
+
+/* Send data to server */
+void
+raw(char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(sbuf, 512, fmt, ap);
+ va_end(ap);
+#ifdef VERBOSE
+ printf("<< %s", sbuf);
+#endif
+ SSL_write(ssl, sbuf, strlen(sbuf));
+}
+
+/* Bot code */
+void
+bot(void)
+{
+ BIO *certbio = NULL, *outbio = NULL;
+ X509 *cert = NULL;
+ X509_NAME *certname = NULL;
+ const SSL_METHOD *method;
+
+ char *user, *ident, *vhost, *command, *where, *message, *sep, *target;
+ int i, j, l, sl, o = -1, start, wordcount;
+ char buf[513];
+ struct addrinfo hints, *res;
+
+ /* Connect to IRC */
+ printf("Connecting to IRC...\n");
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ getaddrinfo(HOST, PORT, &hints, &res);
+ conn = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ connect(conn, res->ai_addr, res->ai_addrlen);
+
+ /* SSL connection */
+ {
+ OpenSSL_add_all_algorithms();
+ ERR_load_BIO_strings();
+ ERR_load_crypto_strings();
+ SSL_load_error_strings();
+ certbio = BIO_new(BIO_s_file());
+ outbio = BIO_new_fp(stdout, BIO_NOCLOSE);
+ if (SSL_library_init() < 0) {
+ BIO_printf(outbio, "Could not initialise the OpenSSL library!\n");
+ exit(1);
+ }
+ method = SSLv23_client_method();
+ if ((ctx = SSL_CTX_new(method)) == NULL) {
+ BIO_printf(outbio, "Unable to create a new SSL context!\n");
+ exit(1);
+ }
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
+ ssl = SSL_new(ctx);
+ SSL_set_fd(ssl, conn);
+ if (SSL_connect(ssl) != 1) {
+ BIO_printf(outbio, "Error: Could not SSL connect!\n");
+ exit(1);
+ }
+ cert = SSL_get_peer_certificate(ssl);
+ if (cert == NULL) {
+ BIO_printf(outbio, "Error: Could not get certificate!\n");
+ exit(1);
+ }
+ certname = X509_NAME_new();
+ certname = X509_get_subject_name(cert);
+ }
+
+ /* IRC user */
+ raw("USER %s 0 0 :%s\r\n", NICK, NICK);
+ raw("NICK %s\r\n", NICK);
+ printf("Joined IRC!\n");
+
+ /* Add initial channel to list */
+ char *chanlist = strdup(CHAN);
+ char *pt;
+ pt = strtok(chanlist,",");
+ while (pt != NULL) {
+ insert_first(pt);
+ pt = strtok(NULL, ",");
+ }
+
+ /* IRC messages */
+ while (sl = SSL_read(ssl, sbuf, 512))
+ for (i = 0; i < sl; i++) {
+ o++;
+ buf[o] = sbuf[i];
+ if ((i > 0 && sbuf[i] == '\n' && sbuf[i-1] == '\r') || o == 512) {
+ buf[o + 1] = '\0';
+ l = o;
+ o = -1;
+
+#ifdef VERBOSE
+ printf(">> %s", buf);
+#endif
+
+ /* Deal with PINGs */
+ if (!strncmp(buf, "PING", 4)) {
+ buf[1] = 'O';
+ raw(buf);
+ /* Messages */
+ } else if (buf[0] == ':') {
+ wordcount = 0;
+ user = command = where = message = NULL;
+ /* Separate words */
+ for (j = 1; j < l; j++) {
+ if (buf[j] == ' ') {
+ buf[j] = '\0';
+ wordcount++;
+ switch (wordcount) {
+ case 1:
+ user = buf + 1;
+ break;
+ case 2:
+ command = buf + start;
+ break;
+ case 3:
+ where = buf + start;
+ break;
+ }
+ if (j == l - 1)
+ continue;
+ start = j + 1;
+ } else if (buf[j] == ':' && wordcount == 3) {
+ if (j < l - 1)
+ message = buf + j + 1;
+ break;
+ }
+ }
+
+ if (wordcount < 2)
+
+ /* Join a channel */
+ sleep(1);
+ if (!strncmp(command, "001", 3)) {
+#ifdef PASS
+ raw("PRIVMSG NickServ :IDENTIFY %s\r\n", PASS);
+ sleep(1);
+ raw("PRIVMSG HostServ :ON\r\n", PASS);
+ sleep(1);
+#endif
+ raw("JOIN %s\r\n", CHAN);
+ printf("Identified for nick, and joined channel!\n");
+ /* Incoming messages */
+ } else if (!strncmp(command, "PRIVMSG", 7)
+ || !strncmp(command, "NOTICE", 6)) {
+ if (where == NULL || message == NULL)
+ continue;
+ if ((sep = strchr(user, '!')) != NULL) {
+ user[sep - user] = '\0';
+ ident = sep + 1;
+ if ((sep = strchr(ident, '@')) != NULL) {
+ ident[sep - ident] = '\0';
+ vhost = sep + 1;
+ }
+ }
+ if (where[0] == '#' || where[0] == '&' || where[0] == '+' || where[0] == '!')
+ target = where;
+ else
+ target = user;
+
+ if (target == user && !strncmp(message, "JOIN ", 5) && auth(ident, vhost)) {
+ raw("%s\r\n", message);
+ char *p = message+5;
+ while (*p++) if (*p == '\n' || *p == '\r' || *p == ' ') *p = '\0';
+ insert_first(message+5);
+ } else
+ if (target == user && !strncmp(message, "LEAVE ", 6) && auth(ident, vhost)) {
+ raw("PART %s\r\n", message+6);
+ char *p = message+5;
+ while (*p++) if (*p == '\n' || *p == '\r' || *p == ' ') *p = '\0';
+ delete(message+6);
+ }
+
+#ifdef VERBOSE
+ printf(
+ "[from: %s] [reply-with: %s] [where: %s] [reply-to: %s] %s",
+ user, command, where, target, message
+ );
+#endif
+ }
+ }
+ }
+ }
+
+ SSL_free(ssl);
+ close(conn);
+ X509_free(cert);
+ SSL_CTX_free(ctx);
+}
diff --git a/src/irc.h b/src/irc.h
new file mode 100644
index 0000000..2e81da0
--- /dev/null
+++ b/src/irc.h
@@ -0,0 +1,9 @@
+#ifndef IRC_H
+#define IRC_H
+
+extern struct Node *head, *current;
+
+void raw(char *fmt, ...);
+void bot(void);
+
+#endif
diff --git a/src/list.c b/src/list.c
new file mode 100644
index 0000000..54e1cff
--- /dev/null
+++ b/src/list.c
@@ -0,0 +1,89 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "list.h"
+#include "irc.h"
+
+/* Insert first item */
+void
+insert_first(char *data)
+{
+ struct Node *link = (struct Node*) malloc(sizeof(struct Node));
+ strcpy(link->data, data);
+ link->next = head;
+ head = link;
+}
+
+/* Delete the first item */
+struct Node *
+delete_first(void)
+{
+ struct Node *tempLink = head;
+ head = head->next;
+ free(tempLink);
+ return tempLink;
+}
+
+/* Check if list is empty */
+bool
+is_empty(void)
+{
+ return head == NULL;
+}
+
+int
+length(void)
+{
+ int length = 0;
+ struct Node *current;
+ for(current = head; current != NULL; current = current->next) {
+ length++;
+ }
+ return length;
+}
+
+/* Find Node with data */
+struct Node *
+find(char *data)
+{
+ struct Node* current = head;
+ if (head == NULL) {
+ return NULL;
+ }
+ while (strncmp(current->data, data, strlen(data))) {
+ if (current->next == NULL) {
+ return NULL;
+ } else {
+ current = current->next;
+ }
+ }
+ return current;
+}
+
+/* Delete a Node */
+struct Node *
+delete(char *data)
+{
+ struct Node* current = head;
+ struct Node* previous = NULL;
+ if (head == NULL) {
+ return NULL;
+ }
+ while (strncmp(current->data, data, strlen(data))) {
+ if(current->next == NULL) {
+ return NULL;
+ } else {
+ previous = current;
+ current = current->next;
+ }
+ }
+ if (current == head) {
+ head = head->next;
+ } else {
+ previous->next = current->next;
+ }
+ free(current);
+ return current;
+}
diff --git a/src/list.h b/src/list.h
new file mode 100644
index 0000000..413235d
--- /dev/null
+++ b/src/list.h
@@ -0,0 +1,18 @@
+#ifndef LIST_H
+#define LIST_H
+
+#include <stdbool.h>
+
+struct Node {
+ char data[32];
+ struct Node *next;
+};
+
+void insert_first(char *data);
+struct Node *delete_first(void);
+bool is_empty(void);
+int length(void);
+struct Node *find(char *data);
+struct Node *delete(char *data);
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..877fb67
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <pthread.h>
+
+#include "config.h"
+#include "irc.h"
+#include "webserver.h"
+
+int
+main(int argc, char *argv[])
+{
+ pthread_t srvThread, ircThread;
+ pthread_create(&srvThread, NULL, (void *) srv, NULL);
+ pthread_create(&ircThread, NULL, (void *) bot, NULL);
+ pthread_join(srvThread, NULL);
+ pthread_join(ircThread, NULL);
+
+ return 0;
+}
diff --git a/src/webserver.c b/src/webserver.c
new file mode 100644
index 0000000..60ad2e5
--- /dev/null
+++ b/src/webserver.c
@@ -0,0 +1,178 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include "config.h"
+#include "list.h"
+#include "irc.h"
+
+int
+create_socket(int port)
+{
+ int s;
+ struct sockaddr_in addr;
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ if (s < 0) {
+ perror("Unable to create socket");
+ exit(EXIT_FAILURE);
+ }
+
+ if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+ perror("Unable to bind");
+ exit(EXIT_FAILURE);
+ }
+
+ if (listen(s, 1) < 0) {
+ perror("Unable to listen");
+ exit(EXIT_FAILURE);
+ }
+
+ return s;
+}
+
+void
+init_openssl(void)
+{
+ SSL_load_error_strings();
+ OpenSSL_add_ssl_algorithms();
+}
+
+void
+cleanup_openssl(void)
+{
+ EVP_cleanup();
+}
+
+SSL_CTX *
+create_context(void)
+{
+ const SSL_METHOD *method;
+ SSL_CTX *ctx;
+
+ method = SSLv23_server_method();
+
+ ctx = SSL_CTX_new(method);
+ if (!ctx) {
+ perror("Unable to create SSL context");
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ return ctx;
+}
+
+void
+configure_context(SSL_CTX *ctx)
+{
+ SSL_CTX_set_ecdh_auto(ctx, 1);
+
+ /* Set the key and cert */
+ if (SSL_CTX_use_certificate_file(ctx, "cert.pem", SSL_FILETYPE_PEM) <= 0) {
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ if (SSL_CTX_use_PrivateKey_file(ctx, "cert.pem", SSL_FILETYPE_PEM) <= 0 ) {
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+}
+
+int
+srv(int argc, char **argv)
+{
+ printf("Spinning up webserver...\n");
+
+ int sock;
+#ifdef SSL_SERVER
+ SSL_CTX *ctx;
+ init_openssl();
+ ctx = create_context();
+ configure_context(ctx);
+#endif
+
+ sock = create_socket(WEB_PORT);
+
+ /* Handle connections */
+ while(1) {
+ struct sockaddr_in addr;
+ uint len = sizeof(addr);
+#ifdef SSL_SERVER
+ SSL *ssl;
+#endif
+ const char reply[] = "test\r\n";
+
+ int client = accept(sock, (struct sockaddr*)&addr, &len);
+ if (client < 0) {
+ perror("Unable to accept");
+ exit(EXIT_FAILURE);
+ }
+
+#ifdef SSL_SERVER
+ ssl = SSL_new(ctx);
+ SSL_set_fd(ssl, client);
+ SSL_accept(ssl);
+#endif
+
+ char buf[1025];
+ char *p, *uri, *cont;
+ int conlen;
+ char key[64];
+
+#ifdef SSL_SERVER
+ SSL_read(ssl, &buf, 1024);
+#else
+ read(client, &buf, 1024);
+#endif
+
+ if (!strncmp(buf, "POST /", 6)) {
+ p = buf + 5;
+ while (*p++) if (*p == '\n' || *p == '\r' || *p == ' ') *p = '\0';
+ uri = buf + 6;
+ cont = uri + strlen(uri) + 1;
+ if ((p = strstr(cont, "Content-Length: ")) != NULL) {
+ sscanf(p+16, "%i", &conlen);
+ }
+ if ((p = strstr(cont, "content=")) != NULL) {
+ p[conlen] = '\0';
+ p += 8;
+
+ if (*(uri) == '\0') {
+ for (current = head; current != NULL; current = current->next) {
+ raw("PRIVMSG %s :%s\r\n", current->data, p);
+ }
+ } else
+ if (*(uri) == '@') {
+ raw("PRIVMSG %s :%s\r\n", buf+7, p);
+ } else {
+ raw("PRIVMSG #%s :%s\r\n", buf+6, p);
+ }
+ }
+ memset(&buf, 0, 1024);
+ }
+
+#ifdef SSL_SERVER
+ SSL_write(ssl, "HTTP/1.1 200 OK\r\n", 17);
+ SSL_shutdown(ssl);
+ SSL_free(ssl);
+#else
+ write(client, "HTTP/1.1 200 OK\r\n", 17);
+#endif
+ close(client);
+ }
+
+ close(sock);
+#ifdef SSL_SERVER
+ SSL_CTX_free(ctx);
+ cleanup_openssl();
+#endif
+}
diff --git a/src/webserver.h b/src/webserver.h
new file mode 100644
index 0000000..05c8d59
--- /dev/null
+++ b/src/webserver.h
@@ -0,0 +1,6 @@
+#ifndef WEBSERVER_H
+#define WEBSERVER_H
+
+void srv(void);
+
+#endif