commit bb055e70f7a276ab1a4ce54b00565285ea84c66b
parent 17fc6b5c307623fb94a2b5179e5732119a08704f
Author: Andrew Kloet <andrew@kloet.net>
Date: Fri, 27 Mar 2026 12:12:12 -0400
rename program to nio
Diffstat:
| M | Makefile | | | 2 | +- |
| D | irc.c | | | 938 | ------------------------------------------------------------------------------- |
| A | nio.c | | | 938 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 939 insertions(+), 939 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,4 +1,4 @@
-BIN = irc
+BIN = nio
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
diff --git a/irc.c b/irc.c
@@ -1,938 +0,0 @@
-#include <assert.h>
-#include <limits.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <string.h>
-#include <time.h>
-#include <errno.h>
-
-#include <curses.h>
-#include <unistd.h>
-#include <arpa/inet.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/select.h>
-#include <sys/ioctl.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <netdb.h>
-#include <locale.h>
-#include <wchar.h>
-#include <openssl/ssl.h>
-
-#undef CTRL
-#define CTRL(x) (x & 037)
-
-#define SCROLL 15
-#define INDENT 23
-#define DATEFMT "%H:%M"
-#define PFMT " %-12s < %s"
-#define PFMTHIGH "> %-12s < %s"
-#define SRV "irc.oftc.net"
-#define PORT "6667"
-
-enum {
- ChanLen = 64,
- LineLen = 512,
- MaxChans = 16,
- 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;
- WINDOW *sw, *mw, *iw;
-} scr;
-
-static struct Chan {
- char name[ChanLen];
- char *buf, *eol;
- int n; /* Scroll offset. */
- size_t sz; /* Size of buf. */
- char high; /* Nick highlight. */
- char new; /* New message. */
- char join; /* Channel was 'j'-oined. */
-} chl[MaxChans];
-
-static int ssl;
-static struct {
- int fd;
- SSL *ssl;
- SSL_CTX *ctx;
-} srv;
-static char nick[64];
-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 void scmd(char *, char *, char *, char *);
-static void tdrawbar(void);
-static void tredraw(void);
-static void treset(void);
-
-static void
-panic(const char *m)
-{
- treset();
- fprintf(stderr, "Panic: %s\n", m);
- exit(1);
-}
-
-static size_t
-utf8validate(Rune *u, size_t i)
-{
- if (*u < utfmin[i] || *u > utfmax[i] || (0xD800 <= *u && *u <= 0xDFFF))
- *u = RuneInvalid;
- for (i = 1; *u > utfmax[i]; ++i)
- ;
- return i;
-}
-
-static Rune
-utf8decodebyte(unsigned char c, size_t *i)
-{
- for (*i = 0; *i < UtfSz + 1; ++(*i))
- if ((c & utfmask[*i]) == utfbyte[*i])
- return c & ~utfmask[*i];
- return 0;
-}
-
-static size_t
-utf8decode(char *c, Rune *u, size_t clen)
-{
- size_t i, j, len, type;
- Rune udecoded;
-
- *u = RuneInvalid;
- if (!clen)
- return 0;
- udecoded = utf8decodebyte(c[0], &len);
- if (len < 1 || len > UtfSz)
- return 1;
- for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
- udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
- if (type != 0)
- return j;
- }
- if (j < len)
- return 0;
- *u = udecoded;
- utf8validate(u, len);
- return len;
-}
-
-static char
-utf8encodebyte(Rune u, size_t i)
-{
- return utfbyte[i] | (u & ~utfmask[i]);
-}
-
-static size_t
-utf8encode(Rune u, char *c)
-{
- size_t len, i;
-
- len = utf8validate(&u, 0);
- if (len > UtfSz)
- return 0;
- for (i = len - 1; i != 0; --i) {
- c[i] = utf8encodebyte(u, 0);
- u >>= 6;
- }
- c[0] = utf8encodebyte(u, len);
- return len;
-}
-
-static void
-sndf(const char *fmt, ...)
-{
- va_list vl;
- size_t n, l = BufSz - (outp - outb);
-
- if (l < 2)
- return;
- va_start(vl, fmt);
- n = vsnprintf(outp, l - 2, fmt, vl);
- va_end(vl);
- outp += n > l - 2 ? l - 2 : n;
- *outp++ = '\r';
- *outp++ = '\n';
-}
-
-static int
-srd(void)
-{
- static char l[BufSz], *p = l;
- char *s, *usr, *cmd, *par, *data;
- int rd;
-
- if (p - l >= BufSz)
- p = l; /* Input buffer overflow, there should something better to do. */
- if (ssl)
- rd = SSL_read(srv.ssl, p, BufSz - (p - l));
- else
- rd = read(srv.fd, p, BufSz - (p - l));
- 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;
- *s++ = 0;
- if (*l == ':') {
- if (!(cmd = strchr(l, ' ')))
- goto lskip;
- *cmd++ = 0;
- usr = l + 1;
- } else {
- usr = 0;
- cmd = l;
- }
- if (!(par = strchr(cmd, ' ')))
- goto lskip;
- *par++ = 0;
- if ((data = strchr(par, ':')))
- *data++ = 0;
- scmd(usr, cmd, par, data);
- lskip:
- memmove(l, s, p - s);
- p -= s - l;
- }
-}
-
-static void
-sinit(const char *key, const char *nick, const char *user)
-{
- if (key)
- sndf("PASS %s", key);
- 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;
-
- 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.";
- 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);
- continue;
- }
- break;
- }
- if (fd == -1)
- return "Cannot connect to host.";
- srv.fd = fd;
- 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.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.";
- }
- 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;
- }
-}
-
-static inline int
-chfind(const char *name)
-{
- int i;
-
- assert(name);
- for (i = nch - 1; i > 0; i--)
- if (!strcmp(chl[i].name, name))
- break;
- return i;
-}
-
-static int
-chadd(const char *name, int joined)
-{
- int n;
-
- if (nch >= MaxChans || strlen(name) >= ChanLen)
- return -1;
- if ((n = chfind(name)) > 0)
- return n;
- strcpy(chl[nch].name, name);
- chl[nch].sz = LogSz;
- chl[nch].buf = malloc(LogSz);
- if (!chl[nch].buf)
- panic("Out of memory.");
- chl[nch].eol = chl[nch].buf;
- chl[nch].n = 0;
- chl[nch].join = joined;
- if (joined)
- ch = nch;
- nch++;
- tdrawbar();
- return nch;
-}
-
-static int
-chdel(char *name)
-{
- int n;
-
- if (!(n = chfind(name)))
- return 0;
- nch--;
- free(chl[n].buf);
- memmove(&chl[n], &chl[n + 1], (nch - n) * sizeof(struct Chan));
- ch = nch - 1;
- tdrawbar();
- return 1;
-}
-
-static char *
-pushl(char *p, char *e)
-{
- int x, cl;
- char *w;
- Rune u[2];
- cchar_t cc;
-
- u[1] = 0;
- if ((w = memchr(p, '\n', e - p)))
- e = w + 1;
- w = p;
- x = 0;
- for (;;) {
- if (x >= scr.x) {
- waddch(scr.mw, '\n');
- for (x = 0; x < INDENT; x++)
- waddch(scr.mw, ' ');
- if (*w == ' ')
- w++;
- x += p - w;
- }
- if (p >= e || *p == ' ' || p - w + INDENT >= scr.x - 1) {
- while (w < p) {
- w += utf8decode(w, u, UtfSz);
- if (wcwidth(*u) > 0 || *u == '\n') {
- setcchar(&cc, u, 0, 0, 0);
- wadd_wch(scr.mw, &cc);
- }
- }
- if (p >= e)
- return e;
- }
- p += utf8decode(p, u, UtfSz);
- if ((cl = wcwidth(*u)) >= 0)
- x += cl;
- }
-}
-
-static void
-pushf(int cn, const char *fmt, ...)
-{
- struct Chan *const c = &chl[cn];
- size_t n, blen = c->eol - c->buf;
- va_list vl;
- time_t t;
- char *s;
- 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.");
- c->eol = c->buf + blen;
- }
- t = time(0);
- if (!(tm = localtime(&t)))
- panic("Localtime failed.");
- n = strftime(c->eol, LineLen, DATEFMT, tm);
- if (!(gmtm = gmtime(&t)))
- panic("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,
- gmtm->tm_year + 1900, gmtm->tm_mon + 1, gmtm->tm_mday,
- 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;
- if (cn == ch && c->n == 0) {
- char *p = c->eol - n - 1;
-
- if (p != c->buf)
- waddch(scr.mw, '\n');
- pushl(p, c->eol - 1);
- wrefresh(scr.mw);
- }
-}
-
-static void
-scmd(char *usr, char *cmd, char *par, char *data)
-{
- int s, c;
- char *pm = strtok(par, " "), *chan;
-
- if (!usr)
- usr = "?";
- else {
- char *bang = strchr(usr, '!');
- 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();
- }
- c = chfind(chan);
- if (strcasestr(data, nick)) {
- pushf(c, PFMTHIGH, usr, data);
- chl[c].high |= ch != c;
- } else
- pushf(c, PFMT, usr, data);
- 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);
- tredraw();
- }
- } 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)");
-}
-
-static void
-uparse(char *m)
-{
- char *p = m;
-
- if (!p[0] || (p[1] != ' ' && p[1] != 0)) {
- pmsg:
- if (ch == 0)
- return;
- m += strspn(m, " ");
- if (!*m)
- return;
- pushf(ch, PFMT, nick, m);
- sndf("PRIVMSG %s :%s", chl[ch].name, m);
- return;
- }
- switch (*p) {
- case 'j': /* Join channels. */
- p += 1 + (p[1] == ' ');
- p = strtok(p, " ");
- while (p) {
- if (chadd(p, 1) < 0)
- break;
- sndf("JOIN %s", p);
- p = strtok(0, " ");
- }
- 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, " ");
- }
- tredraw();
- return;
- case 'm': /* Private message. */
- m = p + 1 + (p[1] == ' ');
- if (!(p = strchr(m, ' ')))
- return;
- *p++ = 0;
- sndf("PRIVMSG %s :%s", m, p);
- return;
- case 'r': /* Send raw. */
- if (p[1])
- sndf("%s", &p[2]);
- return;
- case 'q': /* Quit. */
- quit = 1;
- return;
- default: /* Send on current channel. */
- goto pmsg;
- }
-}
-
-static void
-sigwinch(int sig)
-{
- if (sig)
- winchg = 1;
-}
-
-static void
-tinit(void)
-{
- setlocale(LC_ALL, "");
- signal(SIGWINCH, sigwinch);
- initscr();
- raw();
- noecho();
- getmaxyx(stdscr, scr.y, scr.x);
- if (scr.y < 4)
- panic("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.");
- keypad(scr.iw, 1);
- scrollok(scr.mw, 1);
- if (has_colors() == TRUE) {
- start_color();
- use_default_colors();
- init_pair(1, COLOR_WHITE, COLOR_BLUE);
- wbkgd(scr.sw, COLOR_PAIR(1));
- }
-}
-
-static void
-tresize(void)
-{
- struct winsize ws;
-
- winchg = 0;
- if (ioctl(0, TIOCGWINSZ, &ws) < 0)
- panic("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);
- wresize(scr.sw, 1, scr.x);
- mvwin(scr.iw, scr.y - 1, 0);
- tredraw();
- tdrawbar();
-}
-
-static void
-tredraw(void)
-{
- struct Chan *const c = &chl[ch];
- char *q, *p;
- int nl = -1;
-
- if (c->eol == c->buf) {
- wclear(scr.mw);
- wrefresh(scr.mw);
- return;
- }
- p = c->eol - 1;
- if (c->n) {
- int i = c->n;
- for (; p > c->buf; p--)
- if (*p == '\n' && !i--)
- break;
- if (p == c->buf)
- c->n -= i;
- }
- q = p;
- while (nl < scr.y - 2) {
- while (*q != '\n' && q > c->buf)
- q--;
- nl++;
- if (q == c->buf)
- break;
- q--;
- }
- if (q != c->buf)
- q += 2;
- wclear(scr.mw);
- wmove(scr.mw, 0, 0);
- while (q < p)
- q = pushl(q, p);
- wrefresh(scr.mw);
-}
-
-static void
-tdrawbar(void)
-{
- size_t l;
- int fst = ch;
-
- 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;
- if (fst == ch)
- wattron(scr.sw, A_BOLD);
- waddch(scr.sw, '['), l++;
- if (chl[fst].high)
- waddch(scr.sw, '>'), l++;
- else if (chl[fst].new)
- waddch(scr.sw, '+'), l++;
- for (; *p && l < scr.x; p++, l++)
- waddch(scr.sw, *p);
- if (l < scr.x - 1)
- waddstr(scr.sw, "] "), l += 2;
- if (fst == ch)
- wattroff(scr.sw, A_BOLD);
- }
- wrefresh(scr.sw);
-}
-
-static void
-tgetch(void)
-{
- static char l[BufSz];
- static size_t shft, cu, len;
- size_t dirty = len + 1, i;
- int c;
-
- c = wgetch(scr.iw);
- switch (c) {
- case CTRL('n'):
- ch = (ch + 1) % nch;
- chl[ch].high = chl[ch].new = 0;
- tdrawbar();
- tredraw();
- return;
- case CTRL('p'):
- ch = (ch + nch - 1) % nch;
- chl[ch].high = chl[ch].new = 0;
- tdrawbar();
- tredraw();
- return;
- case KEY_PPAGE:
- chl[ch].n += SCROLL;
- tredraw();
- return;
- case KEY_NPAGE:
- chl[ch].n -= SCROLL;
- if (chl[ch].n < 0)
- chl[ch].n = 0;
- tredraw();
- return;
- case CTRL('a'):
- cu = 0;
- break;
- case CTRL('e'):
- cu = len;
- break;
- case CTRL('b'):
- case KEY_LEFT:
- if (cu)
- cu--;
- break;
- case CTRL('f'):
- case KEY_RIGHT:
- if (cu < len)
- cu++;
- break;
- case CTRL('k'):
- dirty = len = cu;
- break;
- case CTRL('u'):
- if (cu == 0)
- return;
- len -= cu;
- memmove(l, &l[cu], len);
- dirty = cu = 0;
- break;
- case CTRL('d'):
- 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;
- memmove(&l[cu - 1], &l[cu], len - cu);
- dirty = --cu;
- len--;
- break;
- case CTRL('w'):
- if (cu == 0)
- break;
- i = 1;
- while (l[cu - i] == ' ' && cu - i != 0) i++;
- while (l[cu - i] != ' ' && cu - i != 0) i++;
- if (cu - i != 0) i--;
- memmove(&l[cu - i], &l[cu], len - cu);
- cu -= i;
- dirty = cu;
- len -= i;
- break;
- case '\n':
- l[len] = 0;
- uparse(l);
- dirty = cu = len = 0;
- break;
- default:
- if (c > CHAR_MAX || len >= BufSz - 1)
- return; /* Skip other curses codes. */
- memmove(&l[cu + 1], &l[cu], len - cu);
- dirty = cu;
- len++;
- l[cu++] = c;
- break;
- }
- while (cu < shft)
- dirty = 0, shft -= shft >= scr.x / 2 ? scr.x / 2 : shft;
- while (cu >= scr.x + shft)
- dirty = 0, shft += scr.x / 2;
- if (dirty <= shft)
- i = shft;
- else if (dirty > scr.x + shft || dirty > len)
- goto mvcur;
- else
- i = dirty;
- wmove(scr.iw, 0, i - shft);
- wclrtoeol(scr.iw);
- for (; i - shft < scr.x && i < len; i++)
- waddch(scr.iw, l[i]);
-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();
-}
-
-int
-main(int argc, char *argv[])
-{
- const char *user = getenv("USER");
- const char *ircnick = getenv("IRCNICK");
- const char *key = getenv("IRCPASS");
- const char *server = SRV;
- const char *port = PORT;
- char *err;
- int o, reconn, ping;
-
- signal(SIGPIPE, SIG_IGN);
- while ((o = getopt(argc, argv, "thk:n: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);
- case 'l':
- if (!(logfp = fopen(optarg, "a")))
- panic("fopen: logfile");
- break;
- case 'n':
- if (strlen(optarg) >= sizeof nick)
- goto usage;
- strcpy(nick, optarg);
- break;
- case 't':
- ssl = 1;
- break;
- case 'u':
- user = optarg;
- break;
- case 's':
- server = optarg;
- break;
- case 'p':
- port = optarg;
- 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])
- goto usage;
- tinit();
- err = dial(server, port);
- if (err)
- panic(err);
- chadd(server, 0);
- sinit(key, 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);
- FD_ZERO(&rfs);
- FD_SET(0, &rfs);
- if (!reconn) {
- FD_SET(srv.fd, &rfs);
- if (outp != outb)
- FD_SET(srv.fd, &wfs);
- }
- ret = select(srv.fd + 1, &rfs, &wfs, 0, &t);
- if (ret < 0) {
- if (errno == EINTR)
- continue;
- panic("Select failed.");
- }
- if (reconn) {
- hangup();
- if (reconn++ == MaxRecons + 1)
- panic("Link lost.");
- pushf(0, "-!- Link lost, attempting reconnection...");
- if (dial(server, port) != 0)
- continue;
- sinit(key, nick, user);
- for (c = chl; c < &chl[nch]; ++c)
- if (c->join)
- sndf("JOIN %s", c->name);
- reconn = 0;
- }
- if (FD_ISSET(srv.fd, &rfs)) {
- if (!srd()) {
- reconn = 1;
- continue;
- }
- }
- 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);
- if (wr <= 0) {
- reconn = wr < 0;
- continue;
- }
- outp -= wr;
- memmove(outb, outb + wr, outp - outb);
- }
- if (FD_ISSET(0, &rfs)) {
- tgetch();
- wrefresh(scr.iw);
- }
- if (!FD_ISSET(srv.fd, &wfs))
- if (!FD_ISSET(srv.fd, &rfs))
- if (outp == outb)
- if (++ping == PingDelay) {
- sndf("PING %s", server);
- ping = 0;
- }
- }
- hangup();
- while (nch--)
- free(chl[nch].buf);
- treset();
- exit(0);
-}
diff --git a/nio.c b/nio.c
@@ -0,0 +1,938 @@
+#include <assert.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+#include <curses.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <locale.h>
+#include <wchar.h>
+#include <openssl/ssl.h>
+
+#undef CTRL
+#define CTRL(x) (x & 037)
+
+#define SCROLL 15
+#define INDENT 23
+#define DATEFMT "%H:%M"
+#define PFMT " %-12s < %s"
+#define PFMTHIGH "> %-12s < %s"
+#define SRV "irc.oftc.net"
+#define PORT "6667"
+
+enum {
+ ChanLen = 64,
+ LineLen = 512,
+ MaxChans = 16,
+ 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;
+ WINDOW *sw, *mw, *iw;
+} scr;
+
+static struct Chan {
+ char name[ChanLen];
+ char *buf, *eol;
+ int n; /* Scroll offset. */
+ size_t sz; /* Size of buf. */
+ char high; /* Nick highlight. */
+ char new; /* New message. */
+ char join; /* Channel was 'j'-oined. */
+} chl[MaxChans];
+
+static int ssl;
+static struct {
+ int fd;
+ SSL *ssl;
+ SSL_CTX *ctx;
+} srv;
+static char nick[64];
+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 void scmd(char *, char *, char *, char *);
+static void tdrawbar(void);
+static void tredraw(void);
+static void treset(void);
+
+static void
+panic(const char *m)
+{
+ treset();
+ fprintf(stderr, "Panic: %s\n", m);
+ exit(1);
+}
+
+static size_t
+utf8validate(Rune *u, size_t i)
+{
+ if (*u < utfmin[i] || *u > utfmax[i] || (0xD800 <= *u && *u <= 0xDFFF))
+ *u = RuneInvalid;
+ for (i = 1; *u > utfmax[i]; ++i)
+ ;
+ return i;
+}
+
+static Rune
+utf8decodebyte(unsigned char c, size_t *i)
+{
+ for (*i = 0; *i < UtfSz + 1; ++(*i))
+ if ((c & utfmask[*i]) == utfbyte[*i])
+ return c & ~utfmask[*i];
+ return 0;
+}
+
+static size_t
+utf8decode(char *c, Rune *u, size_t clen)
+{
+ size_t i, j, len, type;
+ Rune udecoded;
+
+ *u = RuneInvalid;
+ if (!clen)
+ return 0;
+ udecoded = utf8decodebyte(c[0], &len);
+ if (len < 1 || len > UtfSz)
+ return 1;
+ for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
+ udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
+ if (type != 0)
+ return j;
+ }
+ if (j < len)
+ return 0;
+ *u = udecoded;
+ utf8validate(u, len);
+ return len;
+}
+
+static char
+utf8encodebyte(Rune u, size_t i)
+{
+ return utfbyte[i] | (u & ~utfmask[i]);
+}
+
+static size_t
+utf8encode(Rune u, char *c)
+{
+ size_t len, i;
+
+ len = utf8validate(&u, 0);
+ if (len > UtfSz)
+ return 0;
+ for (i = len - 1; i != 0; --i) {
+ c[i] = utf8encodebyte(u, 0);
+ u >>= 6;
+ }
+ c[0] = utf8encodebyte(u, len);
+ return len;
+}
+
+static void
+sndf(const char *fmt, ...)
+{
+ va_list vl;
+ size_t n, l = BufSz - (outp - outb);
+
+ if (l < 2)
+ return;
+ va_start(vl, fmt);
+ n = vsnprintf(outp, l - 2, fmt, vl);
+ va_end(vl);
+ outp += n > l - 2 ? l - 2 : n;
+ *outp++ = '\r';
+ *outp++ = '\n';
+}
+
+static int
+srd(void)
+{
+ static char l[BufSz], *p = l;
+ char *s, *usr, *cmd, *par, *data;
+ int rd;
+
+ if (p - l >= BufSz)
+ p = l; /* Input buffer overflow, there should something better to do. */
+ if (ssl)
+ rd = SSL_read(srv.ssl, p, BufSz - (p - l));
+ else
+ rd = read(srv.fd, p, BufSz - (p - l));
+ 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;
+ *s++ = 0;
+ if (*l == ':') {
+ if (!(cmd = strchr(l, ' ')))
+ goto lskip;
+ *cmd++ = 0;
+ usr = l + 1;
+ } else {
+ usr = 0;
+ cmd = l;
+ }
+ if (!(par = strchr(cmd, ' ')))
+ goto lskip;
+ *par++ = 0;
+ if ((data = strchr(par, ':')))
+ *data++ = 0;
+ scmd(usr, cmd, par, data);
+ lskip:
+ memmove(l, s, p - s);
+ p -= s - l;
+ }
+}
+
+static void
+sinit(const char *key, const char *nick, const char *user)
+{
+ if (key)
+ sndf("PASS %s", key);
+ 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;
+
+ 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.";
+ 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);
+ continue;
+ }
+ break;
+ }
+ if (fd == -1)
+ return "Cannot connect to host.";
+ srv.fd = fd;
+ 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.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.";
+ }
+ 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;
+ }
+}
+
+static inline int
+chfind(const char *name)
+{
+ int i;
+
+ assert(name);
+ for (i = nch - 1; i > 0; i--)
+ if (!strcmp(chl[i].name, name))
+ break;
+ return i;
+}
+
+static int
+chadd(const char *name, int joined)
+{
+ int n;
+
+ if (nch >= MaxChans || strlen(name) >= ChanLen)
+ return -1;
+ if ((n = chfind(name)) > 0)
+ return n;
+ strcpy(chl[nch].name, name);
+ chl[nch].sz = LogSz;
+ chl[nch].buf = malloc(LogSz);
+ if (!chl[nch].buf)
+ panic("Out of memory.");
+ chl[nch].eol = chl[nch].buf;
+ chl[nch].n = 0;
+ chl[nch].join = joined;
+ if (joined)
+ ch = nch;
+ nch++;
+ tdrawbar();
+ return nch;
+}
+
+static int
+chdel(char *name)
+{
+ int n;
+
+ if (!(n = chfind(name)))
+ return 0;
+ nch--;
+ free(chl[n].buf);
+ memmove(&chl[n], &chl[n + 1], (nch - n) * sizeof(struct Chan));
+ ch = nch - 1;
+ tdrawbar();
+ return 1;
+}
+
+static char *
+pushl(char *p, char *e)
+{
+ int x, cl;
+ char *w;
+ Rune u[2];
+ cchar_t cc;
+
+ u[1] = 0;
+ if ((w = memchr(p, '\n', e - p)))
+ e = w + 1;
+ w = p;
+ x = 0;
+ for (;;) {
+ if (x >= scr.x) {
+ waddch(scr.mw, '\n');
+ for (x = 0; x < INDENT; x++)
+ waddch(scr.mw, ' ');
+ if (*w == ' ')
+ w++;
+ x += p - w;
+ }
+ if (p >= e || *p == ' ' || p - w + INDENT >= scr.x - 1) {
+ while (w < p) {
+ w += utf8decode(w, u, UtfSz);
+ if (wcwidth(*u) > 0 || *u == '\n') {
+ setcchar(&cc, u, 0, 0, 0);
+ wadd_wch(scr.mw, &cc);
+ }
+ }
+ if (p >= e)
+ return e;
+ }
+ p += utf8decode(p, u, UtfSz);
+ if ((cl = wcwidth(*u)) >= 0)
+ x += cl;
+ }
+}
+
+static void
+pushf(int cn, const char *fmt, ...)
+{
+ struct Chan *const c = &chl[cn];
+ size_t n, blen = c->eol - c->buf;
+ va_list vl;
+ time_t t;
+ char *s;
+ 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.");
+ c->eol = c->buf + blen;
+ }
+ t = time(0);
+ if (!(tm = localtime(&t)))
+ panic("Localtime failed.");
+ n = strftime(c->eol, LineLen, DATEFMT, tm);
+ if (!(gmtm = gmtime(&t)))
+ panic("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,
+ gmtm->tm_year + 1900, gmtm->tm_mon + 1, gmtm->tm_mday,
+ 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;
+ if (cn == ch && c->n == 0) {
+ char *p = c->eol - n - 1;
+
+ if (p != c->buf)
+ waddch(scr.mw, '\n');
+ pushl(p, c->eol - 1);
+ wrefresh(scr.mw);
+ }
+}
+
+static void
+scmd(char *usr, char *cmd, char *par, char *data)
+{
+ int s, c;
+ char *pm = strtok(par, " "), *chan;
+
+ if (!usr)
+ usr = "?";
+ else {
+ char *bang = strchr(usr, '!');
+ 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();
+ }
+ c = chfind(chan);
+ if (strcasestr(data, nick)) {
+ pushf(c, PFMTHIGH, usr, data);
+ chl[c].high |= ch != c;
+ } else
+ pushf(c, PFMT, usr, data);
+ 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);
+ tredraw();
+ }
+ } 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)");
+}
+
+static void
+uparse(char *m)
+{
+ char *p = m;
+
+ if (!p[0] || (p[1] != ' ' && p[1] != 0)) {
+ pmsg:
+ if (ch == 0)
+ return;
+ m += strspn(m, " ");
+ if (!*m)
+ return;
+ pushf(ch, PFMT, nick, m);
+ sndf("PRIVMSG %s :%s", chl[ch].name, m);
+ return;
+ }
+ switch (*p) {
+ case 'j': /* Join channels. */
+ p += 1 + (p[1] == ' ');
+ p = strtok(p, " ");
+ while (p) {
+ if (chadd(p, 1) < 0)
+ break;
+ sndf("JOIN %s", p);
+ p = strtok(0, " ");
+ }
+ 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, " ");
+ }
+ tredraw();
+ return;
+ case 'm': /* Private message. */
+ m = p + 1 + (p[1] == ' ');
+ if (!(p = strchr(m, ' ')))
+ return;
+ *p++ = 0;
+ sndf("PRIVMSG %s :%s", m, p);
+ return;
+ case 'r': /* Send raw. */
+ if (p[1])
+ sndf("%s", &p[2]);
+ return;
+ case 'q': /* Quit. */
+ quit = 1;
+ return;
+ default: /* Send on current channel. */
+ goto pmsg;
+ }
+}
+
+static void
+sigwinch(int sig)
+{
+ if (sig)
+ winchg = 1;
+}
+
+static void
+tinit(void)
+{
+ setlocale(LC_ALL, "");
+ signal(SIGWINCH, sigwinch);
+ initscr();
+ raw();
+ noecho();
+ getmaxyx(stdscr, scr.y, scr.x);
+ if (scr.y < 4)
+ panic("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.");
+ keypad(scr.iw, 1);
+ scrollok(scr.mw, 1);
+ if (has_colors() == TRUE) {
+ start_color();
+ use_default_colors();
+ init_pair(1, COLOR_WHITE, COLOR_BLUE);
+ wbkgd(scr.sw, COLOR_PAIR(1));
+ }
+}
+
+static void
+tresize(void)
+{
+ struct winsize ws;
+
+ winchg = 0;
+ if (ioctl(0, TIOCGWINSZ, &ws) < 0)
+ panic("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);
+ wresize(scr.sw, 1, scr.x);
+ mvwin(scr.iw, scr.y - 1, 0);
+ tredraw();
+ tdrawbar();
+}
+
+static void
+tredraw(void)
+{
+ struct Chan *const c = &chl[ch];
+ char *q, *p;
+ int nl = -1;
+
+ if (c->eol == c->buf) {
+ wclear(scr.mw);
+ wrefresh(scr.mw);
+ return;
+ }
+ p = c->eol - 1;
+ if (c->n) {
+ int i = c->n;
+ for (; p > c->buf; p--)
+ if (*p == '\n' && !i--)
+ break;
+ if (p == c->buf)
+ c->n -= i;
+ }
+ q = p;
+ while (nl < scr.y - 2) {
+ while (*q != '\n' && q > c->buf)
+ q--;
+ nl++;
+ if (q == c->buf)
+ break;
+ q--;
+ }
+ if (q != c->buf)
+ q += 2;
+ wclear(scr.mw);
+ wmove(scr.mw, 0, 0);
+ while (q < p)
+ q = pushl(q, p);
+ wrefresh(scr.mw);
+}
+
+static void
+tdrawbar(void)
+{
+ size_t l;
+ int fst = ch;
+
+ 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;
+ if (fst == ch)
+ wattron(scr.sw, A_BOLD);
+ waddch(scr.sw, '['), l++;
+ if (chl[fst].high)
+ waddch(scr.sw, '>'), l++;
+ else if (chl[fst].new)
+ waddch(scr.sw, '+'), l++;
+ for (; *p && l < scr.x; p++, l++)
+ waddch(scr.sw, *p);
+ if (l < scr.x - 1)
+ waddstr(scr.sw, "] "), l += 2;
+ if (fst == ch)
+ wattroff(scr.sw, A_BOLD);
+ }
+ wrefresh(scr.sw);
+}
+
+static void
+tgetch(void)
+{
+ static char l[BufSz];
+ static size_t shft, cu, len;
+ size_t dirty = len + 1, i;
+ int c;
+
+ c = wgetch(scr.iw);
+ switch (c) {
+ case CTRL('n'):
+ ch = (ch + 1) % nch;
+ chl[ch].high = chl[ch].new = 0;
+ tdrawbar();
+ tredraw();
+ return;
+ case CTRL('p'):
+ ch = (ch + nch - 1) % nch;
+ chl[ch].high = chl[ch].new = 0;
+ tdrawbar();
+ tredraw();
+ return;
+ case KEY_PPAGE:
+ chl[ch].n += SCROLL;
+ tredraw();
+ return;
+ case KEY_NPAGE:
+ chl[ch].n -= SCROLL;
+ if (chl[ch].n < 0)
+ chl[ch].n = 0;
+ tredraw();
+ return;
+ case CTRL('a'):
+ cu = 0;
+ break;
+ case CTRL('e'):
+ cu = len;
+ break;
+ case CTRL('b'):
+ case KEY_LEFT:
+ if (cu)
+ cu--;
+ break;
+ case CTRL('f'):
+ case KEY_RIGHT:
+ if (cu < len)
+ cu++;
+ break;
+ case CTRL('k'):
+ dirty = len = cu;
+ break;
+ case CTRL('u'):
+ if (cu == 0)
+ return;
+ len -= cu;
+ memmove(l, &l[cu], len);
+ dirty = cu = 0;
+ break;
+ case CTRL('d'):
+ 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;
+ memmove(&l[cu - 1], &l[cu], len - cu);
+ dirty = --cu;
+ len--;
+ break;
+ case CTRL('w'):
+ if (cu == 0)
+ break;
+ i = 1;
+ while (l[cu - i] == ' ' && cu - i != 0) i++;
+ while (l[cu - i] != ' ' && cu - i != 0) i++;
+ if (cu - i != 0) i--;
+ memmove(&l[cu - i], &l[cu], len - cu);
+ cu -= i;
+ dirty = cu;
+ len -= i;
+ break;
+ case '\n':
+ l[len] = 0;
+ uparse(l);
+ dirty = cu = len = 0;
+ break;
+ default:
+ if (c > CHAR_MAX || len >= BufSz - 1)
+ return; /* Skip other curses codes. */
+ memmove(&l[cu + 1], &l[cu], len - cu);
+ dirty = cu;
+ len++;
+ l[cu++] = c;
+ break;
+ }
+ while (cu < shft)
+ dirty = 0, shft -= shft >= scr.x / 2 ? scr.x / 2 : shft;
+ while (cu >= scr.x + shft)
+ dirty = 0, shft += scr.x / 2;
+ if (dirty <= shft)
+ i = shft;
+ else if (dirty > scr.x + shft || dirty > len)
+ goto mvcur;
+ else
+ i = dirty;
+ wmove(scr.iw, 0, i - shft);
+ wclrtoeol(scr.iw);
+ for (; i - shft < scr.x && i < len; i++)
+ waddch(scr.iw, l[i]);
+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();
+}
+
+int
+main(int argc, char *argv[])
+{
+ const char *user = getenv("USER");
+ const char *ircnick = getenv("IRCNICK");
+ const char *key = getenv("IRCPASS");
+ const char *server = SRV;
+ const char *port = PORT;
+ char *err;
+ int o, reconn, ping;
+
+ signal(SIGPIPE, SIG_IGN);
+ while ((o = getopt(argc, argv, "thk:n:u:s:p:l:")) >= 0)
+ switch (o) {
+ case 'h':
+ case '?':
+ usage:
+ fputs("usage: nio [-n NICK] [-u USER] [-s SERVER] [-p PORT] [-l LOGFILE ] [-t] [-h]\n", stderr);
+ exit(0);
+ case 'l':
+ if (!(logfp = fopen(optarg, "a")))
+ panic("fopen: logfile");
+ break;
+ case 'n':
+ if (strlen(optarg) >= sizeof nick)
+ goto usage;
+ strcpy(nick, optarg);
+ break;
+ case 't':
+ ssl = 1;
+ break;
+ case 'u':
+ user = optarg;
+ break;
+ case 's':
+ server = optarg;
+ break;
+ case 'p':
+ port = optarg;
+ 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])
+ goto usage;
+ tinit();
+ err = dial(server, port);
+ if (err)
+ panic(err);
+ chadd(server, 0);
+ sinit(key, 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);
+ FD_ZERO(&rfs);
+ FD_SET(0, &rfs);
+ if (!reconn) {
+ FD_SET(srv.fd, &rfs);
+ if (outp != outb)
+ FD_SET(srv.fd, &wfs);
+ }
+ ret = select(srv.fd + 1, &rfs, &wfs, 0, &t);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ panic("Select failed.");
+ }
+ if (reconn) {
+ hangup();
+ if (reconn++ == MaxRecons + 1)
+ panic("Link lost.");
+ pushf(0, "-!- Link lost, attempting reconnection...");
+ if (dial(server, port) != 0)
+ continue;
+ sinit(key, nick, user);
+ for (c = chl; c < &chl[nch]; ++c)
+ if (c->join)
+ sndf("JOIN %s", c->name);
+ reconn = 0;
+ }
+ if (FD_ISSET(srv.fd, &rfs)) {
+ if (!srd()) {
+ reconn = 1;
+ continue;
+ }
+ }
+ 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);
+ if (wr <= 0) {
+ reconn = wr < 0;
+ continue;
+ }
+ outp -= wr;
+ memmove(outb, outb + wr, outp - outb);
+ }
+ if (FD_ISSET(0, &rfs)) {
+ tgetch();
+ wrefresh(scr.iw);
+ }
+ if (!FD_ISSET(srv.fd, &wfs))
+ if (!FD_ISSET(srv.fd, &rfs))
+ if (outp == outb)
+ if (++ping == PingDelay) {
+ sndf("PING %s", server);
+ ping = 0;
+ }
+ }
+ hangup();
+ while (nch--)
+ free(chl[nch].buf);
+ treset();
+ exit(0);
+}