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