ref: d08105b69107543a67d13df9664f4a4faaa8f2df
dir: /ircd.c/
#include <u.h> #include <libc.h> char *password = nil; /* nil to disable server password */ char *motdtxt = nil; /* nil for no motd */ enum { Maxclients = 48, Maxchannels = 16, Nicklen = 32, Chanlen = 15, Msglen = 512, /* 510 + "\r\n" */ Timeout = 120 * 1000, }; enum { /* commands */ PING = 1000, PONG, JOIN, PART, QUIT, PRIVMSG, NICK, TOPIC, AWAY, ERROR, MOTD, /* numericals */ RPL_WELCOME = 001, RPL_AWAY = 301, RPL_UNAWAY = 305, RPL_NOWAWAY = 306, RPL_NOTOPIC = 331, RPL_TOPIC = 332, RPL_TOPICWHOTIME = 333, RPL_MOTD = 372, RPL_MOTDSTART = 375, RPL_ENDOFMOTD = 376, ERR_NOSUCHNICK = 401, ERR_NOSUCHCHANNEL = 403, ERR_CANNOTSENDTOCHAN = 404, ERR_NORECIPIENT = 411, ERR_NOTEXTTOSEND = 412, ERR_UNKNOWNCOMMAND = 421, ERR_NOMOTD = 422, ERR_NONICKNAMEGIVEN = 431, ERR_ERRONEUSNICKNAME = 432, ERR_NICKNAMEINUSE = 433, ERR_NOTONCHANNEL = 442, ERR_NOTREGISTERED = 451, ERR_ALREADYREGISTERED = 461, ERR_PASSWDMISMATCH = 464, }; 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; char *away; /* away msg */ }; /* not to mix with thread(2) Channel:s */ struct Chan { char *name; char *topic; char *topicwho; List members; List banned; List silenced; }; /* message that is sent to clients */ struct Reply { int code; char *argv[5]; }; List clients; List channels; QLock clock; char *servername; char *date; 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(); c->away = nil; 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: /* r->argv is: pong */ n = snprint(buf, n, ":%s PONG %s\r\n", servername, r->argv[0]); 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 ERROR: 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 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 RPL_AWAY: /* r->argv is: target, awaymsg */ n = snprint(buf, n, ":%s 301 %s %s :%s\r\n", servername, c->nick, r->argv[0], r->argv[1]); dprint("here\n"); break; case RPL_UNAWAY: n = snprint(buf, n, ":%s 305 %s :You are no longer marked as being away\r\n", servername, c->nick); break; case RPL_NOWAWAY: n = snprint(buf, n, ":%s 306 %s :You are now marked as being away\r\n", servername, c->nick); break; case RPL_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; case RPL_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 RPL_TOPIC: /* 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 RPL_TOPICWHOTIME: /* 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 RPL_MOTD: /* r->argv is: channel, topicwhotime */ n = snprint(buf, n, ":%s 372 %s :%s\r\n", servername, c->nick, r->argv[0]); break; case RPL_MOTDSTART: /* r->argv is: channel, topicwhotime */ n = snprint(buf, n, ":%s 375 %s :Message of the day:\r\n", servername, c->nick); break; case RPL_ENDOFMOTD: /* r->argv is: channel, topicwhotime */ n = snprint(buf, n, ":%s 376 %s :End of message of the day\r\n", servername, c->nick); break; case ERR_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 ERR_NOSUCHCHANNEL: /* r->argv is: channel */ n = snprint(buf, n, ":%s 403 %s %s :No such channel\r\n", servername, c->nick, r->argv[0]); break; case ERR_CANNOTSENDTOCHAN: /* r->argv is: command */ n = snprint(buf, n, ":%s 404 %s %s :Cannot send to channel\r\n", servername, c->nick, r->argv[0]); break; case ERR_NORECIPIENT: n = snprint(buf, n, ":%s 411 %s :No recipient given (%s)\r\n", servername, c->nick, r->argv[0]); break; case ERR_NOTEXTTOSEND: n = snprint(buf, n, ":%s 412 %s :No text to send\r\n", servername, c->nick); break; case ERR_UNKNOWNCOMMAND: /* r->argv is: command */ n = snprint(buf, n, ":%s 421 %s %s :Unknown command\r\n", servername, c->nick, r->argv[0]); break; case ERR_NOMOTD: n = snprint(buf, n, ":%s 422 %s :No motd\r\n", servername, c->nick); break; case ERR_NONICKNAMEGIVEN: n = snprint(buf, n, ":%s 431 %s :No nickname given\r\n", servername, c->nick); break; case ERR_ERRONEUSNICKNAME: /* r->argv[0] is nick */ n = snprint(buf, n, ":%s 432 %s %s :Erroneus nickname\r\n", servername, c->nick, r->argv[0]); break; case ERR_NICKNAMEINUSE: /* 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 ERR_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 ERR_NOTREGISTERED: n = snprint(buf, n, ":%s 451 %s :Connection not registered\r\n", servername, c->nick); break; case ERR_ALREADYREGISTERED: n = snprint(buf, n, ":%s 461 %s :Connection already registered\r\n", servername, c->nick); break; case ERR_PASSWDMISMATCH: n = snprint(buf, n, ":%s 464 %s :Password incorrect\r\n", servername, c->nick); 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 motd(Client *c) { Reply r; char *s; if(motdtxt == nil){ r.code = ERR_NOMOTD; reply(c, &r); }else{ r.code = RPL_MOTDSTART; reply(c, &r); s = strtok(motdtxt, "\n"); do{ r.code = RPL_MOTD; r.argv[0] = s; reply(c, &r); }while(s = strtok(0, "\n")); r.code = RPL_ENDOFMOTD; reply(c, &r); } return 1; } void welcome(Client *c) { Reply r; r.code = RPL_WELCOME; reply(c, &r); motd(c); } int setprefix(Client *c) { /* fatal */ if(password != nil && !c->passok) return 0; /* not fatal */ if(c->user == nil || c->nick == nil) return 1; 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 1; } /* 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 = ERR_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; welcome(c); 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 = ERR_ERRONEUSNICKNAME; 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 = ERR_NICKNAMEINUSE; 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) welcome(c); 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 = RPL_TOPIC; r.argv[0] = ch->name; r.argv[1] = ch->topic; reply(c, &r); if(ch->topicwho == nil) return; r.code = RPL_TOPICWHOTIME; 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); r.code = ERR_NOSUCHCHANNEL; r.argv[0] = chan; reply(c, &r); goto end; } if(!validchan(chan)){ dprint("%d: invalid chan %s\n", c->fd, chan); r.code = ERR_NOSUCHCHANNEL; r.argv[0] = chan; reply(c, &r); 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 = ERR_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 = ERR_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 = RPL_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; } int names(Client *c, char *chan) { Chan *ch; Reply r; qlock(&clock); ch = getchan(&channels, chan); if(ch == nil){ if(!validchan(chan)){ dprint("%d: no channel found for NAMES %s\n", c->fd, chan); r.code = ERR_NOSUCHCHANNEL; r.argv[0] = chan; reply(c, &r); goto end; } ch = addchan(chan); } dprint("%d: NAMES chan=%s\n", c->fd, chan); replynames(c, ch); 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, raway; 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 = ERR_CANNOTSENDTOCHAN; r.argv[0] = target; reply(c, &r); } }else{ r.code = ERR_NOSUCHNICK; r.argv[0] = target; reply(c, &r); } }else{ tc = getclient(&clients, target); if(tc != nil){ if(tc->away != nil){ raway.code = RPL_AWAY; raway.argv[0] = tc->nick; raway.argv[1] = tc->away; reply(c, &raway); } reply(tc, &r); } else{ r.code = ERR_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) { Reply r; if(password == nil) return 1; if(c->passok) return 0; else{ if(p == nil || *p == ':' && !*++p || strcmp(p, password) != 0){ r.code = ERR_PASSWDMISMATCH; reply(c, &r); return 0; }else c->passok = 1; } return 1; } int away(Client *c, char *msg) { Reply r; /* don't mark one as away twice */ if(msg != nil && c->away == nil){ c->away = msg; r.code = RPL_NOWAWAY; }else{ c->away = nil; r.code = RPL_UNAWAY; } reply(c, &r); return 1; } int process(Client *c, char *msg) { char *cmd, *tmp, *tmp2; Reply r; cmd = strtok(msg, " :\r"); if(cmd == nil) return 1; /* silently drop it */ for(int i = 0 ; i < strlen(cmd) ; i++) cmd[i] = toupper(cmd[i]); if(strcmp(cmd, "PING") == 0){ r.argv[0] = strtok(0, " \r"); /* pong */ 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 || *tmp == ':' && !*++tmp){ r.code = ERR_NONICKNAMEGIVEN; reply(c, &r); return 1; } 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 = ERR_NOTREGISTERED; reply(c, &r); return 1; } if(strcmp(cmd, "PRIVMSG") == 0){ tmp = strtok(0, " :"); /* target */ if(tmp == nil){ r.code = ERR_NORECIPIENT; r.argv[0] = cmd; reply(c, &r); return 1; } tmp2 = strtok(0, "\r"); /* msg */ if(tmp2 == nil || *tmp2 == ':' && !*++tmp2){ r.code = ERR_NOTEXTTOSEND; reply(c, &r); return 1; } return privmsg(c, tmp, tmp2); } /* JOIN #c or JOIN #c1,#c2 hence the check for "," */ if(strcmp(cmd, "JOIN") == 0){ tmp = strtok(0, " :\r"); /* channels */ if(tmp == nil) return 0; tmp2 = strtok(tmp, ","); do{ /* if there are multiple channels */ if(tmp2 != nil) join(c, tmp2); }while(tmp2 = strtok(0, ",")); return 1; } if(strcmp(cmd, "NAMES") == 0){ tmp = strtok(0, " :\r"); /* channels */ if(tmp == nil) return 0; tmp2 = strtok(tmp, ","); do{ /* if there are multiple channels */ if(tmp2 != nil) names(c, tmp2); }while(tmp2 = strtok(0, ",")); return 1; } if(strcmp(cmd, "PART") == 0){ tmp = strtok(0, " :"); /* channel */ if(tmp == nil) return 0; tmp2 = strtok(0, "\r"); /* reason */ if(tmp2 == nil) return 0; if(*tmp2 == ':') 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 == ':') tmp2++; return topic(c, tmp, tmp2); } if(strcmp(cmd, "QUIT") == 0 || strcmp(cmd, "ERROR")== 0){ tmp = strtok(0, "\r"); /* reason */ if(tmp == nil) return 0; if(*tmp == ':') tmp++; quit(c, tmp); return 0; } if(strcmp(cmd, "LIST") == 0){ tmp = strtok(0, " :\r"); /* channels */ return list(c, tmp); } /* * TODO: WHO doesn't really mean WHOIS, * but this is enough for some clients to be happy */ if(strcmp(cmd, "WHO") == 0){ tmp = strtok(0, " :\r"); /* nicks */ return whois(c, tmp); } if(strcmp(cmd, "WHOIS") == 0){ tmp = strtok(0, " :\r"); /* nicks */ return whois(c, tmp); } if(strcmp(cmd, "AWAY") == 0){ tmp = strtok(0, " :\r"); /* msg */ return away(c, tmp); } if(strcmp(cmd, "MOTD") == 0) return motd(c); r.code = ERR_UNKNOWNCOMMAND; 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 = getenv("sysname"); date = ctime(time(nil)); 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|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{ errstr(err, sizeof(err)); if(strcmp(err, "") != 0) 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); } } }