1#include <u.h>
2#include <libc.h>
3#include <bio.h>
4#include <draw.h>
5
6char help[] =
7"cmd explanation/example\n"
8"--------------------------------------------\n"
9"/m privmsg #chan/nick message\n"
10"/M mode #chan +nt\n"
11"/j join #chan\n"
12"/p part #chan\n"
13"/q send parameters raw to the server\n"
14"/l list #chan\n"
15"/n nick newnick\n"
16"/N notice #chan/nick message\n"
17"/t set victim\n"
18"/T topic #chan newtopic\n"
19"/W whois nick\n"
20"/w who nick (a shorter whois)\n";
21
22#define NPAR 14
23
24enum state { Cmd, Prefix, Middle, Trail, Ok };
25
26typedef struct handler Handler;
27
28struct handler {
29 char *cmd;
30 int (*fun)(int fd, char *pre, char *cmd, char *par[]);
31};
32
33QLock lck;
34int server_in;
35int server_out;
36int scr;
37char *victim;
38char *nick;
39char *unick;
40int inacme; /* running in acme? */
41int linewidth; /* terminal width in # of characters */
42
43int replay; /* just print the log ma'am */
44
45void setwintitle(char *chan);
46
47int rtcs(int fd, char *cset);
48int wtcs(int fd, char *cset);
49int follow(int fd);
50void getwidth(void); /* establish the width of the terminal, from mc.c */
51
52int pmsg(int fd, char *pre, char *cmd, char *par[]);
53int ntc(int fd, char *pre, char *cmd, char *par[]);
54int generic(int fd, char *pre, char *cmd, char *par[]);
55int misc(int fd, char *pre, char *cmd, char *par[]);
56int numeric(int fd, char *pre, char *cmd, char *par[]);
57
58Handler handlers[] = {
59 {"PRIVMSG", pmsg},
60 {"NOTICE", ntc},
61 {"JOIN", misc},
62 {"PART", misc},
63 {"MODE", misc},
64 {"QUIT", misc},
65 {"TOPIC", misc},
66 {"332", numeric},
67 {"333", numeric},
68 {"352", numeric},
69 {"315", numeric},
70 {"311", numeric},
71 {"319", numeric},
72 {"312", numeric},
73 {"320", numeric},
74 {"317", numeric},
75 {"318", numeric},
76 {nil, nil}
77};
78
79int srvparse(char *line, char **pre, char **cmd, char *par[], int npar);
80int usrparse(char *ln, char *cmd, char *par[], int npar);
81
82void
83usage(void)
84{
85 char usage[] = "usage: irc [-S] [-c charset] [-t victim] [-b lines] [-n nick] [-r file] [/srv/irc] [/tmp/irc]\n";
86 write(1, usage, sizeof(usage)-1);
87 exits("usage");
88}
89
90void
91setwintitle(char *chan)
92{
93 int fd;
94
95 // if /dev/acme doesn't exist you're probably in a normal window
96 // otherwise you can't set the title in a windowless window
97 if ((fd = open("/dev/acme/ctl", OWRITE)) >= 0) {
98 fprint(fd, "name -IRC/%s/guide\n", chan);
99 close(fd);
100 inacme = 1;
101 } else if ((fd = open("/dev/label", OWRITE)) >= 0) {
102 fprint(fd, "%s", chan);
103 close(fd);
104 }
105}
106
107/* try to find out whether we're running in acme's win -e */
108int
109testacme(void)
110{
111 return access("/dev/acme", OREAD) >= 0 ? 1 : 0;
112}
113
114void
115usrin(void)
116{
117 char *line, *p;
118 char *par[2];
119 char cmd;
120 int n, i;
121
122 Biobuf kbd;
123 Binit(&kbd, 0, OREAD);
124 while ((line = Brdstr(&kbd, '\n', 0)) != nil) {
125 n = utflen(line);
126 if(!inacme) {
127 p = malloc(n);
128 for (i = 0; i < n; ++i)
129 p[i] = '\b';
130 write(scr, p, i);
131 free(p);
132 }
133 qlock(&lck);
134 if (!usrparse(line, &cmd, par, 2)) {
135 switch(cmd) {
136 case 'q': /* quote, just send the params ... */
137 if(par[0]) {
138 fprint(server_out, "%s %s\r\n", par[0], par[1] ? par[1] : "");
139 } else {
140 fprint(scr, "/q %s %s: not enough arguments\n", par[0], par[1]);
141 }
142 break;
143 case 'M':
144 if(par[0] && par[1]) {
145 fprint(server_out, "MODE %s %s\r\n", par[0], par[1]);
146 } else {
147 fprint(scr, "/M %s %s: not enough arguments\n", par[0], par[1]);
148 }
149 break;
150 case 'm':
151 if(par[0] && par[1]) {
152 fprint(server_out, "PRIVMSG %s :%s\r\n", par[0], par[1]);
153 } else {
154 fprint(scr, "/m %s %s: not enough arguments\n", par[0], par[1]);
155 }
156 break;
157 case 't':
158 if(par[0] != nil) {
159 free(victim);
160 victim = strdup(par[0]);
161 setwintitle(par[0]);
162 }
163 fprint(scr, "*** default victim set to '%s'\n", par[0]);
164 break;
165 case 'T':
166 if(par[0] == nil)
167 fprint(server_out, "TOPIC %s\r\n", victim);
168 else if(par[1] == nil)
169 fprint(server_out, "TOPIC %s\r\n", par[0]);
170 else
171 fprint(server_out, "TOPIC %s :%s\r\n", par[0], par[1]);
172 break;
173 case 'j':
174 fprint(server_out, "JOIN %s\r\n", par[0] == nil ? victim : par[0]);
175 break;
176 case 'p':
177 fprint(server_out, "PART %s\r\n", par[0] == nil ? victim : par[0]);
178 break;
179 case 'n':
180 if(par[0] != nil) {
181 fprint(server_out, "NICK %s\r\n", par[0]);
182 free(nick);
183 nick = strdup(par[0]);
184 unick = strdup(par[0]);
185 } else {
186 fprint(scr, "%s", help);
187 }
188 break;
189 case 'N':
190 if(par[1] != nil)
191 fprint(server_out, "NOTICE %s :%s\r\n", par[0] == nil ? victim : par[0], par[1]);
192 break;
193 case 'W':
194 fprint(server_out, "WHOIS %s %s\r\n", par[0] == nil ? victim : par[0], par[0]);
195 case 'w':
196 fprint(server_out, "WHO %s\r\n", par[0] == nil ? victim : par[0]);
197 break;
198 case 'l':
199 fprint(server_out, "LIST %s\r\n", par[0] == nil ? victim : par[0]);
200 break;
201 case 'L':
202 fprint(server_out, "NAMES %s\r\n", par[0] == nil ? victim : par[0]);
203 break;
204 case 'f':
205 break;
206 case 'h':
207 case 'H':
208 fprint(scr, "%s", help);
209 break;
210 }
211 } else {
212 fprint(scr, "%s", help);
213 }
214 qunlock(&lck);
215 free(line);
216 }
217 exits(0);
218}
219
220void
221srvin(void)
222{
223 char *line;
224 char *pre, *cmd, *par[NPAR];
225 Biobuf srv;
226 Binit(&srv, server_in, OREAD);
227
228 while ((line = Brdstr(&srv, '\n', 0)) != nil) {
229 if (!srvparse(line, &pre, &cmd, par, NPAR)) {
230 Handler *hp = handlers;
231 qlock(&lck);
232 while (hp->cmd != nil) {
233 if (!strcmp(hp->cmd, cmd)) {
234 hp->fun(server_out, pre, cmd, par);
235 break;
236 }
237 ++hp;
238 }
239 if (hp->cmd == nil)
240 generic(server_out, pre, cmd, par);
241 qunlock(&lck);
242 }
243 free(line);
244 }
245}
246
247void
248replayfile(void)
249{
250 char *line;
251 char *pre, *cmd, *par[NPAR];
252 Biobuf srv;
253 Binit(&srv, server_in, OREAD);
254
255 while ((line = Brdstr(&srv, '\n', 0)) != nil) {
256 if (!srvparse(line, &pre, &cmd, par, NPAR)) {
257 Handler *hp = handlers;
258 qlock(&lck);
259 while (hp->cmd != nil) {
260 if (!strcmp(hp->cmd, cmd)) {
261 hp->fun(server_out, pre, cmd, par);
262 break;
263 }
264 ++hp;
265 }
266 if (hp->cmd == nil)
267 generic(server_out, pre, cmd, par);
268 qunlock(&lck);
269 }
270 free(line);
271 }
272}
273
274/*
275 * display the last N lines from the conversation
276 * if we have a default target only the conversation with
277 * that target will be shown
278 */
279void
280seekback(int fd, int lines)
281{
282 Biobuf srv;
283 int found = 0, off;
284 char c, *line;
285
286 if(lines < 0)
287 return;
288
289 Binit(&srv, fd, OREAD);
290
291 Bseek(&srv, -2, 2);
292 while(((off = Boffset(&srv)) > 0) && found < lines) {
293 c = Bgetc(&srv);
294 Bungetc(&srv);
295 if(c == '\n') {
296 Bseek(&srv, 1, 1);
297 line = Brdstr(&srv, '\n', '\0');
298 if(victim) {
299 if(cistrstr(line, victim))
300 found++;
301 } else {
302 found++;
303 }
304 free(line);
305 }
306 Bseek(&srv, off-1, 0);
307 }
308
309 Bterm(&srv);
310}
311
312void
313main(int argc, char *argv[])
314{
315 char *charset = nil;
316 char buf[32], buf2[32], *out = nil, *in = nil;
317 char *user;
318 int olduser = 0;
319 char *arg;
320 int sb = 10; /* how many lines are we displaying initially */
321 int uipid;
322 int scroll = 1;
323 int wctlfd;
324
325 unick = nil;
326 user = getuser();
327 ARGBEGIN {
328 case 't':
329 victim = strdup(EARGF(usage()));
330 break;
331 case 'b':
332 arg = ARGF();
333 if(arg != nil && arg[0] != '-')
334 sb = atoi(arg);
335 else
336 sb = 0; /* show all text */
337 break;
338 case 'c':
339 charset = EARGF(usage());
340 break;
341 case 'r':
342 replay = 1;
343 sb = 0;
344 break;
345 case 'o':
346 olduser = 1;
347 break;
348 case 'n':
349 unick = strdup(EARGF(usage()));
350 break;
351 case 'S':
352 scroll = 0;
353 break;
354 default:
355 usage();
356 } ARGEND;
357
358 switch(argc) {
359 case 0:
360 break;
361 case 1:
362 if(replay)
363 in = argv[0];
364 else
365 out = argv[0];
366 break;
367 case 2:
368 out = argv[0];
369 in = argv[1];
370 break;
371 default:
372 usage();
373 }
374
375 if(out == nil && in == nil){
376 if(olduser == 1 && strlen(user) > 4)
377 user[4] = '\0';
378 snprint(buf, sizeof buf, "/srv/%sirc", user);
379 snprint(buf2, sizeof buf, "/tmp/%sirc", user);
380 out = strdup(buf);
381 in = strdup(buf2);
382 }
383 if(unick == nil)
384 unick = strdup(user);
385
386 if(scroll == 1){
387 if((wctlfd = open("/dev/wctl", OWRITE)) < 0)
388 print("irc: could not open /dev/wctl: %r\n");
389 else {
390 fprint(wctlfd, "scroll\n");
391 close(wctlfd);
392 }
393 }
394
395 if(!replay && (server_out = open(out, OWRITE)) < 0)
396 sysfatal("open write: %s %r", out);
397 if ((server_in = open(in, OREAD)) < 0)
398 sysfatal("open read: %s %r", in);
399
400 inacme = testacme();
401 getwidth();
402
403 if(sb)
404 seekback(server_in, sb);
405
406 while(read(server_in, buf, 1) > 0)
407 if(*buf == '\n')
408 break;
409
410 if(victim && cistrncmp(victim, "MSGS", 4)){
411 setwintitle(victim);
412 fprint(server_out, "JOIN %s\r\n", victim);
413 }
414 scr = 1;
415
416 server_in = follow(server_in);
417
418 if (charset != nil && strcmp(charset, "utf")) {
419 server_out = wtcs(server_out, charset);
420 server_in = rtcs(server_in, charset);
421 }
422
423 if(replay) {
424 replayfile();
425 } else {
426 if ((uipid = rfork(RFPROC|RFFDG|RFMEM)) == 0)
427 srvin();
428
429 usrin();
430
431 postnote(PNPROC, uipid, "kill");
432 while (waitpid() != uipid);
433 }
434
435 exits(0);
436}
437
438int
439wtcs(int fd, char *cset)
440{
441 int totcs[2];
442 int pid;
443
444 pipe(totcs);
445
446 pid = fork();
447
448 if (pid == 0) {
449 dup(totcs[0], 0);
450 dup(fd, 1);
451 close(totcs[1]);
452 execl("/bin/tcs", "tcs", "-f", "utf", "-t", cset, nil);
453 exits("execfailure");
454 }
455 close(totcs[0]);
456
457 return totcs[1];
458}
459
460int
461rtcs(int fd, char *cset)
462{
463 int fromtcs[2];
464 int pid;
465
466 pipe(fromtcs);
467
468 pid = fork();
469
470 if (pid == 0){
471 dup(fromtcs[1], 1);
472 dup(fd, 0);
473 close(fromtcs[0]);
474 execl("/bin/tcs", "tcs", "-f", cset, "-t", "utf", nil);
475 exits("execfailure");
476 }
477 close(fromtcs[1]);
478
479 return fromtcs[0];
480}
481
482int
483follow(int fd)
484{
485 int p[2], pid;
486 long n;
487 char buf[1024];
488 Dir *dp;
489
490 pipe(p);
491
492 pid = fork();
493 if (pid == 0){
494 dup(p[1], 1);
495 dup(fd, 0);
496 close(p[0]);
497 for(;;){
498 while((n = read(0, buf, sizeof(buf))) > 0)
499 write(1, buf, n);
500 sleep(1000);
501 dp = dirfstat(0);
502 free(dp);
503 }
504 }
505 close(p[1]);
506
507 return p[0];
508}
509
510char *
511prenick(char *p)
512{
513 char *n = p;
514 if (p != nil) {
515 while (*p != '\0' && *p != '!') ++p;
516 if (*p != '!')
517 n = nil;
518 *p = '\0';
519 }
520 return n;
521}
522
523int
524pmsg(int, char *pre, char *, char *par[])
525{
526 int n = 0;
527 char buf[8192];
528 //char *c, *tc;
529 char *c;
530 int privmsg = 0;
531
532/*
533 * if sent to victim, or comes from victim to non-channel, print.
534 * otherwise bail out.
535 */
536 pre = prenick(pre);
537 if(victim) {
538 if((cistrncmp(victim, "MSGS", 4) == 0) && *par[0] != '#') {
539 /* catch-all for messages, fall through */
540
541 } else if(cistrcmp(par[0], victim)){
542 if(*par[0] != '#' && cistrcmp(victim, "privmsg") == 0){
543 if(cistrcmp(par[0], unick) == 0 &&
544 cistrcmp(par[0], victim) != 0)
545 privmsg = 1;
546 } else if(!pre || cistrcmp(pre, victim) || *par[0] == '#')
547 return 0;
548 }
549 }
550
551 if(!pre)
552 sprint(buf, "(%s) ⇐ %s\n", par[0], par[1]);
553 else if(privmsg == 1)
554 sprint(buf, "(privmsg) %s → %s\n", pre, par[1]);
555 else if(*par[0] != '#')
556 sprint(buf, "(%s) ⇒ %s\n", pre, par[1]);
557 else
558 sprint(buf, "(%s) %s → %s\n", par[0], pre, par[1]);
559
560 c = buf;
561 n += fprint(scr, "%s", c);
562 return n;
563}
564
565int
566ntc(int, char *pre, char *, char *par[])
567{
568 int n;
569
570/*
571 * if sent to victim, or comes from victim to non-channel, print.
572 * otherwise bail out.
573 */
574 pre = prenick(pre);
575 if(victim && cistrcmp(par[0], victim))
576 if(!pre || cistrcmp(pre, victim) || *par[0] == '#')
577 return 0;
578
579 if(!pre)
580 n = fprint(scr, "[%s] ⇐\t%s\n", par[0], par[1]);
581 else if(*par[0] != '#')
582 n = fprint(scr, "[%s] ⇒\t%s\n", pre, par[1]);
583 else
584 n = fprint(scr, "[%s] %s →\t%s\n", par[0], pre, par[1]);
585 return n;
586}
587
588int
589generic(int, char *pre, char *cmd, char *par[])
590{
591 int i = 0, r;
592 char *n = prenick(pre);
593
594/*
595 * don't print crud on screens with victim set
596 */
597 if(victim)
598 return 0;
599
600 if (n != nil)
601 r = fprint(scr, "%s (%s)\t", cmd, n);
602 else
603 r = fprint(scr, "%s (%s)\t", cmd, par[i++]);
604
605 for (; par[i] != nil; ++i)
606 r += fprint(scr, " %s", par[i]);
607
608 r += fprint(scr, "\n");
609
610 return r;
611}
612
613int
614misc(int, char *pre, char *cmd, char *par[])
615{
616 int i = 0, r;
617 char *n = prenick(pre);
618
619 if(victim != nil)
620 if(cistrcmp(victim, "privmsg") == 0)
621 return 0;
622 if(cistrcmp(cmd,"QUIT"))
623 if(victim && par[0] && cistrcmp(par[0], victim))
624 return 0;
625
626 if (n != nil)
627 r = fprint(scr, "%s (%s)\t", cmd, n);
628 else
629 r = fprint(scr, "%s %s\t", cmd, par[i++]);
630
631 for (; par[i] != nil; ++i)
632 r += fprint(scr, " %s", par[i]);
633
634 r += fprint(scr, "\n");
635
636 return r;
637}
638
639int
640numeric(int, char *pre, char *cmd, char *par[])
641{
642 int i = 0, r;
643 char *n = prenick(pre);
644
645 if(victim != nil)
646 if(cistrcmp(victim, "privmsg") == 0)
647 return 0;
648
649 if(victim && par[1] && cistrcmp(par[1], victim))
650 return 0;
651
652 if (n != nil)
653 r = fprint(scr, "%s (%s)\t", cmd, n);
654 else
655 r = fprint(scr, "%s (%s)\t", cmd, par[i++]);
656
657 for (; par[i] != nil; ++i)
658 r += fprint(scr, " %s", par[i]);
659
660 r += fprint(scr, "\n");
661
662 return r;
663}
664
665int
666usrparse(char *ln, char *cmd, char *par[], int npar)
667{
668 enum state st = Cmd;
669 int i;
670
671 for(i = 0; i < npar; i++)
672 par[i] = nil;
673
674 if (ln[0] == '/' && npar >= 2) {
675 *cmd = ln[1];
676 for (i = 1; ln[i] != '\0'; ++i) {
677 switch(st) {
678 case Cmd:
679 if (ln[i] == ' ') {
680 ln[i] = '\0';
681 par[0] = ln+i+1;
682 st = Middle;
683 } else if(ln[i] == '\n') {
684 /* enable commands with no arguments */
685 ln[i] = '\0';
686 par[0] = nil;
687 par[1] = nil;
688 st = Ok;
689 }
690 break;
691 case Middle:
692 if (ln[i] == '\r' || ln[i] == '\n') {
693 ln[i] = '\0';
694 st = Ok;
695 }
696 if (ln[i] == ' ') {
697 ln[i] = '\0';
698 par[1] = ln+i+1;
699 st = Trail;
700 }
701 break;
702 case Trail:
703 if (ln[i] == '\r' || ln[i] == '\n') {
704 ln[i] = '\0';
705 st = Ok;
706 }
707 break;
708 case Ok:
709 if (ln[i] == '\r' || ln[i] == '\n')
710 ln[i] = '\0';
711 break;
712 }
713 }
714 } else { /* send line to victim by default */
715 st = Ok;
716 *cmd = 'm';
717 for (i = 0; ln[i] != '\0'; ++i)
718 if (ln[i] == '\r' || ln[i] == '\n')
719 ln[i] = '\0';
720 par[0] = victim;
721 par[1] = ln;
722 }
723 return st == Ok ? 0 : 1;
724}
725
726int
727srvparse(char *line, char **pre, char **cmd, char *par[], int npar)
728{
729 int i;
730 char *p;
731 enum state st = Cmd;
732
733 *pre = *cmd = nil;
734
735 for (*cmd = p = line, i = 0; *p != '\0'; ++p) {
736 switch (st) {
737 case Cmd:
738 if (*p == ':') {
739 *p = '\0';
740 *pre = p + 1;
741 st = Prefix;
742 } else if (*p == ' ') {
743 *p = '\0';
744 par[i] = p + 1;
745 st = Middle;
746 }
747 break;
748 case Prefix:
749 if (*p == ' ') {
750 *p = '\0';
751 *cmd = p + 1;
752 st = Cmd;
753 }
754 break;
755 case Middle:
756 if (*p == '\r' || *p == '\n') {
757 *p = '\0';
758 st = Ok;
759 } else if (*p == ':') {
760 *p = '\0';
761 par[i] = p + 1;
762 st = Trail;
763 } else if (*p == ' ') {
764 *p = '\0';
765 i = (i + 1) % npar;
766 par[i] = p + 1;
767 st = Middle;
768 }
769 break;
770 case Trail:
771 if (*p == '\r' || *p == '\n') {
772 *p = '\0';
773 st = Ok;
774 }
775 break;
776 case Ok:
777 *p = '\0';
778 break;
779 }
780 }
781 par[(i + 1) % npar] = nil;
782 return st == Ok ? 0 : 1;
783}
784
785void // this garbage is bullshit
786getwidth(void)
787{
788 Font *font;
789 int n, fd, mintab;
790 char buf[128], *f[10], *p;
791
792 if(inacme){
793 if((fd = open("/dev/acme/ctl", OREAD)) < 0)
794 return;
795 n = read(fd, buf, sizeof buf-1);
796 close(fd);
797 if(n <= 0)
798 return;
799 buf[n] = 0;
800 n = tokenize(buf, f, nelem(f));
801 if(n < 7)
802 return;
803 if((font = openfont(nil, f[6])) == nil)
804 return;
805 mintab = stringwidth(font, "0");
806 linewidth = atoi(f[5]);
807 linewidth = linewidth/mintab;
808 return;
809 }
810
811 if((p = getenv("font")) == nil)
812 return;
813 if((font = openfont(nil, p)) == nil)
814 return;
815 if((fd = open("/dev/window", OREAD)) < 0)
816 return;
817
818 n = read(fd, buf, 5*12);
819 close(fd);
820
821 if(n < 5*12)
822 return;
823
824 buf[n] = 0;
825
826 /* window stucture:
827 4 bit left edge
828 1 bit gap
829 12 bit scrollbar
830 4 bit gap
831 text
832 4 bit right edge
833 */
834// linewidth = atoi(buf+3*12) - atoi(buf+1*12) - (4+1+12+4+4);
835 linewidth = 20000; // update this when screens are more than 20,000 pixels wide
836 mintab = stringwidth(font, "0");
837 linewidth = linewidth/mintab;
838}