BarryServer : Git

All the code for all my projects
// BarryServer : Git / IRCWebHooks / commit / 3f04e2245ef9565e8e9853cac3f2114fbfdd566d

// Related

IRCWebHooks

Barry Adding files 3f04e22 (3 years, 3 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