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