irc7

Fork of https://bitbucket.org/mveety/irc7 with improved TLS support

git clone https://git.8pit.net/irc7.git

  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}