irc.c - irc - IRC client based on c9x.me/irc client | |
git clone git://git.codemadness.org/irc | |
Log | |
Files | |
Refs | |
README | |
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 <= 0… | |
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 … | |
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->… | |
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… | |
413 c->name, | |
414 gmtm->tm_year + 1900, gmtm->tm_mon + 1, gmtm->tm… | |
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")) { /* J… | |
489 if ((pm = strtok(0, " "))) { | |
490 chdel(pm); | |
491 pushf(0, "-!- Cannot join channel %s (%s)", pm, … | |
492 tredraw(); | |
493 } | |
494 } else if (!strcmp(cmd, "QUIT")) { /* Commands we don't care abo… | |
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… | |
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 - out… | |
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 } |