diff --git a/.gitignore b/.gitignore index 036d972..d636e5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .build ircserv +ircbot .clangd compile_commands.json .cache debug.log +*.o diff --git a/Makefile b/Makefile index db443d1..18d84e1 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,13 @@ NAME := ircserv +BOT_NAME := ircbot CXX := c++ -CXXFLAGS:= -Wall -Wextra -Werror -std=c++20 -Iinc +CXXFLAGS:= -Wall -Wextra -Werror -std=c++20 -Iinc + SRC := \ main.cpp \ - User.cpp \ - Channel.cpp \ Server.cpp \ + Channel.cpp \ + User.cpp \ RecvParser.cpp \ Client.cpp \ Command.cpp \ @@ -15,16 +17,35 @@ SRCS := $(addprefix src/, $(SRC)) OBJS := $(SRCS:src/%.cpp=.build/%.o) DEPS := $(OBJS:.o=.d) +BOT_SRC := bot/main.cpp +BOT_SRCS := $(BOT_SRC) src/RecvParser.cpp +BOT_OBJS := $(BOT_SRC:bot/%.cpp=.build/bot_%.o) .build/RecvParser.o + all: $(NAME) +debug: CXXFLAGS += -g2 -ggdb3 +debug: fclean +debug: all +debug: bot + +bot: $(BOT_NAME) + $(NAME): $(OBJS) echo "🔗 Linking $(NAME)..." $(CXX) $(CXXFLAGS) $(OBJS) -o $@ echo "🎉 Build complete!" +$(BOT_NAME): $(BOT_OBJS) + echo "🔗 Linking $(BOT_NAME)..." + $(CXX) $(CXXFLAGS) $(BOT_OBJS) -o $@ + echo "🎉 Bot build complete!" + .build/%.o: src/%.cpp | .build @$(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@ +.build/bot_%.o: bot/%.cpp | .build + @$(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@ + .build: @mkdir -p .build @@ -34,7 +55,7 @@ clean: fclean: clean echo "🗑️ Removing $(NAME)" - @rm -f $(NAME) + @rm -f $(NAME) $(BOT_NAME) re: echo "🔄 Rebuilding..." @@ -43,4 +64,4 @@ re: -include $(DEPS) .SILENT: -.PHONY: all clean fclean re +.PHONY: all clean fclean re debug bot diff --git a/bot/main.cpp b/bot/main.cpp new file mode 100644 index 0000000..e346e61 --- /dev/null +++ b/bot/main.cpp @@ -0,0 +1,511 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../inc/RecvParser.hpp" + +inline constexpr std::array HELP_LINES = { + "/part {message} - leave the channel, with a message", + "/kick [nick] {message} - kick the user out of the channel, with a message", + "/quit {message} - disconnect from the server, with a message", + "/mode [l/o/t/k/i] - sets the channel modes, i-invite mode, o-operator, t-topic, l-limit, k-key", + "/msg [nick] - send message to user", + "/topic {name} - sets the topic of the channel to {name}", + "/invite [nick] {#channel_name} - invites user to the channel", + "/join {#channel_name} - joins the channel", + "/nick [nick] - tries to change the nick" +}; + + + +static volatile std::sig_atomic_t g_stop = 0; +static volatile std::sig_atomic_t g_userInitiated = 0; +static void handle_stop(int) { g_stop = 1; g_userInitiated = 1; } + +static void fatal_usage() { + std::cerr << "Usage: ./ircbot [options]\n" + << " -s HOST IRC server (default: localhost)\n" + << " -p PORT IRC port (default: 6667)\n" + << " -n NICK Nickname (default: ircbot)\n" + << " -c CHS Comma-separated channels, e.g. \"#test,#bots\"\n" + << " --pass PASS Server password\n"; + std::exit(2); +} + +static void parse_args(int argc, char **argv, std::string &host, + std::string &port, std::string &nick, + std::vector &channels, std::string &pass) { + host = "localhost"; + port = "6667"; + nick = "ircbot"; + for (int i = 1; i < argc; ++i) { + std::string a = argv[i]; + auto need = [&](std::string &out) { + if (i + 1 >= argc) { + fatal_usage(); + } + out = argv[++i]; + }; + if (a == "-s") + need(host); + else if (a == "-p") + need(port); + else if (a == "-n") + need(nick); + else if (a == "-c") { + std::string chs; + need(chs); + size_t start = 0; + while (true) { + size_t pos = chs.find(',', start); + std::string token = (pos == std::string::npos) + ? chs.substr(start) + : chs.substr(start, pos - start); + if (!token.empty()) + channels.push_back(token); + if (pos == std::string::npos) + break; + start = pos + 1; + } + } else if (a == "--pass") + need(pass); + else { + std::cerr << "Unknown arg: " << a << "\n"; + fatal_usage(); + } + } +} + +static int connect_to(const std::string &host, const std::string &port) { + struct addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + struct addrinfo *res = nullptr; + int rc = ::getaddrinfo(host.c_str(), port.c_str(), &hints, &res); + if (rc != 0) { + std::cerr << "getaddrinfo: " << gai_strerror(rc) << "\n"; + return -1; + } + constexpr int CONNECT_TIMEOUT_MS = 5000; + int fd = -1; + for (struct addrinfo *ai = res; ai; ai = ai->ai_next) { + int tmp = ::socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (tmp < 0) + continue; + int flags = ::fcntl(tmp, F_GETFL, 0); + if (flags >= 0) + ::fcntl(tmp, F_SETFL, flags | O_NONBLOCK); + int r = ::connect(tmp, ai->ai_addr, ai->ai_addrlen); + if (r == 0) { + fd = tmp; // Connected immediately + if (flags >= 0) ::fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); + break; + } else if (r < 0 && errno == EINPROGRESS) { + struct pollfd pfd{tmp, POLLOUT, 0}; + int pr = ::poll(&pfd, 1, CONNECT_TIMEOUT_MS); + if (pr > 0 && (pfd.revents & POLLOUT)) { + int err = 0; + socklen_t len = sizeof(err); + if (::getsockopt(tmp, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) { + fd = tmp; + if (flags >= 0) ::fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); + break; + } + } + } + ::close(tmp); + } + ::freeaddrinfo(res); + return fd; +} + +static bool send_line(int fd, const std::string &s) { + std::cerr << ">> " << s << "\n"; + std::string wire = s; + wire += "\r\n"; + const char *p = wire.c_str(); + size_t left = wire.size(); + while (left > 0) { + ssize_t n = ::send(fd, p, left, MSG_NOSIGNAL); + if (n < 0) { + if (errno == EINTR) + continue; + std::cerr << "send failed: " << std::strerror(errno) << "\n"; + return false; + } + left -= static_cast(n); + p += n; + } + return true; +} + +static bool send_privmsg(int fd, const std::string &target, + const std::string &text) { + return send_line(fd, "PRIVMSG " + target + " :" + text); +} + +static void join_channels(int fd, const std::vector &channels) { + for (const auto &ch : channels) + if (!ch.empty()) + send_line(fd, "JOIN " + ch); +} + +static std::string extract_nick_from_prefix(const std::optional &prefix) { + if (!prefix || prefix->empty()) + return ""; + size_t excl = prefix->find('!'); + return (excl == std::string::npos) ? *prefix : prefix->substr(0, excl); +} + +static void log_message(const Message &msg) { + std::cerr << "<< "; + if (msg.prefix) + std::cerr << ":" << *msg.prefix << " "; + std::cerr << msg.command; + for (size_t i = 0; i < msg.params.size(); ++i) { + bool is_last = (i == msg.params.size() - 1); + if (is_last && msg.params[i].find(' ') != std::string::npos) + std::cerr << " :" << msg.params[i]; + else + std::cerr << " " << msg.params[i]; + } + std::cerr << "\n"; +} + +static void send_quit(int fd, const std::string &reason) { + if (fd < 0) return; + send_line(fd, "QUIT :" + reason); + ::close(fd); + /* ::shutdown(fd, SHUT_WR); */ +} + +int main(int argc, char **argv) { + std::signal(SIGINT, handle_stop); + std::signal(SIGTERM, handle_stop); + std::signal(SIGPIPE, SIG_IGN); + + std::string host, port, nick, pass; + std::vector channels; + parse_args(argc, argv, host, port, nick, channels, pass); + + const std::string base_nick = nick; + int nick_attempt = 0; + int backoff = 2, backoff_max = 30; + int retries = 0; + const int MAX_RETRIES = 5; + std::unordered_set joined_channels; + std::unordered_set filter_words; + // Track channel operators (updated from 353 and MODE +o/-o) + std::unordered_map> channel_ops; + // Last command sender (set when a bot command is processed) + std::string last_sender_nick; + + using Cmd = std::function; + std::unordered_map commands; + + int sockfd = -1; + auto reply = [&](const std::string &target, const std::string &text) { + if (sockfd >= 0) + send_privmsg(sockfd, target, text); + }; + commands["!ping"] = [&](const std::string &target, const std::string &args) { + (void)args; + reply(target, "pong!"); + }; + + commands["!help"] = [&](const std::string &target, const std::string &args) { + (void)args; + for (std::string_view line : HELP_LINES) { + reply(target, std::string(line)); + } + }; + + commands["!filter"] = [&](const std::string &target, const std::string &args) { + // Determine the channel context (only allow in a channel) + if (target.empty() || target[0] != '#') { + reply((target == nick ? last_sender_nick : target), "Filter can only be modified from a channel context"); + return; + } + // Require that the sender is a known channel operator + auto chanIt = channel_ops.find(target); + if (chanIt == channel_ops.end() || chanIt->second.find(last_sender_nick) == chanIt->second.end()) { + reply(target, "You are not a channel operator - cannot modify filter"); + return; + } + std::istringstream iss(args); + std::string w; + int added = 0; + while (iss >> w) { + std::string lw; + lw.reserve(w.size()); + for (char c : w) + lw.push_back(static_cast(std::tolower(static_cast(c)))); + if (!lw.empty()) { + if (filter_words.insert(lw).second) + ++added; + } + } + reply(target, "Filter updated: " + std::to_string(filter_words.size()) + + " words total (" + std::to_string(added) + " new)"); + }; + + while (!g_stop) { + sockfd = connect_to(host, port); + if (sockfd < 0) { + ++retries; + std::cerr << "[bot] connect failed (" << retries << "/" << MAX_RETRIES << "), retry in " << backoff << "s\n"; + if (retries >= MAX_RETRIES) { + std::cerr << "[bot] maximum retries reached, exiting\n"; + break; + } + for (int s = 0; s < backoff && !g_stop; ++s) + sleep(1); + backoff = std::min(backoff_max, backoff * 2); + continue; + } + std::cerr << "[bot] connected\n"; + retries = 0; + backoff = 2; + + if (!pass.empty()) + send_line(sockfd, "PASS " + pass); + send_line(sockfd, "NICK " + nick); + send_line(sockfd, "USER " + nick + " 0 * :irc_hive bot"); + + bool joined = false; + + std::queue> msg_queue; + RecvParser parser(msg_queue); + + while (!g_stop) { + struct pollfd pfd{sockfd, POLLIN, 0}; + int pr = ::poll(&pfd, 1, 10000); + if (pr < 0) { + if (errno == EINTR) + continue; + std::cerr << "poll error: " << std::strerror(errno) << "\n"; + break; + } + if (pr == 0) + continue; + if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { + std::cerr << "[bot] socket closed (treating as server shutdown, exiting)\n"; + g_stop = 1; + break; + } + if (pfd.revents & POLLIN) { + char buf[4096]; + ssize_t n = ::recv(sockfd, buf, sizeof(buf), 0); + if (n <= 0) { + std::cerr << "[bot] recv <= 0 (treating as server shutdown, exiting)\n"; + ::close(sockfd); + g_stop = 1; + break; + } + parser.feed(buf, static_cast(n)); + + while (!msg_queue.empty()) { + std::unique_ptr &msg = msg_queue.front(); + + log_message(*msg); + + // PING + if (msg->command == "PING") { + std::string token = msg->params.empty() ? "" : msg->params[0]; + send_line(sockfd, "PONG :" + token); + msg_queue.pop(); + continue; + } + + // Welcome / end of MOTD / no MOTD + if (msg->command == "001" || msg->command == "376" || msg->command == "422") { + if (!joined && !channels.empty()) { + join_channels(sockfd, channels); + joined = true; + } + msg_queue.pop(); + continue; + } + + // Nick in use + if (msg->command == "433") { + ++nick_attempt; + nick = base_nick + "_" + std::to_string(nick_attempt); + std::cerr << "[bot] nick in use, trying " << nick << "\n"; + send_line(sockfd, "NICK " + nick); + msg_queue.pop(); + continue; + } + + // Update channel operators from RPL_NAMREPLY (353) + // Typical: 353 = #channel :@Op1 +VoicedUser User2 + if (msg->command == "353" && msg->params.size() >= 4) { + const std::string &chan = msg->params[2]; + const std::string &namesField = msg->params[3]; + // namesField may already be without leading ':' depending on parser + std::string names = namesField; + if (!names.empty() && names[0] == ':') + names.erase(0, 1); + std::istringstream nss(names); + std::string token; + auto &ops = channel_ops[chan]; // creates if not present + while (nss >> token) { + if (!token.empty() && (token[0] == '@')) { + std::string opNick = token.substr(1); + if (!opNick.empty()) + ops.insert(opNick); + } + } + } + + // Track MODE +o / -o changes: :setter MODE #channel +o nick OR grouped like +oo nick1 nick2 + if (msg->command == "MODE" && msg->params.size() >= 2) { + const std::string &chan = msg->params[0]; + if (!chan.empty() && chan[0] == '#') { + const std::string &modes = msg->params[1]; + // Remaining params are the nick targets consumed by 'o' (and others we ignore) + std::vector modeArgs; + for (size_t i = 2; i < msg->params.size(); ++i) + modeArgs.push_back(msg->params[i]); + + bool adding = true; + size_t argIndex = 0; + auto &ops = channel_ops[chan]; // creates if missing + for (char c : modes) { + if (c == '+') { adding = true; continue; } + if (c == '-') { adding = false; continue; } + if (c == 'o') { + if (argIndex < modeArgs.size()) { + const std::string &targetNick = modeArgs[argIndex++]; + if (!targetNick.empty()) { + if (adding) + ops.insert(targetNick); + else + ops.erase(targetNick); + } + } + } else { + // For other mode letters that take arguments, still consume if needed + // but since we only care about 'o', we just attempt to advance if letter uses an argument. + // Simple heuristic: ignore; most other user modes won't break ordering here for small scale. + } + } + } + } + + // PRIVMSG handling (commands + filter enforcement) + if (msg->command == "PRIVMSG" && msg->params.size() >= 2) { + const std::string &target = msg->params[0]; + const std::string &text = msg->params[1]; + std::string sender_nick = extract_nick_from_prefix(msg->prefix); + + // Bot commands (prefix '!') + if (!text.empty() && text[0] == '!') { + size_t spc = text.find(' '); + std::string key = (spc == std::string::npos) ? text : text.substr(0, spc); + std::string args = (spc == std::string::npos) ? "" : text.substr(spc + 1); + auto it = commands.find(key); + if (it != commands.end()) { + std::string reply_target = + (target == nick && !sender_nick.empty()) ? sender_nick : target; + // Record sender for privilege checks (e.g. !filter) + last_sender_nick = sender_nick; + it->second(reply_target, args); + } + } + + // Content filter (only for channel messages, not from the bot itself) + if (!filter_words.empty() && !sender_nick.empty() && sender_nick != nick && + !target.empty() && target[0] == '#' && + (text.empty() || text[0] != '!')) { + // Tokenize text into words (alnum sequences) + std::string token; + auto flush_token = [&](void) { + if (token.empty()) return false; + if (filter_words.find(token) != filter_words.end()) { + send_line(sockfd, "KICK " + target + " " + sender_nick + " :filtered word"); + return true; + } + token.clear(); + return false; + }; + for (char ch : text) { + if (std::isalnum(static_cast(ch))) { + token.push_back(static_cast(std::tolower(static_cast(ch)))); + } else { + if (flush_token()) + break; + } + } + if (!token.empty()) + flush_token(); + } + } + + // Track successful self JOIN to mark channel as joined + if (msg->command == "JOIN" && msg->params.size() >= 1) { + std::string selfNick = extract_nick_from_prefix(msg->prefix); + if (selfNick == nick) { + const std::string &chan = msg->params[0]; + if (!chan.empty()) + joined_channels.insert(chan); + } + } + + // INVITE handling: always attempt (re)JOIN if not already joined + if (msg->command == "INVITE" && msg->params.size() >= 2) { + const std::string &invitee = msg->params[0]; + const std::string &channel = msg->params[1]; + if (invitee == nick && !channel.empty()) { + // Track desired channel list (only add once) + if (std::find(channels.begin(), channels.end(), channel) == channels.end()) { + channels.push_back(channel); + } + // Re-attempt join if we are not yet in joined set + if (joined_channels.find(channel) == joined_channels.end()) { + send_line(sockfd, "JOIN " + channel); + } + } + } + + msg_queue.pop(); + } + } + } + + if (g_stop && g_userInitiated) { + send_quit(sockfd, "Client exiting"); + struct pollfd tmp{sockfd, POLLIN, 0}; + poll(&tmp, 1, 100); + } + ::close(sockfd); + sockfd = -1; + if (g_stop) + break; + + std::cerr << "[bot] reconnecting in " << backoff << "s\n"; + for (int s = 0; s < backoff && !g_stop; ++s) + sleep(1); + backoff = std::min(backoff_max, backoff * 2); + nick = base_nick; + nick_attempt = 0; + } + return 0; +} diff --git a/inc/Channel.hpp b/inc/Channel.hpp index c9bfbbc..efdadb8 100644 --- a/inc/Channel.hpp +++ b/inc/Channel.hpp @@ -1,21 +1,21 @@ #pragma once #include "User.hpp" #include "Server.hpp" +#include "macro.h" #include #include #include #include #include -class Server; - using std::set; using std::string; +class Server; extern Server *irc; - class User; + /* * @class Channel * @brief Like rooms that can have Users and 1 or more Operators @@ -29,7 +29,7 @@ class User; class Channel { private: time_t _startTime; - string _name, _passwd = "", _topic = ""; + string _name, _passwd, _topic; size_t _limit; set _users, _oper, _invite; set _mode{'s'}; @@ -47,15 +47,15 @@ class Channel { const string& getTopic(void) const; const size_t& getLimit(void) const; const string getTime(void) const; - void setLimit(size_t limit); + bool setLimit(string limit); void setMode(string mode); void unsetMode(string umode); bool setTopic(int fd, string topic); const string addUser(int fd, string passwd = ""); void removeUser(int fd, string msg = "", string cmd = ""); string userList(void) const; - string modeList(void) const; - void makeOperator(int fd, string user); + string modes(void) const; + bool makeOperator(int fd, string user); bool kick(int op, int user); void invite(int fd); bool message(int fd, string name = "", string msg = "", string type = ""); diff --git a/inc/Client.hpp b/inc/Client.hpp index 44a5ee9..a8bb0ec 100644 --- a/inc/Client.hpp +++ b/inc/Client.hpp @@ -5,8 +5,10 @@ #include #include #include +#include "CommandDispatcher.hpp" #include "User.hpp" class User; +class CommandDispatcher; /* * @class Client * @brief Handles events on files registered to epoll @@ -25,6 +27,7 @@ class Client { User* _self; bool _authenticated = false; bool _registered = false; + CommandDispatcher* _test; public: explicit Client(int fd); @@ -37,6 +40,7 @@ class Client { void setHandler(uint32_t eventType, std::function handler); std::function& getHandler(uint32_t eventType); User* getUser(void); + CommandDispatcher* getDispatch(void); void authenticate(void); bool isAuthenticated(void) const; bool& accessRegistered(void); diff --git a/inc/Command.hpp b/inc/Command.hpp index 48aee48..40cc7d3 100644 --- a/inc/Command.hpp +++ b/inc/Command.hpp @@ -5,7 +5,9 @@ #include #include #include +#include "macro.h" +class Server; extern Server *irc; /** diff --git a/inc/CommandDispatcher.hpp b/inc/CommandDispatcher.hpp index d835282..7d81074 100644 --- a/inc/CommandDispatcher.hpp +++ b/inc/CommandDispatcher.hpp @@ -6,6 +6,8 @@ #include "Command.hpp" #include "Message.hpp" +class ICommand; + /** * @class CommandDispatcher * @brief A class for executing commands received from parser @@ -22,7 +24,6 @@ class CommandDispatcher private: std::unordered_map> _handlers; - UnknownCommand _default; void _welcome(int fd); }; diff --git a/inc/Handler.hpp b/inc/Handler.hpp index a564106..1e97b53 100644 --- a/inc/Handler.hpp +++ b/inc/Handler.hpp @@ -3,14 +3,15 @@ #include #include #include -#include "RecvParser.hpp" #include "CommandDispatcher.hpp" +#include "RecvParser.hpp" #include "Client.hpp" #include "Server.hpp" #include #include #include +class Server; extern Server *irc; class Handler { diff --git a/inc/Server.hpp b/inc/Server.hpp index 5b77ee1..5fe9b03 100644 --- a/inc/Server.hpp +++ b/inc/Server.hpp @@ -10,24 +10,33 @@ #include #include #include "Client.hpp" +#include "Handler.hpp" #include "Channel.hpp" #include +#include +#include +#include +#include #include constexpr static const std::array eventTypes{EPOLLIN, EPOLLHUP, EPOLLRDHUP}; class Server { private: - std::map _channels{}; - std::unordered_map _clients{}; - std::vector _events{}; + std::map _channels; + std::unordered_map _clients; + std::vector _events; std::time_t _startTime; const int _fd; + const int _sock; + std::string::size_type _checker; + const int _port; const int _max_events = 100; std::string _password; void _reloadHandler(Client &client) const; + void _addOwnSocket(int sockfd); public: - Server(std::string passwd = ""); + Server(std::string port = "6667", std::string passwd = ""); virtual ~Server(); void addClient(int fd); /* @@ -47,7 +56,6 @@ class Server { * @return string of localtime */ std::string getTime(void) const; - void addOwnSocket(int sockfd); /* * @brief Compare password to servers, or without argument if one is set * @param password the user provided PASS, default "" diff --git a/inc/User.hpp b/inc/User.hpp index ff5f76c..dd21ecb 100644 --- a/inc/User.hpp +++ b/inc/User.hpp @@ -18,7 +18,7 @@ class User { public: void join(Channel *chan); void quit(int fd, string msg); - void setNick(string name); + void setNick(int filde, string name); void setUser(string name); void setHost(string host); string getNick(void) const; diff --git a/inc/macro.h b/inc/macro.h new file mode 100644 index 0000000..4f0938f --- /dev/null +++ b/inc/macro.h @@ -0,0 +1,40 @@ +#pragma once + +#define HOST ":localhost " +#define NICK irc->getClient(fd).getUser()->getNick() +#define USER(X) irc->getClient(X).getUser() +#define PREFIX irc->getClient(fd).getUser()->createPrefix() +#define MSG ":" + USER(user)->getNick() + " " + type + " " + _name + " :" + msg + "\r\n" +#define PARAM msg.params[0] +#define PARAM1 msg.params[1] +#define PARAM2 msg.params[2] +#define R324 ":localhost 324 " + NICK + " " + PARAM + " " + ch->modes() +#define R329 ":localhost 329 " + NICK + " " + PARAM + " " + ch->getTime() +#define R331 "331 " + NICK + " " + PARAM + " :No topic is set" +#define R332 "332 " + NICK + " " + PARAM + " :" + topic +#define R341 "341 :Invitation send" +#define R353 "353 " + NICK + " @ " + PARAM + " :" + names +#define R366 "366 " + NICK + " " + PARAM + " :End of NAMES list" +#define E401 "401 :No such nick" +#define E403 "403 :No such channel" +#define E403REV2 "403 " + NICK + " " + PARAM + " :No such channel" +#define E409 "409 :No origin specified" +#define E411 "411 :No recipient given" +#define E412 "412 :No text to send" +#define E421 "421 :Unknown command" +#define E422 "422 :You're not on that channel" +#define E431 "431 :No nickname given" +#define E432 "432 " + oldNick + " " + newNick + " :Erroneous nickname" +#define E433 "433 * " + newNick + " :Nickname is already in use" +#define E441 "441 :They aren't on that channel" +#define E442 "442 :You're not on that channel" +#define E443 "443 :User already on channel" +#define E461 "461 :Missing parameters" +#define E462 "462 " + NICK + " : You may not reregister" +#define E471 "471 " + nick + " " + _name + " :Cannot join channel (+l)" +#define E472 "472 :Unknown mode" +#define E473 "473 " + nick + " " + _name + " :Cannot join channel (+i)" +#define E475 "475 " + nick + " " + _name + " :Cannot join channel (+k)" +#define E481 "481 :Permission Denied- You're not an IRC operator" +#define E482 "482 :You're not a channel operator" +#define E502 "502 :Users don't match" diff --git a/src/Channel.cpp b/src/Channel.cpp index ef7ebac..9ce266e 100644 --- a/src/Channel.cpp +++ b/src/Channel.cpp @@ -1,9 +1,6 @@ #include "Channel.hpp" -#include -#include -#include -Channel::Channel(std::string channel) : _startTime(time(NULL)), _name(channel) { +Channel::Channel(string channel) : _startTime(time(NULL)), _name(channel), _passwd(), _topic() { } bool Channel::isEmpty(void) const { @@ -42,9 +39,13 @@ const string Channel::getTime(void) const { return std::to_string(_startTime); } -void Channel::setLimit(size_t limit) { - _limit = limit; - +bool Channel::setLimit(string limit) { + try { + _limit = std::stoul(limit); + return true; + } catch (...) { + return false; + } } void Channel::setMode(string mode) { @@ -64,28 +65,12 @@ void Channel::unsetMode(string umode) { } bool Channel::setTopic(int user, string topic) { - if(_mode.contains('t') && !_oper.contains(user)) { + if(_mode.contains('t') && not _oper.contains(user)) { return false; } _topic = topic; - string message = ":" + irc->getClient(user).getUser()->getNick() + " TOPIC " + _name + " :" + topic + "\r\n"; - auto bytes = send(user, message.data(), message.size(), 0); - if (bytes == -1) - return false; - for (auto users : _users) { - if (users == user) - continue; - auto bytes = send(users, message.data(), message.size(), 0); - if (bytes == -1) - return false; - } - for (auto users : _oper) { - if (users == user) - continue; - auto bytes = send(users, message.data(), message.size(), 0); - if (bytes == -1) - return false; - } + string response = ":" + USER(user)->getNick() + " TOPIC " + _name + " :" + topic; + message(-1, response); return true; } @@ -97,44 +82,44 @@ bool Channel::checkUser(int user) { const string Channel::addUser(int user, string passwd) { string ret; - const string nick = irc->getClient(user).getUser()->getNick(); - if (!checkUser(user)) - ret = "443 " + nick + " " - + _name + " :You are already on the channel"; + const string nick = USER(user)->getNick(); + if (not checkUser(user)) + ret = E443; else if (_mode.contains('l') && _users.size() + _oper.size() >= _limit) - ret = "471 " + nick + " " + _name + " :Cannot join channel (+l)"; + ret = E471; else if (_mode.contains('i')) if (_mode.contains('k') && joinWithInvite(user, passwd)) ; else if (_mode.contains('k')) - ret = "475 " + nick + " " + _name + " :Cannot join channel (+k)"; + ret = E475; else - ret = "473 " + nick + " " + _name + " :Cannot join channel (+i)"; + ret = E473; else if (_mode.contains('k')) if (joinWithPassword(user, passwd)) ; else - ret = "475 " + nick + " " + _name + " :Cannot join channel (+k)"; + ret = E475; else if (isEmpty()) { _oper.emplace(user); - irc->getClient(user).getUser()->join(this); + USER(user)->join(this); } else { _users.emplace(user); - irc->getClient(user).getUser()->join(this); + USER(user)->join(this); } return ret; } void Channel::removeUser(int fd, string msg, string cmd) { if (_users.contains(fd)) { + USER(fd)->exitChannel(_name); _users.erase(fd); message(fd, msg, cmd); } else if (_oper.contains(fd)) { + USER(fd)->exitChannel(_name); _oper.erase(fd); if (_oper.empty()) { if (_users.empty()) { - irc->removeChannel(_name); - return ; + irc->removeChannel(_name); } else { auto user = _users.begin(); _oper.emplace(*user); @@ -147,7 +132,7 @@ void Channel::removeUser(int fd, string msg, string cmd) { } else { return ; } - irc->getClient(fd).getUser()->exitChannel(_name); + } bool Channel::joinWithPassword(int fd, string passwd) { @@ -156,7 +141,7 @@ bool Channel::joinWithPassword(int fd, string passwd) { _oper.emplace(fd); else _users.emplace(fd); - irc->getClient(fd).getUser()->join(this); + USER(fd)->join(this); return true; } else { return false; @@ -173,7 +158,7 @@ bool Channel::joinWithInvite(int fd, string passwd) { _invite.erase(fd); _users.emplace(fd); } - irc->getClient(fd).getUser()->join(this); + USER(fd)->join(this); return true; } else { if (joinWithPassword(fd, passwd)) @@ -190,19 +175,19 @@ string Channel::userList(void) const { string ret; for (auto users : _users) { - ret += irc->getClient(users).getUser()->getNick(); + ret += USER(users)->getNick(); ret += " "; } for (auto users : _oper) { ret += '@'; - ret += irc->getClient(users).getUser()->getNick(); + ret += USER(users)->getNick(); ret += " "; } ret.erase(ret.end() - 1); return ret; } -string Channel::modeList(void) const { +string Channel::modes(void) const { string ret("+"); for (auto mode : _mode) @@ -213,20 +198,20 @@ string Channel::modeList(void) const { return ret; } -void Channel::makeOperator(int fd, string uname) { +bool Channel::makeOperator(int fd, string uname) { int newOp = 0; for (auto user : _users) { - if (irc->getClient(user).getUser()->getNick() == uname) { + if (USER(user)->getNick() == uname) { newOp = user; break ; } } if (!newOp) - throw std::runtime_error(""); + return false; _users.erase(newOp); _oper.emplace(newOp); - message(-1, irc->getClient(fd).getUser()->createPrefix() + " MODE " + _name + " +o " + uname); + return message(-1, PREFIX + " MODE " + _name + " +o " + uname); } void Channel::invite(int fd) { @@ -236,19 +221,20 @@ void Channel::invite(int fd) { bool Channel::kick(int op, int user) { if (_users.contains(user) && _oper.contains(op)) { _users.erase(user); - irc->getClient(user).getUser()->exitChannel(_name); + USER(user)->exitChannel(_name); return true; } else { return false; } } -bool Channel::message(int user, string msg, string type, string name) { +bool Channel::message(int user, string msg, string type, string name) { string message; + if (type.empty()) message = msg + "\r\n"; else if (name.empty()) - message = ":" + irc->getClient(user).getUser()->getNick() + " " + type + " " + _name + " :" + msg + "\r\n"; + message = MSG; else message = msg + " " + type + " :" + name + "\r\n"; diff --git a/src/Client.cpp b/src/Client.cpp index 9839712..9566863 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -1,13 +1,14 @@ #include "Client.hpp" -Client::Client(int fd) : _self(new User()), _fd(fd) {} +Client::Client(int fd) : _self(new User()), _test(new CommandDispatcher()), _fd(fd) {} Client::Client(const Client& other) : _IN(other._IN), _RDHUP(other._RDHUP), _HUP(other._HUP), _self(new User(*other._self)), +_test(other._test), _fd(other._fd), _initialized(other._initialized){} @@ -15,6 +16,7 @@ Client& Client::operator=(const Client& other) { if (this != &other) { delete _self; _self = new User(*other._self); + _test = other._test; } return *this; } @@ -27,6 +29,10 @@ User* Client::getUser(void) { return _self; } +CommandDispatcher* Client::getDispatch(void) { + return _test; +} + void Client::setHandler(uint32_t eventType, std::function handler) { switch (eventType) { case EPOLLIN: diff --git a/src/Command.cpp b/src/Command.cpp index fabc928..e4bd271 100644 --- a/src/Command.cpp +++ b/src/Command.cpp @@ -1,15 +1,17 @@ #include "Command.hpp" -#include + + + static void debugLog(const Message &msg) { - std::cout << "[DEBUG] Received unsupported command " << msg.command << " with "; + std::cerr << "Received unsupported command " << msg.command << " with "; if (msg.prefix) - std::cout << "prefix: " << *msg.prefix << ", "; - std::cout << "params:"; + std::cerr << "prefix: " << *msg.prefix << ", "; + std::cerr << "params:"; for (auto param : msg.params) - std::cout << " " << param; - std::cout << std::endl; + std::cerr << " " << param; + std::cerr << std::endl; } static void sendResponse(std::string message, int fd) @@ -22,81 +24,77 @@ static void sendResponse(std::string message, int fd) void NickCommand::execute(const Message &msg, int fd) { if (msg.params.empty()) - return sendResponse("431 :No nickname given", fd); - - const std::string &oldNick = irc->getClient(fd).getUser()->getNick(); - std::string newNick = msg.params[0]; + return sendResponse(E431, fd); + const std::string &oldNick = NICK; + std::string newNick = PARAM; + User* usr = USER(fd); std::regex nickname_regex("^[A-Za-z][A-Za-z0-9-_]*"); - if (newNick.size() < 1 || newNick.size() > 9 || !std::regex_match(newNick, nickname_regex)) - return sendResponse("432 " + oldNick + " " + newNick + " :Erroneous nickname", fd); + if (newNick.size() < 1 || newNick.size() > 9 || not std::regex_match(newNick, nickname_regex)) + return sendResponse(E432, fd); for (auto client : irc->getClients()) - { if (client.second.getUser()->getNick() == newNick) - return sendResponse("433 * " + newNick + " :Nickname is already in use", fd); - } - irc->getClient(fd).getUser()->setNick(newNick); + return sendResponse(E433, fd); + sendResponse(PREFIX + " NICK :" + newNick, fd); + usr->setNick(fd, newNick); + } void UserCommand::execute(const Message &msg, int fd) { if (msg.params.size() < 4) - return sendResponse("461 :Missing parameters", fd); - if (!irc->getClient(fd).getUser()->getUser().empty()) - return sendResponse("462 " + irc->getClient(fd).getUser()->getNick() + - " : You may not reregister", fd); - irc->getClient(fd).getUser()->setUser(msg.params[0]); - irc->getClient(fd).getUser()->setHost(msg.params[1]); + return sendResponse(E461, fd); + if (not USER(fd)->getUser().empty()) + return sendResponse(E462, fd); + USER(fd)->setUser(PARAM); + USER(fd)->setHost(PARAM1); } void JoinCommand::execute(const Message &msg, int fd) { if (msg.params.size() < 1) - return sendResponse("461 :Missing parameters", fd); - else if (msg.params[0].empty()) + return sendResponse(E461, fd); + else if (PARAM.empty()) return ; - std::string nick = irc->getClient(fd).getUser()->getNick(); std::regex channel_regex("^[#][A-Za-z0-9-_]{1,50}*"); - if (!std::regex_match(msg.params[0], channel_regex)) - return sendResponse("403 " + nick + " " + msg.params[0] + " :No such channel", fd); - Channel &channel = irc->addChannel(msg.params[0]); + if (!std::regex_match(PARAM, channel_regex)) + return sendResponse(E403REV2, fd); + Channel &channel = irc->addChannel(PARAM); std::string response; if (msg.params.size() == 1) response = channel.addUser(fd); else - response = channel.addUser(fd, msg.params[1]); + response = channel.addUser(fd, PARAM1); if (!response.empty()) return sendResponse(response, fd); const std::string &topic = channel.getTopic(); if (topic.empty()) - sendResponse("331 " + nick + " " + msg.params[0] + " :No topic is set", fd); + sendResponse(R331, fd); else - sendResponse("332 " + nick + " " + msg.params[0] + " :" + topic, fd); + sendResponse(R332, fd); const std::string &names = channel.userList(); - sendResponse("353 " + nick + " @ " + msg.params[0] + " :" + names, fd); - sendResponse("366 " + nick + " " + msg.params[0] + " :End of NAMES list", fd); - std::string prefix = irc->getClient(fd).getUser()->createPrefix(); + sendResponse(R353, fd); + sendResponse(R366, fd); + std::string prefix = PREFIX; for (auto user : channel.getUsers()) - sendResponse(prefix + " JOIN :" + msg.params[0], user); + sendResponse(prefix + " JOIN :" + PARAM, user); for (auto oper : channel.getOperators()) - sendResponse(prefix + " JOIN :" + msg.params[0], oper); + sendResponse(prefix + " JOIN :" + PARAM, oper); } void PartCommand::execute(const Message &msg, int fd) { if (msg.params.size() < 1) - return sendResponse("461 :Missing parameters", fd); + return sendResponse(E461, fd); Client &client = irc->getClient(fd); std::regex channel_regex("^[#][A-Za-z0-9-_]{1,50}*"); - if (!std::regex_match(msg.params[0], channel_regex)) - return sendResponse("403 " + client.getUser()->getNick() + - " " + msg.params[0] + " :No such channel", fd); - Channel *ch = client.getUser()->getChannel(msg.params[0]); + if (!std::regex_match(PARAM, channel_regex)) + return sendResponse(E403REV2, fd); + Channel *ch = client.getUser()->getChannel(PARAM); if (!ch) - return sendResponse("422 :You're not on that channel", fd); - - std::string response = msg.params[0]; + return sendResponse(E422, fd); + std::string response = PARAM; if (msg.params.size() > 1) - response.append(" :" + msg.params[1]); + response.append(" :" + PARAM1); ch->removeUser(fd, response, "PART"); sendResponse(client.getUser()->createPrefix() + " PART " + response, fd); } @@ -104,69 +102,57 @@ void PartCommand::execute(const Message &msg, int fd) void PrivmsgCommand::execute(const Message &msg, int fd) { if (msg.params.empty()) - return sendResponse("411 :No recipient given", fd); + return sendResponse(E411, fd); if (msg.params.size() < 2) - return sendResponse("412 :No text to send", fd); - if (msg.params[0][0] == '#') + return sendResponse(E412, fd); + if (PARAM[0] == '#') { - Channel *ch = irc->getClient(fd).getUser()->getChannel(msg.params[0]); - if (!ch) - return sendResponse("442 :You're not on the channel", fd); - if (!ch->message(fd, msg.params[1], "PRIVMSG")) + Channel *ch = USER(fd)->getChannel(PARAM); + if (not ch) + return sendResponse(E442, fd); + if (not ch->message(fd, PARAM1, "PRIVMSG")) throw (std::runtime_error("Send() failed in PRIVMSG #channel")); } else { for (auto client : irc->getClients()) - { - if (client.second.getUser()->getNick() == msg.params[0]) - { - sendResponse(":" + irc->getClient(fd).getUser()->getNick() + - " PRIVMSG " + msg.params[0] + " :" + msg.params[1], client.first); - return ; - } - } - sendResponse("401 :No such nick", fd); + if (client.second.getUser()->getNick() == PARAM) + return sendResponse(":" + NICK + " PRIVMSG " + PARAM + " :" + PARAM1, client.first); + sendResponse(E401, fd); } } + + void KickCommand::execute(const Message &msg, int fd) { if (msg.params.size() < 2) - return sendResponse("461 :Missing parameters", fd); - if (!irc->channelExists(msg.params[0])) - return sendResponse("403 :No such channel", fd); - - Channel *ch = irc->getClient(fd).getUser()->getChannel(msg.params[0]); - if (!ch) - return sendResponse("442 :You're not on that channel", fd); - if (!ch->getOperators().contains(fd)) - return sendResponse("482 :You're not a channel operator", fd); + return sendResponse(E461, fd); + if (not irc->channelExists(PARAM)) + return sendResponse(E403, fd); + Channel *ch = irc->getClient(fd).getUser()->getChannel(PARAM); + if (not ch) + return sendResponse(E442, fd); + if (not ch->getOperators().contains(fd)) + return sendResponse(E482, fd); for (auto client : ch->getOperators()) - { - if (irc->getClient(client).getUser()->getNick() == msg.params[1]) - return sendResponse("482 :You can't kick an operator", fd); - } - + if (USER(client)->getNick() == PARAM1) + return sendResponse(E481, fd); Client *target = nullptr; for (auto client : ch->getUsers()) - { - if (irc->getClient(client).getUser()->getNick() == msg.params[1]) + if (USER(client)->getNick() == PARAM1) { target = &irc->getClient(client); break ; } - } - if (!target) - return sendResponse("441 :They aren't on that channel", fd); - - std::string nick = ":" + irc->getClient(fd).getUser()->getNick(); - std::string response = irc->getClient(fd).getUser()->createPrefix() + - " KICK " + msg.params[0] + " " + msg.params[1]; + if (not target) + return sendResponse(E441, fd); + std::string nick = ":" + NICK; + std::string response = PREFIX + " KICK " + PARAM + " " + PARAM1; if (msg.params.size() < 3) response.append(" " + nick); else - response.append(" :" + msg.params[2]); + response.append(" :" + PARAM2); sendResponse(response, fd); ch->message(fd, response); ch->kick(fd, target->_fd); @@ -175,76 +161,71 @@ void KickCommand::execute(const Message &msg, int fd) void InviteCommand::execute(const Message &msg, int fd) { if (msg.params.size() < 2) - return sendResponse("461 :Missing parameters", fd); + return sendResponse(E461, fd); Client *target = nullptr; for (auto client : irc->getClients()) - { - if (client.second.getUser()->getNick() == msg.params[0]) + if (client.second.getUser()->getNick() == PARAM) { target = &(client.second); break ; } - } - if (!target) - return sendResponse("401 :No such nick", fd); + if (not target) + return sendResponse(E401, fd); if (target->_fd == fd) - return sendResponse("443 :You cannot invite yourself", fd); - Channel *ch = irc->findChannel(msg.params[1]); - if (ch != nullptr) + return sendResponse(E443, fd); + Channel *ch = irc->findChannel(PARAM1); + if (ch) { - if (!ch->getUsers().contains(fd) && !ch->getOperators().contains(fd)) - return sendResponse("442: You're not on that channel", fd); - else if (ch->getMode().contains('i') && !ch->getOperators().contains(fd)) - return sendResponse("482 :You're not a channel operator", fd); + if (not ch->getUsers().contains(fd) && not ch->getOperators().contains(fd)) + return sendResponse(E442, fd); + else if (ch->getMode().contains('i') && not ch->getOperators().contains(fd)) + return sendResponse(E482, fd); else if (ch->getUsers().contains(target->_fd) || ch->getOperators().contains(target->_fd)) - return sendResponse("443 :User already on channel", fd); + return sendResponse(E443, fd); else ch->invite(target->_fd); } - std::string invitation = irc->getClient(fd).getUser()->createPrefix(); - invitation.append(" INVITE " + msg.params[0] + " :" + msg.params[1]); + std::string invitation = PREFIX + " INVITE " + PARAM + " :" + PARAM1; sendResponse(invitation, target->_fd); - sendResponse("341 :Invitation send", fd); + sendResponse(R341, fd); } void TopicCommand::execute(const Message &msg, int fd) { if (msg.params.empty()) - return sendResponse("461 :Missing parameters", fd); - if (!irc->channelExists(msg.params[0])) - return sendResponse("403 :Channel doesn't exist", fd); - Channel *ch = irc->getClient(fd).getUser()->getChannel(msg.params[0]); - if (!ch) - return sendResponse("442 :You're not on the channel", fd); + return sendResponse(E461, fd); + if (not irc->channelExists(PARAM)) + return sendResponse(E403, fd); + Channel *ch = irc->getClient(fd).getUser()->getChannel(PARAM); + if (not ch) + return sendResponse(E442, fd); if (msg.params.size() < 2) { - const std::string &nick = irc->getClient(fd).getUser()->getNick(); const std::string &topic = ch->getTopic(); if (topic.empty()) - return sendResponse("331 " + nick + " " + msg.params[0] + " :No topic is set", fd); - return sendResponse("332 " + nick + " " + msg.params[0] + " :" + topic, fd); + return sendResponse(R331, fd); + return sendResponse(R332, fd); } - if (ch->getMode().contains('t') && !ch->getOperators().contains(fd)) - return sendResponse("482 :You're not a channel operator", fd); - ch->setTopic(fd, msg.params[1]); + if (ch->getMode().contains('t') && not ch->getOperators().contains(fd)) + return sendResponse(E482, fd); + ch->setTopic(fd, PARAM1); } -constexpr std::string supported = "itkol", requireParam = "klo"; +constexpr std::string supported = "itkol", required = "klo"; void ModeCommand::execute(const Message &msg, int fd) { auto channel = [&]() { - Channel *ch = irc->getClient(fd).getUser()->getChannel(msg.params[0]); + Channel *ch = irc->getClient(fd).getUser()->getChannel(PARAM); if (!ch) - return sendResponse("442 :You're not on the channel", fd); - Channel backup = *ch; + return sendResponse(E442, fd); if (msg.params.size() < 2) { - sendResponse(":localhost 324 " + irc->getClient(fd).getUser()->getNick() + " " + msg.params[0] + " " + ch->modeList(), fd); - return sendResponse(":localhost 329 " + irc->getClient(fd).getUser()->getNick() + " " + msg.params[0] + " " + ch->getTime(), fd); + sendResponse(R324, fd); + return sendResponse(R329, fd); } else if (!ch->getOperators().contains(fd)) - return sendResponse("482 :Channel operator privileges required", fd); - std::string input = msg.params[1], enable, disable; + return sendResponse(E482, fd); + std::string input = PARAM1, enable, disable; bool plus, valid = true; for (auto c = input.begin(); c != input.end(); ) if ((*c == '+'|| *c == '-') && valid) { @@ -255,81 +236,66 @@ void ModeCommand::execute(const Message &msg, int fd) valid = true; } } else - return sendResponse("472 :Unknown mode", fd); + return sendResponse(E472, fd); size_t paramsNeeded = 2; for (auto c = enable.begin(); c != enable.end(); ++c) if ((supported.find(*c) == std::string::npos) || (std::find(c + 1, enable.end(), *c) != enable.end())) - return sendResponse("472 :Unknown mode", fd); - else if (requireParam.find(*c) != std::string::npos) + return sendResponse(E472, fd); + else if (required.find(*c) != std::string::npos) paramsNeeded++; for (auto c = disable.begin(); c != disable.end(); ++c) if ((supported.find(*c) == std::string::npos) || (std::find(c + 1, disable.end(), *c) != disable.end())) - return sendResponse("472 :Unknown mode", fd); + return sendResponse(E472, fd); if (msg.params.size() < paramsNeeded) - return sendResponse("461 :Need more parameters", fd); + return sendResponse(E461, fd); + Channel backup = *ch; int index = 2; - try { - for (auto c : enable) { - if (c == 'k') { - ch->setPassword(msg.params[index++]); - } else if (c == 'l') { - try { - ch->setLimit(std::stoul(msg.params[index++])); - } catch (std::exception &e) { - sendResponse("472 :Unknown mode", fd); - throw ; - } - } else if (c == 'o') { - try { - ch->makeOperator(fd, msg.params[index++]); - } catch (std::exception &e) { - sendResponse("441 " + - irc->getClient(fd).getUser()->getNick() + - " " + msg.params[0] + - " :They aren't on that channel", fd); - throw ; - } - } + for (auto c : enable) + if (c == 'k') { + ch->setPassword(msg.params[index++]); + } else if (c == 'l' && not ch->setLimit(msg.params[index++])) { + *ch = backup; + return ; + } else if (c == 'o' && not ch->makeOperator(fd, msg.params[index++])) { + sendResponse("441 " + NICK + " " + PARAM + " :They aren't on that channel", fd); + *ch = backup; + return ; } - } catch (std::exception &e) { - *ch = backup; - return ; - } if (disable.empty() && enable == "o") return ; ch->setMode(enable); ch->unsetMode(disable); - ch->message(-1, ":localhost 324 " + irc->getClient(fd).getUser()->getNick() + " " + msg.params[0] + " " + ch->modeList()); + ch->message(-1, R324); }; auto user = [&]() { - if (msg.params[0] != irc->getClient(fd).getUser()->getNick()) - sendResponse("502 :Users don't match", fd); + if (PARAM != NICK) + sendResponse(E502, fd); return ; }; if (msg.params.size() < 1) - return sendResponse("461 :Need more parameters", fd); - msg.params[0][0] == '#' ? channel() : user(); + return sendResponse(E461, fd); + PARAM[0] == '#' ? channel() : user(); } void QuitCommand::execute(const Message &msg, int fd) { if (!msg.params.empty()) - irc->getClient(fd).getUser()->quit(fd, msg.params[0]); + USER(fd)->quit(fd, PARAM); else - irc->getClient(fd).getUser()->quit(fd, "Client quit"); + USER(fd)->quit(fd, "Client quit"); } void CapCommand::execute(const Message &msg, int fd) { if (msg.params.empty()) return sendResponse("461 CAP :Not enough parameters", fd); - if (msg.params[0] == "LS") + if (PARAM == "LS") return sendResponse(":localhost CAP * LS :", fd); - else if (msg.params[0] == "END") + else if (PARAM == "END") return ; else return sendResponse("410 CAP :Unsupported subcommand", fd); @@ -338,29 +304,28 @@ void CapCommand::execute(const Message &msg, int fd) void WhoisCommand::execute(const Message &msg, int fd) { (void)msg; - std::string nick = irc->getClient(fd).getUser()->getNick(); + std::string nick = NICK; sendResponse("318 " + nick + " :End of WHOIS list", fd); } void WhoCommand::execute(const Message &msg, int fd) { if (msg.params.empty()) - sendResponse("461 :Not enough parameters", fd); - Channel *ch = irc->getClient(fd).getUser()->getChannel(msg.params[0]); - if (!ch) - return sendResponse("442 :You're not on the channel", fd); - const std::string &nick = irc->getClient(fd).getUser()->getNick(); + sendResponse(E461, fd); + Channel *ch = irc->getClient(fd).getUser()->getChannel(PARAM); + if (not ch) + return sendResponse(E442, fd); + const std::string &nick = NICK; for (auto id : ch->getUsers()) { - const User *user = irc->getClient(id).getUser(); - sendResponse("352 " + msg.params[0] + " " - + user->getUser() + " " + user->getHost() + - " localhost " + user->getNick() + " H", fd); + const User *user = USER(id); + sendResponse("352 " + PARAM + " " + user->getUser() + " " + + user->getHost() + " localhost " + user->getNick() + " H", fd); } for (auto id : ch->getOperators()) { - const User *user = irc->getClient(id).getUser(); - sendResponse("352 " + nick + " " + msg.params[0] + " " + const User *user = USER(id); + sendResponse("352 " + nick + " " + PARAM + " " + user->getUser() + " " + user->getHost() + " localhost " + user->getNick() + " H @", fd); } @@ -370,8 +335,8 @@ void WhoCommand::execute(const Message &msg, int fd) void PingCommand::execute(const Message &msg, int fd) { if (msg.params.empty()) - return sendResponse("409 :No origin specified", fd); - std::string response = "PONG localhost :" + msg.params[0]; + return sendResponse(E409, fd); + std::string response = "PONG localhost :" + PARAM; sendResponse(response, fd); } @@ -379,14 +344,14 @@ void PassCommand::execute(const Message &msg, int fd) { if (irc->checkPassword()) return ; - else if (!msg.params.empty() && irc->checkPassword(msg.params[0])) + else if (!msg.params.empty() && irc->checkPassword(PARAM)) return irc->getClient(fd).authenticate(); - sendResponse("464 " + irc->getClient(fd).getUser()->getNick() + " :Incorrect password", fd); + sendResponse("464 " + NICK + " :Incorrect password", fd); irc->removeClient(fd); } void UnknownCommand::execute(const Message &msg, int fd) { debugLog(msg); - sendResponse("421 :Unknown command", fd); + sendResponse(E421, fd); } diff --git a/src/CommandDispatcher.cpp b/src/CommandDispatcher.cpp index c78a1b9..8bd7a31 100644 --- a/src/CommandDispatcher.cpp +++ b/src/CommandDispatcher.cpp @@ -1,4 +1,5 @@ #include "CommandDispatcher.hpp" +#include /** * CommandDispatcher installs seperate handlers for each command at construction @@ -20,6 +21,7 @@ CommandDispatcher::CommandDispatcher(void) _handlers["WHO"] = std::make_unique(); _handlers["PING"] = std::make_unique(); _handlers["PASS"] = std::make_unique(); + _handlers["UNKNOWN"] = std::make_unique(); } /** @@ -46,13 +48,13 @@ bool CommandDispatcher::dispatch(const std::unique_ptr &msg, int fd) } cmd->second->execute(*msg, fd); if (msg->command != "QUIT" && - !irc->getClient(fd).getUser()->getNick().empty() && - !irc->getClient(fd).getUser()->getUser().empty() && - !irc->getClient(fd).accessRegistered()) + not irc->getClient(fd).getUser()->getNick().empty() && + not irc->getClient(fd).getUser()->getUser().empty() && + not irc->getClient(fd).accessRegistered()) _welcome(fd); } else - _default.execute(*msg, fd); + _handlers.find("UNKNOWN")->second->execute(*msg, fd); } catch (std::exception &e) { diff --git a/src/Handler.cpp b/src/Handler.cpp index 102a212..f44ff0d 100644 --- a/src/Handler.cpp +++ b/src/Handler.cpp @@ -7,7 +7,6 @@ void Handler::clientWrite(int fd) { vector buf(BUFSIZ); queue> msg_queue; RecvParser parser(msg_queue); - CommandDispatcher dispatcher; messageLen = recv(fd, &buf[0], buf.size(), 0); if (messageLen == -1) @@ -16,7 +15,7 @@ void Handler::clientWrite(int fd) { while (!msg_queue.empty()) { const unique_ptr &msg = msg_queue.front(); - if (!dispatcher.dispatch(msg, fd)) + if (!irc->getClient(fd).getDispatch()->dispatch(msg, fd)) return ; msg_queue.pop(); } diff --git a/src/Server.cpp b/src/Server.cpp index 81a638f..90e7cc7 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -1,16 +1,47 @@ #include "Server.hpp" -Server::Server(std::string passwd) : +Server::Server(std::string port, std::string passwd) : _startTime(time(nullptr)), _fd(epoll_create1(0)), +_sock(socket(AF_INET, SOCK_STREAM, 0)), +_checker(0), +_port(stoi(port, &_checker)), _password(passwd) { if(_fd == -1) throw std::runtime_error("Server::Server: ERROR - Failed to create epoll file"); + else if (port[_checker]) + throw std::runtime_error("Server::Server: ERROR - Bad port number " + port); + auto optval = 1; + setsockopt(_sock, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); + struct sockaddr_in sa_bindy{}; + sa_bindy.sin_family = AF_INET; + sa_bindy.sin_port = htons(_port); + sa_bindy.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(_sock, (struct sockaddr *)&sa_bindy, sizeof(sa_bindy))) { + close(_fd); + close(_sock); + throw std::runtime_error("Server::Server: ERROR - Binding failed to " + port); + } + + if (listen(_sock, 10)) { + close(_fd); + close(_sock); + throw std::runtime_error("Server::Server: ERROR - Failed listen on port " + port); + } + _events.reserve(_max_events * sizeof(epoll_event)); + _addOwnSocket(_sock); } -Server::~Server() { close(_fd); } +Server::~Server() { + for(auto client : _clients) { + delete client.second.getDispatch(); + } + close(_fd); + close(_sock); +} bool Server::checkPassword(std::string password) const { return _password == password; @@ -21,7 +52,7 @@ int Server::getServerFd() const { } Channel& Server::addChannel(std::string name) { - _channels.try_emplace(name, Channel(name)); + _channels.try_emplace(name, name); return _channels.at(name); } @@ -45,8 +76,10 @@ void Server::addClient(int fd) { } void Server::removeClient(const int fd) { - if(!_clients.empty()) + if(not _clients.empty()) { + delete getClient(fd).getDispatch(); _clients.erase(fd); + } close(fd); } @@ -117,12 +150,15 @@ void Server::registerHandler(const int fd, uint32_t eventType, std::function& Server::getClients() const { diff --git a/src/User.cpp b/src/User.cpp index ddca203..f55881d 100644 --- a/src/User.cpp +++ b/src/User.cpp @@ -11,9 +11,9 @@ void User::quit(int fd, string msg) { irc->removeClient(fd); } -void User::setNick(string name) { +void User::setNick(int fd, string name) { for (auto channels : _channels) { - channels->message(-1, createPrefix(), "NICK", name); + channels->message(fd, createPrefix(), "NICK", name); } _nick = name; } diff --git a/src/main.cpp b/src/main.cpp index 5bb36b5..e61cf92 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,81 +2,48 @@ #include "Handler.hpp" #include #include -#include -#include -#include -#include #include using namespace std; Server *irc; namespace { - volatile std::sig_atomic_t gSigStatus = 0; + volatile sig_atomic_t gSigStatus = 0; } int main(int argc, char *argv[]) { - struct sockaddr_in sa_bindy{}; - sa_bindy.sin_family = AF_INET; if (argc != 2 && argc != 3) { cerr << "Usage ./ircserv [port] " << endl; return 1; - } else if (argc == 3) { - irc = new Server(std::string(argv[2])); - } else { - irc = new Server; } - int port; + signal(SIGINT, [](int) { gSigStatus = 1; }); + signal(SIGQUIT, [](int) { gSigStatus = 1; }); try { - port = stoi(argv[1]); - } catch (...) { - cerr << "Faulty port" << endl; + if (argc == 3) + irc = new Server(string(argv[1]), string(argv[2])); + else + irc = new Server(string(argv[1])); + } catch (runtime_error &err) { + cerr << err.what() << endl; return 1; - } - - sa_bindy.sin_port = htons(port); - sa_bindy.sin_addr.s_addr = htonl(INADDR_ANY); - - auto sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock == -1) { - delete irc; - cerr << "Socket creation failed" << endl; - return 1; - } - - int optval = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval)); - - if (bind(sock, (struct sockaddr *)&sa_bindy, sizeof(sa_bindy))) { - delete irc; - close(sock); - cerr << "Binding failed" << endl; + } catch (bad_alloc &a) { + cerr << "Memory allocation failed" << endl; return 1; - } - - if (listen(sock, 10)) { - delete irc; - close(sock); - cerr << "Listening on the port failed" << endl; + } catch (...) { + cerr << "Bad port number" << endl; return 1; } - signal(SIGINT, [](int) { gSigStatus = 1; }); - signal(SIGQUIT, [](int) { gSigStatus = 1; }); - - irc->addOwnSocket(sock); - irc->registerHandler(sock, EPOLLIN, [](int socket) { Handler::acceptClient(socket); }); - - while (!gSigStatus) { + while (not gSigStatus) { try { irc->poll(); } catch (runtime_error &err) { cerr << err.what() << endl; } } - close(sock); + delete irc; }