wm: ircd

Download patch

ref: 2d7dbddc67c655b8661a2860f77ca76af1513a35
author: mkf <mkf@cloud9p.org>
date: Sun Sep 17 15:21:30 EDT 2023

import sources

--- /dev/null
+++ b/ircd.c
@@ -1,0 +1,1134 @@
+#include <u.h>
+#include <libc.h>
+
+enum {
+	Maxclients = 5,
+	Maxchannels = 5,
+	Nicklen = 15,
+	Chanlen = 15,
+	Msglen = 512, /* 510 + "\r\n" */
+	Timeout = 120 * 1000,
+};
+
+enum {
+	Ping = 1,
+	Pong,
+	Welcome,
+	Join,
+	Part,
+	Quit,
+	Privmsg,
+	Nick,
+	Nosuchnick,
+	Erroneousnick,
+	Nickinuse,
+	Notonchannel,
+	Topic,
+	Notopic,
+	Rpltopic,
+	Rpltopicwho,
+	Unknowncmd,
+};
+
+typedef struct Item Item;
+typedef struct List List;
+typedef struct Client Client;
+typedef struct Chan Chan;
+typedef struct Reply Reply;
+
+struct Item {
+	void *data;
+	Item *next;
+};
+
+struct List {
+	int size;
+	Item *head;
+	Item *tail;
+};
+
+struct Client {
+	int fd;
+	NetConnInfo *conninfo;
+	char *user;
+	char *nick;
+	char *prefix;
+	List channels;
+	int pid;
+	int pings; /* unreplied pings */
+	long msgtimer; /* for flood */
+};
+
+/* not to mix with thread(2) Channel:s */
+struct Chan {
+	char *name;
+	char *topic;
+	char *topicwho;
+	List members;
+};
+
+/* message that is sent to clients */
+struct Reply {
+	int code;
+	char *argv[5];
+};
+
+List clients;
+List channels;
+QLock clock;
+char *servername;
+int debug;
+
+int
+dprint(char *fmt, ...)
+{
+	va_list va;
+	int rc;
+
+	if(!debug)
+		return 0;
+	va_start(va, fmt);
+	rc = vfprint(2, fmt, va);
+	va_end(va);
+	return rc;
+}
+
+void *
+emalloc(ulong n)
+{
+	void *p;
+
+	p = mallocz(n, 1);
+	if(p == nil)
+		sysfatal("mallocz: %r");
+	return p;	
+}
+
+char *
+estrdup(char *s)
+{
+	s = strdup(s);
+	if(s == nil)
+		sysfatal("strdup: %r");
+	return s;
+}
+
+void
+add(List *l, void *data)
+{
+	Item *i;
+
+	if(data == nil){
+		fprint(2, "add: nil data\n");
+		assert(0);
+		return;
+	}
+	for(i = l->head; i != nil; i = i->next)
+		if(i->data == data){
+			fprint(2, "add: duplicate data\n");
+			assert(0);
+			return;
+		}
+
+	i = emalloc(sizeof(Item));
+	i->data = data;
+	i->next = nil;
+
+	if(l->head == nil)
+		l->head = i;
+	else
+		l->tail->next = i;
+	l->tail = i;
+	l->size++;
+}
+
+void
+del(List *l, void *data)
+{
+	Item *i, *prev;
+
+	if(data == nil){
+		fprint(2, "del: nil data\n");
+		assert(0);
+		return;
+	}
+	for(i = l->head, prev = nil; i != nil; i = i->next){
+		if(i->data == data){
+			if(prev == nil) /* first */
+				l->head = i->next;				
+			else
+				prev->next = i->next;
+			if(i->next == nil) /* last */
+				l->tail = prev;
+			free(i);
+			l->size--;
+			return;
+		}
+		prev = i;
+	}
+}
+
+void
+freelist(List *l)
+{
+	Item *i, *next;
+
+	for(i = l->head; i != nil; i = next){
+		next = i->next;
+		free(i);
+	}
+	l->size = 0;
+	l->head = nil;
+	l->tail = nil;
+}
+
+Chan *
+addchan(char *name)
+{
+	Chan *ch;
+
+	dprint("adding channel %s\n", name);
+	ch = emalloc(sizeof(Chan));
+	ch->name = estrdup(name);
+	add(&channels, ch);
+	return ch;
+}
+
+void
+delchan(Chan *ch)
+{
+	dprint("deleting channel %s\n", ch->name);
+	del(&channels, ch);
+	if(ch->name != nil)
+		free(ch->name);
+	if(ch->topic != nil)
+		free(ch->topic);
+	if(ch->topicwho != nil)
+		free(ch->topicwho);
+	freelist(&ch->members);
+	free(ch);
+}
+
+Client *
+addclient(int fd)
+{
+	NetConnInfo *ci;
+	Client *c;
+
+	ci = getnetconninfo(nil, fd);
+	if(ci == nil){
+		perror("getnetconninfo");
+		return nil;
+	}
+	c = emalloc(sizeof(Client));
+	c->fd = fd;
+	c->conninfo = ci;
+	c->pid = getpid();
+
+	dprint("%d: rsys=%s rserv=%s raddr=%s pid=%d\n", fd, ci->rsys, ci->rserv, ci->raddr, c->pid);
+
+	qlock(&clock);
+	add(&clients, c);
+	qunlock(&clock);
+	return c;
+}
+
+void
+delclient(Client *c)
+{
+	Item *i;
+	Chan *ch;
+
+	dprint("deleting client %d\n", c->fd);
+	qlock(&clock);
+	del(&clients, c);
+	if(c->conninfo != nil)
+		freenetconninfo(c->conninfo);
+	if(c->user != nil)
+		free(c->user);
+	if(c->nick != nil)
+		free(c->nick);
+	if(c->prefix != nil)
+		free(c->prefix);
+	for(i = c->channels.head; i != nil; i = i->next){
+		ch = i->data;
+		del(&ch->members, c);
+		if(ch->members.size == 0)
+			delchan(ch);
+	}
+	freelist(&c->channels);
+	qunlock(&clock);
+	free(c);
+}
+
+Client *
+getclient(List *l, char *nick)
+{
+	Item *i;
+	Client *c;
+
+	for(i = l->head; i != nil; i = i->next){
+		c = i->data;
+		if(c->nick != nil && strcmp(c->nick, nick) == 0)
+			return c;
+	}
+	return nil;
+}
+
+Chan *
+getchan(List *l, char *name)
+{
+	Item *i;
+	Chan *ch;
+
+	for(i = l->head; i != nil; i = i->next){
+		ch = i->data;
+		if(ch->name != nil && strcmp(ch->name, name) == 0)
+			return ch;
+	}
+	return nil;
+}
+
+void
+ewrite(int fd, char *buf, int n)
+{
+	if(n <= 0)
+		return;
+	n = write(fd, buf, n);
+	if(n < 0)
+		perror("write");
+}
+
+void
+reply(Client *c, Reply *r)
+{
+	static char buf[Msglen+1];
+	int n;
+
+	n = sizeof(buf);
+	switch(r->code){
+	case Ping:
+		n = snprint(buf, n, "PING :%s\r\n", servername);
+		break;
+	case Pong:
+		n = snprint(buf, n, ":%s PONG %s\r\n", servername, servername);
+		break;
+	case Welcome:
+		n = snprint(buf, n, ":%s 001 %s :Welcome %s\r\n", servername, c->nick, c->prefix);
+		break;
+	case Join:
+		/* r->argv is: joiner, channel */
+		n = snprint(buf, n, ":%s JOIN %s\r\n", r->argv[0], r->argv[1]);
+		break;
+	case Part:
+		/* r->argv is: parter, channel, reason */
+		n = snprint(buf, n, ":%s PART %s :%s\r\n", r->argv[0], r->argv[1], r->argv[2]);
+		break;
+	case Quit:
+		/* r->argv is: quitter, reason */
+		n = snprint(buf, n, ":%s QUIT :%s\r\n", r->argv[0], r->argv[1]);
+		break;
+	case Privmsg:
+		/* r->argv is: sender, target, message */
+		n = snprint(buf, n, ":%s PRIVMSG %s :%s\r\n", r->argv[0], r->argv[1], r->argv[2]);
+		break;
+	case Nick:
+		/* r->argv is: old prefix, new nick */
+		n = snprint(buf, n, ":%s NICK %s\r\n", r->argv[0], r->argv[1]);
+		break;
+	case Nosuchnick:
+		/* r->argv[0] is nick/channel */
+		n = snprint(buf, n, ":%s 401 %s %s :No such nick/channel\r\n", servername, c->nick, r->argv[0]);
+		break;
+	case Erroneousnick:
+		/* r->argv[0] is nick */
+		n = snprint(buf, n, ":%s 432 %s %s :Erroneous nickname\r\n", servername, c->nick, r->argv[0]);
+		break;
+	case Nickinuse:
+		/* r->argv[0] is nick */
+		n = snprint(buf, n, ":%s 433 %s %s :Nickname is already in use\r\n", servername, c->nick, r->argv[0]);
+		break;
+	case Notonchannel:
+		/* r->argv[0] is channel */
+		n = snprint(buf, n, ":%s 442 %s %s :You're not on that channel\r\n", servername, c->nick, r->argv[0]);
+		break;
+	case Topic:
+		/* r->argv is: setter, channel, topic */
+		n = snprint(buf, n, ":%s TOPIC %s :%s\r\n", r->argv[0], r->argv[1], r->argv[2]);
+		break;
+	case Rpltopic:
+		/* r->argv is: channel, topic */
+		n = snprint(buf, n, ":%s 332 %s %s :%s\r\n", servername, c->nick, r->argv[0], r->argv[1]);
+		break;
+	case Rpltopicwho:
+		/* r->argv is: channel, topicwhotime */
+		n = snprint(buf, n, ":%s 333 %s %s :%s\r\n", servername, c->nick, r->argv[0], r->argv[1]);
+		break;
+	case Notopic:
+		/* r->argv is: channel */
+		n = snprint(buf, n, ":%s 331 %s %s :No topic is set\r\n", servername, c->nick, r->argv[0]);
+		break;
+	case Unknowncmd:
+		/* r->argv is: command */
+		n = snprint(buf, n, ":%s 421 %s %s :Unknown command\r\n", servername, c->nick, r->argv[0]);
+		break;	
+	default:
+		fprint(2, "%d: reply: unknown code %d\n", c->fd, r->code);
+		assert(0);
+	}
+	if(r->code != Ping && r->code != Pong)
+		dprint("%d: reply: %s", c->fd, buf);
+
+	ewrite(c->fd, buf, n);
+}
+
+void
+replylist(List *l, Reply *r, Client *skipme)
+{
+	Item *i;
+	Client *c;
+
+	for(i = l->head; i != nil; i = i->next){
+		c = i->data;
+		if(c != skipme)
+			reply(c, r);
+	}
+}
+
+void
+replychan(Chan *ch, Reply *r, Client *skipme)
+{
+	replylist(&ch->members, r, skipme);
+}
+
+void
+replychans(Client *c, Reply *r, Client *skipme)
+{
+	Item *i;
+	Chan *ch;
+
+	for(i = c->channels.head; i != nil; i = i->next){
+		ch = i->data;
+		replylist(&ch->members, r, skipme);
+	}
+}
+
+void
+setprefix(Client *c)
+{
+	if(c->user == nil || c->nick == nil)
+		return;
+
+	if(c->prefix != nil)
+		free(c->prefix);
+
+	c->prefix = smprint("%s!~%s@%s", c->nick, c->user, c->conninfo->rsys);
+	if(c->prefix == nil)
+		sysfatal("setprefix: smprint: %r");
+}
+
+/* allowed chars: [0-9A-Za-z_] */
+int
+validname(char *s, int maxlen)
+{
+	int i;
+	char c;
+
+	i = 0;
+	while((c = s[i]) != '\0' && i < maxlen){
+		if(!((c >= 'a' && c <= 'z') ||
+		     (c >= 'A' && c <= 'Z') ||
+		     (c >= '0' && c <= '9') ||
+		     (c == '_')))
+			return 0;
+		i++;
+	}
+	if(i == 0)
+		return 0;
+	if(i == maxlen && c != '\0')
+		return 0;
+	return 1;
+}
+
+int
+validnick(char *s)
+{
+	return validname(s, Nicklen);
+}
+
+int
+validchan(char *s)
+{
+	return *s == '#' && validname(s+1, Chanlen-1);
+}
+
+int
+user(Client *c, char *user)
+{
+	Reply r;
+
+	if(c->user != nil)
+		return 0;
+
+	qlock(&clock);
+	c->user = estrdup(user);
+	setprefix(c);
+	qunlock(&clock);
+
+	dprint("%d: user=%s\n", c->fd, c->user);
+	if(c->nick == nil)
+		return 1;
+
+	r.code = Welcome;
+	reply(c, &r);
+	return 1;
+}
+
+int
+nickinuse(char *nick)
+{
+	return getclient(&clients, nick) != nil;
+}
+
+int
+nick(Client *c, char *nick)
+{
+	Reply r;
+	int first;
+
+	if(!validnick(nick)){
+		dprint("%d: invalid nick %s\n", c->fd, nick);
+		r.code = Erroneousnick;
+		r.argv[0] = nick;
+		reply(c, &r);
+		return 1;
+	}
+	/* no change */
+	if(c->nick != nil && strcmp(nick, c->nick) == 0)
+		return 1;
+
+	first = 0;
+	qlock(&clock);
+	if(nickinuse(nick)){
+		dprint("%d: nick %s is in use\n", c->fd, nick);
+		r.code = Nickinuse;
+		r.argv[0] = nick;
+		reply(c, &r);
+	}else{
+		/* nick change */
+		if(c->nick != nil){
+			r.code = Nick;
+			r.argv[0] = c->prefix; /* old prefix */
+			r.argv[1] = nick;      /* new nick */
+			replychans(c, &r, c);
+			reply(c, &r);
+			free(c->nick);
+		}else
+			first = 1;
+
+		c->nick = estrdup(nick);
+		setprefix(c);
+		dprint("%d: nick=%s\n", c->fd, c->nick);
+	}
+	qunlock(&clock);
+
+	if(first && c->user != nil){
+		r.code = Welcome;
+		reply(c, &r);
+	}
+	return 1;
+}
+
+int
+replynames(Client *c, Chan *ch)
+{
+	static char buf[Msglen+1];
+	Item *i;
+	Client *m;
+	int n, len, room;
+	char *p;
+
+	if(ch->members.size == 0)
+		return 1;
+
+	p = buf;
+	len = 0;
+	room = sizeof(buf);
+	n = snprint(p, room, ":%s 353 %s = %s :", servername, c->nick, ch->name);
+	p += n;
+	len += n;
+	room -= n;
+
+	for(i = ch->members.head; i != nil; i = i->next){
+		m = i->data;
+		if(room < Nicklen+4){ /* nick + " \r\n\0" */
+			assert(room > 2);
+
+			/* finish line */
+			n = snprint(p, room, "\r\n");
+			len += n;
+			dprint("%d: reply: %s", c->fd, buf);
+			ewrite(c->fd, buf, len);
+
+			/* start new */
+			p = buf;
+			len = 0;
+			room = sizeof(buf);
+			n = snprint(p, room, ":%s 353 %s = %s :", servername, c->nick, ch->name);
+			p += n;
+			len += n;
+			room -= n;			
+		}
+		n = snprint(p, room, "%s ", m->nick);
+		p += n;
+		len += n;
+		room -= n;
+	}
+	/* finish line */
+	n = snprint(p, room, "\r\n");
+	len += n;
+	dprint("%d: reply: %s", c->fd, buf);
+	ewrite(c->fd, buf, len);
+
+	/* the end */
+	n = snprint(buf, sizeof(buf), ":%s 366 %s %s :End of /NAMES list\r\n", servername, c->nick, ch->name);
+	dprint("%d: reply: %s", c->fd, buf);
+	ewrite(c->fd, buf, n);
+
+	return 1;	
+}
+
+void
+replytopic(Client *c, Chan *ch)
+{
+	Reply r;
+
+	if(ch->topic == nil)
+		return;
+
+	r.code = Rpltopic;
+	r.argv[0] = ch->name;
+	r.argv[1] = ch->topic;
+	reply(c, &r);
+
+	if(ch->topicwho == nil)
+		return;
+
+	r.code = Rpltopicwho;
+	r.argv[1] = ch->topicwho;
+	reply(c, &r);
+}
+
+int
+joined(Client *c, char *chan)
+{
+	return getchan(&c->channels, chan) != nil;
+}
+
+int
+join(Client *c, char *chan)
+{
+	Chan *ch;
+	Reply r;
+
+	if(joined(c, chan))
+		return 1;
+
+	qlock(&clock);
+	ch = getchan(&channels, chan);
+	if(ch == nil){
+		if(channels.size == Maxchannels){
+			fprint(2, "%d: Maxchannels reached\n", c->fd);
+			/* reply? */
+			goto end;
+		}
+		if(!validchan(chan)){
+			dprint("%d: invalid chan %s\n", c->fd, chan);
+			/* reply? */
+			goto end;
+		}
+		ch = addchan(chan);
+	}
+	add(&ch->members, c);
+	add(&c->channels, ch);	
+	dprint("%d: join chan=%s\n", c->fd, chan);
+	r.code = Join;
+	r.argv[0] = c->prefix;
+	r.argv[1] = chan;
+	replychan(ch, &r, nil);
+	replytopic(c, ch);
+	replynames(c, ch);
+end:
+	qunlock(&clock);
+	return 1;
+}
+
+int
+part(Client *c, char *chan, char *reason)
+{
+	Chan *ch;
+	Reply r;
+
+	qlock(&clock);
+	ch = getchan(&c->channels, chan);
+	if(ch == nil){
+		r.code = Notonchannel;
+		r.argv[0] = chan;
+		reply(c, &r);
+	}else{
+		dprint("%d: part chan=%s\n", c->fd, chan);
+		r.code = Part;
+		r.argv[0] = c->prefix;
+		r.argv[1] = chan;
+		r.argv[2] = reason;
+		replychan(ch, &r, nil);
+		del(&c->channels, ch);
+		del(&ch->members, c);
+		if(ch->members.size == 0)
+			delchan(ch);
+	}
+	qunlock(&clock);
+	return 1;
+}
+
+int
+topic(Client *c, char *chan, char *topic)
+{
+	Chan *ch;
+	Reply r;
+
+	if(!joined(c, chan)){
+		r.code = Notonchannel;
+		r.argv[0] = chan;
+		reply(c, &r);
+		return 1;
+	}
+	qlock(&clock);
+	ch = getchan(&channels, chan);
+	if(ch == nil){
+		assert(0);
+		goto end;
+	}
+	if(topic == nil){ /* get */
+		if(ch->topic == nil){
+			r.code = Notopic;
+			r.argv[0] = chan;
+			reply(c, &r);
+		}else{
+			replytopic(c, ch);
+		}
+	}else{ /* set */
+		if(ch->topic != nil)
+			free(ch->topic);
+		ch->topic = estrdup(topic);
+
+		if(ch->topicwho != nil)
+			free(ch->topicwho);
+		ch->topicwho = smprint("%s %ld", c->prefix, time(0));
+
+		r.code = Topic;
+		r.argv[0] = c->prefix;
+		r.argv[1] = chan;
+		r.argv[2] = topic;
+		replychan(ch, &r, nil);
+	}
+end:
+	qunlock(&clock);
+	return 1;
+}
+
+void
+quit(Client *c, char *reason)
+{
+	Reply r;
+
+	dprint("%d: quit: %s\n", c->fd, reason);
+	r.code = Quit;
+	r.argv[0] = c->prefix;
+	r.argv[1] = reason;
+	qlock(&clock);
+	replychans(c, &r, c);
+	qunlock(&clock);
+}
+
+int
+privmsg(Client *c, char *target, char *msg)
+{
+	Client *tc;
+	Chan *ch;
+	Reply r;
+
+	r.code = Privmsg;
+	r.argv[0] = c->prefix; /* from */
+	r.argv[1] = target;
+	r.argv[2] = msg;
+
+	qlock(&clock);	
+	if(*target == '#'){
+		ch = getchan(&channels, target);
+		if(ch != nil){
+			if(joined(c, target))
+				replychan(ch, &r, c);
+			else{
+				r.code = Notonchannel;
+				r.argv[0] = target;
+				reply(c, &r);
+			}
+		}else{
+			r.code = Nosuchnick;
+			r.argv[0] = target;
+			reply(c, &r);
+		}
+	}else{
+		tc = getclient(&clients, target);
+		if(tc != nil)
+			reply(tc, &r);
+		else{
+			r.code = Nosuchnick;
+			r.argv[0] = target;
+			reply(c, &r);
+		}
+	}
+	qunlock(&clock);
+	return 1;
+}
+
+int
+list(Client *c, char *chans)
+{
+	static char buf[Msglen+1];
+	Item *i;
+	Chan *ch;
+	int nargs, n, j;
+	char *args[5];
+
+	if(chans == nil)
+		nargs = 0;
+	else
+		nargs = getfields(chans, args, 5, 0, ",");
+
+	n = snprint(buf, sizeof(buf), ":%s 321 %s Channel :Users Name\r\n", servername, c->nick);
+	dprint("%d: reply: %s", c->fd, buf);
+	ewrite(c->fd, buf, n);
+
+	qlock(&clock);
+
+	for(i = channels.head; i != nil; i = i->next){
+		ch = i->data;
+		if(nargs == 0)
+			goto match;
+		for(j = 0; j < nargs; j++)
+			if(strcmp(ch->name, args[j]) == 0)
+				goto match;
+		continue;
+match:
+		n = snprint(buf, sizeof(buf), ":%s 322 %s %s %d :%s\r\n", servername, c->nick, ch->name, ch->members.size, ch->topic == nil ? "" : ch->topic);
+		dprint("%d: reply: %s", c->fd, buf);
+		ewrite(c->fd, buf, n);
+	}
+	qunlock(&clock);
+
+	n = snprint(buf, sizeof(buf), ":%s 323 %s :End of /LIST\r\n", servername, c->nick);
+	dprint("%d: reply: %s", c->fd, buf);
+	ewrite(c->fd, buf, n);
+	return 1;
+}
+
+int
+whois(Client *c, char *nicks)
+{
+	static char buf[Msglen+1];
+	Item *i;
+	Client *w;
+	int nargs, n, j;
+	char *args[5];
+
+	if(nicks == nil || strcmp(nicks, "*") == 0)
+		nargs = 0;
+	else
+		nargs = getfields(nicks, args, 5, 0, ",");
+
+	qlock(&clock);
+
+	for(i = clients.head; i != nil; i = i->next){
+		w = i->data;
+		if(w->prefix == nil)
+			continue;
+		if(nargs == 0)
+			goto match;
+		for(j = 0; j < nargs; j++)
+			if(strcmp(w->nick, args[j]) == 0)
+				goto match;
+		continue;
+match:
+		/* skip realname at the end */
+		n = snprint(buf, sizeof(buf), ":%s 311 %s %s %s %s * :\r\n", servername, c->nick, w->nick, w->user, w->conninfo->rsys);
+		dprint("%d: reply: %s", c->fd, buf);
+		ewrite(c->fd, buf, n);
+	}
+	qunlock(&clock);
+
+	n = snprint(buf, sizeof(buf), ":%s 318 %s %s :End of /WHOIS\r\n", servername, c->nick, nicks == nil ? "*" : nicks);
+	dprint("%d: reply: %s", c->fd, buf);
+	ewrite(c->fd, buf, n);
+	return 1;
+}
+
+int
+process(Client *c, char *msg)
+{
+	char *cmd, *tmp, *tmp2;
+	Reply r;
+
+	cmd = strtok(msg, " :\r");
+	if(cmd == nil)
+		return 0;
+
+	if(strcmp(cmd, "PING") == 0){
+		r.code = Pong;
+		reply(c, &r);
+		return 1;
+	}
+	if(strcmp(cmd, "PONG") == 0){
+		c->pings = 0;
+		return 1;
+	}
+	if(strcmp(cmd, "NICK") == 0){
+		tmp = strtok(0, " \r"); /* nick */
+		if(tmp == nil)
+			return 0;
+		return nick(c, tmp);
+	}
+	if(strcmp(cmd, "USER") == 0){
+		tmp = strtok(0, " \r"); /* username */
+		if(tmp == nil)
+			return 0;
+		return user(c, tmp);
+	}
+	if(c->prefix == nil) /* not registered */
+		return 1;
+
+	if(strcmp(cmd, "PRIVMSG") == 0){
+		tmp = strtok(0, " :"); /* target */
+		if(tmp == nil)
+			return 0;
+		tmp2 = strtok(0, "\r"); /* msg */
+		if(tmp2 == nil)
+			return 0;
+		tmp2++;
+		return privmsg(c, tmp, tmp2);
+	}
+	if(strcmp(cmd, "JOIN") == 0){
+		tmp = strtok(0, " \r"); /* channel */
+		if(tmp == nil)
+			return 0;
+		return join(c, tmp);
+	}
+	if(strcmp(cmd, "PART") == 0){
+		tmp = strtok(0, " :"); /* channel */
+		if(tmp == nil)
+			return 0;
+		tmp2 = strtok(0, "\r"); /* reason */
+		if(tmp2 == nil)
+			return 0;
+		tmp2++;
+		return part(c, tmp, tmp2);
+	}
+	if(strcmp(cmd, "TOPIC") == 0){
+		tmp = strtok(0, " :\r"); /* channel */
+		if(tmp == nil)
+			return 0;
+		tmp2 = strtok(0, "\r"); /* topic */
+		if(tmp2 != nil)
+			tmp2++;
+		return topic(c, tmp, tmp2);
+	}
+	if(strcmp(cmd, "QUIT") == 0){
+		tmp = strtok(0, "\r"); /* reason */
+		if(tmp == nil)
+			return 0;
+		tmp++;
+		quit(c, tmp);
+		return 0;
+	}
+	if(strcmp(cmd, "LIST") == 0){
+		tmp = strtok(0, " \r"); /* channels */
+		return list(c, tmp);
+	}
+	if(strcmp(cmd, "WHOIS") == 0){
+		tmp = strtok(0, " \r"); /* nicks */
+		return whois(c, tmp);
+	}
+	r.code = Unknowncmd;
+	r.argv[0] = cmd;
+	reply(c, &r);
+	return 1;
+}
+
+int
+notehandler(void *, char *msg)
+{
+	dprint("pid=%d received note: %s\n", getpid(), msg);
+	if(strcmp(msg, "alarm") == 0)
+		return 1;
+	return 0;
+}
+
+int
+flood(Client *c)
+{
+	long now;
+
+	now = time(0);
+	if(c->msgtimer < now){
+		c->msgtimer = now+2;
+		return 0;
+	}
+	if(c->msgtimer < now+10){
+		c->msgtimer += 2;
+		return 0;
+	}
+	dprint("%d: flood detected\n", c->fd);
+	quit(c, "flooding");
+	return 1;
+}
+
+int
+timeout(Client *c)
+{
+	Reply r;
+
+	dprint("%d: checking timeout\n", c->fd);
+	if(c->pings == 2){
+		dprint("%d: timed out\n", c->fd);
+		quit(c, "timed out");
+		return 1;
+	}
+	r.code = Ping;
+	reply(c, &r);
+	c->pings++;
+	return 0;
+}
+
+void
+usage(void)
+{
+	fprint(2, "%s [-d] [-n servername] addr\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	int afd, lfd, dfd;
+	char adir[40], ldir[40];
+	int n, i, len;
+	char buf[1024];
+	Client *c;
+	char *msg;
+	char err[ERRMAX];
+
+	debug = 0;
+	servername = nil;
+	ARGBEGIN{
+	case 'd':
+		debug++;
+		break;
+	case 'n':
+		servername = EARGF(usage());
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if(argc != 1)
+		usage();
+
+	if(servername == nil)
+		servername = estrdup("ircd");
+
+	afd = announce(argv[0], adir);
+	if(afd < 0)
+		sysfatal("announce: %r");
+
+	dprint("addr=%s afd=%d adir=%s pid=%d\n", argv[0], afd, adir, getpid());
+
+	atnotify(notehandler, 1);
+
+	for(;;){
+		lfd = listen(adir, ldir);
+		if(lfd < 0)
+			sysfatal("listen: %r");
+
+		dprint("lfd=%d ldir=%s\n", lfd, ldir);
+
+		if(clients.size >= Maxclients){
+			fprint(2, "Maxclients reached; reject lfd=%d\n", lfd);
+			close(lfd);
+			continue;
+		}
+		switch(rfork(RFPROC|RFNOMNT|RFMEM)){
+		case -1:
+			perror("fork");
+			close(lfd);
+			break;
+		case 0:
+			dfd = accept(lfd, ldir);
+			close(lfd);
+			if(dfd < 0){
+				perror("accept");
+				exits("no");
+			}
+			dprint("dfd=%d\n", dfd);
+
+			c = addclient(dfd);
+			if(c == nil){
+				close(dfd);
+				exits("no");
+			}
+			for(;;){
+				alarm(Timeout);
+				n = read(dfd, buf, sizeof(buf)-1);
+				alarm(0);
+				if(n == 0){
+					quit(c, "eof");
+					break;
+				}
+				if(n < 0){
+					err[0] = '\0';
+					errstr(err, sizeof(err));
+					if(strcmp(err, "interrupted") == 0){
+						if(timeout(c))
+							break;
+					}else{
+						perror("read");
+						quit(c, "read error");
+						break;
+					}
+				}
+				msg = buf;
+				len = 0;
+				for(i = 0; i <= n; i++){
+					if(buf[i] == '\n' || i == n){
+						buf[i] = '\0';
+						if(len > 0){
+							dprint("%d: msg: %s\n", dfd, msg);
+							if(flood(c))
+								goto bye;
+							if(!process(c, msg))
+								goto bye;
+						}
+						msg = &buf[i+1];
+						len = 0;
+					}else
+						len++;
+				}
+			}
+bye:
+			dprint("dfd=%d disconnected\n", dfd);
+			delclient(c);
+			close(dfd);
+			exits(0);	
+		}
+	}
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,14 @@
+</$objtype/mkfile
+
+TARG=ircd
+
+CFILES=\
+	ircd.c\
+
+OFILES=${CFILES:%.c=%.$O}
+
+#HFILES=common.h
+
+BIN=/$objtype/bin
+
+</sys/src/cmd/mkone