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 }