commit 1e98f8a44d2dda525d94f8945d2344d7f744ae54
parent 17fc6b5c307623fb94a2b5179e5732119a08704f
Author: Andrew Kloet <andrew@kloet.net>
Date: Fri, 3 Apr 2026 01:53:00 -0400
cio release v1.0
This marks the release of cio v1.0, forked from irc.c by Quentin
Carbonneaux.
Notable changes:
- Security: Made TLS default and now verify the server hostname.
- Authentication: Add support for SASL Plain/External.
- scmd():
* Less rigid argument parser into a generic argc/argv.
* Use a switch instead of chaining if-elses.
* Handle a handful of previous unhandled commands
(yo dawg, i herd you like hands).
- Le Correctness:
* Avoid unsafe string manipulation functions.
* Now comes with a standard manual page.
* Adhere closer RFC 2812: case sensitivity, nick collisions,
server-sent JOIN/PART, +more...
* When possible, parse errno into a string and prints.
Diffstat:
| D | .gitignore | | | 3 | --- |
| A | LICENSE | | | 13 | +++++++++++++ |
| M | Makefile | | | 47 | ++++++++++++++++++++++++++++++++++------------- |
| M | README | | | 33 | +++++++++++++++++++++------------ |
| A | TODO | | | 8 | ++++++++ |
| A | cio.1 | | | 168 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| R | irc.c -> cio.c | | | 662 | ++++++++++++++++++++++++++++++++++++++++++++++++------------------------------- |
| A | config.mk | | | 27 | +++++++++++++++++++++++++++ |
8 files changed, 670 insertions(+), 291 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,2 +0,0 @@
-irc
-*.sw[po]
-\ No newline at end of file
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2026 Andrew Kloet <andrew@kloet.net>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
@@ -1,21 +1,42 @@
-BIN = irc
+# cio - minimalist IRC client
+# See LICENSE file for copyright and license details.
-CFLAGS = -std=c99 -Os -D_POSIX_C_SOURCE=201112 -D_GNU_SOURCE -D_XOPEN_CURSES -D_XOPEN_SOURCE_EXTENDED=1 -D_DEFAULT_SOURCE -D_BSD_SOURCE
-LDLIBS = -lncursesw -lssl -lcrypto
+include config.mk
-PREFIX=/usr/local
+SRC = cio.c
+OBJ = ${SRC:.c=.o}
-all: ${BIN}
+all: cio
-install: ${BIN}
- mkdir -p ${PREFIX}/bin
- cp -f ${BIN} ${PREFIX}/bin
- chmod 755 ${PREFIX}/bin/${BIN}
+.c.o:
+ ${CC} -c ${CFLAGS} $<
-uninstall:
- rm -f ${PREFIX}/bin/${BIN}
+{OBJ}: config.mk
+
+cio: ${OBJ}
+ ${CC} -o $@ ${OBJ} ${LDFLAGS}
clean:
- rm -f ${BIN} *.o
+ rm -f cio ${OBJ} cio-${VERSION}.tar.gz
+
+dist: clean
+ mkdir -p cio-${VERSION}
+ cp -R LICENSE TODO README Makefile \
+ config.mk cio.1 ${SRC} cio-${VERSION}
+ tar -cf cio-${VERSION}.tar cio-${VERSION}
+ gzip cio-${VERSION}.tar
+ rm -rf cio-${VERSION}
+
+install: all
+ mkdir -p ${DESTDIR}${PREFIX}/bin
+ cp -f cio ${DESTDIR}${PREFIX}/bin
+ chmod 755 ${DESTDIR}${PREFIX}/bin/cio
+ mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ cp -f cio.1 ${DESTDIR}${MANPREFIX}/man1/cio.1
+ chmod 644 ${DESTDIR}${MANPREFIX}/man1/cio.1
+
+uninstall:
+ rm -f ${DESTDIR}${PREFIX}/bin/cio\
+ ${DESTDIR}${MANPREFIX}/man1/cio.1
-.PHONY: all clean
+.PHONY: all clean dist install uninstall
diff --git a/README b/README
@@ -1,19 +1,28 @@
-IRC client https://c9x.me/irc/
-===========================================================
+cio - chat input/output https://git.kloet.net/cio/
+============================================================
-This is a simple irc client, it requires the ncurses
-library to compile.
+cio is a simple irc client, it requires the ncurses library
+and openssl to compile.
-Usage: irc [OPTIONS]
+cio is meant to be easily hackable; as a result, many IRC
+commands of RFC 2812 do not have an explicit implementation.
+*Most* users should not find this to be a problem as many of
+IRC's commands are purely informative. All unimplemented
+commands are printed to the server console with their
+respective code and message.
- OPTION DEFAULT
+Usage: cio [OPTIONS]
- -n NICK Sets the nick $IRCNICK
- -u USER Sets the username $USER
- -s SERVER Server to connect to irc.oftc.net
- -p PORT Port to connect to 6667
- -l FILE File to log recieved data
- -t Use a secured connection
+ OPTION DEFAULT
+
+ -n NICK Sets the nick $IRCNICK
+ -u USER Sets the username $USER
+ -s SERVER Server to connect to irc.oftc.net
+ -p PORT Port to connect to 6697
+ -l FILE File to log recieved data
+ -c FILE File to load client cert from
+ -T Disable TLS
+ -V Disable TLS hostname check
-h Display help
A password sent to the server can be specified by setting
diff --git a/TODO b/TODO
@@ -0,0 +1,8 @@
+- More RFC 2812 compliance(?).
+ 1. sndf() output should be limited to 512 chars (2.3).
+ 2. strcase{cmp,str} are technically inadequate per (2.2) but who's counting?
+- Might be nice to be able to log the raw IRC packets.
+- It would be really nice to be able to OpenBSD pledge down to tty, stdio after
+ server connect but reconnect needs inet, dns, rpath (for system trust store).
+ 1. rpath can *kind of* be solved by storing all certificate details in memory
+ but loading the entire CA store into memory is a little silly for cio
diff --git a/cio.1 b/cio.1
@@ -0,0 +1,168 @@
+.Dd April 2, 2026
+.Dt CIO 1
+.Os
+.Sh NAME
+.Nm cio
+.Nd a minimal IRC client
+.
+.Sh SYNOPSIS
+.Nm cio
+.Op Fl hvTV
+.Op Fl l Ar logfile
+.Op Fl n Ar nick
+.Op Fl p Ar port
+.Op Fl s Ar server
+.Op Fl u Ar user
+.Op Fl c Ar certfile
+.
+.Sh DESCRIPTION
+.Nm
+is a multiplexing curses interface for IRC featuring SASL authentication,
+infinite scrollback, and automatic reconnection.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl T
+Disable TLS and connect using a plaintext socket.
+.It Fl V
+Disable TLS hostname validation.
+.It Fl l Ar logfile
+Append all IRC activity, including timestamps and channel names, to
+.Ar logfile .
+.It Fl n Ar nick
+Set the IRC nickname.
+If not provided,
+.Nm
+will check the
+.Ev IRCNICK
+environment variable, then the
+.Ev USER
+variable.
+.It Fl p Ar port
+Connect to the server on
+.Ar port .
+Defaults to 6697.
+.It Fl s Ar server
+Connect to the IRC server at
+.Ar server .
+Defaults to irc.oftc.net.
+.It Fl u Ar user
+Set the IRC username (ident).
+Defaults to the value of the
+.Ev USER
+environment variable, or "anonymous".
+.It Fl c Ar certfile
+Use
+.Ar certfile
+as a TLS client certificate.
+This is used for both establishing the encrypted connection and for SASL
+EXTERNAL (CertFP) authentication.
+.El
+.
+.Sh COMMANDS
+.Ss Chat Commands
+Chat commands are submitted with a newline.
+They are not prepended with a '/'.
+Any text not matching a command is sent as a message to the currently
+focused channel.
+.Bl -tag -width Ds
+.It Ic j Ar #channel
+Join a given channel
+.It Ic l Op Ar #channel
+Leave the given channel(s), defaults to current channel.
+.It Ic m Ar recipient message
+Sends a private message.
+.It Ic r Ar message
+Sends a raw IRC message to the server.
+.It Ic q
+Quits
+.Nm .
+.El
+.Ss Interface Commands
+.Bl -tag -width Ds
+.It Ic ^n
+Increases focused window index by 1 (wrapping).
+.It Ic ^p
+Decreases focused window index by 1 (wrapping).
+.It Ic PAGEUP
+Scrolls chat up by 15 lines.
+.It Ic PAGEDOWN
+Scrolls chat down by 15 lines.
+.El
+.Ss Line Editing
+The following key bindings are available when entering text:
+.Bl -tag -width Ds
+.It Ic ^A
+Move cursor to the beginning of the line.
+.It Ic ^E
+Move cursor to the end of the line.
+.It Ic ^B No \&| Ic LEFTARROW
+Move cursor one character to the left.
+.It Ic ^F No \&| Ic RIGHTARROW
+Move cursor one character to the right.
+.It Ic ^K
+Delete from the cursor to the end of the line.
+.It Ic ^U
+Delete from the beginning of the line to the cursor.
+.It Ic ^D
+Delete character under the cursor.
+.It Ic ^H
+Delete character to the left of the cursor.
+.It Ic ^W
+Delete word to the left of the cursor.
+.It Ic BACKSPACE
+Delete character to the left of the cursor.
+.It Ic ENTER
+Submit current line.
+.El
+.
+.Sh ENVIRONMENT
+.Bl -tag -width Ds
+.It Ev USER
+The username to use.
+Overridden by
+.Fl u .
+.It Ev IRCNICK
+The nickname to use.
+Overridden by
+.Fl n .
+.It Ev IRCPASS
+The password to used for SASL PLAIN authentication.
+This is used if no certificate is provided via
+.Fl c .
+.El
+.
+.Sh EXIT STATUS
+.Nm
+exits 0 if requested by the user and >0 if any other error occurs.
+.Sh EXAMPLES
+.Ss Using CertFP
+.Bl -enum
+.It
+Generate a new TLS client certificate:
+.Bd -literal
+$ openssl req -x509 -newkey rsa:4096 -nodes -days 365 \\
+ -keyout user.pem -out user.pem -subj "/CN=CertFP"
+$ chmod 600 user.pem
+.Ed
+.It
+Connect to the server using the certificate:
+.Bd -literal
+$ cio -c user.pem
+.Ed
+.It
+Identify with the server normally then add the certificate fingerprint to
+your account:
+.Bd -literal
+m nickserv cert add
+.Ed
+.El
+.
+.Sh HISTORY
+.Nm
+is a fork of
+.Lk https://c9x.me/irc/ irc.c
+written by
+.An Quentin Carbonneaux Aq Mt qcarbonneaux@gmail.com .
+.
+.Sh AUTHORS
+.An Andrew Kloet Aq Mt andrew@kloet.net
diff --git a/irc.c b/cio.c
@@ -1,4 +1,21 @@
+/* See LICENSE file for copyright and license details.
+ *
+ * cio is driven by a synchronous select(2) loop. It multiplexes stdin, the
+ * network socket, and SIGWINCH for terminal resizing. Each channel's scrollback
+ * is stored in an exponentially-reallocating heap buffer.
+ *
+ * Channels are organized in a fixed-size array. The '0' channel is reserved for
+ * server messages (status), while others are created dynamically upon JOIN or
+ * PRIVMSG. IRC protocol parsing is handled via a string tokenizer, dispatching
+ * commands through a lookup table in scmd().
+ *
+ * UI rendering is handled by ncurses. To support UTF-8, the client manually
+ * decodes runes before passing them to the wide-character add_wch function.
+ *
+ * To understand the lifecycle start reading main().
+ */
#include <assert.h>
+#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
@@ -19,11 +36,12 @@
#include <netinet/tcp.h>
#include <netdb.h>
#include <locale.h>
-#include <wchar.h>
#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
#undef CTRL
-#define CTRL(x) (x & 037)
+#define CTRL(x) (x & 037)
+#define GET_ARG(i) ((argc > (i)) ? argv[i] : "")
#define SCROLL 15
#define INDENT 23
@@ -31,25 +49,46 @@
#define PFMT " %-12s < %s"
#define PFMTHIGH "> %-12s < %s"
#define SRV "irc.oftc.net"
-#define PORT "6667"
+#define PORT "6697"
+
+typedef enum {
+ CMD_UNKNOWN, NOTICE, PRIVMSG, PING, PONG, PART, JOIN, QUIT, NICK, CAP,
+ AUTHENTICATE, RPL_TOPIC, RPL_TOPICWHOTIME, RPL_NAMREPLY, RPL_ENDOFNAMES,
+ ERR_NICKNAMEINUSE, ERR_CHANNELISFULL, ERR_INVITEONLYCHAN, ERR_BADCHANNELKEY,
+ ERR_NOCHANMODES, SASL_OK, SASL_ERR
+} IrcCmd;
+
+struct {
+ const char *name;
+ IrcCmd id;
+} cmd_map[] = {
+ { "NOTICE", NOTICE }, { "PRIVMSG", PRIVMSG }, { "PING", PING },
+ { "PONG", PONG }, { "PART", PART }, { "JOIN", JOIN }, { "QUIT", QUIT },
+ { "NICK", NICK}, { "CAP", CAP }, { "AUTHENTICATE", AUTHENTICATE },
+ { "332", RPL_TOPIC }, { "333", RPL_TOPICWHOTIME }, { "353", RPL_NAMREPLY },
+ { "366", RPL_ENDOFNAMES }, { "477", ERR_NOCHANMODES },
+ { "433", ERR_NICKNAMEINUSE }, { "471", ERR_CHANNELISFULL },
+ { "473", ERR_INVITEONLYCHAN }, { "475", ERR_BADCHANNELKEY },
+ { "903", SASL_OK }, { "904", SASL_ERR }
+};
enum {
- ChanLen = 64,
- LineLen = 512,
- MaxChans = 16,
- BufSz = 2048,
- LogSz = 4096,
- MaxRecons = 10, /* -1 for infinitely many */
- PingDelay = 6,
- UtfSz = 4,
- RuneInvalid = 0xFFFD,
+ ChanLen = 64,
+ LineLen = 512,
+ MaxChans = 16,
+ MaxParams = 15,
+ BufSz = 2048,
+ LogSz = 4096,
+ MaxRecons = 10, /* -1 for infinitely many */
+ PingDelay = 6,
+ UtfSz = 4,
+ RuneInvalid = 0xFFFD,
};
typedef wchar_t Rune;
static struct {
- int x;
- int y;
+ size_t x, y;
WINDOW *sw, *mw, *iw;
} scr;
@@ -63,33 +102,43 @@ static struct Chan {
char join; /* Channel was 'j'-oined. */
} chl[MaxChans];
-static int ssl;
+static int ssl = 1;
+static int sslverify = 1;
static struct {
int fd;
SSL *ssl;
SSL_CTX *ctx;
} srv;
static char nick[64];
+static char cert[PATH_MAX];
+static char key[512];
static int quit, winchg;
static int nch, ch; /* Current number of channels, and current channel. */
static char outb[BufSz], *outp = outb; /* Output buffer. */
static FILE *logfp;
-static unsigned char utfbyte[UtfSz + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
-static unsigned char utfmask[UtfSz + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
-static Rune utfmin[UtfSz + 1] = { 0, 0, 0x80, 0x800, 0x10000};
-static Rune utfmax[UtfSz + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
+static const unsigned char utfbyte[UtfSz + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
+static const unsigned char utfmask[UtfSz + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
+static const Rune utfmin[UtfSz + 1] = {0, 0, 0x80, 0x800, 0x10000};
+static const Rune utfmax[UtfSz + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
-static void scmd(char *, char *, char *, char *);
+static void scmd(char *, char *, int, char **);
static void tdrawbar(void);
static void tredraw(void);
static void treset(void);
static void
-panic(const char *m)
+die(const char *fmt, ...)
{
+ va_list ap;
+
treset();
- fprintf(stderr, "Panic: %s\n", m);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':')
+ fprintf(stderr, " %s", strerror(errno));
+ fputc('\n', stderr);
exit(1);
}
@@ -113,7 +162,7 @@ utf8decodebyte(unsigned char c, size_t *i)
}
static size_t
-utf8decode(char *c, Rune *u, size_t clen)
+utf8decode(const char *c, Rune *u, size_t clen)
{
size_t i, j, len, type;
Rune udecoded;
@@ -136,26 +185,30 @@ utf8decode(char *c, Rune *u, size_t clen)
return len;
}
-static char
-utf8encodebyte(Rune u, size_t i)
-{
- return utfbyte[i] | (u & ~utfmask[i]);
+static int
+empty(const char *str) {
+ return (str == NULL || str[0] == '\0');
}
-static size_t
-utf8encode(Rune u, char *c)
+static char *
+b64_enc(const unsigned char *in, size_t len)
{
- size_t len, i;
+ static char out[BufSz];
+ const char *tab =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ char *p = out;
+ size_t i;
- len = utf8validate(&u, 0);
- if (len > UtfSz)
- return 0;
- for (i = len - 1; i != 0; --i) {
- c[i] = utf8encodebyte(u, 0);
- u >>= 6;
+ for (i = 0; i < len; i += 3) {
+ unsigned int v =
+ in[i] << 16 | (i+1 < len ? in[i+1] << 8 : 0) | (i+2 < len ? in[i+2] : 0);
+ *p++ = tab[(v >> 18) & 0x3F];
+ *p++ = tab[(v >> 12) & 0x3F];
+ *p++ = (i+1 < len) ? tab[(v >> 6) & 0x3F] : '=';
+ *p++ = (i+2 < len) ? tab[v & 0x3F] : '=';
}
- c[0] = utf8encodebyte(u, len);
- return len;
+ *p = '\0';
+ return out;
}
static void
@@ -178,8 +231,8 @@ static int
srd(void)
{
static char l[BufSz], *p = l;
- char *s, *usr, *cmd, *par, *data;
- int rd;
+ char *s, *usr, *cmd, *argv[MaxParams];
+ int rd, argc;
if (p - l >= BufSz)
p = l; /* Input buffer overflow, there should something better to do. */
@@ -187,30 +240,36 @@ srd(void)
rd = SSL_read(srv.ssl, p, BufSz - (p - l));
else
rd = read(srv.fd, p, BufSz - (p - l));
- if (rd <= 0)
- return 0;
+ if (rd <= 0) return 0;
p += rd;
for (;;) { /* Cycle on all received lines. */
if (!(s = memchr(l, '\n', p - l)))
return 1;
- if (s > l && s[-1] == '\r')
- s[-1] = 0;
+ if (s > l && s[-1] == '\r') s[-1] = 0;
*s++ = 0;
- if (*l == ':') {
- if (!(cmd = strchr(l, ' ')))
- goto lskip;
- *cmd++ = 0;
- usr = l + 1;
- } else {
- usr = 0;
- cmd = l;
+ char *tok = l;
+ usr = NULL;
+ if (*tok == ':') {
+ usr = tok + 1;
+ if (!(tok = strchr(tok, ' '))) goto lskip;
+ *tok++ = 0;
}
- if (!(par = strchr(cmd, ' ')))
- goto lskip;
- *par++ = 0;
- if ((data = strchr(par, ':')))
- *data++ = 0;
- scmd(usr, cmd, par, data);
+ while (*tok == ' ') tok++;
+ cmd = tok;
+ if ((tok = strchr(tok, ' ')))
+ *tok++ = 0;
+ argc = 0;
+ while (tok && *tok && argc < MaxParams) {
+ while (*tok == ' ') tok++;
+ if (*tok == ':') {
+ argv[argc++] = tok + 1;
+ break;
+ }
+ argv[argc++] = tok;
+ if ((tok = strchr(tok, ' ')))
+ *tok++ = 0;
+ }
+ if (cmd) scmd(usr, cmd, argc, argv);
lskip:
memmove(l, s, p - s);
p -= s - l;
@@ -218,70 +277,82 @@ srd(void)
}
static void
-sinit(const char *key, const char *nick, const char *user)
+sinit(const char *nick, const char *user)
{
- if (key)
- sndf("PASS %s", key);
+ sndf("CAP LS 302");
sndf("NICK %s", nick);
sndf("USER %s 8 * :%s", user, user);
- sndf("MODE %s +i", nick);
}
static char *
dial(const char *host, const char *service)
{
struct addrinfo hints, *res = NULL, *rp;
- int fd = -1, e;
+ int fd = -1;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */
hints.ai_flags = AI_NUMERICSERV; /* avoid name lookup for port */
hints.ai_socktype = SOCK_STREAM;
- if ((e = getaddrinfo(host, service, &hints, &res)))
- return "Getaddrinfo failed.";
+ if (getaddrinfo(host, service, &hints, &res))
+ return "getaddrinfo failed";
for (rp = res; rp; rp = rp->ai_next) {
- if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1)
- continue;
- if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) {
- close(fd);
+ fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (fd == -1)
continue;
- }
- break;
+ if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0)
+ break;
+ close(fd);
+ fd = -1;
}
- if (fd == -1)
- return "Cannot connect to host.";
- srv.fd = fd;
+ freeaddrinfo(res);
+ if ((srv.fd = fd) == -1)
+ return "cannot connect to host";
if (ssl) {
SSL_load_error_strings();
SSL_library_init();
- srv.ctx = SSL_CTX_new(SSLv23_client_method());
- if (!srv.ctx)
- return "Could not initialize ssl context.";
+ srv.ctx = SSL_CTX_new(TLS_client_method());
+ if (!srv.ctx) return "could not initialize ssl context";
+ if (sslverify) {
+ SSL_CTX_set_verify(srv.ctx, SSL_VERIFY_PEER, NULL);
+ if (SSL_CTX_set_default_verify_paths(srv.ctx) != 1)
+ return "could not load default system trust store";
+ }
srv.ssl = SSL_new(srv.ctx);
- if (SSL_set_fd(srv.ssl, srv.fd) == 0
- || SSL_connect(srv.ssl) != 1)
- return "Could not connect with ssl.";
+ if (!srv.ssl) return "could not create SSL object";
+ if (sslverify) {
+ SSL_set_hostflags(srv.ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
+ if (!SSL_set1_host(srv.ssl, host))
+ return "could not set expected hostname";
+ SSL_set_tlsext_host_name(srv.ssl, host);
+ } else {
+ SSL_CTX_set_verify(srv.ctx, SSL_VERIFY_NONE, NULL);
+ }
+ if (!empty(cert)) {
+ if (SSL_use_certificate_chain_file(srv.ssl, cert) <= 0)
+ return "failed to load certificate chain";
+ if (SSL_use_PrivateKey_file(srv.ssl, cert, SSL_FILETYPE_PEM) <= 0)
+ return "failed to load private key";
+ }
+ SSL_set_fd(srv.ssl, srv.fd);
+ if (SSL_connect(srv.ssl) <= 0) {
+ long verify_err = SSL_get_verify_result(srv.ssl);
+ if (verify_err != X509_V_OK)
+ return (char *)X509_verify_cert_error_string(verify_err);
+ return "SSL handshake failed";
+ }
}
- freeaddrinfo(res);
return 0;
}
static void
hangup(void)
{
- if (srv.ssl) {
- SSL_shutdown(srv.ssl);
- SSL_free(srv.ssl);
- srv.ssl = 0;
- }
- if (srv.fd) {
- close(srv.fd);
- srv.fd = 0;
- }
- if (srv.ctx) {
- SSL_CTX_free(srv.ctx);
- srv.ctx = 0;
- }
+ if (srv.ssl) SSL_shutdown(srv.ssl);
+ SSL_free(srv.ssl);
+ if (srv.fd > 0) close(srv.fd);
+ SSL_CTX_free(srv.ctx);
+ memset(&srv, 0, sizeof(srv));
}
static inline int
@@ -291,7 +362,7 @@ chfind(const char *name)
assert(name);
for (i = nch - 1; i > 0; i--)
- if (!strcmp(chl[i].name, name))
+ if (!strcasecmp(chl[i].name, name))
break;
return i;
}
@@ -305,11 +376,11 @@ chadd(const char *name, int joined)
return -1;
if ((n = chfind(name)) > 0)
return n;
- strcpy(chl[nch].name, name);
+ strlcpy(chl[nch].name, name, sizeof(chl[nch].name));
chl[nch].sz = LogSz;
chl[nch].buf = malloc(LogSz);
if (!chl[nch].buf)
- panic("Out of memory.");
+ die("cio: malloc:");
chl[nch].eol = chl[nch].buf;
chl[nch].n = 0;
chl[nch].join = joined;
@@ -317,20 +388,21 @@ chadd(const char *name, int joined)
ch = nch;
nch++;
tdrawbar();
- return nch;
+ return nch - 1;
}
static int
-chdel(char *name)
+chdel(const char *name)
{
- int n;
+ int n = chfind(name);
+ if (n <= 0) return 0;
+ free(chl[n].buf);
- if (!(n = chfind(name)))
- return 0;
+ if (n < nch - 1)
+ memmove(&chl[n], &chl[n + 1], (nch - n - 1) * sizeof(struct Chan));
nch--;
- free(chl[n].buf);
- memmove(&chl[n], &chl[n + 1], (nch - n) * sizeof(struct Chan));
- ch = nch - 1;
+ if (ch > n || ch >= nch)
+ ch = (ch > 0) ? ch - 1 : 0;
tdrawbar();
return 1;
}
@@ -338,7 +410,7 @@ chdel(char *name)
static char *
pushl(char *p, char *e)
{
- int x, cl;
+ size_t x;
char *w;
Rune u[2];
cchar_t cc;
@@ -357,7 +429,7 @@ pushl(char *p, char *e)
w++;
x += p - w;
}
- if (p >= e || *p == ' ' || p - w + INDENT >= scr.x - 1) {
+ if (p >= e || *p == ' ' || p - w + INDENT >= (ptrdiff_t)scr.x - 1) {
while (w < p) {
w += utf8decode(w, u, UtfSz);
if (wcwidth(*u) > 0 || *u == '\n') {
@@ -365,12 +437,11 @@ pushl(char *p, char *e)
wadd_wch(scr.mw, &cc);
}
}
- if (p >= e)
- return e;
+ if (p >= e) return e;
}
p += utf8decode(p, u, UtfSz);
- if ((cl = wcwidth(*u)) >= 0)
- x += cl;
+ int cl = wcwidth(*u);
+ if (cl >= 0) x += cl;
}
}
@@ -382,27 +453,26 @@ pushf(int cn, const char *fmt, ...)
va_list vl;
time_t t;
char *s;
- struct tm *tm, *gmtm;
+ const struct tm *tm, *gmtm;
if (blen + LineLen >= c->sz) {
c->sz *= 2;
c->buf = realloc(c->buf, c->sz);
if (!c->buf)
- panic("Out of memory.");
+ die("cio: realloc:");
c->eol = c->buf + blen;
}
t = time(0);
if (!(tm = localtime(&t)))
- panic("Localtime failed.");
+ die("cio: localtime failed");
n = strftime(c->eol, LineLen, DATEFMT, tm);
if (!(gmtm = gmtime(&t)))
- panic("Gmtime failed.");
+ die("cio: gmtime failed");
c->eol[n++] = ' ';
va_start(vl, fmt);
s = c->eol + n;
n += vsnprintf(s, LineLen - n - 1, fmt, vl);
va_end(vl);
-
if (logfp) {
fprintf(logfp, "%-12.12s\t%04d-%02d-%02dT%02d:%02d:%02dZ\t%s\n",
c->name,
@@ -410,12 +480,8 @@ pushf(int cn, const char *fmt, ...)
gmtm->tm_hour, gmtm->tm_min, gmtm->tm_sec, s);
fflush(logfp);
}
-
- strcat(c->eol, "\n");
- if (n >= LineLen - 1)
- c->eol += LineLen - 1;
- else
- c->eol += n + 1;
+ strlcat(c->buf, "\n", c->sz);
+ c->eol = c->buf + strlen(c->buf);
if (cn == ch && c->n == 0) {
char *p = c->eol - n - 1;
@@ -426,75 +492,156 @@ pushf(int cn, const char *fmt, ...)
}
}
+static IrcCmd get_cmd_type(const char *cmd) {
+ for (size_t i = 0; i < sizeof(cmd_map)/sizeof(cmd_map[0]); i++)
+ if (!strcmp(cmd, cmd_map[i].name)) return cmd_map[i].id;
+ return CMD_UNKNOWN;
+}
+
static void
-scmd(char *usr, char *cmd, char *par, char *data)
+scmd(char *usr, char *cmd, int argc, char **argv)
{
- int s, c;
- char *pm = strtok(par, " "), *chan;
+ IrcCmd type = get_cmd_type(cmd);
+ int c = 0;
- if (!usr)
- usr = "?";
+ if (!usr) usr = "?";
else {
char *bang = strchr(usr, '!');
- if (bang)
- *bang = 0;
+ if (bang) *bang = '\0';
}
- if (!strcmp(cmd, "PRIVMSG")) {
- if (!pm || !data)
- return;
- if (strchr("&#!+.~", pm[0]))
- chan = pm;
- else
- chan = usr;
- if (!(c = chfind(chan))) {
- if (chadd(chan, 0) < 0)
- return;
- tredraw();
+ switch (type) {
+ case NOTICE:
+ if (argc < 2) break;
+ c = strchr("&#!+.~", argv[0][0]) ? chfind(argv[0]) : 0;
+ if (c < 0) c = 0;
+ pushf(c, PFMT, usr, argv[1]);
+ if (ch != c) {
+ chl[c].new = 1;
+ tdrawbar();
}
- c = chfind(chan);
- if (strcasestr(data, nick)) {
- pushf(c, PFMTHIGH, usr, data);
- chl[c].high |= ch != c;
+ break;
+ case PRIVMSG:
+ if (argc < 2) break;
+ char *chan = strchr("&#!+.~", argv[0][0]) ? argv[0] : (usr ? usr : "server");
+ if (!(c = chfind(chan)) && (c = chadd(chan, 0)) < 0)
+ break;
+ if (strcasestr(argv[1], nick)) {
+ pushf(c, PFMTHIGH, usr, argv[1]);
+ chl[c].high |= (ch != c);
} else
- pushf(c, PFMT, usr, data);
+ pushf(c, PFMT, usr, argv[1]);
if (ch != c) {
chl[c].new = 1;
tdrawbar();
}
- } else if (!strcmp(cmd, "PING")) {
- sndf("PONG :%s", data ? data : "(null)");
- } else if (!strcmp(cmd, "PONG")) {
- /* nothing */
- } else if (!strcmp(cmd, "PART")) {
- if (!pm)
- return;
- pushf(chfind(pm), "-!- %s has left %s", usr, pm);
- } else if (!strcmp(cmd, "JOIN")) {
- if (!pm)
- return;
- pushf(chfind(pm), "-!- %s has joined %s", usr, pm);
- } else if (!strcmp(cmd, "470")) { /* Channel forwarding. */
- char *ch = strtok(0, " "), *fch = strtok(0, " ");
-
- if (!ch || !fch || !(s = chfind(ch)))
- return;
- chl[s].name[0] = 0;
- strncat(chl[s].name, fch, ChanLen - 1);
- tdrawbar();
- } else if (!strcmp(cmd, "471") || !strcmp(cmd, "473")
- || !strcmp(cmd, "474") || !strcmp(cmd, "475")) { /* Join error. */
- if ((pm = strtok(0, " "))) {
- chdel(pm);
- pushf(0, "-!- Cannot join channel %s (%s)", pm, cmd);
+ break;
+ case PING:
+ if (argc >= 2)
+ sndf("PONG %s :%s", argv[0], argv[1]);
+ else if (argc == 1)
+ sndf("PONG :%s", argv[0]);
+ else
+ sndf("PONG :%s", chl[0]);
+ break;
+ case PONG:
+ break;
+ case JOIN:
+ if (argc < 1 || !usr) break;
+ c = chfind(argv[0]);
+ if (!strcasecmp(usr, nick)) {
+ if (c <= 0)
+ c = chadd(argv[0], 1);
+ if (c > 0) {
+ pushf(c, "-!- You have joined %s", argv[0]);
+ tdrawbar();
+ tredraw();
+ }
+ } else if (c > 0) {
+ pushf(c, "-!- %s has joined %s", usr, argv[0]);
+ }
+ break;
+ case PART:
+ if (argc < 1 || !usr) break;
+ c = chfind(argv[0]);
+ if (c <= 0) break;
+ if (!strcasecmp(usr, nick)) {
+ pushf(0, "-!- You have left %s", argv[0]);
+ chdel(argv[0]);
tredraw();
+ } else {
+ pushf(c, "-!- %s has left %s %s", usr, argv[0], GET_ARG(1));
}
- } else if (!strcmp(cmd, "QUIT")) { /* Commands we don't care about. */
- return;
- } else if (!strcmp(cmd, "NOTICE") || !strcmp(cmd, "375")
- || !strcmp(cmd, "372") || !strcmp(cmd, "376")) {
- pushf(0, "%s", data ? data : "");
- } else
- pushf(0, "%s - %s %s", cmd, par, data ? data : "(null)");
+ break;
+ case QUIT:
+ break;
+ case NICK:
+ if (argc < 1) break;
+ if (!strcasecmp(usr, nick)) {
+ strlcpy(nick, argv[0], sizeof(nick));
+ pushf(0, "-!- You are now known as %s", nick);
+ } else
+ pushf(0, "-!- %s is now known as %s", usr, argv[0]);
+ break;
+ case RPL_TOPIC:
+ if (argc >= 3) pushf(chfind(argv[1]), "-!- Topic: %s", argv[2]);
+ break;
+ case RPL_NAMREPLY:
+ if (argc >= 4) pushf(chfind(argv[2]), "-!- Names: %s", argv[3]);
+ break;
+ case RPL_TOPICWHOTIME:
+ case RPL_ENDOFNAMES:
+ break;
+ case ERR_NOCHANMODES:
+ case ERR_CHANNELISFULL:
+ case ERR_INVITEONLYCHAN:
+ case ERR_BADCHANNELKEY:
+ if (argc < 2) break;
+ chdel(argv[1]);
+ pushf(0, "-!- Cannot join %s (%s)", argv[1], cmd);
+ tredraw();
+ break;
+ case ERR_NICKNAMEINUSE:
+ strlcat(nick, "_", sizeof(nick));
+ sndf("NICK %s", nick);
+ break;
+ case CAP:
+ if (argc < 2) break;
+ if (!strcmp(argv[1], "LS"))
+ sndf((!empty(key) || !empty(cert)) && argc > 2 && strstr(argv[2], "sasl")
+ ? "CAP REQ :sasl"
+ : "CAP END");
+ else if (!strcmp(argv[1], "ACK") && argc > 2 && strstr(argv[2], "sasl"))
+ sndf(empty(cert) ? "AUTHENTICATE PLAIN" : "AUTHENTICATE EXTERNAL");
+ else if (!strcmp(argv[1], "NAK") || !strcmp(argv[1], "ACK"))
+ sndf("CAP END");
+ break;
+ case AUTHENTICATE:
+ if (argc < 1 || strcmp(argv[0], "+")) break;
+ if (!empty(cert)) {
+ sndf("AUTHENTICATE +");
+ } else if (!empty(usr) && !empty(key)) {
+ unsigned char raw[512];
+ size_t u_len = strlen(usr);
+ size_t k_len = strlen(key);
+ raw[0] = '\0';
+ memcpy(raw + 1, usr, u_len);
+ raw[1 + u_len] = '\0';
+ memcpy(raw + 2 + u_len, key, k_len);
+ sndf("AUTHENTICATE %s", b64_enc(raw, u_len + k_len + 2));
+ }
+ break;
+ case SASL_OK:
+ case SASL_ERR:
+ sndf("CAP END");
+ pushf(0, "-!- SASL auth %s", (type == SASL_OK ? "successful" : "failed"));
+ break;
+ default:
+ if (isdigit(cmd[0]))
+ pushf(0, "%s: %s", cmd, GET_ARG(argc - 1));
+ else
+ pushf(0, "%s: %s %s", cmd, GET_ARG(0), GET_ARG(1));
+ break;
+ }
}
static void
@@ -504,11 +651,9 @@ uparse(char *m)
if (!p[0] || (p[1] != ' ' && p[1] != 0)) {
pmsg:
- if (ch == 0)
- return;
+ if (ch == 0) return;
m += strspn(m, " ");
- if (!*m)
- return;
+ if (!*m) return;
pushf(ch, PFMT, nick, m);
sndf("PRIVMSG %s :%s", chl[ch].name, m);
return;
@@ -516,28 +661,25 @@ uparse(char *m)
switch (*p) {
case 'j': /* Join channels. */
p += 1 + (p[1] == ' ');
- p = strtok(p, " ");
- while (p) {
- if (chadd(p, 1) < 0)
+ for (char *token = strtok(p, " "); token; token = strtok(NULL, " ")) {
+ if (chadd(token, 1) < 0)
break;
- sndf("JOIN %s", p);
- p = strtok(0, " ");
+ if (strchr("&#!+", token[0]))
+ sndf("JOIN %s", token);
}
tredraw();
return;
case 'l': /* Leave channels. */
p += 1 + (p[1] == ' ');
if (!*p) {
- if (ch == 0)
- return; /* Cannot leave server window. */
- strcat(p, chl[ch].name);
- }
- p = strtok(p, " ");
- while (p) {
- if (chdel(p))
- sndf("PART %s", p);
- p = strtok(0, " ");
+ if (ch == 0) return; /* Cannot leave server window */
+ static char buf[ChanLen];
+ strlcpy(buf, chl[ch].name, sizeof(buf));
+ p = buf;
}
+ for (char *token = strtok(p, " "); token; token = strtok(NULL, " "))
+ if (chdel(token) && strchr("&#!+", token[0]))
+ sndf("PART %s", token);
tredraw();
return;
case 'm': /* Private message. */
@@ -562,8 +704,7 @@ uparse(char *m)
static void
sigwinch(int sig)
{
- if (sig)
- winchg = 1;
+ if (sig) winchg = 1;
}
static void
@@ -576,17 +717,17 @@ tinit(void)
noecho();
getmaxyx(stdscr, scr.y, scr.x);
if (scr.y < 4)
- panic("Screen too small.");
+ die("cio: screen too small");
if ((scr.sw = newwin(1, scr.x, 0, 0)) == 0
|| (scr.mw = newwin(scr.y - 2, scr.x, 1, 0)) == 0
|| (scr.iw = newwin(1, scr.x, scr.y - 1, 0)) == 0)
- panic("Cannot create windows.");
+ die("cio: cannot create windows");
keypad(scr.iw, 1);
scrollok(scr.mw, 1);
if (has_colors() == TRUE) {
start_color();
use_default_colors();
- init_pair(1, COLOR_WHITE, COLOR_BLUE);
+ init_pair(1, COLOR_WHITE, COLOR_BLACK);
wbkgd(scr.sw, COLOR_PAIR(1));
}
}
@@ -598,9 +739,8 @@ tresize(void)
winchg = 0;
if (ioctl(0, TIOCGWINSZ, &ws) < 0)
- panic("Ioctl (TIOCGWINSZ) failed.");
- if (ws.ws_row <= 2)
- return;
+ die("cio: Ioctl (TIOCGWINSZ) failed:");
+ if (ws.ws_row <= 2) return;
resizeterm(scr.y = ws.ws_row, scr.x = ws.ws_col);
wresize(scr.mw, scr.y - 2, scr.x);
wresize(scr.iw, 1, scr.x);
@@ -615,7 +755,7 @@ tredraw(void)
{
struct Chan *const c = &chl[ch];
char *q, *p;
- int nl = -1;
+ int row_idx = -1;
if (c->eol == c->buf) {
wclear(scr.mw);
@@ -632,10 +772,10 @@ tredraw(void)
c->n -= i;
}
q = p;
- while (nl < scr.y - 2) {
+ while (row_idx < (int)scr.y - 2) {
while (*q != '\n' && q > c->buf)
q--;
- nl++;
+ row_idx++;
if (q == c->buf)
break;
q--;
@@ -657,7 +797,6 @@ tdrawbar(void)
for (l = 0; fst > 0 && l < scr.x / 2; fst--)
l += strlen(chl[fst].name) + 3;
-
werase(scr.sw);
for (l = 0; fst < nch && l < scr.x; fst++) {
char *p = chl[fst].name;
@@ -730,30 +869,26 @@ tgetch(void)
dirty = len = cu;
break;
case CTRL('u'):
- if (cu == 0)
- return;
+ if (cu == 0) return;
len -= cu;
memmove(l, &l[cu], len);
dirty = cu = 0;
break;
case CTRL('d'):
- if (cu >= len)
- return;
+ if (cu >= len) return;
memmove(&l[cu], &l[cu + 1], len - cu - 1);
dirty = cu;
len--;
break;
case CTRL('h'):
case KEY_BACKSPACE:
- if (cu == 0)
- return;
+ if (cu == 0) return;
memmove(&l[cu - 1], &l[cu], len - cu);
dirty = --cu;
len--;
break;
case CTRL('w'):
- if (cu == 0)
- break;
+ if (cu == 0) break;
i = 1;
while (l[cu - i] == ' ' && cu - i != 0) i++;
while (l[cu - i] != ' ' && cu - i != 0) i++;
@@ -797,45 +932,51 @@ mvcur: wmove(scr.iw, 0, cu - shft);
static void
treset(void)
{
- if (scr.mw)
- delwin(scr.mw);
- if (scr.sw)
- delwin(scr.sw);
- if (scr.iw)
- delwin(scr.iw);
- endwin();
+ if (scr.mw) delwin(scr.mw);
+ if (scr.sw) delwin(scr.sw);
+ if (scr.iw) delwin(scr.iw);
+ if (!isendwin()) endwin();
}
int
main(int argc, char *argv[])
{
+#ifdef __OpenBSD__
+if (pledge("stdio tty rpath inet dns", NULL) == -1)
+ die("pledge");
+#endif /* __OpenBSD__ */
const char *user = getenv("USER");
const char *ircnick = getenv("IRCNICK");
- const char *key = getenv("IRCPASS");
+ snprintf(key, sizeof(key), "%s", getenv("IRCPASS"));
const char *server = SRV;
const char *port = PORT;
- char *err;
+ const char *err;
int o, reconn, ping;
signal(SIGPIPE, SIG_IGN);
- while ((o = getopt(argc, argv, "thk:n:u:s:p:l:")) >= 0)
+ while ((o = getopt(argc, argv, "hvTVn:c:u:s:p:l:")) >= 0)
switch (o) {
case 'h':
- case '?':
usage:
- fputs("usage: irc [-n NICK] [-u USER] [-s SERVER] [-p PORT] [-l LOGFILE ] [-t] [-h]\n", stderr);
- exit(0);
+ die("usage: %s [-n NICK] [-u USER] [-s SERVER] [-p PORT]\n"
+ " [-l LOGFILE] [-c certificate] [-hvTV]", argv[0]);
+ break;
+ case 'v':
+ die("cio-" VERSION);
+ break;
case 'l':
if (!(logfp = fopen(optarg, "a")))
- panic("fopen: logfile");
+ die("cio: fopen: logfile:");
break;
case 'n':
- if (strlen(optarg) >= sizeof nick)
+ if (strlcpy(nick, optarg, sizeof(nick)) >= sizeof(nick))
goto usage;
- strcpy(nick, optarg);
break;
- case 't':
- ssl = 1;
+ case 'T':
+ ssl = 0;
+ break;
+ case 'V':
+ sslverify = 0;
break;
case 'u':
user = optarg;
@@ -846,29 +987,30 @@ main(int argc, char *argv[])
case 'p':
port = optarg;
break;
+ case 'c':
+ if (strlcpy(cert, optarg, sizeof(cert)) >= sizeof(cert))
+ goto usage;
+ break;
}
if (!user)
user = "anonymous";
- if (!nick[0] && ircnick && strlen(ircnick) < sizeof nick)
- strcpy(nick, ircnick);
- if (!nick[0] && strlen(user) < sizeof nick)
- strcpy(nick, user);
+ if (!nick[0] && ircnick)
+ strlcpy(nick, ircnick, sizeof(nick));
+ if (!nick[0])
+ strlcpy(nick, user, sizeof(nick));
if (!nick[0])
goto usage;
+ atexit(treset);
tinit();
err = dial(server, port);
- if (err)
- panic(err);
+ if (err) die("cio: %s", err);
chadd(server, 0);
- sinit(key, nick, user);
+ sinit(nick, user);
reconn = 0;
ping = 0;
while (!quit) {
struct timeval t = {.tv_sec = 5};
- struct Chan *c;
fd_set rfs, wfs;
- int ret;
-
if (winchg)
tresize();
FD_ZERO(&wfs);
@@ -879,21 +1021,20 @@ main(int argc, char *argv[])
if (outp != outb)
FD_SET(srv.fd, &wfs);
}
- ret = select(srv.fd + 1, &rfs, &wfs, 0, &t);
- if (ret < 0) {
+ if (select(srv.fd + 1, &rfs, &wfs, 0, &t) < 0) {
if (errno == EINTR)
continue;
- panic("Select failed.");
+ die("cio: select failed:");
}
if (reconn) {
hangup();
- if (reconn++ == MaxRecons + 1)
- panic("Link lost.");
- pushf(0, "-!- Link lost, attempting reconnection...");
+ if (reconn > MaxRecons)
+ die("cio: link lost");
+ pushf(0, "-!- Link lost, attempt %d/%d...", reconn++, MaxRecons);
if (dial(server, port) != 0)
continue;
- sinit(key, nick, user);
- for (c = chl; c < &chl[nch]; ++c)
+ sinit(nick, user);
+ for (struct Chan *c = chl; c < &chl[nch]; ++c)
if (c->join)
sndf("JOIN %s", c->name);
reconn = 0;
@@ -905,14 +1046,10 @@ main(int argc, char *argv[])
}
}
if (FD_ISSET(srv.fd, &wfs)) {
- int wr;
-
- if (ssl)
- wr = SSL_write(srv.ssl, outb, outp - outb);
- else
- wr = write(srv.fd, outb, outp - outb);
+ size_t len = outp - outb;
+ int wr = ssl ? SSL_write(srv.ssl, outb, len) : write(srv.fd, outb, len);
if (wr <= 0) {
- reconn = wr < 0;
+ reconn = 1;
continue;
}
outp -= wr;
@@ -922,10 +1059,10 @@ main(int argc, char *argv[])
tgetch();
wrefresh(scr.iw);
}
- if (!FD_ISSET(srv.fd, &wfs))
- if (!FD_ISSET(srv.fd, &rfs))
- if (outp == outb)
- if (++ping == PingDelay) {
+ if ((!FD_ISSET(srv.fd, &wfs))
+ && (!FD_ISSET(srv.fd, &rfs))
+ && (outp == outb)
+ && (++ping >= PingDelay)) {
sndf("PING %s", server);
ping = 0;
}
@@ -933,6 +1070,5 @@ main(int argc, char *argv[])
hangup();
while (nch--)
free(chl[nch].buf);
- treset();
exit(0);
}
diff --git a/config.mk b/config.mk
@@ -0,0 +1,27 @@
+# cio version
+VERSION = 1.0
+
+# Customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+# ncurses
+NCURSESINC = $(shell pkg-config --cflags-only-I ncursesw)
+NCURSESLIB = $(shell pkg-config --libs ncursesw)
+# OpenBSD (uncomment)
+#NCURSESINC =
+#NCURSESLIB = -lncurses
+
+# includes and libs
+INCS = ${NCURSESINC}
+LIBS = -lssl -lcrypto ${NCURSESLIB}
+
+# flags
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSION=\"${VERSION}\"
+CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS}
+#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS}
+LDFLAGS = ${LIBS}
+
+CC = cc