ref: 4251d493d8eb4371fb6f59a0f511cb80e4a24588
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;
};
/* 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();
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;
}
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, "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);
}
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");
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);
}
}
}