shithub: 9scripts

Download patch

ref: dd77de7a509fa0e8273e3395161e691549edca52
parent: 907105032b0cd6036b42e4e9c7854f03027038ca
author: glenda <glenda@9front.local>
date: Thu Dec 17 16:13:41 EST 2020

added neindaw script

diff: cannot open b/install/irc7/.hg/store/data//null: file does not exist: 'b/install/irc7/.hg/store/data//null' diff: cannot open b/install/irc7/.hg/store//null: file does not exist: 'b/install/irc7/.hg/store//null' diff: cannot open b/install/irc7/.hg//null: file does not exist: 'b/install/irc7/.hg//null' diff: cannot open b/install/irc7//null: file does not exist: 'b/install/irc7//null'
--- /dev/null
+++ b/install/install-neindaw.rc
@@ -1,0 +1,6 @@
+#!/bin/rc
+mkdir -p $home/code
+cd $home/code
+git/clone https://git@git.sr.ht/~ft/neindaw
+cd neindaw
+mk install
binary files /dev/null b/install/irc7/.hg/00changelog.i differ
--- /dev/null
+++ b/install/irc7/.hg/branch
@@ -1,0 +1,1 @@
+default
--- /dev/null
+++ b/install/irc7/.hg/branchheads.cache
@@ -1,0 +1,2 @@
+ac16a1e78cdfc12ffd5af66949e50846e580c5e5 1
+ac16a1e78cdfc12ffd5af66949e50846e580c5e5 default
binary files /dev/null b/install/irc7/.hg/dirstate differ
--- /dev/null
+++ b/install/irc7/.hg/hgrc
@@ -1,0 +1,2 @@
+[paths]
+default = https://code.9front.org/hg/irc7
--- /dev/null
+++ b/install/irc7/.hg/requires
@@ -1,0 +1,3 @@
+revlogv1
+store
+fncache
binary files /dev/null b/install/irc7/.hg/store/00changelog.i differ
binary files /dev/null b/install/irc7/.hg/store/00manifest.i differ
binary files /dev/null b/install/irc7/.hg/store/data/irc.c.i differ
binary files /dev/null b/install/irc7/.hg/store/data/irc.h.i differ
binary files /dev/null b/install/irc7/.hg/store/data/irc.man.i differ
binary files /dev/null b/install/irc7/.hg/store/data/ircsrv.c.i differ
binary files /dev/null b/install/irc7/.hg/store/data/mkfile.i differ
--- /dev/null
+++ b/install/irc7/.hg/store/fncache
@@ -1,0 +1,5 @@
+data/irc.c.i
+data/irc.h.i
+data/irc.man.i
+data/ircsrv.c.i
+data/mkfile.i
binary files /dev/null b/install/irc7/.hg/store/undo differ
--- /dev/null
+++ b/install/irc7/.hg/undo.branch
@@ -1,0 +1,1 @@
+default
\ No newline at end of file
binary files /dev/null b/install/irc7/6.irc differ
binary files /dev/null b/install/irc7/6.ircsrv differ
binary files /dev/null b/install/irc7/irc.6 differ
--- /dev/null
+++ b/install/irc7/irc.c
@@ -1,0 +1,854 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+
+char help[] =
+"cmd		explanation/example\n"
+"--------------------------------------------\n"
+"/m		privmsg #chan/nick message\n"
+"/M		mode #chan +nt\n"
+"/j		join #chan\n"
+"/p		part #chan\n"
+"/q		send parameters raw to the server\n"
+"/l		list #chan\n"
+"/n		nick newnick\n"
+"/N		notice #chan/nick message\n"
+"/t		set victim\n"
+"/T		topic #chan newtopic\n"
+"/W		whois nick\n"
+"/w		who nick (a shorter whois)\n";
+
+#define NPAR	14
+
+enum state { Time, Cmd, Prefix, Middle, Trail, Ok };
+
+typedef struct handler Handler;
+
+struct handler {
+	char *cmd;
+	int (*fun)(int fd, char *time, char *pre, char *cmd, char *par[]);
+};
+
+QLock lck;
+int server_in;
+int server_out;
+int scr;
+char *victim;
+char *nick;
+int inacme;		/* running in acme? */
+int	linewidth; 	/* terminal width in # of characters */
+
+int replay;		/* just print the log ma'am */
+
+void setwintitle(char *chan);
+
+int rtcs(int fd, char *cset);
+int wtcs(int fd, char *cset);
+int follow(int fd);
+void getwidth(void);	/* establish the width of the terminal, from mc.c */
+
+int pmsg(int fd, char *time, char *pre, char *cmd, char *par[]);
+int ntc(int fd, char *time, char *pre, char *cmd, char *par[]);
+int generic(int fd, char *time, char *pre, char *cmd, char *par[]);
+int misc(int fd, char *time, char *pre, char *cmd, char *par[]);
+int numeric(int fd, char *time, char *pre, char *cmd, char *par[]);
+
+Handler handlers[] = {
+	{"PRIVMSG", pmsg},
+	{"NOTICE", ntc},
+	{"JOIN", misc},
+	{"PART", misc},
+	{"MODE", misc},
+	{"QUIT", misc},
+	{"TOPIC", misc},
+	{"332", numeric},
+	{"333", numeric},
+	{"352", numeric},
+	{"315", numeric},
+	{"311", numeric},
+	{"319", numeric},
+	{"312", numeric},
+	{"320", numeric},
+	{"317", numeric},
+	{"318", numeric},
+	{nil, nil}
+};
+
+int srvparse(char *line, char **time, char **pre, char **cmd, char *par[], int npar);
+int usrparse(char *ln, char *cmd, char *par[], int npar);
+
+void
+usage(void)
+{
+	char usage[] = "usage: irc [-c charset] [-t victim] [-b lines] [-r file] [/srv/irc [/tmp/irc]]\n";
+	write(1, usage, sizeof(usage)-1);
+	exits("usage");
+}
+
+void
+setwintitle(char *chan)
+{
+	int fd;
+
+	if ((fd = open("/dev/label", OWRITE)) >= 0) {
+		fprint(fd, "%s", chan);
+		close(fd);
+	}
+	if ((fd = open("/dev/acme/ctl", OWRITE)) >= 0) {
+		fprint(fd, "name -IRC/%s\n", chan);
+		close(fd);
+		inacme = 1;
+	}
+}
+
+/* try to find out whether we're running in acme's win -e */
+int
+testacme(void)
+{
+	return access("/dev/acme", OREAD) >= 0 ? 1 : 0;
+}
+
+void
+usrin(void)
+{
+	char *line, *p;
+	char *par[2];
+	char cmd;
+	int n, i;
+
+	Biobuf kbd;
+	Binit(&kbd, 0, OREAD);
+	while ((line = Brdstr(&kbd, '\n', 0)) != nil) {
+		n = utflen(line);
+		if(!inacme) {
+			p = malloc(n);
+			for (i = 0; i < n; ++i)
+				p[i] = '\b';
+			write(scr, p, i);
+			free(p);
+		}
+		qlock(&lck);
+		if (!usrparse(line, &cmd, par, 2)) {
+			switch(cmd) {
+			case 'q':	/* quote, just send the params ... */
+				if(par[0]) {
+					fprint(server_out, "%s %s\r\n", par[0], par[1] ? par[1] : "");
+				} else {
+					fprint(scr, "/q %s %s: not enough arguments\n", par[0], par[1]);
+				}
+				break;
+			case 'M':
+				if(par[0] && par[1]) {
+					fprint(server_out, "MODE %s %s\r\n", par[0], par[1]);
+				} else {
+					fprint(scr, "/M %s %s: not enough arguments\n", par[0], par[1]);
+				}
+				break;
+			case 'm':
+				if(par[0] && par[1]) {
+					fprint(server_out, "PRIVMSG %s :%s\r\n", par[0], par[1]);
+				} else {
+					fprint(scr, "/m %s %s: not enough arguments\n", par[0], par[1]);
+				}
+				break;
+			case 't':
+				if(par[0] != nil) {
+					free(victim);
+					victim = strdup(par[0]);
+					setwintitle(par[0]);
+				}
+				fprint(scr, "*** default victim set to '%s'\n", par[0]);
+				break;
+			case 'T':
+				if(par[0] == nil) 
+					fprint(server_out, "TOPIC %s\r\n", victim);
+				else if(par[1] == nil)
+					fprint(server_out, "TOPIC %s\r\n", par[0]);
+				else
+					fprint(server_out, "TOPIC %s :%s\r\n", par[0], par[1]);
+				break;
+			case 'j':
+				fprint(server_out, "JOIN %s\r\n", par[0] == nil ? victim : par[0]);
+				break;
+			case 'p':
+				fprint(server_out, "PART %s\r\n", par[0] == nil ? victim : par[0]);
+				break;
+			case 'n':
+				if(par[0] != nil) {
+					fprint(server_out, "NICK %s\r\n", par[0]);
+					free(nick);
+					nick = strdup(par[0]);
+				} else {
+					fprint(scr, "%s", help);
+				}
+				break;
+			case 'N':
+				if(par[1] != nil)
+					fprint(server_out, "NOTICE %s :%s\r\n", par[0] == nil ? victim : par[0], par[1]);
+				break;
+			case 'W':
+				fprint(server_out, "WHOIS %s %s\r\n", par[0] == nil ? victim : par[0], par[0]);
+			case 'w':
+				fprint(server_out, "WHO %s\r\n", par[0] == nil ? victim : par[0]);
+				break;
+			case 'l':
+				fprint(server_out, "LIST %s\r\n", par[0] == nil ? victim : par[0]);
+				break;
+			case 'L':
+				fprint(server_out, "NAMES %s\r\n", par[0] == nil ? victim : par[0]);
+				break;
+			case 'f':
+				break;
+			case 'h':
+			case 'H':
+				fprint(scr, "%s", help);
+				break;
+			}
+		} else {
+			fprint(scr, "%s", help);
+		}
+		qunlock(&lck);
+		free(line);
+	}
+	exits(0);
+}
+
+void
+timestamp(char *logtime, char *scrtime, int maxlen)
+{
+	static char *wday[] = { 
+		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
+	};
+	static char *mon[] = { 
+		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+	};
+	static int day = 0;
+	Tm *t;
+
+	t = localtime(atol(logtime));
+
+	if (t->mday != day) {
+		day = t->mday;
+		fprint(scr, "-- %s, %02d %s %d --\n",
+		       wday[t->wday], t->mday, mon[t->mon],
+		       t->year + 1900);
+	}
+
+	snprint(scrtime, maxlen, "%02d:%02d:%02d",
+	        t->hour, t->min, t->sec);
+}
+
+void
+srvin(void)
+{
+	char *line;
+	char *time, *pre, *cmd, *par[NPAR];
+	char scrtime[32];
+	Biobuf srv;
+	Binit(&srv, server_in, OREAD);
+
+	while ((line = Brdstr(&srv, '\n', 0)) != nil) {
+		if (!srvparse(line, &time, &pre, &cmd, par, NPAR)) {
+			Handler *hp = handlers;
+			qlock(&lck);
+			timestamp(time, scrtime, sizeof(scrtime));
+			while (hp->cmd != nil) {
+				if (!strcmp(hp->cmd, cmd)) {
+					hp->fun(server_out, scrtime, pre, cmd, par);
+					break;
+				}
+				++hp;
+			}
+			if (hp->cmd == nil)
+				generic(server_out, scrtime, pre, cmd, par);
+			qunlock(&lck);
+		}
+		free(line);
+	}
+}
+
+void
+replayfile(void)
+{
+	char *line;
+	char *time, *pre, *cmd, *par[NPAR];
+	char scrtime[32];
+	Biobuf srv;
+	Binit(&srv, server_in, OREAD);
+
+	while ((line = Brdstr(&srv, '\n', 0)) != nil) {
+		if (!srvparse(line, &time, &pre, &cmd, par, NPAR)) {
+			Handler *hp = handlers;
+			qlock(&lck);
+			timestamp(time, scrtime, sizeof(scrtime));
+			while (hp->cmd != nil) {
+				if (!strcmp(hp->cmd, cmd)) {
+					hp->fun(server_out, scrtime, pre, cmd, par);
+					break;
+				}
+				++hp;
+			}
+			if (hp->cmd == nil)
+				generic(server_out, scrtime, pre, cmd, par);
+			qunlock(&lck);
+		}
+		free(line);
+	}
+}
+
+/* 
+ * display the last N lines from the conversation
+ * if we have a default target only the conversation with
+ * that target will be shown
+ */
+void
+seekback(int fd, int lines)
+{
+	Biobuf srv;
+	int found = 0, off;
+	char c, *line;
+
+	if(lines < 0)
+		return;
+
+	Binit(&srv, fd, OREAD);
+
+	Bseek(&srv, -2, 2);
+	while(((off = Boffset(&srv)) > 0) && found < lines) {
+		c = Bgetc(&srv);
+		Bungetc(&srv);
+		if(c == '\n') {
+			Bseek(&srv, 1, 1);
+			line = Brdstr(&srv, '\n', '\0');
+			if(victim) {
+				if(cistrstr(line, victim))
+					found++;
+			} else {
+				found++;
+			}
+			free(line);
+		}
+		Bseek(&srv, off-1, 0);
+	}
+
+	Bterm(&srv);
+}
+
+void
+main(int argc, char *argv[])
+{
+	char *charset = nil;
+	char buf[32], buf2[32], *out = nil, *in = nil;
+	char *arg;
+	int sb = 10;	/* how many lines are we displaying initially */
+	int uipid;
+
+	ARGBEGIN {
+	case 't':
+		victim = strdup(EARGF(usage()));
+		break;
+	case 'b':
+		arg = ARGF();
+		if(arg != nil && arg[0] != '-') 
+			sb = atoi(arg);
+		else 
+			sb = 0;	/* show all text */
+		break;
+	case 'c':
+		charset = EARGF(usage());
+		break;
+	case 'r':
+		replay = 1;
+		sb = 0;
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	switch(argc) {
+	case 0:
+		break;
+	case 1:
+		if(replay)
+			in = argv[0];
+		else 
+			out = argv[0];
+		break;
+	case 2:
+		out = argv[0];
+		in = argv[1];
+		break;
+	default:
+		usage();
+	}
+
+	if(out == nil) {
+		out = getuser();
+		if(strlen(out) > 4)
+			out[4] = 0;
+		snprint(buf, sizeof buf, "/srv/%sirc", out);
+		out = buf;
+	}
+	if(in == nil) {
+		in = getuser();
+		if(strlen(in) > 4)
+			in[4] = 0;
+		snprint(buf2, sizeof buf2, "/tmp/%sirc", in);
+		in = buf2;
+	}
+
+	if(!replay && (server_out = open(out, OWRITE)) < 0)
+			sysfatal("open write: %s %r", out);
+	if ((server_in = open(in, OREAD)) < 0)
+			sysfatal("open read: %s %r", in);
+
+	inacme = testacme();
+	getwidth();
+
+	if(sb)
+		seekback(server_in, sb);
+
+	while(read(server_in, buf, 1) > 0)
+		if(*buf == '\n')
+			break;
+
+	if(victim && cistrncmp(victim, "MSGS", 4)){
+		setwintitle(victim);
+		fprint(server_out, "JOIN %s\r\n", victim);
+	}
+	scr = 1;
+
+	server_in = follow(server_in);
+
+	if (charset != nil && strcmp(charset, "utf")) {
+		server_out = wtcs(server_out, charset);
+		server_in = rtcs(server_in, charset);
+	}
+
+	if(replay) {
+		replayfile();
+	} else {
+		if ((uipid = rfork(RFPROC|RFFDG|RFMEM)) == 0)
+			srvin();
+
+		usrin();
+
+		postnote(PNPROC, uipid, "kill");
+		while (waitpid() != uipid);
+	}
+
+	exits(0);
+}
+
+int
+wtcs(int fd, char *cset)
+{
+	int totcs[2];
+	int pid;
+
+	pipe(totcs);
+
+	pid = fork();
+
+	if (pid == 0) {
+		dup(totcs[0], 0);
+		dup(fd, 1);
+		close(totcs[1]);
+		execl("/bin/tcs", "tcs", "-f", "utf", "-t", cset, nil);
+		exits("execfailure");
+	}
+	close(totcs[0]);
+
+	return totcs[1];
+}
+
+int
+rtcs(int fd, char *cset)
+{
+	int fromtcs[2];
+	int pid;
+
+	pipe(fromtcs);
+
+	pid = fork();
+
+	if (pid == 0){
+		dup(fromtcs[1], 1);
+		dup(fd, 0);
+		close(fromtcs[0]);
+		execl("/bin/tcs", "tcs", "-f", cset, "-t", "utf", nil);
+		exits("execfailure");
+	}
+	close(fromtcs[1]);
+
+	return fromtcs[0];
+}
+
+int
+follow(int fd)
+{
+	int p[2], pid;
+	long n;
+	char buf[1024];
+	Dir *dp;
+
+	pipe(p);
+
+	pid = fork();
+	if (pid == 0){
+		dup(p[1], 1);
+		dup(fd, 0);
+		close(p[0]);
+		for(;;){
+			while((n = read(0, buf, sizeof(buf))) > 0)
+				write(1, buf, n);
+			sleep(1000);
+			dp = dirfstat(0);
+			free(dp);
+		}
+	}
+	close(p[1]);
+
+	return p[0];
+}
+
+char *
+prenick(char *p)
+{
+	char *n = p;
+	if (p != nil) {
+		while (*p != '\0' && *p != '!') ++p;
+		if (*p != '!')
+			n = nil;
+		*p = '\0';
+	}
+	return n;
+}
+
+int
+pmsg(int, char *time, char *pre, char *, char *par[])
+{
+	int n = 0;
+	char *buf;
+	char *c, *tc;
+
+/*
+ *	if sent to victim, or comes from victim to non-channel, print.
+ *	otherwise bail out.
+ */
+	pre = prenick(pre);
+	if(victim) {
+		if((cistrncmp(victim, "MSGS", 4) == 0) && *par[0] != '#') {
+			/* catch-all for messages, fall through */
+		
+		} else if(cistrcmp(par[0], victim))
+			if(!pre || cistrcmp(pre, victim) || *par[0] == '#')
+				return 0;
+	}
+
+	if(!pre)
+		buf = smprint("%s (%s) ⇐ %s\n", time, par[0], par[1]);
+	else if(*par[0] != '#')
+		buf = smprint("%s (%s) ⇒ %s\n", time, pre, par[1]);
+	else
+		buf = smprint("%s %s → %s\n", time, pre, par[1]);
+	
+	if(!buf)
+		sysfatal("failed to allocate space for message: %r\n");
+
+	c = buf;
+again:
+	if(strlen(c) >= linewidth) {
+		for(tc = c + linewidth; tc > c; tc--) {
+			switch(*tc) {
+			case ' ':
+				*tc = '\0';
+				n += fprint(scr, "%s\n", c);
+				c = tc+1;
+				goto again;
+				break;
+			default:
+				break;
+			}
+		}
+	}
+	n += fprint(scr, "%s", c);
+	free(buf);
+	return n;
+}
+
+int
+ntc(int, char *time, char *pre, char *, char *par[])
+{
+	int n;
+
+/*
+ *	if sent to victim, or comes from victim to non-channel, print.
+ *	otherwise bail out.
+ */
+	pre = prenick(pre);
+	if(victim && cistrcmp(par[0], victim))
+		if(!pre || cistrcmp(pre, victim) || *par[0] == '#')
+			return 0;
+
+	if(!pre)
+		n = fprint(scr, "%s [%s] ⇐\t%s\n", time, par[0], par[1]);
+	else if(*par[0] != '#')
+		n = fprint(scr, "%s [%s] ⇒\t%s\n", time, pre, par[1]);
+	else
+		n = fprint(scr, "%s [%s] %s →\t%s\n", time, par[0], pre, par[1]);
+	return n;
+}
+
+int
+generic(int, char *time, char *pre, char *cmd, char *par[])
+{
+	int i = 0, r;
+	char *nick = prenick(pre);
+
+/*
+ *	don't print crud on screens with victim set
+ */
+	if(victim)
+		return 0;
+
+	if (nick != nil) 
+		r = fprint(scr, "%s %s (%s)\t", time, cmd, nick);
+	else
+		r = fprint(scr, "%s %s (%s)\t", time, cmd, par[i++]);
+
+	for (; par[i] != nil; ++i)
+		r += fprint(scr, " %s", par[i]);
+
+	r += fprint(scr, "\n");
+
+	return r;
+}
+
+int
+misc(int, char *time, char *pre, char *cmd, char *par[])
+{
+	int i = 0, r;
+	char *nick = prenick(pre);
+
+	if(cistrcmp(cmd,"QUIT"))
+		if(victim && par[0] && cistrcmp(par[0], victim))
+			return 0;	
+
+	if (nick != nil) 
+		r = fprint(scr, "%s %s (%s)\t", time, cmd, nick);
+	else
+		r = fprint(scr, "%s %s %s\t", time, cmd, par[i++]);
+
+	for (; par[i] != nil; ++i)
+		r += fprint(scr, " %s", par[i]);
+
+	r += fprint(scr, "\n");
+
+	return r;
+}
+
+int
+numeric(int, char *time, char *pre, char *cmd, char *par[])
+{
+	int i = 0, r;
+	char *nick = prenick(pre);
+
+	if(victim && par[1] && cistrcmp(par[1], victim))
+		return 0;
+
+	if (nick != nil) 
+		r = fprint(scr, "%s %s (%s)\t", time, cmd, nick);
+	else
+		r = fprint(scr, "%s %s (%s)\t", time, cmd, par[i++]);
+
+	for (; par[i] != nil; ++i)
+		r += fprint(scr, " %s", par[i]);
+
+	r += fprint(scr, "\n");
+
+	return r;
+}
+
+int
+usrparse(char *ln, char *cmd, char *par[], int npar)
+{
+	enum state st = Cmd;
+	int i;
+
+	for(i = 0; i < npar; i++)
+		par[i] = nil;
+
+	if (ln[0] == '/' && npar >= 2) { 
+		*cmd = ln[1];
+		for (i = 1; ln[i] != '\0'; ++i) {
+			switch(st) {
+			case Cmd:
+				if (ln[i] == ' ') {
+					ln[i] = '\0';
+					par[0] = ln+i+1;
+					st = Middle;
+				} else if(ln[i] == '\n') {
+					/* enable commands with no arguments */
+					ln[i] = '\0';
+					par[0] = nil;
+					par[1] = nil;
+					st = Ok;
+				}
+				break;
+			case Middle:
+				if (ln[i] == '\r' || ln[i] == '\n') {
+					ln[i] = '\0';
+					st = Ok;
+				}
+				if (ln[i] == ' ') {
+					ln[i] = '\0';
+					par[1] = ln+i+1;
+					st = Trail;
+				}
+				break;
+			case Trail:
+				if (ln[i] == '\r' || ln[i] == '\n') {
+					ln[i] = '\0';
+					st = Ok;
+				}
+				break;
+			case Ok:
+				if (ln[i] == '\r' || ln[i] == '\n')
+					ln[i] = '\0';
+				break;
+			}
+		}
+	} else {	/* send line to victim by default */
+		st = Ok;
+		*cmd = 'm';
+		for (i = 0; ln[i] != '\0'; ++i)
+			if (ln[i] == '\r' || ln[i] == '\n')
+				ln[i] = '\0';
+		par[0] = victim;
+		par[1] = ln;
+	}
+	return st == Ok ? 0 : 1;
+}
+
+int
+srvparse(char *line, char **time, char **pre, char **cmd, char *par[], int npar)
+{
+	int i;
+	char *p;
+	enum state st = Time;
+
+	*time = *pre = *cmd = nil;
+
+	for (*time = p = line, i = 0; *p != '\0'; ++p) {
+		switch (st) {
+		case Time:
+			if (*p == ' ') {
+				*p = '\0';
+				*cmd = p + 1;
+				st = Cmd;
+			}
+			break;
+		case Cmd:
+			if (*p == ':') {
+				*p = '\0';
+				*pre = p + 1;
+				st = Prefix;
+			} else if (*p == ' ') {
+				*p = '\0';
+				par[i] = p + 1;
+				st = Middle;
+			}
+			break;
+		case Prefix:
+			if (*p == ' ') {
+				*p = '\0';
+				*cmd = p + 1;
+				st = Cmd;
+			}
+			break;
+		case Middle:
+			if (*p == '\r' || *p == '\n') {
+				*p = '\0';
+				st = Ok;
+			} else if (*p == ':') {
+				*p = '\0';
+				par[i] = p + 1;
+				st = Trail;
+			} else if (*p == ' ') {
+				*p = '\0';
+				i = (i + 1) % npar;
+				par[i] = p + 1;
+				st = Middle;
+			}
+			break;
+		case Trail:
+			if (*p == '\r' || *p == '\n') {
+				*p = '\0';
+				st = Ok;
+			}
+			break;
+		case Ok:
+			*p = '\0';
+			break;
+		}
+	}
+	par[(i + 1) % npar] = nil;
+	return st == Ok ? 0 : 1;
+}
+
+void
+getwidth(void)
+{
+	Font *font;
+	int n, fd, mintab;
+	char buf[128], *f[10], *p;
+
+	if(inacme){
+		if((fd = open("/dev/acme/ctl", OREAD)) < 0)
+			return;
+		n = read(fd, buf, sizeof buf-1);
+		close(fd);
+		if(n <= 0)
+			return;
+		buf[n] = 0;
+		n = tokenize(buf, f, nelem(f));
+		if(n < 7)
+			return;
+		if((font = openfont(nil, f[6])) == nil)
+			return;
+		mintab = stringwidth(font, "0");
+		linewidth = atoi(f[5]);
+		linewidth = linewidth/mintab;
+		return;
+	}
+
+	if((p = getenv("font")) == nil)
+		return;
+	if((font = openfont(nil, p)) == nil)
+		return;
+	if((fd = open("/dev/window", OREAD)) < 0)
+		return;
+
+	n = read(fd, buf, 5*12);
+	close(fd);
+
+	if(n < 5*12)
+		return;
+
+	buf[n] = 0;
+	
+	/* window stucture:
+		4 bit left edge
+		1 bit gap
+		12 bit scrollbar
+		4 bit gap
+		text
+		4 bit right edge
+	*/
+	linewidth = atoi(buf+3*12) - atoi(buf+1*12) - (4+1+12+4+4);
+	mintab = stringwidth(font, "0");
+	linewidth = linewidth/mintab;
+}
--- /dev/null
+++ b/install/irc7/irc.h
@@ -1,0 +1,36 @@
+enum {
+	Pmsg,	/* private message */
+	Smsg,	/* server message */
+	Nmsg, 	/* notice */
+	Lmsg, 	/* message sent by the client to server */
+	Cmd,	/* some other event such as a quit/join */
+	Err = -1;
+};
+
+typedef struct Line Line;
+struct Line
+{
+	int type;
+	char *from;		/* who sent the message, can be nil for server messages */	
+	char *uhost;	/* host where the message came from */
+	int mid;		/* message id for server messages	
+	char *to;		/* target for the message */
+	char *cmd;		/* JOIN/QUIT, etc. may be nil */
+	char *text;		/* message text */
+};
+#pragma varargck type "L" Line*
+
+void setwintitle(char *chan);
+
+int rtcs(int fd, char *cset);
+int wtcs(int fd, char *cset);
+int follow(int fd);
+
+int pmsg(int fd, char *pre, char *cmd, char *par[]);
+int ntc(int fd, char *pre, char *cmd, char *par[]);
+int generic(int fd, char *pre, char *cmd, char *par[]);
+int misc(int fd, char *pre, char *cmd, char *par[]);
+int numeric(int fd, char *pre, char *cmd, char *par[]);
+
+#define dprint if(debug) print
+
--- /dev/null
+++ b/install/irc7/irc.man
@@ -1,0 +1,131 @@
+.TH IRC 1
+.SH NAME
+ircsrv, irc \- internet relay chat client and ui
+.SH SYNOPSIS
+.B ircsrv
+[
+.I -s serv
+]
+[
+.I -f file
+]
+.I nickname
+.I [tcp!]irc.server.org
+.br
+.B irc
+[
+.I -e
+]
+[
+.I -c charset
+]
+[
+.I -t victim
+]
+[
+.I -b lines
+]
+[
+.I -r file
+]
+[
+.I -p password
+]
+[
+.I /srv/serv
+[
+.I /tmp/file
+]]
+.SH DESCRIPTION
+.I Ircsrv
+makes a connection to an irc server and reconnects if the connection
+gets broken. It posts a service to /srv and creates (if necessary) an
+append mode log file to /tmp. Default file and service name is a 
+concatenation of 4 first letters of username and the string 
+.I irc
+.
+.PP
+.I Irc
+is a client program that makes use of the service provided by
+.I ircsrv
+and provides a user interface for sending and receiving
+messages.
+.PP
+The command language understood by
+.I irc
+is as follows
+.EX
+/h  this message
+/H	this message
+/m	privmsg #chan/nick message
+/M	mode #chan +nt
+/j	join [ #chan ]
+/p	part [ #chan ]
+/q	send parameters raw to the server
+/l	list [ #chan ]
+/n	nick newnick
+/N	notice #chan/nick message
+/t	set [ victim ]
+/T	topic [ #chan [ newtopic ]]
+/W	whois nick
+/w	who nick (a shorter whois)
+.EE
+.PP
+The
+.I -e
+flag enables SSL, the
+.I -c
+flag sets character set conversion using
+.IR tcs (1),
+the
+.I -r
+flag will cause irc to replay the conversation from the raw file supplied as argument without user interaction,
+the
+.I -b
+flag causes the client to display the last n lines of the default target or 
+conversations if no target is set. Without any arguments
+.I -b
+prints the conversation from the very beginning.
+The
+.I -t
+flag sets the default target to which the messages are
+sent by default if a line written to
+.I irc
+does not begin with /. Irc also prints messages to/from
+the target exclusively if one is set. Irc prints only private messages if the target is set to "MSGS", useful for being notified when a message is sent to your nick without having to monitor all the status information.
+.SH EXAMPLES
+To start up ircsrv on a remote cpu server
+.EX
+cpu -c ircsrv glenda irc.freenode.net
+.EE
+.PP
+To use ircsrv started earlier on a cpu server
+.EX
+import $cpu /srv
+import $cpu /tmp
+irc
+.EE
+.PP
+To start/see/continue a conversation with a specific target (can be #chan or nick)
+.EX
+irc -t \'#plan9\'
+irc -t f2f
+.EE
+.PP
+To run irc within acme start irc inside 
+"win -e":
+.EX
+win -e
+irc -t f2f
+.EE
+.SH SOURCE
+/sys/src/cmd/ircsrv.c
+.br
+/sys/src/cmd/irc.c
+.SH SEE ALSO
+.IR tcs (1),
+.IR faces (1),
+.IR nedmail (1)
+.SH BUGS
+Probably.  Missing functionality includes opening new windows when a
+message from a new target arrives.
binary files /dev/null b/install/irc7/ircsrv.6 differ
--- /dev/null
+++ b/install/irc7/ircsrv.c
@@ -1,0 +1,255 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <libsec.h>
+
+char *post;
+char *file;
+int ircfd = -1;	// the irc server
+int logfd;
+int enctls = 0; // ssl/tls
+QLock lck;
+
+char *server;
+char *passwd;
+char *nickname;
+char *realname;
+char *username;
+char *mode = "foo";
+char *unused = "bar";
+
+void ircsrv(void);
+void logger(void);
+void die(void*, char*);
+void reconnect(void);
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-e] [-s service] [-f file] [-p pass] nickname [net!]ircserver[!port]\n", argv0);
+	exits("usage");
+}
+
+
+void
+killall(void)
+{
+	postnote(PNGROUP, getpid(), "quit");
+	while(waitpid() != -1)
+		;
+	remove(post);
+	exits(nil);
+}
+
+void
+die(void *, char *)
+{
+	killall();
+}
+
+void
+main(int argc, char *argv[])
+{
+	char *tmp;
+	int p[2], fd;
+
+	ARGBEGIN{
+	case 'f':
+		file = EARGF(usage());
+		break;
+	case 's':
+		post = EARGF(usage());
+		break;
+	case 'r':
+		realname = EARGF(usage());
+		break;
+	case 'e':
+		enctls = 1;
+		break;
+	case 'p':
+		passwd = EARGF(usage());	
+		/* try to obfuscate the password so ps -a won't see it */
+		tmp = passwd;
+		passwd = smprint("%s", tmp);
+		if(passwd) 
+			memset(tmp, '\0', strlen(tmp));
+		else
+			passwd = tmp;
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+	if(argc < 2)
+		usage();
+
+
+	nickname = argv[0];
+	server = argv[1];
+
+	username = getuser();
+
+	if(strlen(username) > 4)
+		username[4] = '\0';
+
+
+	if(post == nil)
+		post = smprint("/srv/%sirc", username);
+	else
+		post = smprint("/srv/%s", post);
+
+	if(file == nil)
+		file = smprint("/tmp/%sirc", username);
+
+	if((logfd = create(file, OWRITE, 0600 | DMAPPEND)) < 0)
+		sysfatal("create(%s): %r", file);
+
+	if((fd = create(post, OWRITE, 0600)) < 0)
+		sysfatal("create(%s): %r", post);
+	if(pipe(p) == -1)
+		sysfatal("pipe: %r");
+	fprint(fd, "%d", p[1]);
+	close(fd);
+	close(p[1]);
+	close(0);
+	close(1);
+	close(2);
+	dup(p[0], 0);
+
+	if(rfork(RFMEM|RFFDG|RFREND|RFPROC|RFNOTEG|RFCENVG|RFNOWAIT) == 0) {
+		notify(die);
+		reconnect();
+		switch(rfork(RFPROC|RFMEM)){
+		case -1:
+			sysfatal("rfork: %r");
+		case 0:
+			notify(die);
+			logger();
+			break;
+		default:
+			ircsrv();
+			break;
+		}
+	}
+	exits(nil);
+}
+
+long
+readln(int fd, void *vp, long len)
+{
+	char *b = vp;
+	while(len > 0 && read(fd, b, 1) > 0){
+		if(*b++ == '\n')
+			break;
+		len--;
+	}
+	return b - (char*)vp;
+}
+
+void
+reregister(void)
+{
+	int n;
+	char nbuf[32];
+
+	strncpy(nbuf, nickname, sizeof(nbuf) - 2);
+	switch(nbuf[strlen(nbuf) - 1]) {
+	case '0':
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7':
+	case '8':
+		nbuf[strlen(nbuf) - 1]++;
+		break;
+	case '9':
+		qlock(&lck);
+		fprint(logfd, "%ld can not register nick, bailing out\n", time(0));
+		qunlock(&lck);
+		die(nil, nil);
+	default:
+		n = strlen(nbuf);
+		nbuf[n] = '0';
+		nbuf[n+1] = '\0';
+		break;
+	}
+	qlock(&lck);
+	fprint(ircfd, "NICK %s\r\n", nbuf);
+	fprint(logfd, "%ld NICK %s\r\n", time(0), nickname);
+	qunlock(&lck);
+}
+
+void
+reconnect(void)
+{
+	TLSconn *conn;
+	if(ircfd >= 0)
+		close(ircfd);
+	if((ircfd = dial(netmkaddr(server, "tcp", "6667"), nil, nil, nil)) < 0)
+		sysfatal("dial %r");
+	if(enctls > 0) {
+		conn = (TLSconn *)mallocz(sizeof *conn, 1);
+        	ircfd = tlsClient(ircfd, conn);
+		if (ircfd < 0) { sysfatal ("tls: %r"); }
+	}
+	if(passwd && strcmp(passwd, ""))
+		fprint(ircfd, "PASS %s\r\n", passwd);
+	fprint(ircfd, "USER %s %s %s :%s\r\n",
+		nickname, mode, unused, realname);
+	fprint(ircfd, "NICK %s\r\n", nickname);
+}
+
+
+void
+logger(void)
+{
+	char buf[513];
+	char *f[3];
+	long n;
+
+	for(;;){
+		while((n = readln(ircfd, buf, sizeof(buf)-1)) > 0){
+			fprint(logfd, "%ld ", time(0));
+			write(logfd, buf, n);
+			buf[n] = 0;
+			n = tokenize(buf, f, nelem(f));
+			if(n == 3 && *f[0] == ':' && !cistrcmp(f[1], "PING")){
+				qlock(&lck);
+				fprint(ircfd, "PONG %s\r\n", f[2]);
+				fprint(logfd, "%ld PONG %s\r\n", time(0), f[2]);
+				qunlock(&lck);
+			} else if(n == 2 && !cistrcmp(f[0], "PING")){
+				qlock(&lck);
+				fprint(ircfd, "PONG %s\r\n", f[1]);
+				fprint(logfd, "%ld PONG %s\r\n", time(0), f[1]);
+				qunlock(&lck);
+			} else if(n == 3 && atoi(f[1]) == 433) {
+				reregister();
+			}
+		}
+		reconnect();
+	}
+}
+
+void
+ircsrv(void)
+{
+	char buf[512];
+	long n;
+
+	while((n = readln(0, buf, sizeof(buf)-1)) > 0){
+		qlock(&lck);
+		fprint(logfd, "%ld ", time(0));
+		if(write(logfd, buf, n) != n)
+			fprint(2, "write to irclog: %r\n");
+		if(write(ircfd, buf, n) != n)
+			fprint(2, "write to ircserver: %r\n");
+		qunlock(&lck);
+	}
+	killall();
+}
+
+
--- /dev/null
+++ b/install/irc7/mkfile
@@ -1,0 +1,15 @@
+</$objtype/mkfile
+MAN=/sys/man/1
+
+TARG=\
+	irc\
+	ircsrv\
+
+BIN=/$objtype/bin
+
+</sys/src/cmd/mkmany
+
+ircman:V:
+	cp irc.man /sys/man/1/irc
+
+install: ircman