commit 09c2e6fa0d0558c82973f0772c4f2fb3c3d7ba5b Author: Thomas Mannay Date: Sun Sep 18 15:31:33 2016 +0100 Initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..809f783 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright © 2016 Thomas Mannay +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..62ba1ed --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +CC=cc +override CFLAGS+=-Wall -Wpedantic -O2 +LDFLAGS=-lcrypto -lssl +PREFIX=/usr/local +MANPREFIX=${PREFIX}/man + +it: + ${CC} ${CFLAGS} ${LDFLAGS} tubes.c -o tubes + +install: it + echo installing executable to ${DESTDIR}${PREFIX}/bin + mkdir -p ${DESTDIR}${PREFIX}/bin + cp -f tubes ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/tubes + echo installing manpage to ${DESTDIR}/${MANPREFIX}/man1 + cp -f tubes.1 ${DESTDIR}/${MANPREFIX}/man1 + chmod 644 ${DESTDIR}/${MANPREFIX}/man1/tubes.1 + +uninstall: + echo removing executable file from ${DESTDIR}${PREFIX}/bin + rm -f ${DESTDIR}${PREFIX}/bin/tubes + echo removing manpage from ${DESTDIR}/${MANPREFIX}/man1 + rm -f ${DESTDIR}/${MANPREFIX}/man1/tubes.1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed8d4a0 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +tubes is a small program that provides an interface to irc bots that follows +the UNIX philosophy. Messages to and from the server are exposed in FIFO +buffers in /tmp named for the server and appended with either .in or .out: +the server writes to in and the bot writes to out. + +Installing +========== + +tubes is configured within tubes.c by changing the values of the server and +port variables at compile time. OpenSSL is a dependency of tubes; download +the devel package from your distro. + +Custom compiler flags can be appended to the defaults, e.g. `CFLAGS=-fPIC`. diff --git a/tubes.1 b/tubes.1 new file mode 100644 index 0000000..5a25167 --- /dev/null +++ b/tubes.1 @@ -0,0 +1,40 @@ +.TH TUBES 1 tubes-1.0.0 +.SH NAME +tubes \- irc pipes +.SH SYNOPSIS +.B tubes +.RB [ \-S ] +.RB [ \-s +.IR server ] +.RB [ \-p +.IR port ] +.RB [ \-v ] +.SH DESCRIPTION +.B tubes +is a small daemon that provides a minimalistic interface for irc bots. +Messages to and from the server are represented by two FIFO buffers in /tmp. +For the default server (freenode) these would be chat.freenode.net.in and +chat.freenode.net.out for incoming and outgoing messages respectively. + +.SH OPTIONS +.TP +.B \-S +Use SSL +.TP +.BI \-s " server" +Use a host other than the default (chat.freenode.net) +.TP +.BI \-p " port" +Use a port other than the default (6667) +.TP +.B \-v +Prints version information and exits + +.SH FILES +The incoming and outgoing FIFO buffers are stored in /tmp and are named for +the server tubes is connected to with .in or .out appended to the end. +~/.tubes.err is used to store a string explaining why tubes has exited, if any. +.SH RETURN VALUE +Returns -1 when something has gone wrong or 0 on success. +.SH AUTHOR +Copyright \(co 2016 Thomas Mannay . diff --git a/tubes.c b/tubes.c new file mode 100644 index 0000000..adf20f8 --- /dev/null +++ b/tubes.c @@ -0,0 +1,242 @@ +/** + * See the LICENSE file for licensing information + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PING_TIMEOUT 240 +#define VERSION "1.0.0" + +static char *server = "chat.freenode.net"; +static char *port = "6667"; +static FILE *log; +static SSL_CTX *ctx; +static SSL *ssl; +static short use_ssl = 0; +static unsigned int last_response; + +/* connect to irc server */ +int dial(char *server, char *port); +/* get SSL up and running */ +int sslify(int *sockfd); +/* set up the error logging file */ +FILE *slog(char *file); +/* mop up so we exit cleanly */ +void mop(void); + +int main(int argc, char **argv) +{ + int sockfd, in, out; + int maxfd; + fd_set rd; + struct timeval tv; + char buf[512]; + int i, r; + + for (i = 1; i < argc; i++) { + char c = argv[i][1]; + if (argv[i][0] != '-' || argv[i][2]) + c = -1; + switch (c) { + case 'S': + use_ssl = 1; + break; + case 's': + if (++i < argc) + server = argv[i]; + break; + case 'p': + if (++i < argc) + port = argv[i]; + break; + case 'v': + fprintf(stderr, "tubes-%s © 2016 Thomas Mannay\n", VERSION); + exit(0); + default: + fprintf(stderr, + "usage: tubes [-S] [-s server] [-p port] [-v]\n"); + exit(0); + } + } + + if ((log = slog(".tubes.err")) == NULL) { + fprintf(stderr, "tubes: error on slog()"); + exit(-1); + } + + if (daemon(0, 0) == -1) { + fprintf(log, "tubes: error on daemon()\n"); + exit(-1); + } + + + if ((sockfd = dial(server, port)) == -1) + exit(-1); + + if (use_ssl && sslify(&sockfd) == -1) + exit(-1); + + snprintf(buf, 512, "/tmp/%s.in", server); + unlink(buf); + mknod(buf, S_IFIFO | 0660, 0); + in = open(buf, O_RDWR | O_NONBLOCK); + snprintf(buf, 512, "/tmp/%s.out", server); + unlink(buf); + mknod(buf, S_IFIFO | 0660, 0); + out = open(buf, O_RDWR); + + atexit(mop); + + for (;;) { + FD_ZERO(&rd); + maxfd = (out >= sockfd) ? out : sockfd; + FD_SET(out, &rd); + FD_SET(sockfd, &rd); + + tv.tv_sec = 10; + tv.tv_usec = 0; + r = select(maxfd+1, &rd, 0, 0, &tv); + if (r < 0) { + if (errno == EINTR) + continue; + fprintf(log, "tubes: error on select()\n"); + exit(-1); + } else if (r == 0) { + if (time(NULL) - last_response >= PING_TIMEOUT) { + fprintf(log, "tubes: ping timeout\n"); + exit(-1); + } + } + if (FD_ISSET(out, &rd)) + if ((i = read(out, buf, sizeof(buf))) > 0) { + buf[i] = 0; + if (use_ssl) + SSL_write(ssl, buf, strlen(buf)); + else + send(sockfd, buf, strlen(buf), 0); + } + if (FD_ISSET(sockfd, &rd)) { + if (use_ssl) { + int blocked; + do { + blocked = 0; + i = SSL_read(ssl, buf, sizeof(buf)); + if (SSL_get_error(ssl, i) + == SSL_ERROR_WANT_READ) + blocked = 1; + } while (SSL_pending(ssl) && !blocked); + } else + i = recv(sockfd, buf, sizeof(buf), 0); + if (i != -1) { + if (i == 0) { + fprintf(log, "tubes: connection closed\n"); + close(sockfd); + if (use_ssl) + SSL_CTX_free(ctx); + exit(0); + } + buf[i] = 0; + if (write(in, buf, strlen(buf)) < 0) { + if (errno == EINTR) + continue; + fprintf(log, "tubes: error on write()\n"); + exit(-1); + } + last_response = time(NULL); + } + } + } + + return 0; +} + +int dial(char *server, char *port) +{ + int sockfd, err; + struct addrinfo hints, *serv; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + if ((err = getaddrinfo(server, port, &hints, &serv)) != 0) { + fprintf(log, "getaddrinfo: %s\n", gai_strerror(err)); + return -1; + } else if ((sockfd = socket(serv->ai_family, serv->ai_socktype, + serv->ai_protocol)) == -1) { + fprintf(log, "tubes: error on socket()\n"); + return -1; + } else if (connect(sockfd, serv->ai_addr, serv->ai_addrlen) == -1) { + fprintf(log, "tubes: error on connect().\n"); + close(sockfd); + return -1; + } + freeaddrinfo(serv); + + return sockfd; +} + +int sslify(int *sockfd) +{ + int r; + + SSL_library_init(); + SSL_load_error_strings(); + + ctx = SSL_CTX_new(SSLv23_client_method()); + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 + | SSL_OP_SINGLE_DH_USE); + ssl = SSL_new(ctx); + SSL_set_fd(ssl, *sockfd); + SSL_set_connect_state(ssl); + + if ((r = SSL_connect(ssl)) < 1) { + fprintf(log, "tubes: %s\n", strerror(SSL_get_error(ssl, r))); + SSL_CTX_free(ctx); + return -1; + } + + return 0; +} + +FILE *slog(char *file) +{ + const char *home = getenv("HOME"); + char path[100]; + FILE *fp; + + snprintf(path, 100, "%s/%s", home, file); + mknod(path, 0 | 0666, 0); + fp = fopen(path, "w"); + + return fp; +} + +void mop(void) +{ + char str[512]; + + ERR_free_strings(); + EVP_cleanup(); + SSL_shutdown(ssl); + SSL_free(ssl); + SSL_CTX_free(ctx); + + snprintf(str, 512, "/tmp/%s.in", server); + unlink(str); + snprintf(str, 512, "/tmp/%s.out", server); + unlink(str); +}