From cffeb8a3552d33e7e3de3fb6370881bfe6bcee9e Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Thu, 11 Aug 2016 13:20:39 +0200 Subject: Working bot answering PING, and its C wrapper. --- .gitignore | 3 ++ GNUmakefile | 62 ++++++++++++++++++++++ README | 11 ++++ bot.b98 | 3 ++ src/b98.c | 112 ++++++++++++++++++++++++++++++++++++++++ src/b98.h | 8 +++ src/bot.c | 18 +++++++ src/config.h | 7 +++ src/tcp.c | 65 +++++++++++++++++++++++ src/tcp.h | 8 +++ src/tls.c | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/tls.h | 13 +++++ 12 files changed, 475 insertions(+) create mode 100644 .gitignore create mode 100644 GNUmakefile create mode 100644 README create mode 100644 bot.b98 create mode 100644 src/b98.c create mode 100644 src/b98.h create mode 100644 src/bot.c create mode 100644 src/config.h create mode 100644 src/tcp.c create mode 100644 src/tcp.h create mode 100644 src/tls.c create mode 100644 src/tls.h 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 +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include + +#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 + +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 -- cgit v1.2.3