/* * 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 static int join(struct Message *msg, struct Peer *peer) { size_t i, j; /* 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), 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 (i = 0; i < channels_c; ++i) { if (strcmp(channels[i].name, msg->params[0])) continue; for (j = 0; j < channels[i].users_c; ++j) ensure( strcmp(channels[i].users[j]->nick, peer->nick), (void)0, 0 ); ensure(!channel_join(&channels[i], peer), reply(peer, 471), 0); 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); 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 nick(struct Message *msg, struct Peer *peer) { ensure(msg->params[0] && msg->params[0], reply(peer, 431), 0); ensure(!(peer->modes & RESTRICTED), reply(peer, 484), 0); user_reg(peer, msg->params[0], NULL, NULL, NULL); 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), 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 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 PRIVMSG %s :%s", peer->nick, peer->user, peer->host, 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 PRIVMSG %s :%s", peer->nick, peer->user, peer->host, 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) { size_t i, j; ensure(peer->type, (void)0, 1) writef( peer->fd, ":%s!%s@%s QUIT :%s", getnick(peer), peer->user, peer->host, msg->params[0] ? msg->params[0] : "Client Quit" ); for (i = 0; i < peer->channels_c; ++i) { channel_exit(peer->channels[i], peer); for (j = 0; j < peer->channels[i]->users_c; ++j) peer->channels[i]->users[j]->flags |= ANNOUNCE; } for (i = 0; i < peers_c; ++i) if (peers[i].flags & ANNOUNCE) { writef( peers[i].fd, ":%s!%s@%s QUIT :%s", getnick(peer), peer->user, peer->host, msg->params[0] ? msg->params[0] : "Client Quit" ); peers[i].flags &= ~ANNOUNCE; } return 1; } static int user(struct Message *msg, struct Peer *peer) { size_t i; ensure(!peer->type, reply(peer, 462), 0); for (i = 0; i < 4; ++i) ensure(msg->params[i] && *msg->params[i], reply(peer, 461), 0); user_reg(peer, NULL, msg->params[0], msg->params[3], msg->params[1]); 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 }, { "nick", nick }, { "part", part }, { "privmsg", privmsg }, { "quit", quit }, { "user", user }, }; 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; }