summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--GNUmakefile62
-rw-r--r--README11
-rw-r--r--bot.b983
-rw-r--r--src/b98.c112
-rw-r--r--src/b98.h8
-rw-r--r--src/bot.c18
-rw-r--r--src/config.h7
-rw-r--r--src/tcp.c65
-rw-r--r--src/tcp.h8
-rw-r--r--src/tls.c165
-rw-r--r--src/tls.h13
12 files changed, 475 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..72b27f0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+src/*.d
+src/*.o
+.*.swp
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..69a7d90
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,62 @@
+# Common
+prefix= /usr/local
+
+CC=clang
+CFLAGS= -Wall -Werror -Wextra
+#CFLAGS+= -std=c99
+CFLAGS+= -std=gnu99
+#CFLAGS+= -Os
+CFLAGS+= -g
+
+LDFLAGS= -lgnutls -lpthread -ldl -rdynamic
+LDFLAGS+= -g
+#LDFLAGS+= -Os
+
+# Target: cbot
+cbot_BIN= cbot
+cbot_OBJ= $(subst .c,.o,$(wildcard src/*.c))
+
+# Targets: plugins
+plugins_SO= $(subst .c,.so,$(wildcard src/plugin/*.c))
+
+# Rules
+all: $(cbot_BIN) $(plugins_SO)
+
+$(cbot_BIN): $(cbot_OBJ)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $(cbot_BIN) $(cbot_OBJ)
+
+%.so: %.c
+ $(CC) -shared -nostartfiles -fPIC $(CFLAGS) $*.c -o $*.so
+ $(CC) -MM $(CFLAGS) $*.c > $*.d
+ @mv -f $*.d $*.d.tmp
+ @sed -e 's|.*:|$*.o:|' < $*.d.tmp > $*.d
+ @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $*.d
+ @rm -f $*.d.tmp
+
+-include $(cbot_OBJ:.o=.d)
+
+%.o: %.c
+ $(CC) -c $(CFLAGS) $*.c -o $*.o
+ $(CC) -MM $(CFLAGS) $*.c > $*.d
+ @mv -f $*.d $*.d.tmp
+ @sed -e 's|.*:|$*.o:|' < $*.d.tmp > $*.d
+ @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $*.d
+ @rm -f $*.d.tmp
+
+clean:
+ $(RM) */*.o */*.d */*/*.o */*/*.d */*/*.so
+ $(RM) $(cbot_BIN) AllTests
+ $(RM) -rf clang-analyzer
+
+check:
+ scan-build -o clang-analyzer make
+ clang -o AllTests $(wildcard tests/*.c)
+ ./AllTests
+
+tags:
+ ctags -o .tags -a $(wildcard src/*.[hc])
+
+.PHONY: all clean install uninstall tags
+
+start:
+ ./cbot
diff --git a/README b/README
new file mode 100644
index 0000000..2a44ea5
--- /dev/null
+++ b/README
@@ -0,0 +1,11 @@
+Dependencies :
+ - Fungi : cabal install fungi
+ - gnutls-devel
+
+Configuring :
+ - irc server and port : src/config.h
+ - password and NICK in bot.b98
+
+Running the bot :
+ - make
+ - ./cbot
diff --git a/bot.b98 b/bot.b98
new file mode 100644
index 0000000..cf3f206
--- /dev/null
+++ b/bot.b98
@@ -0,0 +1,3 @@
+<>>#;>:#,_v;"PASS my_secrets"da"NICK b98"da"USER b98 0 0 b98"da"JOIN #test"da
+ >~'P-#v_~'I-#v_~'N-#v_~'G-#v_ad"uoarg GNOP"ck,v
+ ^ < < < < <
diff --git a/src/b98.c b/src/b98.c
new file mode 100644
index 0000000..2ca408f
--- /dev/null
+++ b/src/b98.c
@@ -0,0 +1,112 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include "b98.h"
+#include "tls.h"
+
+pid_t pid = 0;
+int from_b98[2];
+int to_b98[2];
+
+#define PARENT_READ from_b98[0]
+#define CHILD_WRITE from_b98[1]
+#define CHILD_READ to_b98[0]
+#define PARENT_WRITE to_b98[1]
+
+int bot_init(void)
+{
+ if (pipe (from_b98)) {
+ perror("from_b98 pipe");
+ return 1;
+ }
+ if (pipe (to_b98)) {
+ perror("to_b98 pipe");
+ return 1;
+ }
+ pid = fork ();
+ if (pid == (pid_t) 0) { // Child process
+ close(PARENT_WRITE);
+ close(PARENT_READ);
+
+ dup2(CHILD_READ, STDIN_FILENO); close(CHILD_READ);
+ dup2(CHILD_WRITE, STDOUT_FILENO); close(CHILD_WRITE);
+
+ execvp("fungi", (char *[]) { "fungi", "bot.b98" } );
+ perror("execv");
+ return 1;
+ }
+ else if (pid < (pid_t) 0) {
+ fprintf (stderr, "Fork failed.\n");
+ return 1;
+ }
+ else { // Parent process
+ close(CHILD_READ);
+ close(CHILD_WRITE);
+ return 0;
+ }
+}
+
+int bot_loop(int socket_fd)
+{
+ fd_set rfds;
+ while (1) {
+ struct timeval timeout;
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+
+ FD_ZERO(&rfds);
+ FD_SET(PARENT_READ, &rfds);
+ FD_SET(socket_fd, &rfds);
+ select(20, &rfds, NULL, NULL, &timeout);
+ if ( FD_ISSET(PARENT_READ, &rfds) ) {
+ static char sbuf[512]; /* string buffer */
+ static int n = 0; /* nition in buffer */
+ ssize_t len = n + read(PARENT_READ, sbuf + n, 512 - n);
+ while (n < len) {
+ if ((n > 0 && sbuf[n] == '\n' && sbuf[n - 1] == '\r') ) { /* If we got a full message */
+ tls_send(sbuf, n);
+ sbuf[n - 1] = '\0';
+ printf(">>> %s\n", sbuf);
+ memmove(sbuf, sbuf + n + 1, len - n - 1);
+ len = len - n - 1;
+ if (len == 1)
+ len = 0;
+ n = 0; /* we reinitialise n for the next run */
+ } else if (n == 512) { /* If we got a full buffer without finding a \r\n */
+ fprintf(stderr, "We got a full buffer without finding a \r\n");
+ return 2;
+ } else { /* Nothing to do otherwise but roll the next char */
+ n++;
+ }
+ }
+ } else if ( FD_ISSET(socket_fd, &rfds) ) {
+ tls_read(&bot_handle_input);
+ }
+ }
+}
+
+int bot_handle_input(const char* const sbuf, int len)
+{
+ static char msg[512];
+ static int pos = 0; /* position in msg buffer */
+ for (int n = 0; n < len; n++) {
+ msg[pos] = sbuf[n];
+ if ((n > 0 && sbuf[n] == '\n' && sbuf[n - 1] == '\r') ) { /* If we got a full message */
+ write(PARENT_WRITE, msg, n);
+ msg[pos - 1] = '\0';
+ printf("<<< %s\n", msg);
+ pos = 0; /* we reinitialise pos for the next run */
+ } else if (pos == sizeof(msg)) { /* If we got a full buffer without finding a \r\n */
+ fprintf(stderr, "We got a full buffer without finding a \r\n");
+ return 2;
+ } else { /* Nothing to do otherwise but roll the next char */
+ pos++;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/b98.h b/src/b98.h
new file mode 100644
index 0000000..3414814
--- /dev/null
+++ b/src/b98.h
@@ -0,0 +1,8 @@
+#ifndef _B98_H_
+#define _B98_H_
+
+int bot_init(void);
+int bot_loop(int socket_fd);
+int bot_handle_input(const char* const sbuf, int len);
+
+#endif
diff --git a/src/bot.c b/src/bot.c
new file mode 100644
index 0000000..f18e8a1
--- /dev/null
+++ b/src/bot.c
@@ -0,0 +1,18 @@
+#include "b98.h"
+#include "config.h"
+#include "tls.h"
+
+int main()
+{
+ int err = bot_init();
+ if (err)
+ return err;
+
+ tls_init();
+ int socket_fd = tls_connect(HOST, PORT);
+
+ bot_loop(socket_fd);
+
+ tls_clean();
+ return 0;
+}
diff --git a/src/config.h b/src/config.h
new file mode 100644
index 0000000..ffad107
--- /dev/null
+++ b/src/config.h
@@ -0,0 +1,7 @@
+#ifndef _CONFIG_H_
+#define _CONFIG_H_
+
+#define HOST "example.com"
+#define PORT 6667
+
+#endif
diff --git a/src/tcp.c b/src/tcp.c
new file mode 100644
index 0000000..659a39e
--- /dev/null
+++ b/src/tcp.c
@@ -0,0 +1,65 @@
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tcp.h"
+
+int tcp_connect(const char* const host, const int port)
+{
+ char ip[INET6_ADDRSTRLEN];
+ hostname_to_ip(host , ip);
+ printf("%s resolved to %s\n" , host, ip);
+
+ struct sockaddr_in sa;
+ int sd = socket(AF_INET, SOCK_STREAM, 0);
+ memset(&sa, '\0', sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(port);
+ inet_pton(AF_INET, ip, &sa.sin_addr);
+
+ int err = connect(sd, (struct sockaddr *) &sa, sizeof(sa));
+ if (err < 0) {
+ fprintf(stderr, "Couldn't connect %s:%d : %s\n", host, port, strerror(errno));
+ return -1;
+ }
+
+ return sd;
+}
+
+void tcp_close(const int sd)
+{
+ shutdown(sd, SHUT_RDWR);
+ close(sd);
+}
+
+int hostname_to_ip(const char* const hostname, char *ip)
+{
+ struct addrinfo hints, *servinfo; //, *p;
+ struct sockaddr_in *h;
+ int rv, ret = 0;
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6
+ hints.ai_socktype = SOCK_STREAM;
+
+ if ( (rv = getaddrinfo( hostname , "http" , &hints , &servinfo)) != 0)
+ {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
+ ret = 1;
+ } else {
+ //// loop through all the results and connect to the first we can
+ //for(p = servinfo; p != NULL; p = p->ai_next)
+ //{
+ // h = (struct sockaddr_in *) p->ai_addr;
+ // strcpy(ip , inet_ntoa( h->sin_addr ) );
+ //}
+ h = (struct sockaddr_in *) servinfo->ai_addr;
+ strcpy(ip, inet_ntoa(h->sin_addr));
+ }
+
+ freeaddrinfo(servinfo); // all done with this structure
+ return ret;
+}
diff --git a/src/tcp.h b/src/tcp.h
new file mode 100644
index 0000000..248f941
--- /dev/null
+++ b/src/tcp.h
@@ -0,0 +1,8 @@
+#ifndef _TCP_H_
+#define _TCP_H_
+
+int hostname_to_ip(const char* const hostname, char *ip);
+int tcp_connect(const char* const host, const int port);
+void tcp_close(const int sd);
+
+#endif
diff --git a/src/tls.c b/src/tls.c
new file mode 100644
index 0000000..6b81bd3
--- /dev/null
+++ b/src/tls.c
@@ -0,0 +1,165 @@
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tcp.h"
+#include "tls.h"
+
+int sd = 0;
+gnutls_session_t session;
+gnutls_certificate_credentials_t xcred;
+
+void tls_init(void)
+{
+ gnutls_global_init();
+ gnutls_certificate_allocate_credentials(&xcred);
+ //gnutls_certificate_set_x509_trust_file(xcred, cafile, GNUTLS_X509_FMT_PEM);
+ //gnutls_certificate_set_verify_function(xcred, _verify_certificate_callback);
+ //gnutls_certificate_set_x509_key_file (xcred, "cert.pem", "key.pem", GNUTLS_X509_FMT_PEM);
+ gnutls_init(&session, GNUTLS_CLIENT);
+
+ // Only usefull if accessing a virtual hosting server
+ //gnutls_session_set_ptr(session, (void *) "my_host_name");
+ //gnutls_server_name_set(session, GNUTLS_NAME_DNS, "my_host_name", strlen("my_host_name"));
+
+ gnutls_set_default_priority(session);
+ gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
+}
+
+int tls_connect(const char* const host, const unsigned short port)
+{
+ int ret;
+ sd = tcp_connect(host, port);
+ if (sd < 0) {
+ tls_clean();
+ exit(101);
+ }
+
+ gnutls_transport_set_int(session, sd);
+ gnutls_handshake_set_timeout(session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+
+ /* Perform the TLS handshake */
+ do {
+ ret = gnutls_handshake(session);
+ }
+ while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+
+ if (ret < 0) {
+ fprintf(stderr, "TLS Handshake failed\n");
+ gnutls_perror(ret);
+ tls_clean();
+ exit(102);
+ } else {
+ char *desc;
+
+ desc = gnutls_session_get_desc(session);
+ printf("# TLS Session info: %s\n", desc);
+ gnutls_free(desc);
+ }
+
+ int sf = fcntl (sd, F_GETFL, 0);
+ if (sf == -1) {
+ perror("fcntl get");
+ return -1;
+ }
+ fcntl(sd, F_SETFL, sf | O_NONBLOCK);
+ if (sf == -1) {
+ perror("fcntl set");
+ return -1;
+ }
+
+ return sd;
+}
+
+int tls_read(int (*input_handler_callback)(const char* const sbuf, const int len))
+{
+ char sbuf[512]; /* string buffer */
+ int len = 0; /* len read from the server */
+ len = gnutls_record_recv(session, sbuf, sizeof(sbuf));
+ if (len == 0) {
+ printf("Server has closed the TLS connection\n");
+ tls_clean();
+ return 1;
+ } else if (len < 0 && gnutls_error_is_fatal(len) == 0) {
+ fprintf(stderr, "*** Warning: %s\n", gnutls_strerror(len));
+ return 3;
+ } else if (len < 0) {
+ fprintf(stderr, "*** Error: %s\n", gnutls_strerror(len));
+ tls_clean();
+ return 2;
+ } else {
+ if (input_handler_callback(sbuf, len) != 0) {
+ return 0;
+ }
+ }
+ return 4;
+}
+
+void tls_send(char *buf, int len)
+{
+ gnutls_record_send(session, buf, len);
+}
+
+void tls_close(void) {
+ gnutls_bye(session, GNUTLS_SHUT_RDWR);
+ tls_clean();
+}
+
+void tls_clean(void)
+{
+ tcp_close(sd);
+ gnutls_deinit(session);
+ gnutls_certificate_free_credentials(xcred);
+ gnutls_global_deinit();
+}
+
+/* This function will verify the peer's certificate, and check
+ * if the hostname matches, as well as the activation, expiration dates. */
+//static int _verify_certificate_callback(gnutls_session_t session)
+//{
+// unsigned int status;
+// int ret, type;
+// const char *hostname;
+// gnutls_datum_t out;
+//
+// /* read hostname */
+// hostname = gnutls_session_get_ptr(session);
+//
+// /* This verification function uses the trusted CAs in the credentials
+// * structure. So you must have installed one or more CA certificates. */
+// gnutls_typed_vdata_st data[2];
+//
+// memset(data, 0, sizeof(data));
+//
+// data[0].type = GNUTLS_DT_DNS_HOSTNAME;
+// data[0].data = (void*)hostname;
+//
+// data[1].type = GNUTLS_DT_KEY_PURPOSE_OID;
+// data[1].data = (void*)GNUTLS_KP_TLS_WWW_SERVER;
+//
+// ret = gnutls_certificate_verify_peers(session, data, 2, &status);
+// if (ret < 0) {
+// printf("Error\n");
+// return GNUTLS_E_CERTIFICATE_ERROR;
+// }
+//
+// type = gnutls_certificate_type_get(session);
+//
+// ret = gnutls_certificate_verification_status_print(status, type, &out, 0);
+// if (ret < 0) {
+// printf("Error\n");
+// return GNUTLS_E_CERTIFICATE_ERROR;
+// }
+//
+// printf("%s", out.data);
+//
+// gnutls_free(out.data);
+//
+// if (status != 0) /* Certificate is not trusted */
+// return GNUTLS_E_CERTIFICATE_ERROR;
+//
+// /* notify gnutls to continue handshake normally */
+// return 0;
+//}
diff --git a/src/tls.h b/src/tls.h
new file mode 100644
index 0000000..ff7998d
--- /dev/null
+++ b/src/tls.h
@@ -0,0 +1,13 @@
+#ifndef _TLS_H_
+#define _TLS_H_
+
+#include <gnutls/gnutls.h>
+
+void tls_init(void);
+int tls_connect(const char* const host, const unsigned short port);
+int tls_read(int (*input_handler_callback)(const char* const sbuf, const int len));
+void tls_send(char *buf, int len);
+void tls_close(void);
+void tls_clean(void);
+
+#endif