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