wm: ircd

ref: 0746920981e7794ebaaa2e402671c32c729e44bd
dir: /ircd.c/

View raw version
#include <u.h>
#include <libc.h>

char *password = nil; /* nil to disable server password */

enum {
	Maxclients = 48,
	Maxchannels = 5,
	Nicklen = 32,
	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,
	Notregistered,
	Alreadyregistered,
	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 */
	int passok; 
};

/* 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;
char date[32];
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"
		":%s 002 %s :Your host is %s, running version %s\r\n" 
		":%s 003 %s :This server was created %s\r\n"
		":%s 004 %s \r\n"
		":%s 005 %s NICKLEN=%d CHANLEN=%d CASEMAPPING=ascii :are supported by this server\r\n",

		servername, c->nick, c->prefix, /* 001 */
		servername, c->nick, servername, "ircd", /* 002 */
		servername, c->nick, date, /* 003 */
		servername, c->nick, /* 004, we don't support anything yet */
		servername, c->nick, Nicklen, Chanlen); /* 005 */
 		break;
		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 Notregistered:
		n = snprint(buf, n, ":%s 451 %s :Connection not registered\r\n", servername, c->nick);
		break;
	case Alreadyregistered:
		n = snprint(buf, n, ":%s 461 %s :Connection already registered", servername, c->nick);
		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);
	}
}

int
setprefix(Client *c)
{
	/* fatal */
	if(password != nil && !c->passok)
	{
		delclient(c);
		return 1;
	}

	/* not fatal */
	if(c->user == nil || c->nick == nil)
		return 0;

	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");
	return 0;
}

/* allowed chars: [0-9A-Za-z_] */
int
validname(char *s, int maxlen)
{
	int i;
	char c;
	char ok[] = ";0123456789-";

	/* first char my not be from ok*/
	if(strchr(ok, s[0]))
		return 0;
	i = 0;
	while((c = s[i]) != '\0' && i < maxlen){
		if (c < 'A' && !strchr(ok, c))
			return 0;
		if (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)
	{
		r.code = Alreadyregistered;
		reply(c, &r);
		return 1;
	}

	qlock(&clock);
	c->user = estrdup(user);
	
	if(setprefix(c))
		return 0;

	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);
		if(setprefix(c))
			return 0;
		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
pass(Client *c, char *p)
{
	if(password == nil)
		return 1;

	if(c->passok)
	{
		dprint("here\n");
		close(c->fd);
		return 0;
	}
	else
	{
		if(strcmp(p, password) != 0)
		{
			close(c->fd);
			dprint("pass: %s, p: %s\n", password, p);
		}
		else
			c->passok = 1;
	}
	return 1;
}

int
process(Client *c, char *msg)
{
	char *cmd, *tmp, *tmp2;
	Reply r;

	cmd = strtok(msg, " :\r");

	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(strcmp(cmd, "PASS") == 0){
		tmp = strtok(0, " \r"); /* password */
		return pass(c, tmp);
	}

	if(c->prefix == nil){ /* not registered */
		r.code = Notregistered;
		reply(c, &r);
		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+1;
		return 0;
	}
	if(c->msgtimer < now+10){
		c->msgtimer += 1;
		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");

	tmfmtinstall();
	if(!sprint(date, "%τ", tmfmt(gmtime(time(nil)), "MMM _D hh:mm:ss YYYY")))
		sysfatal("sprint(date, ...): %r");

	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);	
		}
	}
}