nio

a simple irc client
Download | Log | Files | Refs | README

nio.c (20943B)


      1 #include <assert.h>
      2 #include <ctype.h>
      3 #include <limits.h>
      4 #include <signal.h>
      5 #include <stdio.h>
      6 #include <stdlib.h>
      7 #include <stdarg.h>
      8 #include <string.h>
      9 #include <time.h>
     10 #include <errno.h>
     11 
     12 #include <curses.h>
     13 #include <unistd.h>
     14 #include <arpa/inet.h>
     15 #include <sys/types.h>
     16 #include <sys/socket.h>
     17 #include <sys/select.h>
     18 #include <sys/ioctl.h>
     19 #include <netinet/in.h>
     20 #include <netinet/tcp.h>
     21 #include <netdb.h>
     22 #include <locale.h>
     23 #include <wchar.h>
     24 #include <openssl/ssl.h>
     25 #include <openssl/x509v3.h>
     26 
     27 #undef CTRL
     28 #define CTRL(x)  (x & 037)
     29 
     30 #define SCROLL   15
     31 #define INDENT   23
     32 #define DATEFMT  "%H:%M"
     33 #define PFMT     "  %-12s < %s"
     34 #define PFMTHIGH "> %-12s < %s"
     35 #define SRV      "irc.oftc.net"
     36 #define PORT     "6697"
     37 
     38 typedef enum {
     39 	CMD_UNKNOWN, PRIVMSG, PING, PONG, PART, JOIN, QUIT,
     40 	CAP, AUTHENTICATE, SASL_OK, SASL_ERR, ERR_NICKNAMEINUSE = 433,
     41 	ERR_FORWARD = 470, ERR_FULL = 471, ERR_INVITE = 473,
     42 	ERR_BANNED = 474, ERR_BADKEY = 475
     43 } IrcCmd;
     44 
     45 struct {
     46 	const char *name;
     47 	IrcCmd id;
     48 } cmd_map[] = {
     49 	{ "PRIVMSG", PRIVMSG }, { "PING", PING }, { "PONG", PONG },
     50 	{ "PART", PART }, { "JOIN", JOIN }, { "QUIT", QUIT }, { "CAP", CAP },
     51 	{ "AUTHENTICATE", AUTHENTICATE }, { "903", SASL_OK }, { "904", SASL_ERR },
     52 	{ "433", ERR_NICKNAMEINUSE }, { "470", ERR_FORWARD }, { "471", ERR_FULL },
     53 	{ "473", ERR_INVITE }, { "474", ERR_BANNED }, { "475", ERR_BADKEY }
     54 };
     55 
     56 enum {
     57 	ChanLen		= 64,
     58 	LineLen		= 512,
     59 	MaxChans	= 16,
     60 	BufSz		= 2048,
     61 	LogSz		= 4096,
     62 	MaxRecons	= 10, /* -1 for infinitely many */
     63 	PingDelay	= 6,
     64 	UtfSz		= 4,
     65 	RuneInvalid	= 0xFFFD,
     66 };
     67 
     68 typedef wchar_t Rune;
     69 
     70 static struct {
     71 	size_t x, y;
     72 	WINDOW *sw, *mw, *iw;
     73 } scr;
     74 
     75 static struct Chan {
     76 	char name[ChanLen];
     77 	char *buf, *eol;
     78 	int n;     /* Scroll offset. */
     79 	size_t sz; /* Size of buf. */
     80 	char high; /* Nick highlight. */
     81 	char new;  /* New message. */
     82 	char join; /* Channel was 'j'-oined. */
     83 } chl[MaxChans];
     84 
     85 static int ssl = 1;
     86 static int sslverify = 1;
     87 static struct {
     88 	int fd;
     89 	SSL *ssl;
     90 	SSL_CTX *ctx;
     91 } srv;
     92 static char nick[64];
     93 static char cert[PATH_MAX];
     94 static char key[512];
     95 static int quit, winchg;
     96 static int nch, ch; /* Current number of channels, and current channel. */
     97 static char outb[BufSz], *outp = outb; /* Output buffer. */
     98 static FILE *logfp;
     99 
    100 static unsigned char utfbyte[UtfSz + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
    101 static unsigned char utfmask[UtfSz + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
    102 static Rune utfmin[UtfSz + 1] = {       0,    0,  0x80,  0x800,  0x10000};
    103 static Rune utfmax[UtfSz + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
    104 
    105 static void scmd(char *, char *, char *, char *);
    106 static void tdrawbar(void);
    107 static void tredraw(void);
    108 static void treset(void);
    109 
    110 static void
    111 panic(const char *m)
    112 {
    113 	treset();
    114 	fprintf(stderr, "Panic: %s\n", m);
    115 	exit(1);
    116 }
    117 
    118 static size_t
    119 utf8validate(Rune *u, size_t i)
    120 {
    121 	if (*u < utfmin[i] || *u > utfmax[i] || (0xD800 <= *u && *u <= 0xDFFF))
    122 		*u = RuneInvalid;
    123 	for (i = 1; *u > utfmax[i]; ++i);
    124 	return i;
    125 }
    126 
    127 static Rune
    128 utf8decodebyte(unsigned char c, size_t *i)
    129 {
    130 	for (*i = 0; *i < UtfSz + 1; ++(*i))
    131 		if ((c & utfmask[*i]) == utfbyte[*i])
    132 			return c & ~utfmask[*i];
    133 	return 0;
    134 }
    135 
    136 static size_t
    137 utf8decode(char *c, Rune *u, size_t clen)
    138 {
    139 	size_t i, j, len, type;
    140 	Rune udecoded;
    141 
    142 	*u = RuneInvalid;
    143 	if (!clen)
    144 		return 0;
    145 	udecoded = utf8decodebyte(c[0], &len);
    146 	if (len < 1 || len > UtfSz)
    147 		return 1;
    148 	for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
    149 		udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
    150 		if (type != 0)
    151 			return j;
    152 	}
    153 	if (j < len)
    154 		return 0;
    155 	*u = udecoded;
    156 	utf8validate(u, len);
    157 	return len;
    158 }
    159 
    160 static int
    161 is_empty(const char *str) {
    162 	return (str == NULL || str[0] == '\0');
    163 }
    164 
    165 static char *
    166 b64_enc(const unsigned char *in, size_t len)
    167 {
    168 	static char out[BufSz];
    169 	const char *tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    170 	char *p = out;
    171 	size_t i;
    172 
    173 	for (i = 0; i < len; i += 3) {
    174 		unsigned int v = in[i] << 16 | (i+1 < len ? in[i+1] << 8 : 0) | (i+2 < len ? in[i+2] : 0);
    175 		*p++ = tab[(v >> 18) & 0x3F];
    176 		*p++ = tab[(v >> 12) & 0x3F];
    177 		*p++ = (i+1 < len) ? tab[(v >> 6) & 0x3F] : '=';
    178 		*p++ = (i+2 < len) ? tab[v & 0x3F] : '=';
    179 	}
    180 	*p = '\0';
    181 	return out;
    182 }
    183 
    184 static void
    185 sndf(const char *fmt, ...)
    186 {
    187 	va_list vl;
    188 	size_t n, l = BufSz - (outp - outb);
    189 
    190 	if (l < 2)
    191 		return;
    192 	va_start(vl, fmt);
    193 	n = vsnprintf(outp, l - 2, fmt, vl);
    194 	va_end(vl);
    195 	outp += n > l - 2 ? l - 2 : n;
    196 	*outp++ = '\r';
    197 	*outp++ = '\n';
    198 }
    199 
    200 static int
    201 srd(void)
    202 {
    203 	static char l[BufSz], *p = l;
    204 	char *s, *usr, *cmd, *par, *data;
    205 	int rd;
    206 
    207 	if (p - l >= BufSz)
    208 		p = l; /* Input buffer overflow, there should something better to do. */
    209 	if (ssl)
    210 		rd = SSL_read(srv.ssl, p, BufSz - (p - l));
    211 	else
    212 		rd = read(srv.fd, p, BufSz - (p - l));
    213 	if (rd <= 0) return 0;
    214 	p += rd;
    215 	for (;;) { /* Cycle on all received lines. */
    216 		if (!(s = memchr(l, '\n', p - l)))
    217 			return 1;
    218 		if (s > l && s[-1] == '\r')
    219 			s[-1] = 0;
    220 		*s++ = 0;
    221 		if (*l == ':') {
    222 			if (!(cmd = strchr(l, ' ')))
    223 				goto lskip;
    224 			*cmd++ = 0;
    225 			usr = l + 1;
    226 		} else {
    227 			usr = 0;
    228 			cmd = l;
    229 		}
    230 		if (!(par = strchr(cmd, ' ')))
    231 			goto lskip;
    232 		*par++ = 0;
    233 		if ((data = strchr(par, ':')))
    234 			*data++ = 0;
    235 		scmd(usr, cmd, par, data);
    236 	lskip:
    237 		memmove(l, s, p - s);
    238 		p -= s - l;
    239 	}
    240 }
    241 
    242 static void
    243 sinit(const char *nick, const char *user)
    244 {
    245 	sndf("CAP LS 302");
    246 	sndf("NICK %s", nick);
    247 	sndf("USER %s 8 * :%s", user, user);
    248 }
    249 
    250 static char *
    251 dial(const char *host, const char *service)
    252 {
    253 	struct addrinfo hints, *res = NULL, *rp;
    254 	int fd = -1;
    255 
    256 	memset(&hints, 0, sizeof(hints));
    257 	hints.ai_family = AF_UNSPEC;     /* allow IPv4 or IPv6 */
    258 	hints.ai_flags = AI_NUMERICSERV; /* avoid name lookup for port */
    259 	hints.ai_socktype = SOCK_STREAM;
    260 	if (getaddrinfo(host, service, &hints, &res))
    261 		return "Getaddrinfo failed.";
    262 	for (rp = res; rp; rp = rp->ai_next) {
    263 		fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
    264 		if (fd == -1)
    265 			continue;
    266 		if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0)
    267 			break;
    268 		close(fd);
    269 		fd = -1;
    270 	}
    271 	freeaddrinfo(res);
    272 	if ((srv.fd = fd) == -1)
    273 		return "Cannot connect to host.";
    274 	if (ssl) {
    275 		SSL_library_init();
    276 		SSL_load_error_strings();
    277 		srv.ctx = SSL_CTX_new(TLS_client_method());
    278 		if (!srv.ctx) return "Could not initialize ssl context.";
    279 		if (sslverify) {
    280 			SSL_CTX_set_verify(srv.ctx, SSL_VERIFY_PEER, NULL);
    281 			if (SSL_CTX_set_default_verify_paths(srv.ctx) != 1)
    282 				return "Could not load default system trust store.";
    283 		}
    284 		srv.ssl = SSL_new(srv.ctx);
    285 		if (!srv.ssl) return "Could not create SSL object.";
    286 		if (sslverify) {
    287 			SSL_set_hostflags(srv.ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
    288 			if (!SSL_set1_host(srv.ssl, host))
    289 				return "Could not set expected hostname.";
    290 			SSL_set_tlsext_host_name(srv.ssl, host);
    291 		} else {
    292 			SSL_CTX_set_verify(srv.ctx, SSL_VERIFY_NONE, NULL);
    293 		}
    294 		if (!is_empty(cert)) {
    295 		  if (SSL_use_certificate_chain_file(srv.ssl, cert) <= 0)
    296 				return "Failed to load certificate chain.";
    297 			if (SSL_use_PrivateKey_file(srv.ssl, cert, SSL_FILETYPE_PEM) <= 0)
    298 				return "Failed to load private key.";
    299 		}
    300 		SSL_set_fd(srv.ssl, srv.fd);
    301 		if (SSL_connect(srv.ssl) <= 0)  {
    302 			long verify_err = SSL_get_verify_result(srv.ssl);
    303 			if (verify_err != X509_V_OK)
    304 				return (char *)X509_verify_cert_error_string(verify_err);
    305 			return "SSL handshake failed.";
    306 		}
    307 	}
    308 	return 0;
    309 }
    310 
    311 static void
    312 hangup(void)
    313 {
    314 	if (srv.ssl) SSL_shutdown(srv.ssl);
    315 	SSL_free(srv.ssl);
    316 	if (srv.fd) close(srv.fd);
    317 	SSL_CTX_free(srv.ctx);
    318 	memset(&srv, 0, sizeof(srv));
    319 }
    320 
    321 static inline int
    322 chfind(const char *name)
    323 {
    324 	int i;
    325 
    326 	assert(name);
    327 	for (i = nch - 1; i > 0; i--)
    328 		if (!strcmp(chl[i].name, name))
    329 			break;
    330 	return i;
    331 }
    332 
    333 static int
    334 chadd(const char *name, int joined)
    335 {
    336 	int n;
    337 
    338 	if (nch >= MaxChans || strlen(name) >= ChanLen)
    339 		return -1;
    340 	if ((n = chfind(name)) > 0)
    341 		return n;
    342 	strlcpy(chl[nch].name, name, sizeof(chl[nch].name));
    343 	chl[nch].sz = LogSz;
    344 	chl[nch].buf = malloc(LogSz);
    345 	if (!chl[nch].buf)
    346 		panic("Out of memory.");
    347 	chl[nch].eol = chl[nch].buf;
    348 	chl[nch].n = 0;
    349 	chl[nch].join = joined;
    350 	if (joined)
    351 		ch = nch;
    352 	nch++;
    353 	tdrawbar();
    354 	return nch - 1;
    355 }
    356 
    357 static int
    358 chdel(char *name)
    359 {
    360 	int n = chfind(name);
    361 	if (n <= 0) return 0;
    362 	free(chl[n].buf);
    363 
    364 	if (n < nch - 1)
    365 		memmove(&chl[n], &chl[n + 1], (nch - n - 1) * sizeof(struct Chan));
    366 	nch--;
    367 	if (ch > n || ch >= nch)
    368 		ch = (ch > 0) ? ch - 1 : 0;
    369 	tdrawbar();
    370 	return 1;
    371 }
    372 
    373 static char *
    374 pushl(char *p, char *e)
    375 {
    376 	size_t x;
    377 	int cl;
    378 	char *w;
    379 	Rune u[2];
    380 	cchar_t cc;
    381 
    382 	u[1] = 0;
    383 	if ((w = memchr(p, '\n', e - p)))
    384 		e = w + 1;
    385 	w = p;
    386 	x = 0;
    387 	for (;;) {
    388 		if (x >= scr.x) {
    389 			waddch(scr.mw, '\n');
    390 			for (x = 0; x < INDENT; x++)
    391 				waddch(scr.mw, ' ');
    392 			if (*w == ' ')
    393 				w++;
    394 			x += p - w;
    395 		}
    396 		if (p >= e || *p == ' ' || p - w + INDENT >= (ptrdiff_t)scr.x - 1) {
    397 			while (w < p) {
    398 				w += utf8decode(w, u, UtfSz);
    399 				if (wcwidth(*u) > 0 || *u == '\n') {
    400 					setcchar(&cc, u, 0, 0, 0);
    401 					wadd_wch(scr.mw, &cc);
    402 				}
    403 			}
    404 			if (p >= e) return e;
    405 		}
    406 		p += utf8decode(p, u, UtfSz);
    407 		if ((cl = wcwidth(*u)) >= 0)
    408 			x += cl;
    409 	}
    410 }
    411 
    412 static void
    413 pushf(int cn, const char *fmt, ...)
    414 {
    415 	struct Chan *const c = &chl[cn];
    416 	size_t n, blen = c->eol - c->buf;
    417 	va_list vl;
    418 	time_t t;
    419 	char *s;
    420 	struct tm *tm, *gmtm;
    421 
    422 	if (blen + LineLen >= c->sz) {
    423 		c->sz *= 2;
    424 		c->buf = realloc(c->buf, c->sz);
    425 		if (!c->buf)
    426 			panic("Out of memory.");
    427 		c->eol = c->buf + blen;
    428 	}
    429 	t = time(0);
    430 	if (!(tm = localtime(&t)))
    431 		panic("Localtime failed.");
    432 	n = strftime(c->eol, LineLen, DATEFMT, tm);
    433 	if (!(gmtm = gmtime(&t)))
    434 		panic("Gmtime failed.");
    435 	c->eol[n++] = ' ';
    436 	va_start(vl, fmt);
    437 	s = c->eol + n;
    438 	n += vsnprintf(s, LineLen - n - 1, fmt, vl);
    439 	va_end(vl);
    440 	if (logfp) {
    441 		fprintf(logfp, "%-12.12s\t%04d-%02d-%02dT%02d:%02d:%02dZ\t%s\n",
    442 			c->name,
    443 			gmtm->tm_year + 1900, gmtm->tm_mon + 1, gmtm->tm_mday,
    444 			gmtm->tm_hour, gmtm->tm_min, gmtm->tm_sec, s);
    445 		fflush(logfp);
    446 	}
    447 	strlcat(c->buf, "\n", c->sz);
    448 	c->eol = c->buf + strlen(c->buf);
    449 	if (cn == ch && c->n == 0) {
    450 		char *p = c->eol - n - 1;
    451 
    452 		if (p != c->buf)
    453 			waddch(scr.mw, '\n');
    454 		pushl(p, c->eol - 1);
    455 		wrefresh(scr.mw);
    456 	}
    457 }
    458 
    459 static void
    460 scmd(char *usr, char *cmd, char *par, char *data)
    461 {
    462 	IrcCmd type = CMD_UNKNOWN;
    463 	int c;
    464 	char *pm = strtok(par, " ");
    465 	char *chan;
    466 
    467 	for (size_t i = 0; i < sizeof(cmd_map)/sizeof(cmd_map[0]); i++) {
    468 		if (!strcmp(cmd, cmd_map[i].name)) {
    469 			type = cmd_map[i].id;
    470 				break;
    471 		}
    472 	}
    473 	if (!usr)
    474 		usr = "?";
    475 	else {
    476 		char *bang = strchr(usr, '!');
    477 		if (bang) *bang = 0;
    478 	}
    479 	switch (type) {
    480 	case PRIVMSG:
    481 		if (!pm || !data) break;
    482 		chan = strchr("&#!+.~", pm[0]) ? pm : usr;
    483 		if (!(c = chfind(chan)) && (c = chadd(chan, 0)) < 0)
    484 			break;
    485 		if (strcasestr(data, nick)) {
    486 			pushf(c, PFMTHIGH, usr, data);
    487 			chl[c].high |= ch != c;
    488 		} else
    489 			pushf(c, PFMT, usr, data);
    490 		if (ch != c) {
    491 			chl[c].new = 1;
    492 			tdrawbar();
    493 		}
    494 		break;
    495 	case PING:
    496 		sndf("PONG :%s", data ? data : "(null)");
    497 		break;
    498 	case PONG:
    499 		break;
    500 	case JOIN:
    501 	case PART:
    502 		if (pm) pushf(chfind(pm), "-!- %s has %s %s",
    503 			usr, (type == JOIN ? "joined" : "left"), pm);
    504 		break;
    505 	case QUIT:
    506 		break;
    507 	case ERR_FORWARD: {
    508 		char *oldch = strtok(NULL, " ");
    509 		char *newch = strtok(NULL, " ");
    510 		if (!oldch || !newch || !(c = chfind(oldch)))
    511 			break;
    512 		chl[c].name[0] = 0;
    513 		strlcat(chl[c].name, newch, sizeof(chl[c].name));
    514 		tdrawbar();
    515 		break;
    516 	}
    517 	case ERR_FULL:
    518 	case ERR_INVITE:
    519 	case ERR_BANNED:
    520 	case ERR_BADKEY:
    521 		if (!(pm = strtok(NULL, " ")))
    522 			break;
    523 		chdel(pm);
    524 		pushf(0, "-!- Cannot join %s (%s)", pm, cmd);
    525 		tredraw();
    526 		break;
    527 	case ERR_NICKNAMEINUSE:
    528 		strlcat(nick, "_", sizeof(nick));
    529 		sndf("NICK %s", nick);
    530 		break;
    531 	case CAP:
    532 		if (!(pm = strtok(NULL, " "))) break;
    533 		if (!strcmp(pm, "LS"))
    534 			sndf((!is_empty(key) || !is_empty(cert)) && data && strstr(data, "sasl") ? "CAP REQ :sasl" : "CAP END");
    535 		else if (!strcmp(pm, "ACK") && data && strstr(data, "sasl"))
    536 			sndf(is_empty(cert) ? "AUTHENTICATE PLAIN" : "AUTHENTICATE EXTERNAL");
    537 		else if (!strcmp(pm, "NAK") || !strcmp(pm, "ACK"))
    538 			sndf("CAP END");
    539 		break;
    540 	case AUTHENTICATE:
    541 		if (!par || *par != '+') break;
    542 		if (!is_empty(cert)) {
    543 			sndf("AUTHENTICATE +");
    544 		} else {
    545 			char raw[512];
    546 			int r = snprintf(raw, sizeof(raw), "%s%c%s%c%s", nick, 0, nick, 0, key);
    547 			sndf("AUTHENTICATE %s", b64_enc((unsigned char *)raw, r));
    548 		}
    549 		break;
    550 	case SASL_OK:
    551 	case SASL_ERR:
    552 		sndf("CAP END");
    553 		pushf(0, "-!- SASL auth %s", (type == SASL_OK ? "successful" : "failed!"));
    554 		break;
    555 	default:
    556 		if (isdigit(cmd[0]) || !strcmp(cmd, "NOTICE"))
    557 			pushf(0, "%s", data ? data : "");
    558 		else
    559 			pushf(0, "%s - %s %s", cmd, par, data ? data : "(null)");
    560 		break;
    561 	}
    562 }
    563 
    564 static void
    565 uparse(char *m)
    566 {
    567 	char *p = m;
    568 
    569 	if (!p[0] || (p[1] != ' ' && p[1] != 0)) {
    570 	pmsg:
    571 		if (ch == 0) return;
    572 		m += strspn(m, " ");
    573 		if (!*m) return;
    574 		pushf(ch, PFMT, nick, m);
    575 		sndf("PRIVMSG %s :%s", chl[ch].name, m);
    576 		return;
    577 	}
    578 	switch (*p) {
    579 	case 'j': /* Join channels. */
    580 		p += 1 + (p[1] == ' ');
    581 		for (char *token = strtok(p, " "); token; token = strtok(NULL, " ")) {
    582 			if (chadd(token, 1) < 0)
    583 				break;
    584 			if (strchr("&#!+", token[0]))
    585 				sndf("JOIN %s", token);
    586 		}
    587 		tredraw();
    588 		return;
    589 	case 'l': /* Leave channels. */
    590 		p += 1 + (p[1] == ' ');
    591 		if (!*p) {
    592 			if (ch == 0) return; /* Not in a channel */
    593 			static char buf[ChanLen];
    594 			strlcpy(buf, chl[ch].name, sizeof(buf));
    595 			p = buf;
    596 		}
    597 		for (char *token = strtok(p, " "); token; token = strtok(NULL, " "))
    598 			if (chdel(token) && strchr("&#!+", token[0]))
    599 				sndf("PART %s", token);
    600 		tredraw();
    601 		return;
    602 	case 'm': /* Private message. */
    603 		m = p + 1 + (p[1] == ' ');
    604 		if (!(p = strchr(m, ' ')))
    605 			return;
    606 		*p++ = 0;
    607 		sndf("PRIVMSG %s :%s", m, p);
    608 		return;
    609 	case 'r': /* Send raw. */
    610 		if (p[1])
    611 			sndf("%s", &p[2]);
    612 		return;
    613 	case 'q': /* Quit. */
    614 		quit = 1;
    615 		return;
    616 	default: /* Send on current channel. */
    617 		goto pmsg;
    618 	}
    619 }
    620 
    621 static void
    622 sigwinch(int sig)
    623 {
    624 	if (sig) winchg = 1;
    625 }
    626 
    627 static void
    628 tinit(void)
    629 {
    630 	setlocale(LC_ALL, "");
    631 	signal(SIGWINCH, sigwinch);
    632 	initscr();
    633 	raw();
    634 	noecho();
    635 	getmaxyx(stdscr, scr.y, scr.x);
    636 	if (scr.y < 4)
    637 		panic("Screen too small.");
    638 	if ((scr.sw = newwin(1, scr.x, 0, 0)) == 0
    639 	|| (scr.mw = newwin(scr.y - 2, scr.x, 1, 0)) == 0
    640 	|| (scr.iw = newwin(1, scr.x, scr.y - 1, 0)) == 0)
    641 		panic("Cannot create windows.");
    642 	keypad(scr.iw, 1);
    643 	scrollok(scr.mw, 1);
    644 	if (has_colors() == TRUE) {
    645 		start_color();
    646 		use_default_colors();
    647 		init_pair(1, COLOR_WHITE, COLOR_BLACK);
    648 		wbkgd(scr.sw, COLOR_PAIR(1));
    649 	}
    650 }
    651 
    652 static void
    653 tresize(void)
    654 {
    655 	struct winsize ws;
    656 
    657 	winchg = 0;
    658 	if (ioctl(0, TIOCGWINSZ, &ws) < 0)
    659 		panic("Ioctl (TIOCGWINSZ) failed.");
    660 	if (ws.ws_row <= 2) return;
    661 	resizeterm(scr.y = ws.ws_row, scr.x = ws.ws_col);
    662 	wresize(scr.mw, scr.y - 2, scr.x);
    663 	wresize(scr.iw, 1, scr.x);
    664 	wresize(scr.sw, 1, scr.x);
    665 	mvwin(scr.iw, scr.y - 1, 0);
    666 	tredraw();
    667 	tdrawbar();
    668 }
    669 
    670 static void
    671 tredraw(void)
    672 {
    673 	struct Chan *const c = &chl[ch];
    674 	char *q, *p;
    675 	int nl = -1;
    676 
    677 	if (c->eol == c->buf) {
    678 		wclear(scr.mw);
    679 		wrefresh(scr.mw);
    680 		return;
    681 	}
    682 	p = c->eol - 1;
    683 	if (c->n) {
    684 		int i = c->n;
    685 		for (; p > c->buf; p--)
    686 			if (*p == '\n' && !i--)
    687 				break;
    688 		if (p == c->buf)
    689 			c->n -= i;
    690 	}
    691 	q = p;
    692 	while (nl < (int)scr.y - 2) {
    693 		while (*q != '\n' && q > c->buf)
    694 			q--;
    695 		nl++;
    696 		if (q == c->buf)
    697 			break;
    698 		q--;
    699 	}
    700 	if (q != c->buf)
    701 		q += 2;
    702 	wclear(scr.mw);
    703 	wmove(scr.mw, 0, 0);
    704 	while (q < p)
    705 		q = pushl(q, p);
    706 	wrefresh(scr.mw);
    707 }
    708 
    709 static void
    710 tdrawbar(void)
    711 {
    712 	size_t l;
    713 	int fst = ch;
    714 
    715 	for (l = 0; fst > 0 && l < scr.x / 2; fst--)
    716 		l += strlen(chl[fst].name) + 3;
    717 	werase(scr.sw);
    718 	for (l = 0; fst < nch && l < scr.x; fst++) {
    719 		char *p = chl[fst].name;
    720 		if (fst == ch)
    721 			wattron(scr.sw, A_BOLD);
    722 		waddch(scr.sw, '['), l++;
    723 		if (chl[fst].high)
    724 			waddch(scr.sw, '>'), l++;
    725 		else if (chl[fst].new)
    726 			waddch(scr.sw, '+'), l++;
    727 		for (; *p && l < scr.x; p++, l++)
    728 			waddch(scr.sw, *p);
    729 		if (l < scr.x - 1)
    730 			waddstr(scr.sw, "] "), l += 2;
    731 		if (fst == ch)
    732 			wattroff(scr.sw, A_BOLD);
    733 	}
    734 	wrefresh(scr.sw);
    735 }
    736 
    737 static void
    738 tgetch(void)
    739 {
    740 	static char l[BufSz];
    741 	static size_t shft, cu, len;
    742 	size_t dirty = len + 1, i;
    743 	int c;
    744 
    745 	c = wgetch(scr.iw);
    746 	switch (c) {
    747 	case CTRL('n'):
    748 		ch = (ch + 1) % nch;
    749 		chl[ch].high = chl[ch].new = 0;
    750 		tdrawbar();
    751 		tredraw();
    752 		return;
    753 	case CTRL('p'):
    754 		ch = (ch + nch - 1) % nch;
    755 		chl[ch].high = chl[ch].new = 0;
    756 		tdrawbar();
    757 		tredraw();
    758 		return;
    759 	case KEY_PPAGE:
    760 		chl[ch].n += SCROLL;
    761 		tredraw();
    762 		return;
    763 	case KEY_NPAGE:
    764 		chl[ch].n -= SCROLL;
    765 		if (chl[ch].n < 0)
    766 			chl[ch].n = 0;
    767 		tredraw();
    768 		return;
    769 	case CTRL('a'):
    770 		cu = 0;
    771 		break;
    772 	case CTRL('e'):
    773 		cu = len;
    774 		break;
    775 	case CTRL('b'):
    776 	case KEY_LEFT:
    777 		if (cu)
    778 			cu--;
    779 		break;
    780 	case CTRL('f'):
    781 	case KEY_RIGHT:
    782 		if (cu < len)
    783 			cu++;
    784 		break;
    785 	case CTRL('k'):
    786 		dirty = len = cu;
    787 		break;
    788 	case CTRL('u'):
    789 		if (cu == 0) return;
    790 		len -= cu;
    791 		memmove(l, &l[cu], len);
    792 		dirty = cu = 0;
    793 		break;
    794 	case CTRL('d'):
    795 		if (cu >= len) return;
    796 		memmove(&l[cu], &l[cu + 1], len - cu - 1);
    797 		dirty = cu;
    798 		len--;
    799 		break;
    800 	case CTRL('h'):
    801 	case KEY_BACKSPACE:
    802 		if (cu == 0) return;
    803 		memmove(&l[cu - 1], &l[cu], len - cu);
    804 		dirty = --cu;
    805 		len--;
    806 		break;
    807 	case CTRL('w'):
    808 		if (cu == 0) break;
    809 		i = 1;
    810 		while (l[cu - i] == ' ' && cu - i != 0) i++;
    811 		while (l[cu - i] != ' ' && cu - i != 0) i++;
    812 		if (cu - i != 0) i--;
    813 		memmove(&l[cu - i], &l[cu], len - cu);
    814 		cu -= i;
    815 		dirty = cu;
    816 		len -= i;
    817 		break;
    818 	case '\n':
    819 		l[len] = 0;
    820 		uparse(l);
    821 		dirty = cu = len = 0;
    822 		break;
    823 	default:
    824 		if (c > CHAR_MAX || len >= BufSz - 1)
    825 			return; /* Skip other curses codes. */
    826 		memmove(&l[cu + 1], &l[cu], len - cu);
    827 		dirty = cu;
    828 		len++;
    829 		l[cu++] = c;
    830 		break;
    831 	}
    832 	while (cu < shft)
    833 		dirty = 0, shft -= shft >= scr.x / 2 ? scr.x / 2 : shft;
    834 	while (cu >= scr.x + shft)
    835 		dirty = 0, shft += scr.x / 2;
    836 	if (dirty <= shft)
    837 		i = shft;
    838 	else if (dirty > scr.x + shft || dirty > len)
    839 		goto mvcur;
    840 	else
    841 		i = dirty;
    842 	wmove(scr.iw, 0, i - shft);
    843 	wclrtoeol(scr.iw);
    844 	for (; i - shft < scr.x && i < len; i++)
    845 		waddch(scr.iw, l[i]);
    846 mvcur:	wmove(scr.iw, 0, cu - shft);
    847 }
    848 
    849 static void
    850 treset(void)
    851 {
    852 	if (scr.mw)
    853 		delwin(scr.mw);
    854 	if (scr.sw)
    855 		delwin(scr.sw);
    856 	if (scr.iw)
    857 		delwin(scr.iw);
    858 	endwin();
    859 }
    860 
    861 int
    862 main(int argc, char *argv[])
    863 {
    864 	const char *user = getenv("USER");
    865 	const char *ircnick = getenv("IRCNICK");
    866 	snprintf(key, sizeof(key), "%s", getenv("IRCPASS"));
    867 	const char *server = SRV;
    868 	const char *port = PORT;
    869 	char *err;
    870 	int o, reconn, ping;
    871 
    872 	signal(SIGPIPE, SIG_IGN);
    873 	while ((o = getopt(argc, argv, "hTVn:c:u:s:p:l:")) >= 0)
    874 		switch (o) {
    875 		case 'h':
    876 		case '?':
    877 		usage:
    878 			fputs("usage: nio [-n NICK] [-u USER] [-s SERVER] [-p PORT] [-l LOGFILE ] [-c certficiate] [-hTV]\n", stderr);
    879 			exit(0);
    880 		case 'l':
    881 			if (!(logfp = fopen(optarg, "a")))
    882 				panic("fopen: logfile");
    883 			break;
    884 		case 'n':
    885 			if (strlcpy(nick, optarg, sizeof(nick)) >= sizeof(nick))
    886 				goto usage;
    887 			break;
    888 		case 'T':
    889 			ssl = 0;
    890 			break;
    891 		case 'V':
    892 			sslverify = 0;
    893 			break;
    894 		case 'u':
    895 			user = optarg;
    896 			break;
    897 		case 's':
    898 			server = optarg;
    899 			break;
    900 		case 'p':
    901 			port = optarg;
    902 			break;
    903 		case 'c':
    904 			if (strlcpy(cert, optarg, sizeof(cert)) >= sizeof(cert))
    905 				goto usage;
    906 			break;
    907 		}
    908 	if (!user)
    909 		user = "anonymous";
    910 	if (!nick[0] && ircnick)
    911 		strlcpy(nick, ircnick, sizeof(nick));
    912 	if (!nick[0])
    913 		strlcpy(nick, user, sizeof(nick));
    914 	if (!nick[0])
    915 		goto usage;
    916 	tinit();
    917 	err = dial(server, port);
    918 	if (err) panic(err);
    919 	chadd(server, 0);
    920 	sinit(nick, user);
    921 	reconn = 0;
    922 	ping = 0;
    923 	while (!quit) {
    924 		struct timeval t = {.tv_sec = 5};
    925 		struct Chan *c;
    926 		fd_set rfs, wfs;
    927 		int ret;
    928 		if (winchg)
    929 			tresize();
    930 		FD_ZERO(&wfs);
    931 		FD_ZERO(&rfs);
    932 		FD_SET(0, &rfs);
    933 		if (!reconn) {
    934 			FD_SET(srv.fd, &rfs);
    935 			if (outp != outb)
    936 				FD_SET(srv.fd, &wfs);
    937 		}
    938 		ret = select(srv.fd + 1, &rfs, &wfs, 0, &t);
    939 		if (ret < 0) {
    940 			if (errno == EINTR)
    941 				continue;
    942 			panic("Select failed.");
    943 		}
    944 		if (reconn) {
    945 			hangup();
    946 			if (reconn++ == MaxRecons + 1)
    947 				panic("Link lost.");
    948 			pushf(0, "-!- Link lost, attempting reconnection...");
    949 			if (dial(server, port) != 0)
    950 				continue;
    951 			sinit(nick, user);
    952 			for (c = chl; c < &chl[nch]; ++c)
    953 				if (c->join)
    954 					sndf("JOIN %s", c->name);
    955 			reconn = 0;
    956 		}
    957 		if (FD_ISSET(srv.fd, &rfs)) {
    958 			if (!srd()) {
    959 				reconn = 1;
    960 				continue;
    961 			}
    962 		}
    963 		if (FD_ISSET(srv.fd, &wfs)) {
    964 			int wr;
    965 
    966 			if (ssl)
    967 				wr = SSL_write(srv.ssl, outb, outp - outb);
    968 			else
    969 				wr = write(srv.fd, outb, outp - outb);
    970 			if (wr <= 0) {
    971 				reconn = wr < 0;
    972 				continue;
    973 			}
    974 			outp -= wr;
    975 			memmove(outb, outb + wr, outp - outb);
    976 		}
    977 		if (FD_ISSET(0, &rfs)) {
    978 			tgetch();
    979 			wrefresh(scr.iw);
    980 		}
    981 		if (!FD_ISSET(srv.fd, &wfs))
    982 		if (!FD_ISSET(srv.fd, &rfs))
    983 		if (outp == outb)
    984 		if (++ping == PingDelay) {
    985 			sndf("PING %s", server);
    986 			ping = 0;
    987 		}
    988 	}
    989 	hangup();
    990 	while (nch--)
    991 		free(chl[nch].buf);
    992 	treset();
    993 	exit(0);
    994 }