irc.c - irc - A minimalistic IRC client, forked from https://c9x.me/irc/ | |
git clone git://vernunftzentrum.de/irc.git | |
Log | |
Files | |
Refs | |
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 <= 0… | |
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 … | |
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->… | |
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… | |
492 c->name, | |
493 gmtm->tm_year + 1900, gmtm->tm_mon + 1, gmtm->tm… | |
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", … | |
554 } | |
555 strlcpy(nick, data, sizeof(nick)); | |
556 } else { | |
557 pushf(chfind(data), "%s - is now known as %s", u… | |
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 chan… | |
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, "=") || !strcm… | |
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")) { /* J… | |
619 if ((pm = strtok(0, " "))) { | |
620 chdel(pm); | |
621 pushf(0, "-!- Cannot join channel %s (%s)", pm, … | |
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,… | |
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… | |
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 - out… | |
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 } |