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