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 }