/* * Copyright (c) 2026 Nakidai Perumenei * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "ircd.h" #include #include #include #include #include static struct Oper opers[OPERS_MAX]; size_t opers_c; static int names(struct Message *msg, struct Peer *peer); static int join(struct Message *msg, struct Peer *peer) { struct Message namesmsg; size_t i; char *cp; /* TODO: support comma separated list of channels, keys */ ensure(peer->type, reply(peer, 451), 0); ensure(msg->params[0] && *msg->params[0], reply(peer, 461, msg->command), 0); ensure(peer->channels_c != lengthof(peer->channels), reply(peer, 405, msg->params[0]), 0) ensure(*msg->params[0] == '#', reply(peer, 403, msg->params[0]), 0); for (cp = msg->params[0]; *cp; ++cp) ensure( *cp != '\a' && *cp != ',' && *cp != ':', reply(peer, 403, msg->params[0]), 0 ); for (i = 0; i < peer->channels_c; ++i) ensure(strcmp(channels[i].name, msg->params[0]), (void)0, 0); namesmsg = (struct Message){ .params = { msg->params[0] } }; for (i = 0; i < channels_c; ++i) { if (strcmp(channels[i].name, msg->params[0])) continue; if (channel_join(&channels[i], peer)) reply(peer, 471); else names(&namesmsg, peer); return 0; } channels[channels_c] = (struct Channel){0}; strlcpy(channels[channels_c].name, msg->params[0], sizeof(channels->name)); channel_join(&channels[channels_c++], peer); names(&namesmsg, peer); return 0; } static int mode(struct Message *msg, struct Peer *peer) { int set = 1, m; ensure(peer->type, reply(peer, 451), 0); ensure(msg->params[0] && *msg->params[0], reply(peer, 431), 0); ensure(msg->params[1] && *msg->params[1], reply(peer, 221), 0); switch (*msg->params[1]) { case '-': set = !set; case '+': ++msg->params[1]; default: { for (; *msg->params[1]; ++msg->params[1]) { m = user_mode(*msg->params[1]); if (!m) { reply(peer, 501, msg->params[1]); continue; } if (m & AWAY) continue; if (set && !(m & (OPER | LOCALOPER))) peer->modes |= m; else if (!(m & RESTRICTED)) peer->modes &= ~m; } } } return 0; } static int names(struct Message *msg, struct Peer *peer) { static char buf[MESSAGE_MAX]; size_t i, j; for (i = 0; i < peer->channels_c; ++i) { if (msg->params[0] && strcmp(msg->params[0], peer->channels[i]->name)) continue; memset(buf, 0, sizeof(buf)); for (j = 0; j < peer->channels[i]->users_c; ++j) { strlcat(buf, peer->channels[i]->users[j]->nick, sizeof(buf)); if (strlen(buf) >= MESSAGE_MAX - 4*PEER_NICK_MAX) { reply(peer, 353, peer->channels[i]->name, buf); memset(buf, 0, sizeof(buf)); } else { strlcat(buf, " ", sizeof(buf)); } } if (*buf) reply(peer, 353, peer->channels[i]->name, buf); } reply(peer, 366, msg->params[0] ? msg->params[0] : "*"); return 0; } static int nick(struct Message *msg, struct Peer *peer) { size_t i; char *cp; ensure(msg->params[0] && *msg->params[0], reply(peer, 431), 0); ensure(!(peer->modes & RESTRICTED), reply(peer, 484), 0); cp = msg->params[0]; ensure( isupper(*cp) || islower(*cp) || 0x5b <= *cp && *cp <= 0x60 || 0x7b <= *cp && *cp <= 0x7d, reply(peer, 432, msg->params[0]), 0 ); for (++cp; *cp; ++cp) ensure( isupper(*cp) || islower(*cp) || isdigit(*cp) || 0x5b <= *cp && *cp <= 0x60 || 0x7b <= *cp && *cp <= 0x7d || *cp == '-', reply(peer, 432, msg->params[0]), 0 ); for (i = 0; i < peers_c; ++i) if (peers[i].fd != peer->fd) ensure( strcmp(peers[i].nick, msg->params[0]), reply(peer, 433, msg->params[0]), 0 ); if (peer->type) announce( peer, ":%s!%s@%s NICK %s", getnick(peer), peer->user, peer->host, msg->params[0] ); user_reg(peer, msg->params[0], NULL, NULL, NULL); return 0; } static int oper(struct Message *msg, struct Peer *peer) { size_t i; ensure(peer->type, reply(peer, 451), 0); ensure(msg->params[0] && *msg->params[0], reply(peer, 461, msg->command), 0); ensure(msg->params[1] && *msg->params[1], reply(peer, 464), 0); for (i = 0; i < opers_c; ++i) if (!strcmp(opers[i].nick, msg->params[0])) break; ensure(i != opers_c && !strcmp(opers[i].pass, msg->params[1]), reply(peer, 464), 0); peer->modes |= OPER; reply(peer, 381); return 0; } static int part(struct Message *msg, struct Peer *peer) { size_t i; ensure(peer->type, reply(peer, 451), 0); ensure(msg->params[0] && *msg->params[0], reply(peer, 461, msg->command), 0); for (i = 0; i < peer->channels_c; ++i) if (!strcmp(peer->channels[i]->name, msg->params[0])) break; ensure(i != peer->channels_c, reply(peer, 442, msg->params[0]), 0) writechanf( 0, peer->channels[i], ":%s!%s@%s PART %s :%s", getnick(peer), peer->user, peer->host, msg->params[0], msg->params[1] ? msg->params[1] : getnick(peer) ); channel_exit(peer->channels[i], peer); return 0; } static int ping(struct Message *msg, struct Peer *peer) { ensure(msg->params[0] && *msg->params[0], reply(peer, 409), 0); writef( peer->fd, ":%s PONG :%s", hostname, msg->params[0] ); return 0; } static int pong(struct Message *msg, struct Peer *peer) { ensure(msg->params[0] && *msg->params[0], reply(peer, 409), 0); peer->ping = 0; return 0; } static int privmsg(struct Message *msg, struct Peer *peer) { size_t i; ensure(peer->type, reply(peer, 451), 0); ensure(msg->params[0] && *msg->params[0], reply(peer, 411), 0); ensure(msg->params[1] && *msg->params[1], reply(peer, 412), 0); /* TODO: implement channels */ ensure(!strchr("!+&", *msg->params[0]), writef( peer->fd, ":%s 421 %s :Channels are not supported yet", hostname, getnick(peer) ), 0); /* TODO: implement server-to-server communication */ switch (*msg->params[0]) { case '#': { for (i = 0; i < channels_c; ++i) if (!strcmp(channels[i].name, msg->params[0])) { writechanf( peer, &channels[i], ":%s!%s@%s %s %s :%s", peer->nick, peer->user, peer->host, *msg->command == 'p' ? "PRIVMSG" : "NOTICE", msg->params[0], msg->params[1] ); break; } ensure(i != channels_c, reply(peer, 401, msg->params[0]), 0); } break; default: { for (i = 0; i < peers_c; ++i) { if (peers[i].type != CLIENT || strcmp(peers[i].nick, msg->params[0])) continue; writef( peers[i].fd, ":%s!%s@%s %s %s :%s", peer->nick, peer->user, peer->host, *msg->command == 'p' ? "PRIVMSG" : "NOTICE", msg->params[0], msg->params[1] ); break; } ensure(i != peers_c, reply(peer, 401, msg->params[0]), 0); } break; } return 0; } static int quit(struct Message *msg, struct Peer *peer) { strlcpy(peer->quit, msg->params[0] ? msg->params[0] : "Client Quit", PEER_QUIT_MAX); return 1; } static int setcreation(struct Message *msg, struct Peer *peer) { ensure(peer->type == CONFIG, reply(peer, 481, "You're not a config file"), 0); ensure(msg->params[0] && *msg->params[0], reply(peer, 461, msg->command), 0); strlcpy(creation, msg->params[0], sizeof(creation)); return 0; } static int setinfo(struct Message *msg, struct Peer *peer) { ensure(peer->type == CONFIG, reply(peer, 481, "You're not a config file"), 0); ensure(msg->params[0] && *msg->params[0], reply(peer, 461, msg->command), 0); strlcpy(info, msg->params[0], sizeof(info)); return 0; } static int setoper(struct Message *msg, struct Peer *peer) { size_t i; ensure(peer->type == CONFIG, reply(peer, 481, "You're not a config file"), 0); ensure(msg->params[0] && *msg->params[0], reply(peer, 461, msg->command), 0); ensure(msg->params[1], reply(peer, 461, msg->command), 0); ensure( !*msg->params[1] || *msg->params[1] && opers_c < OPERS_MAX, reply(peer, 420, msg->params[0]), 0 ); for (i = 0; i < opers_c; ++i) if (!strcmp(opers[i].nick, msg->params[0])) break; if (*msg->params[1]) { ++opers_c; strlcpy(opers[i].nick, msg->params[0], sizeof(opers->pass)); strlcpy(opers[i].pass, msg->params[1], sizeof(opers->pass)); } else if (i < PEERS_MAX) { opers[i] = opers[--opers_c]; } return 0; } static int user(struct Message *msg, struct Peer *peer) { size_t i; char *cp; ensure(!peer->type, reply(peer, 462), 0); for (i = 0; i < 4; ++i) ensure(msg->params[i] && *msg->params[i], reply(peer, 461, msg->command), 0); for (cp = msg->params[0]; *cp; ++cp) if (*cp == '@') { *cp = 0; break; } user_reg(peer, NULL, msg->params[0], msg->params[3], msg->params[1]); return 0; } static int whois(struct Message *msg, struct Peer *peer) { size_t i; ensure(peer->type, reply(peer, 451), 0); ensure(msg->params[0] && *msg->params[0], reply(peer, 431), 0); for (i = 0; i < peers_c; ++i) if (!strcmp(msg->params[0], peers[i].nick)) break; ensure(i != peers_c, reply(peer, 401, msg->params[0]), 0); reply( peer, 311, peers[i].nick, peers[i].user, peers[i].host, peers[i].real ); reply( peer, 312, peers[i].nick, hostname, info ); if (peers[i].modes & OPER) reply( peer, 313, peers[i].nick ); reply(peer, 318, peers[i].nick); return 0; } static int default_handler(struct Message *msg, struct Peer *peer) { ensure(peer->type, (void)0, 0); reply(peer, 421, msg->command); return 0; } static struct Handler { const char *name; Handler *handler; } handlers[] = { { "join", join }, { "mode", mode }, { "names", names }, { "nick", nick }, { "notice", privmsg }, { "oper", oper }, { "part", part }, { "ping", ping }, { "pong", pong }, { "privmsg", privmsg }, { "quit", quit }, { "setcreation", setcreation }, { "setinfo", setinfo }, { "setoper", setoper }, { "user", user }, { "whois", whois }, }; Handler * find(const char *command) { struct Handler *l, *m, *r; int res; l = handlers; r = handlers + lengthof(handlers) - 1; while (l <= r) { m = l + (r - l) / 2; res = strcmp(command, m->name); if (res > 0) l = ++m; else if (res < 0) r = --m; else return m->handler; } return default_handler; }