diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5eec986 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.claude diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..702328c --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +NAME = ircserv +CXX = c++ +CXXFLAGS = -Wall -Wextra -Werror -std=c++98 +INCLUDES = -I includes + +SRCS = main.cpp \ + srcs/Server.cpp \ + srcs/ParseBuffer.cpp \ + srcs/IrcParser.cpp \ + srcs/IrcMessage.cpp \ + srcs/CommandValidator.cpp + +OBJS = $(SRCS:.cpp=.o) + +all: $(NAME) + +$(NAME): $(OBJS) + $(CXX) $(CXXFLAGS) $(OBJS) -o $(NAME) + +%.o: %.cpp + $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ + +clean: + rm -f $(OBJS) + +fclean: clean + rm -f $(NAME) + +re: fclean all + +.PHONY: all clean fclean re diff --git a/en.subject.pdf b/en.subject.pdf new file mode 100644 index 0000000..b325f2f Binary files /dev/null and b/en.subject.pdf differ diff --git a/includes/Server.hpp b/includes/Server.hpp index 461f60b..e809446 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -14,6 +14,7 @@ # include # include # include +# include "CommandValidator.hpp" struct ConnectedUser { @@ -27,6 +28,7 @@ struct ConnectedUser bool userOk; ParseBuffer parseBuffer; + ConnectedUser(void); explicit ConnectedUser(int fd); bool isRegistered(void) const; }; @@ -43,6 +45,12 @@ class Server void acceptNew(void); void handleRecv(int fd); void removeUser(int fd); + void dispatch(int fd, const IrcMessage &msg); + void handlePass(int fd, const IrcMessage &msg); + void handleNick(int fd, const IrcMessage &msg); + void handleUser(int fd, const IrcMessage &msg); + void handlePing(int fd, const IrcMessage &msg); + void handleQuit(int fd, const IrcMessage &msg); public: Server(int port, const std::string &password); diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..2dfaacc --- /dev/null +++ b/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include "Server.hpp" + +int main(int argc, char **argv) +{ + if (argc != 3) + { + std::cerr << "Usage: ./ircserv " << std::endl; + return (1); + } + int port = std::atoi(argv[1]); + if (port <= 0 || port > 65535) + { + std::cerr << "Error: invalid port" << std::endl; + return (1); + } + try + { + Server server(port, argv[2]); + server.start(); + server.run(); + } + catch (std::exception &e) + { + std::cerr << "Error: " << e.what() << std::endl; + return (1); + } + return (0); +} diff --git a/srcs/Server.cpp b/srcs/Server.cpp index 78d3da5..47056d8 100644 --- a/srcs/Server.cpp +++ b/srcs/Server.cpp @@ -36,4 +36,211 @@ void Server::start(void) pollfd.events = POLLIN; pollfd.revents = 0; _fds.push_back(pollfd); +} + +ConnectedUser::ConnectedUser(void) : fd(-1), passOk(false), nickOk(false), userOk(false) +{ +} + +ConnectedUser::ConnectedUser(int fd) : fd(fd), passOk(false), nickOk(false), userOk(false) +{ +} + +bool ConnectedUser::isRegistered(void) const +{ + return (passOk && nickOk && userOk); +} + +Server::Server(int port, const std::string &password) +{ + _port = port; + _password = password; + _serverFd = -1; +} + +Server::Server(const Server &other) +{ + *this = other; +} + +Server &Server::operator=(const Server &other) +{ + if (this != &other) + { + _serverFd = other._serverFd; + _port = other._port; + _password = other._password; + _fds = other._fds; + _users = other._users; + } + return *this; +} + +Server::~Server(void) +{ + for (size_t i = 0; i < _fds.size(); i++) + { + close(_fds[i].fd); + } +} + +void Server::sendReply(int fd, const std::string &msg) +{ + send(fd, msg.c_str(), msg.size(), 0); +} + +void Server::removeUser(int fd) +{ + close(fd); + _users.erase(fd); + for (size_t i = 0; i < _fds.size(); i++) + { + if (_fds[i].fd == fd) + { + _fds.erase(_fds.begin() + i); + break ; + } + } +} + +void Server::acceptNew(void) +{ + int newFd = accept(_serverFd, NULL, NULL); + if (newFd == -1) + throw std::runtime_error(strerror(errno)); + fcntl(newFd, F_SETFL, O_NONBLOCK); + struct pollfd pfd; + pfd.fd = newFd; + pfd.events = POLLIN; + pfd.revents = 0; + _fds.push_back(pfd); + _users.insert(std::make_pair(newFd, ConnectedUser(newFd))); +} + +void Server::handleRecv(int fd) +{ + char buf[512]; + int n; + n = recv(fd, buf, sizeof(buf), 0); + if (n == 0) + { + removeUser(fd); + return ; + } + else if (n == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return ; + else + { + removeUser(fd); + return ; + } + } + _users[fd].parseBuffer.append(buf, n); + std::vector messages = _users[fd].parseBuffer.extractMessages(); + for (size_t i = 0; i < messages.size(); i++) + dispatch(fd, messages[i]); +} + +void Server::run(void) +{ + while (true) + { + int ret = poll(_fds.data(), _fds.size(), -1); + if (ret == -1) + break ; + for (size_t i = 0; i < _fds.size(); i++) + { + if (i == 0 && _fds[0].revents & POLLIN) + acceptNew(); + else if (_fds[i].revents & (POLLHUP | POLLERR)) + { + int fd = _fds[i].fd; + removeUser(fd); + i--; + } + else if ( _fds[i].revents & POLLIN) + handleRecv(_fds[i].fd); + } + } +} + +void Server::dispatch(int fd, const IrcMessage &msg) +{ + if (!msg.isValid()) + return ; + const std::string &cmd = msg.getCommand(); + if (CommandValidator::needsRegistration(cmd) && !_users[fd].isRegistered()) + { + sendReply(fd, "451 :You have not registered\r\n"); + return ; + } + else if (!CommandValidator::hasEnoughParams(msg)) + { + sendReply(fd, "461 " + cmd + " :Not enough parameters\r\n"); + return ; + } + else if (cmd == "PASS") + handlePass(fd, msg); + else if (cmd == "NICK") + handleNick(fd, msg); + else if (cmd == "USER") + handleUser(fd, msg); + else if (cmd == "PING") + handlePing(fd, msg); + else if (cmd == "QUIT") + handleQuit(fd, msg); +} + +void Server::handlePass(int fd, const IrcMessage &msg) +{ + if (msg.param(0) == _password) + _users[fd].passOk = true; + else + { + sendReply(fd, "464 :Password incorrect\r\n"); + removeUser(fd); + } +} + +void Server::handleNick(int fd, const IrcMessage &msg) +{ + std::string newNick = msg.param(0); + std::map::iterator it = _users.begin(); + for (; it != _users.end(); it++) + { + if (it->second.nick == newNick && it->first != fd) + { + sendReply(fd, "433 * " + newNick + " :Nickname is already in use\r\n"); + return ; + } + } + _users[fd].nick = newNick; + _users[fd].nickOk = true; +} + +void Server::handleUser(int fd, const IrcMessage &msg) +{ + if (_users[fd].isRegistered() == true) + return ; + _users[fd].username = msg.param(0); + _users[fd].realname = msg.param(3); + _users[fd].userOk = true; + if (_users[fd].isRegistered()) + { + sendReply(fd, ":server 001 " + _users[fd].nick + " :Welcome to the IRC server\r\n"); + sendReply(fd, ":server 002 " + _users[fd].nick + " :Your host is server\r\n"); + } +} + +void Server::handlePing(int fd, const IrcMessage &msg) +{ + sendReply(fd, "PONG :" + msg.param(0) + "\r\n"); +} + +void Server::handleQuit(int fd, const IrcMessage &msg) +{ + removeUser(fd); + (void)msg; } \ No newline at end of file