diff options
| -rw-r--r-- | Makefile | 3 | ||||
| -rw-r--r-- | channel.c | 80 | ||||
| -rw-r--r-- | handle.c | 150 | ||||
| -rw-r--r-- | ircd.h | 32 | ||||
| -rw-r--r-- | loop.c | 74 | ||||
| -rw-r--r-- | reply.c | 209 | ||||
| -rw-r--r-- | user.c | 10 | ||||
| -rw-r--r-- | writef.c | 39 |
8 files changed, 507 insertions, 90 deletions
diff --git a/Makefile b/Makefile index 5953be1..59070a7 100644 --- a/Makefile +++ b/Makefile @@ -2,14 +2,17 @@ NAME ?= ircd CFLAGS += -Wall -Wextra +OBJS += channel.o OBJS += handle.o OBJS += loop.o OBJS += main.o OBJS += message.o OBJS += peer.o +OBJS += reply.o OBJS += user.o OBJS += writef.o +all: ${NAME} ${NAME}: ${OBJS} ${CC} -o ${NAME} ${CFLAGS} ${LDFLAGS} ${OBJS} clean: diff --git a/channel.c b/channel.c new file mode 100644 index 0000000..0cf0b30 --- /dev/null +++ b/channel.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org> + * + * 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 <stddef.h> +#include <string.h> + +#include <err.h> + + +struct Channel channels[CHANNELS_MAX]; +size_t channels_c; + +int +channel_join(struct Channel *channel, struct Peer *peer) +{ + if (channel->users_c == lengthof(channel->users) + || peer->channels_c == lengthof(peer->channels)) + return 1; + + peer->channels[peer->channels_c++] = channel; + channel->users[channel->users_c++] = peer; + writechanf( + 0, + channel, + ":%s!%s@%s JOIN %s", + getnick(peer), + peer->user, + peer->host, + channel->name + ); + return 0; +} + +int +channel_exit(struct Channel *channel, struct Peer *peer) +{ + size_t i; + + for (i = 0; i < channel->users_c; ++i) + if (channel->users[i]->fd == peer->fd) + break; + ensure(i != channel->users_c, (void)0, 1); + channel->users[i] = channel->users[--channel->users_c]; + + for (i = 0; i < peer->channels_c; ++i) + if (!strcmp(peer->channels[i]->name, channel->name)) + break; + ensure(i != peer->channels_c, warnx( + "channel_exit(): channel had user, but user hadn't channel" + ), 1) + peer->channels[i] = peer->channels[--peer->channels_c]; + + if (!channel->users_c) + { + for (i = 0; i < channels_c; ++i) + if (!strcmp(channels[i].name, channel->name)) + break; + ensure(i != channels_c, warnx( + "channel_exit(): couldn't find empty channel" + ), 1); + channels[i] = channels[--channels_c]; + } + + return 0; +} diff --git a/handle.c b/handle.c index 79a3b9a..61b34f7 100644 --- a/handle.c +++ b/handle.c @@ -20,18 +20,46 @@ #include <stdio.h> #include <string.h> +#include <err.h> -#define ensure(cond, iffalse, doexit) if (!(cond)) { iffalse; if (doexit >= 0) return doexit; } + +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 nick(struct Message *msg, struct Peer *peer) { - ensure(msg->params[0] && msg->params[0], writef( - peer->fd, - ":%s 431 %s :No nickname given", - hostname, - getnick(peer) - ), 0); + ensure(msg->params[0] && msg->params[0], reply(peer, 431), 0); user_reg(peer, msg->params[0], NULL, NULL); @@ -39,31 +67,43 @@ nick(struct Message *msg, struct Peer *peer) } 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, writef( - peer->fd, - ":%s 451 %s :You have not registered", - hostname, - getnick(peer) - ), 0); - ensure(msg->params[0] && *msg->params[0], writef( - peer->fd, - ":%s 411 %s :No recipient given (PRIVMSG)", - hostname, - getnick(peer) - ), 0); - ensure(msg->params[1] && *msg->params[1], writef( - peer->fd, - ":%s 412 %s :No text to send", - hostname, - getnick(peer) - ), 0); + 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( + ensure(!strchr("!+&", *msg->params[0]), writef( peer->fd, ":%s 421 %s :Channels are not supported yet", hostname, @@ -71,17 +111,48 @@ privmsg(struct Message *msg, struct Peer *peer) ), 0); /* TODO: implement server-to-server communication */ - for (i = 0; i < PEERS_MAX; ++i) - if (peers[i].type == CLIENT && !strcmp(peers[i].nick, msg->params[0])) + 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, - peer->nick, + msg->params[0], msg->params[1] ); + break; + } + ensure(i != peers_c, reply(peer, 401, msg->params[0]), 0); + } break; + } return 0; } @@ -96,20 +167,9 @@ user(struct Message *msg, struct Peer *peer) { size_t i; - ensure(!peer->type, writef( - peer->fd, - ":%s 462 %s :Unauthorized command (already registered)", - hostname, - getnick(peer) - ), 0); + ensure(!peer->type, reply(peer, 462), 0); for (i = 0; i < 4; ++i) - ensure(msg->params[i] && *msg->params[i], writef( - peer->fd, - ":%s 461 %s %s :Not enough parameters", - hostname, - getnick(peer), - msg->command - ), 0); + ensure(msg->params[i] && *msg->params[i], reply(peer, 461), 0); user_reg(peer, NULL, msg->params[0], msg->params[3]); @@ -119,9 +179,9 @@ user(struct Message *msg, struct Peer *peer) static int default_handler(struct Message *msg, struct Peer *peer) { - ensure(peer->type, 0, 0); + ensure(peer->type, (void)0, 0); - writef(peer->fd, ":%s 421 %s %s :Unknown command", hostname, getnick(peer), msg->command); + reply(peer, 421, msg->command); return 0; } @@ -130,7 +190,9 @@ static struct Handler { Handler *handler; } handlers[] = { + { "join", join }, { "nick", nick }, + { "part", part }, { "privmsg", privmsg }, { "quit", quit }, { "user", user }, diff --git a/ircd.h b/ircd.h index 5e787da..8097c26 100644 --- a/ircd.h +++ b/ircd.h @@ -21,11 +21,16 @@ #define PARAM_MAX 15 -#define PEERS_MAX 512 + #define MESSAGE_MAX 512 +#define PEERS_MAX 512 +#define CHANNELS_MAX 512 +#define USERS_PER_CHANNEL 512 +#define MODES_MAX 512 #define BIT(x) x##_BIT, x = 1 << x##_BIT, x##_BIT_ = x##_BIT #define lengthof(X) (sizeof(X) / sizeof(*(X))) +#define ensure(cond, iffalse, doexit) if (!(cond)) { iffalse; if (doexit >= 0) return doexit; } struct Message { @@ -35,13 +40,16 @@ struct Message struct Peer { int fd; - enum ClientType { UNREGED, CLIENT, SERVER, SERVICE } type; + enum ClientType { UNREGD, CLIENT, SERVER, SERVICE } type; char nick[16], user[16], real[32], host[64]; - char **channels; - size_t channels_count; - char modes[52]; + struct Channel *channels[32]; + enum PeerStatus { + BIT(DELETE), + BIT(ANNOUNCE), + } flags; char buf[MESSAGE_MAX]; - size_t recvd; + size_t recvd, channels_c; + char modes[52]; }; struct Channel @@ -51,13 +59,16 @@ struct Channel struct { char mode; char param[MESSAGE_MAX - 1]; - } *modes; - size_t modes_count; + } modes[MODES_MAX]; + struct Peer *users[USERS_PER_CHANNEL]; + size_t modes_c, users_c; }; typedef int Handler(struct Message *msg, struct Peer *peer); +extern struct Channel channels[CHANNELS_MAX]; extern struct Peer peers[PEERS_MAX]; +extern size_t channels_c, peers_c; extern const char *hostname; extern const char *host; extern int port; @@ -65,10 +76,15 @@ extern int port; const char *getnick(const struct Peer *peer); void user_reg(struct Peer *peer, const char *nick, const char *user, const char *real); +int channel_join(struct Channel *channel, struct Peer *peer); +int channel_exit(struct Channel *channel, struct Peer *peer); + int parse_message(char *buf, struct Message *msg); int handle(struct Peer *peer); +int reply(const struct Peer *peer, int number, ...); Handler *find(const char *command); int writef(int fd, const char *fmt, ...); +int writechanf(const struct Peer *except, const struct Channel *channel, const char *fmt, ...); void ircd(void); #endif /* __IRCD_H__ */ diff --git a/loop.c b/loop.c index ba14a4d..b2430b8 100644 --- a/loop.c +++ b/loop.c @@ -29,9 +29,9 @@ #include <sys/types.h> -struct Peer peers[PEERS_MAX]; static struct pollfd pfd[PEERS_MAX + 1]; -static size_t remove_schedule[PEERS_MAX]; +struct Peer peers[PEERS_MAX]; +size_t peers_c; const char *host; int port; @@ -39,7 +39,7 @@ int port; void ircd(void) { - size_t i, schedulei, connected, pid; + size_t i, j, k; struct sockaddr_in addr, clientaddr; int res, sfd, client; nfds_t passed; @@ -51,9 +51,7 @@ ircd(void) res = inet_pton(AF_INET, host, &addr.sin_addr); if (res == -1) err(1, "inet_pton()"); - - sfd = socket(AF_INET, SOCK_STREAM, 0); - if (sfd == -1) + sfd = socket(AF_INET, SOCK_STREAM, 0); if (sfd == -1) err(1, "socket()"); res = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); @@ -69,27 +67,27 @@ ircd(void) err(1, "listen()"); signal(SIGPIPE, SIG_IGN); - for (connected = 0;;schedulei = 0) + for (peers_c = channels_c = 0;;) { - pfd[connected] = (struct pollfd){ .fd = sfd, .events = POLLIN }; - for (i = 0; i < connected; ++i) + pfd[peers_c] = (struct pollfd){ .fd = sfd, .events = POLLIN }; + for (i = 0; i < peers_c; ++i) pfd[i] = (struct pollfd) { .fd = peers[i].fd, .events = POLLIN, }; - res = poll(pfd, (passed = connected) + 1, -1); + res = poll(pfd, (passed = peers_c) + 1, -1); if (res == -1) err(1, "poll()"); - if (pfd[connected].revents & POLLIN) + if (pfd[peers_c].revents & POLLIN) { client = accept(sfd, (void *)&clientaddr, &(int){sizeof(clientaddr)}); if (client == -1) { - warn("accept(sfd)"); + warn("accept(%d)", sfd); } - else if (connected == PEERS_MAX) + else if (peers_c == PEERS_MAX) { /* * TODO: maybe send 005 or smth? @@ -98,9 +96,9 @@ ircd(void) } else { - peers[connected++] = (struct Peer){ .fd = client }; + peers[peers_c++] = (struct Peer){ .fd = client }; strlcpy( - peers[connected - 1].host, + peers[peers_c - 1].host, inet_ntoa(clientaddr.sin_addr), sizeof(peers->host) ); @@ -119,16 +117,46 @@ ircd(void) ); peers[i].recvd += recvd; if (recvd == -1 || !recvd || handle(&peers[i])) - remove_schedule[schedulei++] = i; + peers[i].flags |= DELETE; } - for (i = 0; i < schedulei; ++i) + for (i = 0; i < peers_c; ++i) { - pid = remove_schedule[i]; - /* TODO: announce_quit(&peers[pid]); */ - writef(peers[pid].fd, "ERROR :Closing Link: %s", peers[pid].host); - close(peers[pid].fd); - peers[pid] = peers[--connected]; + if (!(peers[i].flags & DELETE)) + continue; + + if (!peers[i].type) + goto skip; + writef( + peers[i].fd, + ":%s!%s@%s QUIT", + getnick(&peers[i]), + peers[i].user, + peers[i].host + ); + for (j = 0; j < peers[i].channels_c; ++j) + { + channel_exit(peers[i].channels[j], &peers[i]); + for (k = 0; k < peers[i].channels[j]->users_c; ++k) + peers[i].channels[j]->users[k]-> + flags |= ANNOUNCE; + } + for (j = 0; j < peers_c; ++j) + if (peers[j].flags & ANNOUNCE) + { + writef( + peers[j].fd, + "%s!%s@%s QUIT", + getnick(&peers[i]), + peers[i].user, + peers[i].host + ); + peers[j].flags &= ~ANNOUNCE; + } +skip: + writef(peers[i].fd, "ERROR :Closing Link: %s", peers[i].host); + close(peers[i].fd); + peers[i] = peers[--peers_c]; + i = 0; } - schedulei = 0; } } diff --git a/reply.c b/reply.c new file mode 100644 index 0000000..b73fbea --- /dev/null +++ b/reply.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2026 Nakidai Perumenei <nakidai at disroot dot org> + * + * 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 <stdarg.h> + +#include <err.h> + + +typedef int Reply(const struct Peer *peer, va_list ap); + +#define REPLY(n) static int r##n (const struct Peer *peer, va_list ap) +#define ARG(n) const char *n = va_arg(ap, const char *) + +REPLY(1) +{ + return writef( + peer->fd, + ":%s 001 %s Welcome to the Internet Relay Network %s!%s@%s", + hostname, + getnick(peer), + getnick(peer), + peer->user, + peer->host + ); +} + +REPLY(401) +{ + ARG(name); + return writef( + peer->fd, + ":%s 401 %s %s :No such nick/channel", + hostname, + getnick(peer), + name + ); +} + +REPLY(403) +{ + ARG(channel); + return writef( + peer->fd, + ":%s 403 %s %s :No such channel", + hostname, + getnick(peer), + channel + ); +} + +REPLY(405) +{ + ARG(channel); + return writef( + peer->fd, + ":%s 405 %s %s :You have joined too many channels", + hostname, + getnick(peer), + channel + ); +} + +REPLY(411) +{ + return writef( + peer->fd, + ":%s 411 %s :No recipient given (PRIVMSG)", + hostname, + getnick(peer) + ); +} + +REPLY(412) +{ + return writef( + peer->fd, + ":%s 412 %s :No text to send", + hostname, + getnick(peer) + ); +} + +REPLY(421) +{ + ARG(command); + return writef( + peer->fd, + ":%s 421 %s %s :Unknown command", + hostname, + getnick(peer), + command + ); +} + +REPLY(431) +{ + return writef( + peer->fd, + ":%s 431 %s :No nickname given", + hostname, + getnick(peer) + ); +} + +REPLY(442) +{ + ARG(channel); + return writef( + peer->fd, + ":%s 442 %s %s :You're not on that channel", + hostname, + getnick(peer), + channel + ); +} + +REPLY(451) +{ + return writef( + peer->fd, + ":%s 451 %s :You have not registered", + hostname, + getnick(peer) + ); +} + +REPLY(461) +{ + ARG(command); + return writef( + peer->fd, + ":%s 461 %s %s :Not enough parameters", + hostname, + getnick(peer), + command + ); +} + +REPLY(462) +{ + return writef( + peer->fd, + ":%s 462 %s :Unauthorized command (already registered)", + hostname, + getnick(peer) + ); +} + +REPLY(471) +{ + ARG(channel); + return writef( + peer->fd, + ":%s 471 %s %s :Cannot join channel (+l)", + hostname, + getnick(peer), + channel + ); +} + +#undef REPLY +#define REPLY(n) [n] = r##n +static Reply *replies[] = { + REPLY( 1), + REPLY(401), + REPLY(403), + REPLY(405), + REPLY(411), + REPLY(412), + REPLY(421), + REPLY(431), + REPLY(442), + REPLY(451), + REPLY(461), + REPLY(462), + REPLY(471), +}; + +int +reply(const struct Peer *peer, int number, ...) +{ + Reply *choice; + va_list args; + int ret; + + choice = replies[number]; + if (!choice) + warnx("YOU FUCKING STUPID SHIT IMPLEMENT REPLY #%d NOW!!!", number); + va_start(args, number); + ret = choice(peer, args); + va_end(args); + + return ret; +} diff --git a/user.c b/user.c index 4411144..e37452b 100644 --- a/user.c +++ b/user.c @@ -37,14 +37,6 @@ user_reg(struct Peer *peer, const char *nick, const char *user, const char *real if (*peer->nick && *peer->user && *peer->real) { peer->type = CLIENT; - writef( - peer->fd, - ":%s 001 %s :Welcome to the Internet Relay Network %s!%s@%s", - hostname, - getnick(peer), - getnick(peer), - peer->user, - peer->host - ); + reply(peer, 1); } } diff --git a/writef.c b/writef.c index 7d57e3b..e010ca4 100644 --- a/writef.c +++ b/writef.c @@ -23,18 +23,45 @@ #include <unistd.h> +static char buf[MESSAGE_MAX + 1]; +static int written; + +static void +vstoref(const char *fmt, va_list ap) +{ + written = vsnprintf(buf, sizeof(buf) - 2, fmt, ap); + written = written < sizeof(buf) - 2 ? written : sizeof(buf) - 2; + memcpy(buf + written, "\r\n", 2); + written += 2; +} + int writef(int fd, const char *fmt, ...) { - static char buf[MESSAGE_MAX + 1]; va_list args; - int written; va_start(args, fmt); - written = vsnprintf(buf, sizeof(buf) - 2, fmt, args); + vstoref(fmt, args); va_end(args); - written = written < sizeof(buf) - 2 ? written : sizeof(buf) - 2; - memcpy(buf + written, "\r\n", 3); - return write(fd, buf, written + 3); + return write(fd, buf, written); +} + +int +writechanf(const struct Peer *except, const struct Channel *channel, const char *fmt, ...) +{ + va_list args; + int count; + size_t i; + + va_start(args, fmt); + vstoref(fmt, args); + va_end(args); + + count = 0; + for (i = 0; i < channel->users_c; ++i) + if (!except || channel->users[i]->fd != except->fd) + count += write(channel->users[i]->fd, buf, written) == written; + + return count; } |