├── conf ├── operhost.config ├── botquotes.txt ├── motd2.txt ├── motd.txt └── motd3.txt ├── includes ├── parser.hpp ├── irc.hpp ├── bot.hpp ├── utils.hpp ├── channel.hpp ├── exceptions.hpp ├── User.hpp ├── commands.hpp ├── Server.hpp └── replies.hpp ├── srcs ├── bot │ ├── utils.cpp │ ├── answers │ │ ├── ping.cpp │ │ ├── time.cpp │ │ └── quote.cpp │ └── bot.cpp ├── commands │ ├── cap.cpp │ ├── die.cpp │ ├── pass.cpp │ ├── version.cpp │ ├── time.cpp │ ├── quit.cpp │ ├── ping.cpp │ ├── info.cpp │ ├── pong.cpp │ ├── oper.cpp │ ├── service.cpp │ ├── list.cpp │ ├── motd.cpp │ ├── kill.cpp │ ├── user.cpp │ ├── topic.cpp │ ├── nick.cpp │ ├── names.cpp │ ├── part.cpp │ ├── invite.cpp │ ├── kick.cpp │ ├── who.cpp │ ├── join.cpp │ ├── whois.cpp │ ├── notice.cpp │ ├── privmsg.cpp │ └── mode.cpp ├── utils │ ├── errors.cpp │ ├── welcome.cpp │ ├── command_utils.cpp │ └── parsing.cpp ├── main.cpp ├── parser │ └── mask.cpp ├── server │ ├── exceptions.cpp │ └── Server.cpp ├── channel │ └── channel.cpp └── user │ └── User.cpp ├── .gitignore ├── README.md └── Makefile /conf/operhost.config: -------------------------------------------------------------------------------- 1 | 127.0.0.1 2 | -------------------------------------------------------------------------------- /includes/parser.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PARSER_HPP 2 | # define PARSER_HPP 3 | 4 | bool matchMask(const char *text, const char *regex); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /includes/irc.hpp: -------------------------------------------------------------------------------- 1 | #ifndef IRC_HPP 2 | # define IRC_HPP 3 | 4 | # include 5 | 6 | // Server 7 | int server(int port, std::string password); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /conf/botquotes.txt: -------------------------------------------------------------------------------- 1 | La vie est un mystère qu'il faut vivre, et non un problème à résoudre -Gandhi 2 | Choisissez un travail que vous aimez et vous n'aurez pas à travailler un seul jour de votre vie -Confucius 3 | Là où il y a du poil, il y a de la joie 4 | Il ne faut pas mettre la charrue avant les boeufs -------------------------------------------------------------------------------- /includes/bot.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BOT_HPP 2 | # define BOT_HPP 3 | 4 | # include 5 | 6 | // Answers 7 | std::string botQuote(void); 8 | std::string botTime(void); 9 | std::string botPing(const std::string &message); 10 | 11 | // Utils 12 | int printError(std::string message, int code, bool with_errno = false); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /srcs/bot/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int printError(std::string message, int code, bool with_errno = false) 7 | { 8 | std::cout << message; 9 | if (with_errno) 10 | std::cout << std::strerror(errno); 11 | std::cout << std::endl; 12 | return (code); 13 | } 14 | -------------------------------------------------------------------------------- /srcs/bot/answers/ping.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | std::string botPing(const std::string &message) 7 | { 8 | std::string response; 9 | 10 | response 11 | .append("PONG ") 12 | .append(message.substr(message.find("PING") + 5)); 13 | 14 | return (response); 15 | } 16 | -------------------------------------------------------------------------------- /srcs/commands/cap.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | 3 | /** 4 | * @brief The CAP command is used for capability negotiation between a server 5 | * and a client. 6 | * 7 | * Not implemented here as it it not part of RFC 2812 8 | * 9 | */ 10 | 11 | void cap(const int &, const std::vector &, \ 12 | const std::string &, Server *) 13 | {} 14 | -------------------------------------------------------------------------------- /srcs/utils/errors.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/utils.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int printError(std::string message, int code, bool with_errno = false) 8 | { 9 | std::cout << message; 10 | if (with_errno) 11 | std::cout << std::strerror(errno); 12 | std::cout << std::endl; 13 | return (code); 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Editor 2 | .vscode 3 | *~ 4 | \#*\# 5 | 6 | ## Other 7 | .DS_Store 8 | ircserv 9 | helper 10 | ircbot 11 | 12 | # Debug files 13 | *.dSYM/ 14 | *.su 15 | *.idb 16 | *.pdb 17 | 18 | # Object files 19 | *.o 20 | *.ko 21 | *.obj 22 | *.elf 23 | 24 | # Libraries 25 | *.lib 26 | *.a 27 | *.la 28 | *.lo 29 | 30 | # Executables 31 | *.exe 32 | *.out 33 | *.app 34 | *.i*86 35 | *.x86_64 36 | *.hex 37 | 38 | helper -------------------------------------------------------------------------------- /srcs/bot/answers/time.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | std::string botTime(void) 7 | { 8 | time_t rawtime = time(NULL); 9 | struct tm *timeinfo; 10 | std::string strTime; 11 | 12 | timeinfo = localtime(&rawtime); 13 | strTime = std::string(asctime(timeinfo)); 14 | strTime = strTime.substr(0, strTime.length() - 1); 15 | 16 | return (strTime); 17 | } 18 | -------------------------------------------------------------------------------- /srcs/commands/die.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/replies.hpp" 4 | 5 | void die(const int &fd, const std::vector &, const std::string &, 6 | Server *srv) 7 | { 8 | if (srv->getUserByFd(fd)->hasMode(MOD_OPER)) { 9 | informSUsers(srv, "Server is shutting down"); 10 | throw Server::pollWaitException(); 11 | } 12 | else 13 | return (srv->sendClient(fd, numericReply(srv, fd, "481", 14 | ERR_NOPRIVILEGES))); 15 | } 16 | -------------------------------------------------------------------------------- /conf/motd2.txt: -------------------------------------------------------------------------------- 1 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣶⣿⣿⣷⣶⣄⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 2 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣾⣿⣿⡿⢿⣿⣿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀ 3 | ⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⡟⠁⣰⣿⣿⣿⡿⠿⠻⠿⣿⣿⣿⣿⣧⠀⠀⠀⠀ 4 | ⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⠏⠀⣴⣿⣿⣿⠉⠀⠀⠀⠀⠀⠈⢻⣿⣿⣇⠀⠀⠀ 5 | ⠀⠀⠀⠀⢀⣠⣼⣿⣿⡏⠀⢠⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⡀⠀⠀ 6 | ⠀⠀⠀⣰⣿⣿⣿⣿⣿⡇⠀⢸⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⡇⠀⠀ 7 | ⠀⠀⢰⣿⣿⡿⣿⣿⣿⡇⠀⠘⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⢀⣸⣿⣿⣿⠁⠀⠀ 8 | ⠀⠀⣿⣿⣿⠁⣿⣿⣿⡇⠀⠀⠻⣿⣿⣿⣷⣶⣶⣶⣶⣶⣿⣿⣿⣿⠃⠀⠀⠀ 9 | ⠀⢰⣿⣿⡇⠀⣿⣿⣿⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀ 10 | ⠀⢸⣿⣿⡇⠀⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠉⠛⠛⠛⠉⢉⣿⣿⠀⠀⠀⠀⠀⠀ 11 | ⠀⢸⣿⣿⣇⠀⣿⣿⣿⠀⠀⠀⠀⠀⢀⣤⣤⣤⡀⠀⠀⢸⣿⣿⣿⣷⣦⠀⠀⠀ 12 | ⠀⠀⢻⣿⣿⣶⣿⣿⣿⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣦⡀⠀⠉⠉⠻⣿⣿⡇⠀⠀ 13 | ⠀⠀⠀⠛⠿⣿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠈⠹⣿⣿⣇⣀⠀⣠⣾⣿⣿⡇⠀⠀ 14 | ⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⣦⣤⣤⣤⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⠀ 15 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⢿⣿⣿⣿⣿⣿⣿⠿⠋⠉⠛⠋⠉⠉⠁⠀⠀⠀⠀ 16 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠁ -------------------------------------------------------------------------------- /conf/motd.txt: -------------------------------------------------------------------------------- 1 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣤⣤⣤⣤⣶⣦⣤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀ 2 | ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⡿⠛⠉⠙⠛⠛⠛⠛⠻⢿⣿⣷⣤⡀⠀⠀⠀⠀⠀ 3 | ⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠋⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠈⢻⣿⣿⡄⠀⠀⠀⠀ 4 | ⠀⠀⠀⠀⠀⠀⠀⣸⣿⡏⠀⠀⠀⣠⣶⣾⣿⣿⣿⠿⠿⠿⢿⣿⣿⣿⣄⠀⠀⠀ 5 | ⠀⠀⠀⠀⠀⠀⠀⣿⣿⠁⠀⠀⢰⣿⣿⣯⠁⠀⠀⠀⠀⠀⠀⠀⠈⠙⢿⣷⡄⠀ 6 | ⠀⠀⣀⣤⣴⣶⣶⣿⡟⠀⠀⠀⢸⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣷⠀ 7 | ⠀⢰⣿⡟⠋⠉⣹⣿⡇⠀⠀⠀⠘⣿⣿⣿⣿⣷⣦⣤⣤⣤⣶⣶⣶⣶⣿⣿⣿⠀ 8 | ⠀⢸⣿⡇⠀⠀⣿⣿⡇⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀ 9 | ⠀⣸⣿⡇⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠉⠻⠿⣿⣿⣿⣿⡿⠿⠿⠛⢻⣿⡇⠀⠀ 10 | ⠀⣿⣿⠁⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣧⠀⠀ 11 | ⠀⣿⣿⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⠀⠀ 12 | ⠀⣿⣿⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⠀⠀ 13 | ⠀⢿⣿⡆⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⡇⠀⠀ 14 | ⠀⠸⣿⣧⡀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠃⠀⠀ 15 | ⠀⠀⠛⢿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⣰⣿⣿⣷⣶⣶⣶⣶⠶⠀⢠⣿⣿⠀⠀⠀ 16 | ⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⣿⣿⡇⠀⣽⣿⡏⠁⠀⠀⢸⣿⡇⠀⠀⠀ 17 | ⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⣿⣿⡇⠀⢹⣿⡆⠀⠀⠀⣸⣿⠇⠀⠀⠀ 18 | ⠀⠀⠀⠀⠀⠀⠀⢿⣿⣦⣄⣀⣠⣴⣿⣿⠁⠀⠈⠻⣿⣿⣿⣿⡿⠏⠀⠀⠀⠀ 19 | ⠀⠀⠀⠀⠀⠀⠀⠈⠛⠻⠿⠿⠿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -------------------------------------------------------------------------------- /srcs/commands/pass.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/Server.hpp" 4 | 5 | void pass(const int &fd, const std::vector ¶ms, const std::string &, 6 | Server *srv) 7 | { 8 | std::string replyMsg; 9 | User *user = srv->getUserByFd(fd); 10 | 11 | if (user != 0) 12 | { 13 | if (params.empty() || params[0].empty()) { 14 | replyMsg = numericReply(srv, fd, "461", 15 | ERR_NEEDMOREPARAMS(std::string("PASS"))); 16 | } 17 | else if (user->getPassword() == true) { 18 | replyMsg = numericReply(srv, fd, "462", ERR_ALREADYREGISTRED); 19 | } 20 | else if (!user->getPassword() && srv->getPassword() 21 | == params[0]) { 22 | user->setPassword(true); 23 | } 24 | } 25 | srv->sendClient(fd, replyMsg); 26 | return ; 27 | } 28 | -------------------------------------------------------------------------------- /srcs/bot/answers/quote.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define FILENAME "conf/botquotes.txt" 8 | 9 | std::string botQuote(void) 10 | { 11 | std::string quote; 12 | std::ifstream infile; 13 | std::string line; 14 | std::deque quotes; 15 | int quoteNum; 16 | 17 | // initialize random seed 18 | std::srand(time(NULL)); 19 | 20 | // Try to open file 21 | infile.exceptions(std::ifstream::failbit); 22 | try { infile.open(FILENAME, std::fstream::in); } 23 | catch (std::ios_base::failure &e) 24 | { return ("No quote today."); } 25 | 26 | // Read the file one line at a time and append the line to the quote list 27 | try { 28 | while (std::getline(infile, line)) 29 | quotes.push_back(line); 30 | } catch (std::ios_base::failure &e) {} 31 | 32 | // pick one quote in the list 33 | quoteNum = std::rand() % quotes.size(); 34 | return (quotes[quoteNum]); 35 | } 36 | -------------------------------------------------------------------------------- /srcs/commands/version.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/Server.hpp" 3 | #include "../../includes/utils.hpp" 4 | #include "../../includes/exceptions.hpp" 5 | 6 | /** 7 | * @brief The VERSION command is used to query the version of the server 8 | * program. An optional parameter is used to query the version 9 | * of the server program which a client is not directly connected to. 10 | * 11 | * Errors handled: 12 | * - ERR_NOSUCHSERVER 13 | * 14 | */ 15 | 16 | void version(const int &fd, \ 17 | const std::vector ¶ms, const std::string &,Server *srv) 18 | { 19 | // COMMAND EXECUTION 20 | try { 21 | if (params.size() > 0) { 22 | if (params[0].compare(srv->getHostname()) != 0) 23 | throw nosuchserverException(params[0]); 24 | } 25 | srv->sendClient(fd, numericReply(srv, fd, "351", \ 26 | RPL_VERSION(std::string(VERSION), std::string(""), srv->getHostname(), \ 27 | std::string(VCOMMENT)))); 28 | } 29 | // EXCEPTIONS 30 | catch (nosuchserverException &e) {e.reply(srv, fd); return; } 31 | } 32 | -------------------------------------------------------------------------------- /srcs/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | 6 | #include "../includes/irc.hpp" 7 | #include "../includes/utils.hpp" 8 | #include "../includes/Server.hpp" 9 | 10 | #define DEF_PORT 6667 11 | #define DEF_PASS "pwd" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | void signalHandler( int signum ) { 18 | (void)signum; 19 | std::cout << "\r "; 20 | } 21 | 22 | 23 | int main(int ac, char **av) 24 | { 25 | // parameters 26 | std::stringstream ss; 27 | int port = DEF_PORT; 28 | std::string password = DEF_PASS; 29 | 30 | // basic using parameters ------------------------------------------------ / 31 | if (ac != 3) 32 | return (printError("Parameters error", 1, false)); 33 | ss << av[1]; 34 | ss >> port; 35 | password = av[2]; 36 | 37 | // signal CTRL-C handler ------------------------------------------------- / 38 | signal(SIGINT, signalHandler); 39 | 40 | // server creation ------------------------------------------------------- / 41 | Server ircServer(port, password); 42 | ircServer.start(); 43 | return (0); 44 | } 45 | -------------------------------------------------------------------------------- /conf/motd3.txt: -------------------------------------------------------------------------------- 1 | . . . . . . . 2 | . . . . . ______ 3 | . . . //////// 4 | . . ________ . . ///////// . . 5 | . |.____. /\ .///////// . 6 | . .// \/ |\ ///////// 7 | . . .// \ | \ ///////// . . . 8 | ||. . .| | ///////// . . 9 | . . || | |//`,///// . 10 | . \\ ./ // / \/ . 11 | . \\.___./ //\` ' ,_\ . . 12 | . . \ //////\ , / \ . . 13 | . ///////// \| ' | . 14 | . . ///////// . \ _ / . 15 | ///////// . 16 | . .///////// . . 17 | . -------- . .. . 18 | . . . . . 19 | ________________________ 20 | ____________------------ -------------_________ -------------------------------------------------------------------------------- /srcs/commands/time.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/Server.hpp" 3 | #include "../../includes/utils.hpp" 4 | #include "../../includes/exceptions.hpp" 5 | 6 | /** 7 | * @brief The time command is used to query local time from the specified 8 | * server. If the parameter is not given, the server receiving 9 | * the command must reply to the query. 10 | * 11 | * Errors handled: 12 | * - ERR_NOSUCHSERVER 13 | * 14 | */ 15 | 16 | void time(const int &fd, \ 17 | const std::vector ¶ms, const std::string &,Server *srv) 18 | { 19 | time_t rawtime = time(NULL); 20 | struct tm *timeinfo; 21 | std::string strTime; 22 | 23 | // COMMAND EXECUTION 24 | try { 25 | if (params.size() > 0) { 26 | if (params[0].compare(srv->getHostname()) != 0) 27 | throw nosuchserverException(params[0]); 28 | } 29 | timeinfo = localtime(&rawtime); 30 | strTime = std::string(asctime(timeinfo)); 31 | strTime = strTime.substr(0, strTime.length() - 1); 32 | srv->sendClient(fd, \ 33 | numericReply(srv, fd, "391", RPL_TIME(srv->getHostname(), strTime))); 34 | } 35 | // EXCEPTIONS 36 | catch (nosuchserverException &e) {e.reply(srv, fd); return; } 37 | } 38 | -------------------------------------------------------------------------------- /srcs/parser/mask.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../../includes/parser.hpp" 5 | 6 | static bool match(const char *text, const char *regex); 7 | static bool matchStar(char c, const char *regex, const char *text) 8 | { 9 | do { 10 | if (match(text, regex)) 11 | return (true); 12 | } while (*text != '\0' && (*text++ == c || c == '?')); 13 | return (false); 14 | } 15 | 16 | static bool match(const char *text, const char *regex) 17 | { 18 | if (regex[0] == '\0') 19 | return (true); 20 | if (regex[0] == '\\' && (regex[1] == '*' || regex[1] == '?')) 21 | { 22 | if (*text != '\0' && (regex[1] == *text)) 23 | return (match(text + 1, regex + 2)); 24 | } 25 | if (regex[1] == '*') 26 | return (matchStar(regex[0], regex + 2, text)); 27 | if (*text != '\0' && (regex[0] == '?' || regex[0] == *text)) 28 | return (match(text + 1, regex + 1)); 29 | return (false); 30 | } 31 | 32 | // special function to convert * to ?* 33 | bool matchMask(const char *text, const char *regex) 34 | { 35 | std::string res(regex); 36 | int i = 0; 37 | 38 | while (res[i] != '\0') 39 | { 40 | if (res[i] == '*') 41 | { 42 | res.replace(i, 1, "?*"); 43 | i += 2; 44 | } 45 | else 46 | ++i; 47 | } 48 | return (match(text, res.c_str())); 49 | } 50 | -------------------------------------------------------------------------------- /srcs/commands/quit.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/Server.hpp" 4 | 5 | void quit(const int &fd, const std::vector ¶ms, const std::string &, Server *srv) 6 | { 7 | std::string replyMsg; 8 | User *user = srv->getUserByFd(fd); 9 | 10 | // Send ERROR to the user encapsulated in NOTICE ----------------------------- / 11 | serverQuitNotice(fd, srv, user->getNickname(), 12 | params.empty() == true ? CLIENT_ERROR : CLIENT_ERRORMSG(params[0])); 13 | 14 | // Make the replyMsg and get channels from the user before killing the fd ----- / 15 | replyMsg = clientReply(srv, fd, CLIENT_QUIT(std::string("QUIT"), 16 | (params.empty() == true ? std::string() : params[0]))); 17 | std::deque channelsToReplyTo = user->getChannelsJoined(); 18 | std::deque::iterator it; 19 | 20 | // Kill the client fd -------------------------------------------------------- / 21 | try { srv->killConnection(fd); } 22 | catch (Server::pollDelException &e) 23 | { printError(e.what(), 1, true); } 24 | catch (Server::invalidFdException &e) 25 | { printError(e.what(), 1, false); } 26 | 27 | // Send a client reply from origin FD to all channels where the user was ----- / 28 | try { 29 | for (it = channelsToReplyTo.begin(); it < channelsToReplyTo.end(); ++it) { 30 | srv->sendChannel(*it, replyMsg); 31 | } 32 | } 33 | catch (Server::invalidChannelException &e) 34 | { printError(e.what(), 1, true); } 35 | 36 | return ; 37 | } 38 | -------------------------------------------------------------------------------- /srcs/commands/ping.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/exceptions.hpp" 4 | 5 | /** 6 | * @brief The PING command is used to test the presence of an active client or 7 | * server at the other end of the connection. 8 | * 9 | * Errors handled: 10 | * - ERR_NOORIGIN 11 | * - ERR_NOSUCHSERVER 12 | * 13 | */ 14 | 15 | void ping(const int &fd, const std::vector ¶ms, \ 16 | const std::string &, Server *srv) 17 | { 18 | std::string origin; 19 | std::string servername; 20 | 21 | // COMMAND EXECUTION 22 | try { 23 | if (params.size() == 0) 24 | throw nooriginException(); 25 | if (params.size() >= 1) 26 | { 27 | origin = params[0]; 28 | if (params.size() == 2) 29 | { 30 | // In mono-server, the target server must be the current one 31 | servername = params[1]; 32 | if (srv->getHostname().compare(servername) != 0) 33 | throw nosuchserverException(servername); 34 | } 35 | // answer with a pong, use fd instead of origin as we are not in 36 | // multiserv and PING queries cannot be forwarded 37 | srv->sendClient(fd, PONG(srv->getHostname())); 38 | } 39 | } 40 | 41 | // EXCEPTIONS 42 | catch (nooriginException &e) {e.reply(srv, fd); return; } 43 | catch (nosuchserverException &e) {e.reply(srv, fd); return; } 44 | } 45 | -------------------------------------------------------------------------------- /srcs/commands/info.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/replies.hpp" 4 | #include "../../includes/exceptions.hpp" 5 | 6 | /** 7 | * @brief The INFO command is REQUIRED to return information describing the 8 | * server 9 | * 10 | * Errors handled: 11 | * - ERR_NOSUCHSERVER 12 | * 13 | */ 14 | 15 | std::string createInfoStr(Server *srv, const int &fd) { 16 | std::string replyMsg; 17 | replyMsg 18 | .append(numericReply(srv, fd, "371", \ 19 | RPL_INFO(std::string("Version: ").append(VERSION)))) 20 | .append(numericReply(srv, fd, "371", \ 21 | RPL_INFO(std::string("Version comment: ").append(VCOMMENT)))) 22 | .append(numericReply(srv, fd, "371", \ 23 | RPL_INFO(std::string("Compilation Date: ").append(COMPILDATE)))); 24 | return (replyMsg); 25 | } 26 | 27 | void info(const int &fd, const std::vector ¶ms, \ 28 | const std::string &,Server *srv) 29 | { 30 | std::string reply; 31 | 32 | 33 | // COMMAND EXECUTION 34 | try { 35 | if (params.size() > 0) { 36 | if (params[0].compare(srv->getHostname()) != 0) 37 | throw nosuchserverException(params[0]); 38 | } 39 | reply 40 | .append(createInfoStr(srv, fd)) 41 | .append(numericReply(srv, fd, "374", RPL_ENDOFINFO)); 42 | srv->sendClient(fd, reply); 43 | } 44 | // EXCEPTIONS 45 | catch (nosuchserverException &e) {e.reply(srv, fd); return; } 46 | } 47 | -------------------------------------------------------------------------------- /srcs/commands/pong.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/exceptions.hpp" 4 | 5 | /** 6 | * @brief PONG message is a reply to ping message. If it is sent by client to 7 | * the server, then the server updates the client status to mark it as alive. 8 | * 9 | * Errors handled: 10 | * - ERR_NOORIGIN 11 | * - ERR_NOSUCHSERVER 12 | * 13 | */ 14 | 15 | void pong(const int &fd, const std::vector ¶ms, \ 16 | const std::string &, Server *srv) 17 | { 18 | std::string origin; 19 | std::string servername; 20 | User *user; 21 | 22 | // COMMAND EXECUTION 23 | try { 24 | if (params.size() == 0) 25 | throw nooriginException(); 26 | if (params.size() >= 1) 27 | { 28 | origin = params[0]; 29 | if (params.size() == 2) 30 | { 31 | // In mono-server, the target server must be the current one 32 | servername = params[1]; 33 | if (srv->getHostname().compare(servername) != 0) 34 | throw nosuchserverException(servername); 35 | } 36 | 37 | // update client status with the fd, as we are not in multiserv 38 | if ((user = srv->getUserByFd(fd)) != NULL) 39 | user->setStatus(ST_ALIVE); 40 | } 41 | } 42 | 43 | // EXCEPTIONS 44 | catch (nooriginException &e) {e.reply(srv, fd); return; } 45 | catch (nosuchserverException &e) {e.reply(srv, fd); return; } 46 | } 47 | -------------------------------------------------------------------------------- /srcs/commands/oper.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | 4 | bool isOperHost(std::string hostname) { 5 | std::string configFile = OPERCONF; 6 | const char* file; 7 | std::ifstream input; 8 | std::string str; 9 | 10 | file = configFile.c_str(); 11 | 12 | try { input.open(file, std::ios::in); } 13 | catch (std::ifstream::failure &e) 14 | { printError(e.what(), 1, true); return false; } 15 | try { 16 | if (input.is_open()) { 17 | while (getline(input, str)) { 18 | if (str == hostname) 19 | return true; 20 | } 21 | } 22 | } 23 | catch (std::ifstream::failure &e) 24 | { printError(e.what(), 1, true); return false; } 25 | return false; 26 | } 27 | 28 | void oper(const int &fd, const std::vector ¶ms, const std::string &, 29 | Server *srv) { 30 | 31 | std::string replyMsg; 32 | User *user = srv->getUserByFd(fd); 33 | 34 | if (params.empty() || params.size() < 2 || emptyParams(params)) 35 | replyMsg = numericReply(srv, fd, "461", ERR_NEEDMOREPARAMS(std::string("OPER"))); 36 | else if (isOperHost(user->getHostname()) == false) 37 | replyMsg = numericReply(srv, fd, "491", ERR_NOOPERHOST); 38 | else if (user->hasMode(MOD_RESTRICTED)) 39 | replyMsg = numericReply(srv, fd, "484", ERR_RESTRICTED); 40 | else if (params[1] != srv->getPassword()) 41 | replyMsg = numericReply(srv, fd, "464", ERR_PASSWDMISMATCH); 42 | else { 43 | std::vector nickname; 44 | nickname.push_back(user->getNickname()); 45 | user->addMode(MOD_OPER); 46 | mode(fd, nickname, "MODE", srv); 47 | } 48 | if (!replyMsg.empty()) 49 | srv->sendClient(fd, replyMsg); 50 | } 51 | -------------------------------------------------------------------------------- /srcs/commands/service.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | 4 | bool areValidServiceParams(const std::vector ¶ms) 5 | { 6 | if (forbiddenNick(params[0]) || params[0].find(' ') != std::string::npos) 7 | return false; 8 | else if (params[1][0] != '*' || params[1].size() > 1) 9 | return false; 10 | else if (params[2].find(HOSTADDRESS) == std::string::npos) 11 | return false; 12 | else if (isNumber(params[3]) && std::atol(params[3].c_str()) > 9) 13 | return false; 14 | else if (isNumber(params[4]) && std::atol(params[4].c_str()) > 9) 15 | return false; 16 | return true; 17 | } 18 | 19 | void service(const int &fd, const std::vector ¶ms, const std::string &, 20 | Server *srv) 21 | { 22 | std::string replyMsg; 23 | User *user = srv->getUserByFd(fd); 24 | 25 | if (user != 0 && user->getPassword() == true) { 26 | if (params.empty() || params.size() < 6 || emptyParams(params)) { 27 | replyMsg = numericReply(srv, fd, "461", 28 | ERR_NEEDMOREPARAMS(std::string("SERVICE"))); 29 | } 30 | else if (srv->getUserByNickname(params[0]) != 0) 31 | replyMsg = numericReply(srv, fd, "433", ERR_NICKNAMEINUSE(params[0])); 32 | else if (!user->getUsername().empty()) 33 | replyMsg = numericReply(srv, fd, "462", ERR_ALREADYREGISTRED); 34 | else if (areValidServiceParams(params) == true) { 35 | user->setNickname(params[0]); 36 | user->setUsername(params[0]); 37 | user->setFullname(params[5]); 38 | user->setIsBot(true); 39 | if (isAuthenticatable(user)) 40 | authenticateUser(fd, srv); 41 | return ; 42 | } 43 | } 44 | if (!replyMsg.empty()) 45 | srv->sendClient(fd, replyMsg); 46 | return ; 47 | } 48 | -------------------------------------------------------------------------------- /srcs/commands/list.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/channel.hpp" 2 | #include "../../includes/replies.hpp" 3 | #include "../../includes/utils.hpp" 4 | #include "../../includes/commands.hpp" 5 | 6 | /** 7 | * @brief List channels with their topics 8 | * 9 | * No error handled 10 | * 11 | */ 12 | 13 | void listAllChannel(const int &fdUser, Server *server) 14 | { 15 | std::map::iterator itChannel; 16 | 17 | for (itChannel = server->_channelList.begin(); 18 | itChannel != server->_channelList.end(); itChannel++) 19 | { 20 | server->sendClient(fdUser, numericReply(server, fdUser, 21 | "322", RPL_LIST(itChannel->first, itChannel->second->getTopic()))); 22 | } 23 | server->sendClient(fdUser, numericReply(server, fdUser, 24 | "323", RPL_LISTEND)); 25 | } 26 | 27 | void listSpecificChannel(const int &fdUser, std::vector parameter, 28 | Server *server) 29 | { 30 | std::vector channelList = splitByComma(parameter[0]); 31 | std::vector::iterator itChannelName; 32 | std::map::iterator itChannel; 33 | 34 | if (channelList.empty() == true) 35 | return ; 36 | for (itChannelName = channelList.begin(); 37 | itChannelName != channelList.end(); itChannelName++) 38 | { 39 | itChannel = server->_channelList.find(*itChannelName); 40 | if (itChannel != server->_channelList.end()) 41 | server->sendClient(fdUser, numericReply(server, fdUser, 42 | "322", RPL_LIST(itChannel->first, itChannel->second->getTopic()))); 43 | } 44 | } 45 | 46 | void list(const int &fdUser, const std::vector ¶meter, 47 | const std::string &, Server *server) 48 | { 49 | // Channel list must exist 50 | if (server->_channelList.empty() == true) 51 | return ; 52 | if (parameter.empty() == true) 53 | listAllChannel(fdUser, server); 54 | else 55 | listSpecificChannel(fdUser, parameter, server); 56 | } 57 | -------------------------------------------------------------------------------- /srcs/utils/welcome.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/Server.hpp" 4 | 5 | bool isAuthenticationCmd(std::string cmd) { 6 | if (cmd.compare("USER") == 0 || cmd.compare("NICK") == 0 7 | || cmd.compare("PASS") == 0 || cmd.compare("QUIT") == 0 8 | || cmd.compare("SERVICE") == 0) 9 | return true; 10 | return false; 11 | } 12 | 13 | bool isAuthenticatable(User *user) 14 | { 15 | if (user->getAuthenticated() == true) 16 | return false; 17 | else if (user->getPassword() == false) 18 | return false; 19 | else if (user->getNickname() == "*" || user->getUsername().empty()) 20 | return false; 21 | return true; 22 | } 23 | 24 | void addDefaultMode(Server *srv, User *user, const int fd, short mod) { 25 | std::vector params; 26 | user->addMode(mod); 27 | params.push_back(user->getNickname()); 28 | mode(fd, params, "MODE", srv); 29 | } 30 | 31 | void authenticateUser(const int fd, Server *srv) 32 | { 33 | std::string replyMsg; 34 | std::vector params; 35 | User* user = srv->getUserByFd(fd); 36 | bool isBot = user->getIsBot(); 37 | 38 | if (!isBot) 39 | replyMsg.append(numericReply(srv, fd, "001", 40 | RPL_WELCOME(user->getNickname(), user->getUsername(), user->getHostname()))); 41 | else 42 | replyMsg.append(numericReply(srv, fd, "383", 43 | RPL_YOURESERVICE(user->getNickname()))); 44 | replyMsg.append(numericReply(srv, fd, "002", 45 | RPL_YOURHOST(srv->getHostname(), srv->getVersion()))); 46 | if (!isBot) 47 | replyMsg.append(numericReply(srv, fd, "003", RPL_CREATED(srv->getDate()))); 48 | replyMsg.append(numericReply(srv, fd, "004", 49 | RPL_MYINFO(srv->getHostname(), srv->getVersion(), USERMODES, CHANNELMODES))); 50 | srv->sendClient(fd, replyMsg); 51 | if (!isBot) { 52 | motd(fd, params, "MOTD", srv); 53 | addDefaultMode(srv, user, fd, MOD_INVISIBLE); 54 | } 55 | else 56 | addDefaultMode(srv, user, fd, MOD_BOT); 57 | user->setAuthenticated(true); // is client answering smth? 58 | return ; 59 | } 60 | -------------------------------------------------------------------------------- /srcs/commands/motd.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/replies.hpp" 4 | #include "../../includes/exceptions.hpp" 5 | 6 | #ifndef MOTD_FILE 7 | # define MOTD_FILE "conf/motd.txt" 8 | #endif 9 | 10 | /** 11 | * @brief The MOTD command is used to get the "Message Of The Day" of the given 12 | * server, or current server if is omitted. 13 | * 14 | * Errors handled: 15 | * - ERR_NOMOTD 16 | * 17 | */ 18 | 19 | std::string createModtStr(Server *srv, const int &fd, std::string &filename) 20 | { 21 | std::ifstream infile; 22 | std::string line; 23 | std::string replyMsg; 24 | 25 | // Try to open file 26 | infile.exceptions(std::ifstream::failbit); 27 | try { infile.open(filename.c_str(), std::fstream::in); } 28 | catch (std::ios_base::failure &e) 29 | { throw nomotdException(); } 30 | 31 | // Read the file one line at a time and append the line to the reply 32 | replyMsg.append(numericReply(srv, fd, "372", RPL_MOTD(std::string("Bienvenue - welcome - bouno vengudo - i bisimila - degemer mad - benvinguts - velkommen")))); 33 | try { 34 | while (std::getline(infile, line)) 35 | replyMsg.append(numericReply(srv, fd, "372", RPL_MOTD(line))); 36 | } catch (std::ios_base::failure &e) {} 37 | replyMsg.append(numericReply(srv, fd, "372", RPL_MOTD(std::string("Des bisous de la Space team <3")))); 38 | 39 | return (replyMsg); 40 | } 41 | 42 | void motd(const int &fd, const std::vector &, \ 43 | const std::string &,Server *srv) 44 | { 45 | std::string reply; 46 | std::string motdFile(MOTD_FILE); 47 | 48 | // COMMAND EXECUTION 49 | try { 50 | reply 51 | .append(numericReply(srv, fd, "375", RPL_MOTDSTART(srv->getHostname()))) 52 | .append(createModtStr(srv, fd, motdFile)) 53 | .append(numericReply(srv, fd, "376", RPL_ENDOFMOTD)); 54 | srv->sendClient(fd, reply); 55 | } 56 | 57 | // EXCEPTIONS 58 | catch (nomotdException &e) {e.reply(srv, fd); return; } 59 | } 60 | -------------------------------------------------------------------------------- /includes/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_HPP 2 | # define UTILS_HPP 3 | 4 | # include "Server.hpp" 5 | # include "replies.hpp" 6 | 7 | # include 8 | # include 9 | # include 10 | # include 11 | # include 12 | # include 13 | # include 14 | # include 15 | # include 16 | 17 | 18 | // Util functions 19 | int printError(std::string message, int code, 20 | bool with_errno); 21 | bool emptyParams(const std::vector ¶ms); 22 | bool forbiddenNick(std::string param); 23 | bool isNumber(std::string num); 24 | 25 | // Parsing 26 | //std::vector splitBy(std::string str, const std::string &delimiter); 27 | std::vector splitBy(std::string str, const std::string &delimiter, std::string *buffer); 28 | void splitCmds(std::vector cmd_strings, 29 | std::vector *cmds); 30 | void displayCommands(std::vector cmds); 31 | 32 | // Replies util functions 33 | std::string numericReply(Server *irc, const int &fd, std::string code, 34 | std::string replyMsg); 35 | std::string clientReply(Server *irc, const int &fd, std::string replyMsg); 36 | std::string eventChannel(Server *irc, const int &fd, std::string eventType, 37 | std::string channelName); 38 | void serverQuitNotice(const int &fd, Server *srv, const std::string 39 | &destNick, std::string msg); 40 | void informSUsers(Server *srv, std::string msg); 41 | 42 | // Channel util functions 43 | std::vector splitByComma(std::string parameter); 44 | bool isChannel(std::string channel_name); 45 | std::string getChannelTopic(std::string channelName, std::map channelList); 47 | std::vector getChannelKey(std::vector parameter); 48 | bool findUserOnChannel(std::deque userList, User *currentUser); 49 | bool findBannedUserOnChannel(std::deque userList, std::string currentUser); 50 | 51 | // Authenticate users 52 | bool isAuthenticationCmd(std::string cmd); 53 | bool isAuthenticatable(User *user); 54 | void authenticateUser(const int fd, Server *srv); 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /srcs/commands/kill.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/exceptions.hpp" 4 | 5 | /** 6 | * @brief The KILL command is used to cause a client-server connection to be 7 | * closed by the server which has the actual connection. 8 | * 9 | * Errors handled: 10 | * - ERR_NOPRIVILEGES 11 | * - ERR_NEEDMOREPARAMS 12 | * - ERR_NOSUCHNICK 13 | * - ERR_CANTKILLSERVER 14 | * 15 | */ 16 | 17 | void kill(const int &fd, const std::vector ¶ms, \ 18 | const std::string &,Server *srv) 19 | { 20 | std::string nickname; 21 | std::string comment; 22 | User *target; 23 | 24 | // COMMAND EXECUTION 25 | try { 26 | // check params 27 | if (params.size() < 2) 28 | throw needmoreparamsException("KILL"); 29 | 30 | nickname = params[0]; 31 | comment = params[1]; 32 | // check if nickname is actually the server hostname 33 | if (nickname.compare(srv->getHostname()) == 0 34 | && srv->getUserByNickname(nickname) == NULL) 35 | throw cantkillserverException(); 36 | 37 | // check if nickname exists 38 | target = srv->getUserByNickname(nickname); 39 | if (target == NULL) 40 | throw nosuchnickException(nickname); 41 | 42 | // check if user associated with the connection is Op 43 | if (!srv->getUserByFd(fd)->hasMode(MOD_OPER)) 44 | throw noprivilegesException(); 45 | 46 | // all is ok, execute the KILL 47 | try { srv->killConnection(target->getFd()); } 48 | catch (Server::pollDelException &e) { printError(e.what(), 1, true); } 49 | catch (Server::invalidFdException &e) { printError(e.what(), 1, false);} 50 | 51 | // add the nickname to the list of unavailable nicknames with a timer 52 | srv->_unavailableNicknames[nickname] = time(NULL); 53 | } 54 | 55 | // EXCEPTIONS 56 | catch (noprivilegesException &e) {e.reply(srv, fd); return; } 57 | catch (nosuchnickException &e) {e.reply(srv, fd); return; } 58 | catch (needmoreparamsException &e) {e.reply(srv, fd); return; } 59 | catch (cantkillserverException &e) {e.reply(srv, fd); return; } 60 | } 61 | -------------------------------------------------------------------------------- /srcs/commands/user.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/Server.hpp" 4 | 5 | // user = 1*( %x01-09 / %x0B-0C / %x0E-1F / %x21-3F / %x41-FF ) 6 | // ; any octet except NUL, CR, LF, " " and "@" 7 | 8 | bool forbiddenUsername(std::string param) 9 | { 10 | if (param.empty()) 11 | return true; 12 | else if (param.find('\0') != std::string::npos) 13 | return true; 14 | else if (param.find('\n') != std::string::npos) 15 | return true; 16 | else if (param.find('\r') != std::string::npos) 17 | return true; 18 | else if (param.find('@') != std::string::npos) 19 | return true; 20 | return false; 21 | } 22 | 23 | bool isNumber(std::string num) { 24 | for (unsigned int i = 0; i < num.size(); i++) { 25 | if (isdigit(num[i]) == 0) 26 | return false; 27 | } 28 | return true; 29 | } 30 | 31 | bool areValidParams(const std::vector ¶ms) 32 | { 33 | if (forbiddenUsername(params[0]) || params[0].find(' ') != std::string::npos) 34 | return false; 35 | else if (isNumber(params[1]) && std::atol(params[1].c_str()) > 256) 36 | return false; 37 | else if (isNumber(params[1]) == false && (params[1] != params[0])) 38 | return false; 39 | else if (params[3].size() < 1 || forbiddenUsername(params[3])) 40 | return false; 41 | return true; 42 | } 43 | 44 | void user(const int &fd, const std::vector ¶ms, const std::string &, 45 | Server *srv) 46 | { 47 | std::string replyMsg; 48 | User *user = srv->getUserByFd(fd); 49 | 50 | if (user != 0 && user->getPassword() == true) { 51 | if (params.empty() || params.size() < 4 || emptyParams(params)) { 52 | replyMsg = numericReply(srv, fd, "461", 53 | ERR_NEEDMOREPARAMS(std::string("USER"))); 54 | } 55 | else if (!user->getUsername().empty()) { 56 | replyMsg = numericReply(srv, fd, "462", ERR_ALREADYREGISTRED); 57 | } 58 | else if (areValidParams(params) == true) { 59 | user->setUsername(params[0]); 60 | if (params[1] != params[0]) 61 | user->addMode(std::atol(params[1].c_str())); 62 | user->setFullname(params[3]); 63 | if (isAuthenticatable(user)) 64 | authenticateUser(fd, srv); 65 | return ; 66 | } 67 | } 68 | if (!replyMsg.empty()) 69 | srv->sendClient(fd, replyMsg); 70 | return ; 71 | } 72 | -------------------------------------------------------------------------------- /includes/channel.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CHANNEL_HPP 2 | #define CHANNEL_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "User.hpp" 10 | 11 | // The available modes are as follows: 12 | // none ? => 0 0000 0000 13 | // o - channel operator; => 1 0000 0001 14 | // k - key locked; => 3 0000 0010 15 | // i - invite only; => 3 0000 0100 16 | // b - ban user; => 4 0000 1000 17 | 18 | #define MOD_NONE (0 << 0) 19 | #define MOD_OPERATOR (1 << 0) 20 | #define MOD_KEY (1 << 2) 21 | #define MOD_INVITE (1 << 3) 22 | #define MOD_BAN (1 << 4) 23 | 24 | class Channel 25 | { 26 | private: 27 | std::string _topic; 28 | std::string _channelName; 29 | std::string _key; 30 | 31 | public: 32 | /** Public attributes **/ 33 | short _mode; 34 | std::deque _operators; 35 | std::deque _users; 36 | std::deque _bannedUsers; 37 | std::deque _invitees; 38 | 39 | /** Constructors and destructor **/ 40 | 41 | Channel(std::string name, User *currentUser); 42 | Channel(std::string name, std::string key, User *currentUser); 43 | ~Channel(void); 44 | Channel(const Channel &cpy); 45 | 46 | Channel &operator=(Channel const &cpy); 47 | 48 | /** Getters **/ 49 | 50 | std::string getTopic(void) const; 51 | std::string getChannelName(void) const; 52 | std::string getKey(void) const; 53 | std::deque getUsers(void) const; 54 | int getLimitNumberOfUsers(void) const; 55 | 56 | /** Setters **/ 57 | 58 | void setTopic(std::string topic); 59 | void setKey(std::string key); 60 | void setMode(char mode); 61 | 62 | /** Member functions **/ 63 | 64 | void addUser(User *newUser); 65 | void removeUser(User *userToDelete); 66 | void removeOperator(User *userToDelete); 67 | void removeBannedUser(std::string userToDelete); 68 | void removeInvitee(User *userToDelete); 69 | void addOperator(User *newOperator); 70 | void addBannedUser(std::string newBannedUser); 71 | void addInvitee(User *newInvitee); 72 | 73 | // mode 74 | void addMode(short mode); 75 | void removeMode(short mode); 76 | bool hasMode(short mode); 77 | }; 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /includes/exceptions.hpp: -------------------------------------------------------------------------------- 1 | #ifndef EXCEPTIONS_HPP 2 | # define EXCEPTIONS_HPP 3 | 4 | # include 5 | # include 6 | 7 | # include "Server.hpp" 8 | 9 | class ircException : public std::exception 10 | { 11 | protected: 12 | std::string code; 13 | std::string rpl; 14 | public: 15 | virtual ~ircException() throw() {} 16 | virtual void reply(Server *srv, const int &fd) const throw(); 17 | }; 18 | 19 | class grammarException : public ircException 20 | { 21 | private: 22 | const char *msg; 23 | public: 24 | virtual ~grammarException() throw() {} 25 | grammarException(const char *msg = NULL) : msg(msg) {} 26 | virtual const char *what() const throw(); 27 | }; 28 | 29 | class nosuchnickException : public ircException 30 | { public: nosuchnickException(std::string nickname, std::string code = "401");}; 31 | 32 | class norecipientException : public ircException 33 | { public: norecipientException(std::string command, std::string code = "411");}; 34 | 35 | class notexttosendException : public ircException 36 | { public: notexttosendException(std::string code = "412"); }; 37 | 38 | class toomanytargetsException : public ircException 39 | { public: toomanytargetsException(std::string target, std::string errcode, \ 40 | std::string abortmsg, std::string code = "407"); 41 | }; 42 | 43 | class notoplevelException : public ircException 44 | { public: notoplevelException(std::string mask, std::string code = "413");}; 45 | 46 | class wildtoplevelException : public ircException 47 | { public: wildtoplevelException(std::string mask, std::string code = "414");}; 48 | 49 | class cannotsendtochanException : public ircException 50 | { public: cannotsendtochanException(std::string channame, \ 51 | std::string code = "404"); 52 | }; 53 | 54 | class nooriginException : public ircException 55 | { public: nooriginException(std::string code = "409"); }; 56 | 57 | class nosuchserverException : public ircException 58 | { public: nosuchserverException(std::string servername, \ 59 | std::string code = "402");}; 60 | 61 | class noprivilegesException : public ircException 62 | { public: noprivilegesException(std::string code = "481");}; 63 | 64 | class needmoreparamsException : public ircException 65 | { public: needmoreparamsException(std::string command, \ 66 | std::string code = "461");}; 67 | 68 | class cantkillserverException : public ircException 69 | { public: cantkillserverException(std::string code = "483");}; 70 | 71 | class nomotdException : public ircException 72 | { public: nomotdException(std::string code = "422");}; 73 | 74 | class nonicknamegivenException : public ircException 75 | { public: nonicknamegivenException(std::string code = "431");}; 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /srcs/commands/topic.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/channel.hpp" 2 | #include "../../includes/replies.hpp" 3 | #include "../../includes/utils.hpp" 4 | #include "../../includes/commands.hpp" 5 | 6 | /** 7 | * @brief View or change the topic of a channel 8 | * 9 | * Errors handled: 10 | * - ERR_NEEDMOREPARAMS 11 | * - ERR_NOTONCHANNEL 12 | * - ERR_NOSUCHCHANNEL 13 | * 14 | */ 15 | 16 | int checkTopicParameter(std::map::iterator itChannel, 17 | Server *server, const int &fdUser) 18 | { 19 | User *currentUser = server->getUserByFd(fdUser); 20 | 21 | if (findUserOnChannel(itChannel->second->_users, currentUser) == false) 22 | { 23 | server->sendClient(fdUser, numericReply(server, fdUser, 24 | "442", ERR_NOTONCHANNEL(itChannel->first))); 25 | return (-2); 26 | } 27 | return (0); 28 | } 29 | 30 | int checkGeneralParameter(const int &fdUser, const std::vector ¶meter, 31 | Server *server, std::map::iterator itChannel) 32 | { 33 | if (server->_channelList.empty() == true) 34 | { 35 | server->sendClient(fdUser, numericReply(server, fdUser, 36 | "461", ERR_NEEDMOREPARAMS(std::string("TOPIC")))); 37 | return (-1); 38 | } 39 | if (itChannel == server->_channelList.end()) 40 | { 41 | server->sendClient(fdUser, numericReply(server, fdUser, 42 | "403", ERR_NOSUCHCHANNEL(parameter[0]))); 43 | return (-2); 44 | } 45 | return (0); 46 | } 47 | 48 | 49 | void changeTopic(std::string topic, std::map::iterator itChannel, 50 | Server *server, const int &fdUser) 51 | { 52 | if (checkTopicParameter(itChannel, server, fdUser) < 0) 53 | return ; 54 | itChannel->second->setTopic(topic); 55 | return (server->sendChannel(itChannel->first, clientReply(server, fdUser, 56 | "TOPIC " + itChannel->first + " " + topic))); 57 | } 58 | 59 | void topic(const int &fdUser, const std::vector ¶meter, 60 | const std::string &, Server *server) 61 | { 62 | std::string channel; 63 | std::map::iterator itChannel; 64 | 65 | if (parameter.size() < 1) 66 | return (server->sendClient(fdUser, numericReply(server, fdUser, 67 | "461", ERR_NEEDMOREPARAMS(std::string("TOPIC"))))); 68 | channel = parameter[0]; 69 | itChannel = server->_channelList.find(channel); 70 | if (checkGeneralParameter(fdUser, parameter, server, itChannel) < 0) 71 | return ; 72 | if (parameter.size() > 1) 73 | changeTopic(parameter[1], itChannel, server, fdUser); 74 | else if (itChannel->second->getTopic().empty() == false) 75 | server->sendClient(fdUser, numericReply(server, fdUser, 76 | "332", RPL_TOPIC(channel, itChannel->second->getTopic()))); 77 | else 78 | server->sendClient(fdUser, numericReply(server, fdUser, 79 | "331", RPL_NOTOPIC(channel))); 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ft_irc 2 | 3 | ## What is it? ## 4 | 5 | Create our own server that will be able to receive client connexions. In our case, netcat and irssi connexions. 6 | With this server we will be able to communicate with different users. 7 | 8 | ## How does it work? ## 9 | 10 | ### Launching the server ### 11 | `./ircserv [port number] [password]` 12 | 13 | For example `./ircserv 6667 pwd` 14 | 15 | ### Launching a netcat client ### 16 | `nc -C 127.0.0.1 6667` 17 | 18 | We used the -C option since our reply messages end with \r\n as defined in the RFC 2812 standards. 19 | 20 | We used 127.0.0.1 since our server is launched in localhost. 21 | 22 | We used 6667 since it is the port, we specified, to our server to listen to. 23 | 24 | **You will then need to do the authentification process** 25 | 26 | `pass pwd` 27 | 28 | `user lea 0 * :lea` 29 | 30 | `nick lea` 31 | 32 | Our user is now connected and known in the server as lea. 33 | 34 | To understand more abour the authentification process please look [here](https://www.rfc-editor.org/rfc/rfc2812#section-3.1.1). 35 | 36 | ### Launching an irssi client ### 37 | `irssi` 38 | 39 | `/connect 127.0.0.1 6667 pwd` 40 | 41 | The authentification process is automatically done by irssi. 42 | 43 | Your user will be known in the server as plop. 44 | 45 | ## Commands we implemented ## 46 | 47 | We implemented 24 commands. 48 | 49 | ### Server related commands ### 50 | * DIE 51 | * INFO 52 | * MOTD 53 | * PING 54 | * PONG 55 | * QUIT 56 | * SERVICES 57 | * TIME 58 | * VERSION 59 | 60 | ### User related commands ### 61 | * USER 62 | * KILL 63 | * MODE (B, i, o, r, s) 64 | * NICK 65 | * OPER 66 | * PASS 67 | * WHO 68 | * WHOIS 69 | 70 | ### Channel related commands ### 71 | * INVITE 72 | * JOIN 73 | * KICK 74 | * LIST 75 | * MODE (b, i, k, o) 76 | * NAMES 77 | * PART 78 | * TOPIC 79 | 80 | ### Messages commands ### 81 | * PRIVMSG (msg on irssi) 82 | * NOTICE 83 | 84 | ## Bonus ## 85 | 86 | ### First bonus : file transfer ### 87 | If you choose irssi as your reference client, it is automatically handled by irssi. 88 | 89 | **Sending file to another client** 90 | 91 | `/dcc send [nickname of user] [path of the file between quotes]` 92 | 93 | For example `/dcc send plop_ "Documents/test.txt"` 94 | 95 | **Receiving file from a client** 96 | 97 | `/dcc get [nickname of user][name of the file between quotes]` 98 | 99 | For example `/dcc get plop "test.text"` 100 | 101 | DCC is handled as a PRIVMSG. 102 | 103 | 104 | ### Second bonus : implement a simple bot ### 105 | 106 | We implemented it as a simple client using the command services. 107 | 108 | Our bot sends you some quote with command QUOTE and can give you the time with command TIME. 109 | 110 | It is handled as a private conversation between the user and the bot. 111 | 112 | `./ircbot [hostname] [password]` 113 | 114 | ## Ressources ## 115 | 116 | * [General RFC](https://www.rfc-editor.org/rfc/rfc2812#section-3.1.1) 117 | * [Channel related RFC](https://www.rfc-editor.org/rfc/rfc2811) 118 | * [Tutorial on how to implement a server in french](http://vidalc.chez.com/lf/socket.html) 119 | * [Website made by 42 students from Paris](https://ircgod.com/) 120 | -------------------------------------------------------------------------------- /includes/User.hpp: -------------------------------------------------------------------------------- 1 | #ifndef USER_HPP 2 | # define USER_HPP 3 | 4 | # include 5 | # include 6 | # include 7 | # include 8 | # include 9 | 10 | # define ST_ALIVE 1 11 | # define ST_DEAD 0 12 | # define ST_PINGED 2 13 | 14 | // The available modes are: 15 | // none => 0 0000 0000 16 | // a - user is flagged as away; => 1 0000 0001 17 | // w - user receives wallops; => 2 0000 0010 18 | // i - marks a users as invisible; => 3 0000 0100 19 | // o - operator flag; => 4 0000 1000 20 | // B - marks a bot; => 5 0001 0000 21 | // s - marks a user for receipt of server notices. => 6 0010 0000 22 | // r - restricted user connection; => 7 0100 0000 23 | 24 | #define MOD_NONE (0 << 0) 25 | #define MOD_AWAY (1 << 0) 26 | #define MOD_WALLOPS (1 << 2) 27 | #define MOD_INVISIBLE (1 << 3) 28 | #define MOD_OPER (1 << 4) 29 | #define MOD_BOT (1 << 5) 30 | #define MOD_SRVNOTICES (1 << 6) 31 | #define MOD_RESTRICTED (1 << 7) 32 | 33 | class User { 34 | 35 | private: 36 | const int _fd; 37 | std::string _nickname; // max 9 characters 38 | std::string _username; 39 | std::string _fullname; 40 | std::string _hostname; 41 | short _mode; 42 | bool _password; 43 | bool _authenticated; 44 | std::deque _channelsJoined; 45 | int _status; 46 | time_t _lastActivityTime; 47 | time_t _pingTime; 48 | bool _isBot; 49 | 50 | 51 | public: 52 | User(const int fd, std::string hostname); 53 | User(const User &src); 54 | ~User(); 55 | 56 | User &operator=(User const &rhs); 57 | 58 | int getFd(void) const; 59 | std::string getNickname(void) const; 60 | std::string getUsername(void) const; 61 | std::string getFullname(void) const; 62 | std::string getHostname(void) const; 63 | short getMode(void) const; 64 | bool getPassword(void) const; 65 | bool getAuthenticated(void) const; 66 | std::deque getChannelsJoined(void) const; 67 | int getStatus(void) const; 68 | time_t getLastActivityTime(void) const; 69 | time_t getPingTime(void) const; 70 | bool getIsBot(void) const; 71 | 72 | void setNickname(std::string nickname); 73 | void setUsername(std::string username); 74 | void setFullname(std::string fullname); 75 | void setHostname(std::string fullname); 76 | void setPassword(bool pass); 77 | void setAuthenticated(bool authenticated); 78 | void setStatus(int status); 79 | void setLastActivityTime(void); 80 | void setPingTime(void); 81 | void setIsBot(bool bot); 82 | 83 | // mode 84 | void addMode(short mode); 85 | void removeMode(short mode); 86 | bool hasMode(short mode); 87 | 88 | bool addChannelJoined(std::string channelName); // if channel is already in the list, return false, else add + return true 89 | bool removeChannelJoined(std::string channelName); // if channel is found in the list, erase it + return true, else do nothing and return false 90 | }; 91 | 92 | std::ostream & operator<<(std::ostream &o, User const &rhs); // for printing 'nickname!username@host.name.com ...' 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /srcs/commands/nick.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/Server.hpp" 4 | 5 | // nickname = ( letter / special ) *8( letter / digit / special / "-" ) 6 | // 1 => A-Z / a-z ";" "[", "]", "\", "`", "_", "^", "{", "|", "}" 7 | // 8 => A-Z / a-z 0/9 ";" "[", "]", "\", "`", "_", "^", "{", "|", "}", "-" 8 | 9 | bool forbiddenNick(std::string param) 10 | { 11 | if (param.empty()) 12 | return true; 13 | else if (!isalpha(param[0]) && std::string(NAMESPECIALS).find(param[0]) 14 | == std::string::npos) 15 | return true; 16 | else if (param.length() > 9) 17 | return true; 18 | else { 19 | for (unsigned int i = 0; i < param.length(); i++) { 20 | if (!isalnum(param[i]) && std::string(NAMESPECIALS).find(param[i]) 21 | == std::string::npos && param[i] != 45) 22 | return true; 23 | } 24 | } 25 | return false; 26 | } 27 | 28 | bool isInKillList(Server *srv, std::string nick) { 29 | 30 | std::map::iterator it = srv->_unavailableNicknames.find(nick); 31 | std::map::iterator ite = srv->_unavailableNicknames.end(); 32 | double seconds; 33 | 34 | if (it != ite) { 35 | seconds = difftime(time(NULL), (srv->_unavailableNicknames.find(nick))->second); 36 | if (seconds < KILLTIME) 37 | return (true); 38 | else { 39 | srv->_unavailableNicknames.erase(it); 40 | return (false); 41 | } 42 | } 43 | return (false); 44 | } 45 | 46 | void sendClientOrChannel(Server *srv, const int &fd, User *user, std::string replyMsg) 47 | { 48 | std::deque listChannelJoined; 49 | std::deque::iterator itChannel; 50 | std::string latestChannelJoined; 51 | 52 | // Case where the user didn't join any channel 53 | if (user->getChannelsJoined().empty() == true) 54 | srv->sendClient(fd, replyMsg); 55 | // Case where the user joined some channel 56 | // Every user on the channel must be noticed 57 | listChannelJoined = user->getChannelsJoined(); 58 | for (itChannel = listChannelJoined.begin(); itChannel != listChannelJoined.end(); 59 | itChannel++) 60 | srv->sendChannel(*itChannel, replyMsg); 61 | } 62 | 63 | void nick(const int &fd, const std::vector ¶ms, const std::string &, 64 | Server *srv) 65 | { 66 | std::string replyMsg; 67 | User *user = srv->getUserByFd(fd); 68 | 69 | if (user != 0 && user->getPassword() == true) 70 | { 71 | if (params.empty() || params[0].empty()) 72 | replyMsg = numericReply(srv, fd, "431", ERR_NONICKNAMEGIVEN); 73 | else if (forbiddenNick(params[0]) == true) 74 | replyMsg = numericReply(srv, fd, "432", ERR_ERRONEUSNICKNAME(params[0])); 75 | else if (srv->getUserByNickname(params[0]) != 0) 76 | replyMsg = numericReply(srv, fd, "433", ERR_NICKNAMEINUSE(params[0])); 77 | else if (isInKillList(srv, params[0])) 78 | replyMsg = numericReply(srv, fd, "437", ERR_UNAVAILRESOURCE(params[0])); 79 | else if (user->hasMode(MOD_RESTRICTED)) 80 | replyMsg = numericReply(srv, fd, "484", ERR_RESTRICTED); 81 | else if (user->getNickname() == "*") { 82 | user->setNickname(params[0]); 83 | if (isAuthenticatable(user)) 84 | authenticateUser(fd, srv); 85 | return ; 86 | } 87 | else { 88 | replyMsg = clientReply(srv, fd, "NICK " + params[0]); 89 | user->setNickname(params[0]); 90 | } 91 | } 92 | sendClientOrChannel(srv, fd, user, replyMsg); 93 | return ; 94 | } 95 | -------------------------------------------------------------------------------- /srcs/server/exceptions.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/exceptions.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/Server.hpp" 4 | 5 | /* ************************************************************************** */ 6 | /* ERR and RPL EXCEPTIONS FOR COMMANDS */ 7 | /* ************************************************************************** */ 8 | // Generic functions 9 | void ircException::reply(Server *srv, const int &fd) const throw() 10 | { srv->sendClient(fd, numericReply(srv, fd, this->code, this->rpl)); } 11 | 12 | // Grammar exception 13 | const char *grammarException::what() const throw() 14 | { return (msg); } 15 | 16 | // 401 - nosuchnickException 17 | nosuchnickException::nosuchnickException(std::string nickname, std::string code) 18 | { this->code = code; this->rpl = ERR_NOSUCHNICK(nickname); } 19 | 20 | // 411 - norecipientException 21 | norecipientException::norecipientException(std::string command, std::string code) 22 | { this->code = code; this->rpl = ERR_NORECIPIENT(command); } 23 | 24 | // 412 - notexttosendException 25 | notexttosendException::notexttosendException(std::string code) 26 | { this->code = code; this->rpl = ERR_NOTEXTTOSEND; } 27 | 28 | // 412 - notexttosendException 29 | toomanytargetsException::toomanytargetsException(std::string target, \ 30 | std::string errcode, std::string abortmsg, std::string code) 31 | { this->code = code; this->rpl = ERR_TOOMANYTARGETS(target, errcode, abortmsg);} 32 | 33 | // 413 - notoplevelException 34 | notoplevelException::notoplevelException(std::string mask, std::string code) 35 | { this->code = code; this->rpl = ERR_NOTOPLEVEL(mask); } 36 | 37 | // 414 - wildtoplevelException 38 | wildtoplevelException::wildtoplevelException(std::string mask, std::string code) 39 | { this->code = code; this->rpl = ERR_WILDTOPLEVEL(mask); } 40 | 41 | // 404 - cannotsendtochanException 42 | cannotsendtochanException::cannotsendtochanException(std::string channame, \ 43 | std::string code) 44 | { this->code = code; this->rpl = ERR_CANNOTSENDTOCHAN(channame); } 45 | 46 | // 409 - nooriginException 47 | nooriginException::nooriginException(std::string code) 48 | { this->code = code; this->rpl = ERR_NOORIGIN; } 49 | 50 | // 402 - nosuchserverException 51 | nosuchserverException::nosuchserverException(std::string servername, \ 52 | std::string code) 53 | { this->code = code; this->rpl = ERR_NOSUCHSERVER(servername); } 54 | 55 | // 481 - noprivilegesException 56 | noprivilegesException::noprivilegesException(std::string code) 57 | { this->code = code; this->rpl = ERR_NOPRIVILEGES; } 58 | 59 | // 461 - needmoreparamsException 60 | needmoreparamsException::needmoreparamsException(std::string command, \ 61 | std::string code) 62 | { this->code = code; this->rpl = ERR_NEEDMOREPARAMS(command); } 63 | 64 | // 483 - cantkillserverException 65 | cantkillserverException::cantkillserverException(std::string code) 66 | { this->code = code; this->rpl = ERR_CANTKILLSERVER; } 67 | 68 | // 422 - nomotdException 69 | nomotdException::nomotdException(std::string code) 70 | { this->code = code; this->rpl = ERR_NOMOTD; } 71 | 72 | // 431 - nonicknamegivenException 73 | nonicknamegivenException::nonicknamegivenException(std::string code) 74 | { this->code = code; this->rpl = ERR_NONICKNAMEGIVEN; } 75 | -------------------------------------------------------------------------------- /srcs/utils/command_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/Server.hpp" 2 | #include "../../includes/utils.hpp" 3 | 4 | bool emptyParams(const std::vector ¶ms) { 5 | if (params.empty()) 6 | return true; 7 | for (unsigned int i = 0; i < params.size(); i++) { 8 | if (params[i].empty()) 9 | return true; 10 | } 11 | return false; 12 | } 13 | 14 | std::string numericReply(Server *irc, const int &fd, std::string code, std::string replyMsg) 15 | { 16 | std::string reply = ":" + irc->getHostname() + " " + code + " " 17 | + irc->getUserByFd(fd)->getNickname() + " " + replyMsg; 18 | return (reply); 19 | } 20 | 21 | void serverQuitNotice(const int &fd, Server *srv, const std::string &destNick, std::string msg) 22 | { 23 | std::string reply = ":" + srv->getHostname() + " NOTICE " + destNick + " " + ":" + msg; 24 | srv->sendClient(fd, reply); 25 | } 26 | 27 | void informSUsers(Server *srv, std::string msg) 28 | { 29 | std::deque users = srv->getAllUsers(); 30 | std::deque::iterator it = users.begin(); 31 | std::deque::iterator ite = users.end(); 32 | 33 | for (; it != ite; it++) { 34 | if ((*it)->hasMode(MOD_SRVNOTICES)) 35 | serverQuitNotice((*it)->getFd(), srv, (*it)->getNickname(), msg); 36 | } 37 | } 38 | 39 | std::string clientReply(Server *irc, const int &originFd, std::string replyMsg) 40 | { 41 | std::string reply = ":" + irc->getUserByFd(originFd)->getNickname() + "!" 42 | + irc->getUserByFd(originFd)->getUsername() + "@:" 43 | + irc->getUserByFd(originFd)->getHostname() + " " + replyMsg 44 | + "\r\n"; 45 | return (reply); 46 | } 47 | 48 | std::vector splitByComma(std::string parameter) 49 | { 50 | std::vector tab; 51 | 52 | // Case where no comma were found 53 | 54 | if (parameter.find(',') == std::string::npos) 55 | { 56 | tab.push_back(parameter); 57 | return (tab); 58 | } 59 | 60 | // Case where one comma was found 61 | 62 | std::string temp; 63 | std::istringstream stream(parameter); 64 | while (std::getline(stream, temp, ',')) 65 | { 66 | transform(temp.begin(), temp.end(), temp.begin(), ::tolower); 67 | tab.push_back(temp); 68 | } 69 | return (tab); 70 | } 71 | 72 | bool isChannel(std::string channelName) 73 | { 74 | if (channelName.find("#") != std::string::npos 75 | || channelName.find("&") != std::string::npos) 76 | { 77 | return (true); 78 | } 79 | return (false); 80 | } 81 | 82 | bool findUserOnChannel(std::deque userList, User *currentUser) 83 | { 84 | std::deque::iterator it; 85 | 86 | if (userList.empty() == true) 87 | return (false); 88 | it = userList.begin(); 89 | for (; it != userList.end(); it++) 90 | { 91 | if (*it == currentUser) 92 | return (true); 93 | } 94 | return (false); 95 | } 96 | 97 | bool findBannedUserOnChannel(std::deque userList, std::string currentUser) 98 | { 99 | std::deque::iterator it; 100 | 101 | if (userList.empty() == true) 102 | return (false); 103 | it = userList.begin(); 104 | for (; it != userList.end(); it++) 105 | { 106 | if (*it == currentUser) 107 | return (true); 108 | } 109 | return (false); 110 | } 111 | -------------------------------------------------------------------------------- /srcs/commands/names.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/channel.hpp" 2 | #include "../../includes/replies.hpp" 3 | #include "../../includes/utils.hpp" 4 | #include "../../includes/commands.hpp" 5 | 6 | /** 7 | * @brief List all nicknames on a channel 8 | * 9 | * No error handled 10 | * 11 | */ 12 | 13 | void listUser(const int &fdUser, Server *server, 14 | std::map::iterator itChannel) 15 | { 16 | std::string nicknameList; 17 | std::deque listUser = itChannel->second->_users; 18 | std::deque listOperator = itChannel->second->_operators; 19 | std::deque::iterator itUser; 20 | std::deque::iterator itOperator; 21 | std::string reply; 22 | 23 | for (itUser = listUser.begin(); itUser != listUser.end(); itUser++) 24 | { 25 | // Case if user is an operator 26 | for (itOperator = listOperator.begin(); itOperator != listOperator.end(); 27 | itOperator++) 28 | { 29 | if (*itUser == *itOperator) 30 | nicknameList += "@"; 31 | } 32 | nicknameList += (*itUser)->getNickname() + " "; 33 | } 34 | reply = ":" + server->getHostname() + " " + "353" + " " 35 | + server->getUserByFd(fdUser)->getNickname() + " = " 36 | + itChannel->second->getChannelName() + " "; 37 | reply += nicknameList + "\r\n"; 38 | server->sendClient(fdUser, reply); 39 | } 40 | 41 | void listUserInChannel(const int &fdUser, Server *server) 42 | { 43 | std::string latestChannelJoined; 44 | std::map::iterator itChannel; 45 | 46 | if (server->getUserByFd(fdUser)->getChannelsJoined().empty() == true) 47 | return; 48 | latestChannelJoined = server->getUserByFd(fdUser)->getChannelsJoined().back(); 49 | itChannel = server->_channelList.find(latestChannelJoined); 50 | if (itChannel == server->_channelList.end()) 51 | return; 52 | listUser(fdUser, server, itChannel); 53 | server->sendClient(fdUser, numericReply(server, fdUser, 54 | "366", RPL_ENDOFNAMES(latestChannelJoined))); 55 | } 56 | 57 | void listUserInSpecificChannel(const int &fdUser, const std::vector parameter, 58 | Server *server) 59 | { 60 | std::vector channelList = splitByComma(parameter[0]); 61 | std::vector::iterator itChannelName; 62 | std::map::iterator itChannel; 63 | 64 | if (channelList.empty() == true) 65 | return; 66 | for (itChannelName = channelList.begin(); 67 | itChannelName != channelList.end(); itChannelName++) 68 | { 69 | itChannel = server->_channelList.find(*itChannelName); 70 | if (itChannel != server->_channelList.end()) 71 | { 72 | listUser(fdUser, server, itChannel); 73 | server->sendClient(fdUser, numericReply(server, fdUser, 74 | "366", RPL_ENDOFNAMES(*itChannelName))); 75 | } 76 | } 77 | } 78 | 79 | void names(const int &fdUser, const std::vector ¶meter, 80 | const std::string &, Server *server) 81 | { 82 | if (server->_channelList.empty() == true) 83 | return; 84 | if (parameter.empty() == true) 85 | listUserInChannel(fdUser, server); 86 | else 87 | listUserInSpecificChannel(fdUser, parameter, server); 88 | } 89 | -------------------------------------------------------------------------------- /includes/commands.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COMMANDS_HPP 2 | # define COMMANDS_HPP 3 | 4 | # include 5 | # include 6 | # include 7 | # include 8 | # include 9 | 10 | class Server; 11 | class User; 12 | 13 | // Connexion commands 14 | void pass(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 15 | void nick(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 16 | void user(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 17 | void mode(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 18 | void oper(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 19 | void quit(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 20 | 21 | // Server queries and commands 22 | void motd(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 23 | void time(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 24 | void version(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 25 | void admin(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 26 | void info(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 27 | 28 | // Commands to ignore 29 | void cap(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 30 | 31 | // Channel command 32 | void join(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 33 | void part(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 34 | void invite(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 35 | void kick(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 36 | void topic(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 37 | void list(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 38 | void names(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 39 | 40 | // Miscellaneous commands 41 | void kill(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 42 | void ping(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 43 | void pong(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 44 | void die(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 45 | 46 | // Sending message commands 47 | void privmsg(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 48 | void notice(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 49 | 50 | // User based queries 51 | void who(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 52 | void whois(const int &fd, const std::vector ¶ms, const std::string &prefix, Server *srv); 53 | 54 | // Services commands 55 | void service(const int &fd, const std::vector ¶ms, const std::string &, 56 | Server *srv); 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # PROGRAM INFOS 2 | ################################################################################ 3 | VERSION = "1.0" 4 | VCOMMENT = "This version is almost perfect." 5 | SERVERINFO = "Every time you start this server, we plant a tree 🌲." 6 | HOSTNAME = $(shell hostname) 7 | COMPILDATE = "$(shell date)" 8 | 9 | # COMMANDS 10 | ################################################################################ 11 | RM = rm -f 12 | RMRF = rm -rf 13 | CC = c++ 14 | CD = cd 15 | MKDIR = mkdir 16 | GCLONE = git clone 17 | 18 | # SOURCES 19 | ################################################################################ 20 | SRCS = srcs/main.cpp \ 21 | srcs/server/Server.cpp \ 22 | srcs/server/exceptions.cpp \ 23 | srcs/channel/channel.cpp \ 24 | srcs/commands/join.cpp \ 25 | srcs/commands/part.cpp \ 26 | srcs/commands/invite.cpp \ 27 | srcs/commands/kick.cpp \ 28 | srcs/commands/topic.cpp \ 29 | srcs/commands/list.cpp \ 30 | srcs/commands/names.cpp \ 31 | srcs/user/User.cpp \ 32 | srcs/commands/cap.cpp \ 33 | srcs/commands/die.cpp \ 34 | srcs/commands/info.cpp \ 35 | srcs/commands/kill.cpp \ 36 | srcs/commands/mode.cpp \ 37 | srcs/commands/motd.cpp \ 38 | srcs/commands/nick.cpp \ 39 | srcs/commands/oper.cpp \ 40 | srcs/commands/pass.cpp \ 41 | srcs/commands/ping.cpp \ 42 | srcs/commands/pong.cpp \ 43 | srcs/commands/quit.cpp \ 44 | srcs/commands/service.cpp \ 45 | srcs/commands/time.cpp \ 46 | srcs/commands/user.cpp \ 47 | srcs/commands/privmsg.cpp \ 48 | srcs/commands/notice.cpp \ 49 | srcs/commands/version.cpp \ 50 | srcs/commands/who.cpp \ 51 | srcs/commands/whois.cpp \ 52 | srcs/utils/errors.cpp \ 53 | srcs/utils/parsing.cpp \ 54 | srcs/utils/welcome.cpp \ 55 | srcs/utils/command_utils.cpp \ 56 | srcs/parser/mask.cpp 57 | 58 | BOTSRCS = srcs/bot/bot.cpp \ 59 | srcs/bot/utils.cpp \ 60 | srcs/bot/answers/ping.cpp \ 61 | srcs/bot/answers/quote.cpp \ 62 | srcs/bot/answers/time.cpp 63 | 64 | OBJS = $(SRCS:.cpp=.o) 65 | BOTOBJS = $(BOTSRCS:.cpp=.o) 66 | 67 | # EXECUTABLES & LIBRARIES 68 | ################################################################################ 69 | NAME = ircserv 70 | BOTNAME = ircbot 71 | 72 | # DIRECTORIES 73 | ################################################################################ 74 | HEADERS = -Iincludes -Isrcs/server 75 | 76 | # FLAGS 77 | ################################################################################ 78 | CPPFLAGS := -Wall -Wextra -Werror -std=c++98 79 | 80 | PROGRAMVAR := -DHOSTNAME=\"$(HOSTNAME)\" -DVERSION=\"$(VERSION)\" \ 81 | -DVCOMMENT=\"$(VCOMMENT)\" -DCOMPILDATE=\"$(COMPILDATE)\" \ 82 | -DSERVERINFO=\"$(SERVERINFO)\" 83 | 84 | ifeq ($(DEBUG), true) 85 | CPPFLAGS += -fsanitize=address -g3 -O0 86 | endif 87 | 88 | ifeq ($(VERBOSE), true) 89 | CPPFLAGS += -DVERBOSE 90 | endif 91 | 92 | # RULES 93 | ################################################################################ 94 | .cpp.o: 95 | $(CC) $(CPPFLAGS) -c $< -o $(<:.cpp=.o) $(HEADERS) $(PROGRAMVAR) 96 | 97 | $(NAME): $(OBJS) 98 | $(CC) $(CPPFLAGS) $(OBJS) -o $(NAME) $(HEADERS) 99 | 100 | $(BOTNAME): $(BOTOBJS) 101 | $(CC) $(CPPFLAGS) $(BOTOBJS) -o $(BOTNAME) $(HEADERS) 102 | 103 | all: $(NAME) 104 | 105 | clean: 106 | $(RM) $(OBJS) 107 | $(RM) $(BOTOBJS) 108 | 109 | fclean: clean 110 | $(RM) $(NAME) 111 | $(RM) $(BOTNAME) 112 | 113 | re: fclean all 114 | 115 | bonus: $(NAME) $(BOTNAME) 116 | 117 | .PHONY: all clean fclean cpp.o re bonus 118 | -------------------------------------------------------------------------------- /srcs/channel/channel.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/channel.hpp" 2 | 3 | Channel::Channel(std::string name, User *currentUser): _channelName(name), 4 | _mode(MOD_NONE) 5 | { 6 | addOperator(currentUser); 7 | addUser(currentUser); 8 | } 9 | 10 | Channel::Channel(std::string name, std::string key, User *currentUser): _channelName(name), 11 | _key(key), _mode(MOD_NONE) 12 | { 13 | addOperator(currentUser); 14 | addUser(currentUser); 15 | this->addMode(MOD_KEY); 16 | } 17 | 18 | Channel::~Channel(void) {} 19 | 20 | Channel::Channel(const Channel &src): _topic(src._topic), _channelName(src._channelName), 21 | _key(src._key), _mode(src._mode), _operators(src._operators), _bannedUsers(src._bannedUsers), 22 | _invitees(src._invitees) { } 23 | 24 | Channel &Channel::operator=(Channel const &cpy) 25 | { 26 | if (this != &cpy) 27 | { 28 | this->_topic = cpy._topic; 29 | this->_channelName = cpy._channelName; 30 | this->_key = cpy._key; 31 | this->_mode = cpy._mode; 32 | this->_operators = cpy._operators; 33 | this->_bannedUsers = cpy._bannedUsers; 34 | this->_invitees = cpy._invitees; 35 | } 36 | return (*this); 37 | } 38 | 39 | /** Getters **/ 40 | 41 | std::string Channel::getTopic(void) const { return (this->_topic); } 42 | 43 | std::string Channel::getChannelName(void) const { return (this->_channelName); } 44 | 45 | std::string Channel::getKey(void) const { return (this->_key); } 46 | 47 | std::deque Channel::getUsers(void) const { return (this->_users); } 48 | 49 | /** Setters **/ 50 | 51 | void Channel::setTopic(std::string topic) { this->_topic = topic; } 52 | 53 | void Channel::setKey(std::string key) { this->_key = key; } 54 | 55 | void Channel::addUser(User *newUser) 56 | { 57 | this->_users.push_back(newUser); 58 | } 59 | 60 | void Channel::addOperator(User *newOperator) 61 | { 62 | this->_operators.push_back(newOperator); 63 | } 64 | 65 | void Channel::addBannedUser(std::string newBannedUser) 66 | { 67 | this->_bannedUsers.push_back(newBannedUser); 68 | } 69 | 70 | void Channel::addInvitee(User *newInvitee) 71 | { 72 | this->_invitees.push_back(newInvitee); 73 | } 74 | 75 | void Channel::removeUser(User *userToDelete) 76 | { 77 | std::deque::iterator pos = std::find(this->_users.begin(), this->_users.end(), userToDelete); 78 | if (pos != this->_users.end()) 79 | { 80 | this->_users.erase(pos); 81 | } 82 | } 83 | 84 | void Channel::removeOperator(User *userToDelete) 85 | { 86 | std::deque::iterator pos = std::find(this->_operators.begin(), 87 | this->_operators.end(), userToDelete); 88 | if (pos != this->_operators.end()) 89 | { 90 | this->_operators.erase(pos); 91 | } 92 | } 93 | 94 | void Channel::removeBannedUser(std::string userToDelete) 95 | { 96 | std::deque::iterator pos = std::find(this->_bannedUsers.begin(), 97 | this->_bannedUsers.end(), userToDelete); 98 | if (pos != this->_bannedUsers.end()) 99 | { 100 | this->_bannedUsers.erase(pos); 101 | } 102 | } 103 | 104 | void Channel::removeInvitee(User *userToDelete) 105 | { 106 | std::deque::iterator pos = std::find(this->_invitees.begin(), 107 | this->_invitees.end(), userToDelete); 108 | if (pos != this->_invitees.end()) 109 | { 110 | this->_invitees.erase(pos); 111 | } 112 | } 113 | 114 | /* Mode */ 115 | 116 | void Channel::addMode(short mode) { this->_mode |= mode; } 117 | void Channel::removeMode(short mode) { this->_mode &= ~mode; } 118 | bool Channel::hasMode(short mode) { return ((this->_mode & mode) > 0); } 119 | -------------------------------------------------------------------------------- /srcs/commands/part.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/channel.hpp" 2 | #include "../../includes/replies.hpp" 3 | #include "../../includes/utils.hpp" 4 | #include "../../includes/commands.hpp" 5 | 6 | /** 7 | * @brief Leave an existing channel 8 | * If all users part from the channel the channel will be destroyed 9 | * 10 | * Errors handled: 11 | * - ERR_NEEDMOREPARAMS 12 | * - ERR_NOTONCHANNEL 13 | * - ERR_NOSUCHCHANNEL 14 | * 15 | */ 16 | 17 | int checkPartParameter(std::map channelList, 18 | std::string channelName, User *currentUser, Server *server, const int &fdUser) 19 | { 20 | // Channel must exist 21 | std::map::iterator it = channelList.find(channelName); 22 | if (it == channelList.end()) 23 | { 24 | server->sendClient(fdUser, numericReply(server, fdUser, "403", 25 | ERR_NOSUCHCHANNEL(channelName))); 26 | return (-1); 27 | } 28 | // Current user must be part of the channel 29 | if (findUserOnChannel(it->second->_users, currentUser) == false) 30 | { 31 | server->sendClient(fdUser, numericReply(server, fdUser, 32 | "442", ERR_NOTONCHANNEL(channelName))); 33 | return (-2); 34 | } 35 | return (0); 36 | } 37 | 38 | void checkChannelMustBeDeleted(Server *server, 39 | std::map::iterator channelPos) 40 | { 41 | Channel *channel = channelPos->second; 42 | 43 | if (channel->_users.empty() == true) 44 | { 45 | server->_channelList.erase(channelPos); 46 | delete channel; 47 | } 48 | } 49 | 50 | void part(const int &fdUser, const std::vector ¶meter, 51 | const std::string &, Server *server) 52 | { 53 | std::vector channel; 54 | std::string partMessage; 55 | Channel *currentChannel; 56 | std::map::iterator itMap; 57 | std::vector::iterator it; 58 | 59 | if (parameter.size() < 1) 60 | return (server->sendClient(fdUser, numericReply(server, fdUser, 61 | "461", ERR_NEEDMOREPARAMS(std::string("PART"))))); 62 | channel = splitByComma(parameter[0]); 63 | // Not enough parameter 64 | // If channel list is empty, you can't part from any channel 65 | if (server->_channelList.empty() == true) 66 | return (server->sendClient(fdUser, numericReply(server, fdUser, "403", 67 | ERR_NOSUCHCHANNEL(channel[0])))); 68 | 69 | if (parameter.size() > 1) 70 | partMessage = parameter[1]; 71 | 72 | it = channel.begin(); 73 | // Check part parameters 74 | for (; it != channel.end(); it++) 75 | { 76 | if (checkPartParameter(server->_channelList, *it, server->getUserByFd(fdUser), 77 | server, fdUser) < 0) 78 | return; 79 | 80 | itMap = server->_channelList.find(*it); 81 | currentChannel = itMap->second; 82 | // Effectively part from channel 83 | server->sendChannel(*it, clientReply(server, fdUser, "PART " + *it + " :" + partMessage)); 84 | currentChannel->removeUser(server->getUserByFd(fdUser)); 85 | server->getUserByFd(fdUser)->removeChannelJoined(*it); 86 | // Check if the user was invited and remove him from the invitee list 87 | if (findUserOnChannel(currentChannel->_invitees, 88 | server->getUserByFd(fdUser)) == true) 89 | currentChannel->removeInvitee(server->getUserByFd(fdUser)); 90 | // Check if the user was operator and remove him from the operator list 91 | if (findUserOnChannel(currentChannel->_operators, 92 | server->getUserByFd(fdUser)) == true) 93 | currentChannel->removeOperator(server->getUserByFd(fdUser)); 94 | // If channel is empty it must be deleted 95 | checkChannelMustBeDeleted(server, itMap); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /srcs/user/User.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/User.hpp" 2 | #include 3 | 4 | // CONSTRUCTORS 5 | User::User(const int fd, std::string hostname) : _fd(fd), _nickname("*"), 6 | _username(""), _fullname(""), _hostname(hostname), 7 | _mode(MOD_NONE), _password(false), _authenticated(false), _channelsJoined(), 8 | _status(ST_ALIVE), _lastActivityTime(time(NULL)), _isBot(false) { } 9 | 10 | User::User(const User &src) : _fd(src._fd), _status(ST_ALIVE), _lastActivityTime(time(NULL)){ 11 | *this = src; 12 | } 13 | 14 | // DESTRUCTORS 15 | User::~User() { } 16 | 17 | // ASSIGN OVERLOAD 18 | User& User::operator=(User const &rhs) { 19 | if (this != &rhs) { 20 | this->_nickname = rhs._nickname; 21 | this->_username = rhs._username; 22 | this->_fullname = rhs._fullname; 23 | this->_hostname = rhs._hostname; 24 | this->_mode = rhs._mode; 25 | this->_password = rhs._password; 26 | this->_authenticated = rhs._authenticated; 27 | this->_channelsJoined = rhs._channelsJoined; 28 | this->_isBot = rhs._isBot; 29 | } 30 | return *this; 31 | } 32 | 33 | // GETTERS 34 | int User::getFd(void) const { return this->_fd; } 35 | std::string User::getNickname(void) const { return this->_nickname; } 36 | std::string User::getUsername(void) const { return this->_username; } 37 | std::string User::getFullname(void) const { return this->_fullname; } 38 | std::string User::getHostname(void) const { return this->_hostname; } 39 | short User::getMode(void) const { return this->_mode; } 40 | bool User::getPassword(void) const { return this->_password; } 41 | bool User::getAuthenticated(void) const { 42 | return this->_authenticated; 43 | } 44 | std::deque User::getChannelsJoined(void) const { 45 | return this->_channelsJoined; 46 | } 47 | int User::getStatus(void) const { return this->_status; } 48 | time_t User::getLastActivityTime(void) const { return this->_lastActivityTime; } 49 | time_t User::getPingTime(void) const { return this->_pingTime; } 50 | bool User::hasMode(short mode) { return ((this->_mode & mode) > 0); } 51 | bool User::getIsBot(void) const { return this->_isBot; } 52 | 53 | // SETTERS 54 | void User::setNickname(std::string nickname) { this->_nickname = nickname; } 55 | void User::setUsername(std::string username) { this->_username = username; } 56 | void User::setFullname(std::string fullname) { this->_fullname = fullname; } 57 | void User::setHostname(std::string hostname) { this->_hostname = hostname; } 58 | void User::setPassword(bool password) { this->_password = password; } 59 | void User::setAuthenticated(bool authenticated) { 60 | this->_authenticated = authenticated; 61 | } 62 | void User::setStatus(int status) { this->_status = status; } 63 | void User::setLastActivityTime(void) { this->_lastActivityTime = time(NULL); } 64 | void User::setPingTime(void) { this->_pingTime = time(NULL); } 65 | void User::addMode(short mode) { this->_mode |= mode; } 66 | void User::removeMode(short mode) { this->_mode &= ~mode; } 67 | void User::setIsBot(bool bot) { this->_isBot = bot; } 68 | 69 | // CHANNEL JOINED MANAGEMENT 70 | bool User::addChannelJoined(std::string channelName) { 71 | std::deque::iterator it; 72 | std::deque::iterator ite = this->_channelsJoined.end(); 73 | 74 | for (it = this->_channelsJoined.begin(); it < ite; ++it) { 75 | if (*it == channelName) 76 | return false; 77 | } 78 | this->_channelsJoined.push_back(channelName); 79 | return true; 80 | } 81 | 82 | bool User::removeChannelJoined(std::string channelName) { 83 | std::deque::iterator it; 84 | std::deque::iterator ite = this->_channelsJoined.end(); 85 | 86 | for (it = this->_channelsJoined.begin(); it < ite; ++it) { 87 | if (*it == channelName) { 88 | this->_channelsJoined.erase(it); 89 | return true; 90 | } 91 | } 92 | return false; 93 | } 94 | 95 | // OSTREAM 96 | std::ostream & operator<<(std::ostream &o, User const &rhs) { 97 | if (rhs.getAuthenticated() == true) 98 | o << rhs.getNickname() << "!" << rhs.getUsername(); 99 | return o; 100 | } 101 | -------------------------------------------------------------------------------- /srcs/commands/invite.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/channel.hpp" 2 | #include "../../includes/replies.hpp" 3 | #include "../../includes/utils.hpp" 4 | #include "../../includes/commands.hpp" 5 | 6 | /** 7 | * @brief Invite a user to a channel 8 | * The user will be added to the invitee list 9 | * The user will be able to join an existing channel with mode i. 10 | * 11 | * Errors handled: 12 | * - ERR_NEEDMOREPARAMS 13 | * - ERR_NOTONCHANNEL 14 | * - ERR_NOSUCHNICK 15 | * - ERR_USERONCHANNEL 16 | * - ERR_NOSUCHCHANNEL 17 | * - ERR_CHANOPRIVSNEEDED 18 | * 19 | */ 20 | 21 | int checkParameterInvite(std::string nickname, std::string channel, 22 | const int &fdUser, Server *server, User *userToInvite) 23 | { 24 | // Nickname and channel must not be empty 25 | if (nickname.empty() == true || channel.empty() == true) 26 | { 27 | server->sendClient(fdUser, numericReply(server, fdUser, 28 | "461", ERR_NEEDMOREPARAMS(std::string("INVITE")))); 29 | return (-1); 30 | } 31 | // Channel list must not be empty 32 | if (server->_channelList.empty() == true) 33 | { 34 | server->sendClient(fdUser, numericReply(server, fdUser, 35 | "403", ERR_NOSUCHCHANNEL(channel))); 36 | return (-1); 37 | } 38 | // User must exist 39 | if (server->getUserByNickname(nickname) == NULL) 40 | { 41 | server->sendClient(fdUser, numericReply(server, fdUser, 42 | "401", ERR_NOSUCHNICK(nickname))); 43 | return (-2); 44 | } 45 | // According to RFC "There is no requirement that the 46 | // channel the target user is being invited to must exist or be a valid 47 | // channel". We chose to have an existing channel as a requirement 48 | std::map::iterator it = server->_channelList.find(channel); 49 | if (it == server->_channelList.end()) 50 | { 51 | server->sendClient(fdUser, numericReply(server, fdUser, 52 | "403", ERR_NOSUCHCHANNEL(channel))); 53 | return (-3); 54 | } 55 | // User is already on channel 56 | if (findUserOnChannel(it->second->_users, server->getUserByNickname(nickname)) == true) 57 | { 58 | server->sendClient(fdUser, numericReply(server, fdUser, 59 | "443", ERR_USERONCHANNEL(userToInvite->getFullname(), channel))); 60 | return (-4); 61 | } 62 | // Current user must be in channel 63 | if (findUserOnChannel(it->second->_users, 64 | server->getUserByFd(fdUser)) == false) 65 | { 66 | server->sendClient(fdUser, numericReply(server, fdUser, 67 | "442", ERR_NOTONCHANNEL(channel))); 68 | return (-5); 69 | } 70 | // Current user must be an operator if channel is invite only 71 | if (it->second->hasMode(MOD_INVITE) && findUserOnChannel(it->second->_operators, 72 | server->getUserByFd(fdUser)) == false) 73 | { 74 | server->sendClient(fdUser, numericReply(server, fdUser, 75 | "482", ERR_CHANOPRIVSNEEDED(channel))); 76 | return (-6); 77 | } 78 | return (0); 79 | } 80 | 81 | void invite(const int &fdUser, const std::vector ¶meter, const std::string &, Server *server) 82 | { 83 | std::string nickname; 84 | std::string channel; 85 | User *userToInvite; 86 | Channel *channelPos; 87 | 88 | if (parameter.size() < 2) 89 | return (server->sendClient(fdUser, numericReply(server, fdUser, 90 | "461", ERR_NEEDMOREPARAMS(std::string("INVITE"))))); 91 | nickname = parameter[0]; 92 | channel = parameter[1]; 93 | userToInvite = server->getUserByNickname(nickname); 94 | if (checkParameterInvite(nickname, channel, fdUser, server, userToInvite) < 0) 95 | return; 96 | // Add user to the list of invitee and return reply 97 | channelPos = server->_channelList.find(channel)->second; 98 | channelPos->addInvitee(server->getUserByNickname(nickname)); 99 | server->sendClient(server->getUserByNickname(nickname)->getFd(), 100 | clientReply(server, fdUser, "INVITE " + 101 | nickname + " " + channel)); 102 | } 103 | -------------------------------------------------------------------------------- /srcs/utils/parsing.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/Server.hpp" 2 | #include "../../includes/utils.hpp" 3 | 4 | // Split a string by irc message delimiter (\n\r) and return a string vector 5 | std::vector splitBy(std::string str, const std::string &delimiter, std::string *buffer) 6 | { 7 | std::vector result; 8 | size_t end; 9 | 10 | // first check 11 | end = str.find(delimiter); 12 | if (end == std::string::npos) 13 | return (std::vector()); 14 | // save first command in vector 15 | if (end + delimiter.length() > MAX_CMD_LEN) 16 | throw std::runtime_error("Request too long"); 17 | result.push_back(str.substr(0, end)); 18 | 19 | // update str 20 | str.erase(0, end + delimiter.length()); 21 | 22 | // loop for other commands 23 | end = str.find(delimiter); 24 | while (end != std::string::npos) 25 | { 26 | if (end + delimiter.length() > MAX_CMD_LEN) 27 | throw std::runtime_error("Request too long"); 28 | result.push_back(str.substr(0, end)); 29 | str.erase(0, end + delimiter.length()); 30 | end = str.find(delimiter); 31 | } 32 | (*buffer).clear(); 33 | if (!str.empty()) 34 | (*buffer) = str; 35 | return (result); 36 | } 37 | 38 | /** 39 | * @brief Convert a vector of strings into a vector of Command by splitting 40 | * parameters and command 41 | * 42 | * @param cmd_strings Vector of commands, not splitted by space 43 | * @return vector of Command where [ PREFIX ], CMD and PARAMS are separated 44 | */ 45 | void splitCmds(std::vector cmd_strings, std::vector *cmds) 46 | { 47 | std::vector::iterator mess; 48 | size_t end; 49 | std::string prefix; 50 | 51 | for (mess = cmd_strings.begin(); mess != cmd_strings.end(); ++mess) 52 | { 53 | // extract CMD name 54 | end = mess->find(' '); 55 | if (end == std::string::npos) // only the command 56 | { 57 | if (mess->find(':') == 0) // the only token is a prefix :'( 58 | throw std::runtime_error("IRC message must have a command"); 59 | cmds->push_back(Command(mess->substr(0, end))); 60 | } 61 | else // CMD + params 62 | { 63 | // CMD WITH PREFIX 64 | if (mess->find(':') == 0) 65 | { 66 | prefix = mess->substr(1, end); 67 | mess->erase(0, end + 1); 68 | end = mess->find(' '); 69 | if (end == std::string::npos) 70 | { 71 | cmds->push_back(Command(*mess, prefix)); 72 | mess->clear(); 73 | } 74 | else 75 | { 76 | cmds->push_back(Command(mess->substr(0, end), \ 77 | prefix)); 78 | mess->erase(0, end + 1); 79 | } 80 | } 81 | else // CMD WITH NO PREFIX 82 | { 83 | cmds->push_back(Command(mess->substr(0, end))); 84 | end = mess->find(' '); 85 | if (end == std::string::npos) 86 | mess->clear(); 87 | else 88 | mess->erase(0, end + 1); 89 | } 90 | // PARAMS 91 | if (!mess->empty()) 92 | { 93 | while (end != std::string::npos) 94 | { 95 | // if long param starting with ":" 96 | if (mess->find(':') == 0) 97 | { 98 | cmds->back().params.push_back(mess->substr(1, \ 99 | mess->length() - 1)); 100 | end = std::string::npos; 101 | } 102 | else 103 | { 104 | end = mess->find(' '); 105 | if (end == std::string::npos) 106 | cmds->back().params.push_back(*mess); 107 | else 108 | { 109 | cmds->back().params 110 | .push_back(mess->substr(0, end)); 111 | mess->erase(0, end + 1); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | void displayCommands(std::vector cmds) { 121 | 122 | std::vector::iterator it; 123 | std::vector::iterator it2; 124 | for (it = cmds.begin(); it < cmds.end(); ++it) 125 | { 126 | std::cout << "\nPREFIX : " << it->prefix << std::endl; 127 | std::cout << "CMD : " << it->command << std::endl; 128 | for (it2 = it->params.begin(); it2 < it->params.end(); ++it2) 129 | { 130 | std::cout << "PARAM : " << *it2 << std::endl; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /srcs/bot/bot.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "../../includes/bot.hpp" 15 | 16 | #define BOTNAME "Impostor" 17 | 18 | #define PORT 6667 19 | 20 | #define MAXDATASIZE 100 21 | 22 | 23 | void closefd(int fd) { 24 | if (close(fd) == -1) 25 | printError("close error", 1, true); 26 | return ; 27 | } 28 | 29 | bool setupClientBot(int *sockfd, const char *hostname, int port) { 30 | 31 | struct hostent *he; 32 | struct sockaddr_in their_addr; 33 | 34 | if ((he=gethostbyname(hostname)) == NULL) { 35 | printError("gethostbyame error", 1, true); 36 | return (false); 37 | } 38 | if ((*sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { 39 | printError("socket error", 1, true); 40 | return (false); 41 | } 42 | their_addr.sin_family = AF_INET; 43 | their_addr.sin_port = htons(port); 44 | their_addr.sin_addr = *(reinterpret_cast(he->h_addr)); 45 | bzero(&(their_addr.sin_zero), 8); 46 | if (connect(*sockfd, reinterpret_cast(&their_addr), \ 47 | sizeof(struct sockaddr)) == -1) { 48 | printError("connect error", 1, true); 49 | closefd(*sockfd); 50 | return (false); 51 | } 52 | return (true); 53 | } 54 | 55 | bool connectServiceBot(int *sockfd, std::string password, std::string botName) { 56 | 57 | std::string registration; 58 | 59 | registration = "PASS "; 60 | registration.append(password) 61 | .append("\r\n") 62 | .append("SERVICE ") 63 | .append(botName) 64 | .append(" * *.42paris.fr 0 0 :Our bot offer amazing services such as quotes messaging.") 65 | .append("\r\n"); 66 | if (send(*sockfd, registration.c_str(), registration.size(), 0) == -1) { 67 | printError("send", 1, true); 68 | return (false); 69 | } 70 | std::cout << "IRCbot connection request: " << std::endl << registration << std::endl; 71 | return (true); 72 | } 73 | 74 | int reconnect(std::string received, int registered, int *sockfd, std::string pwd, std::string *botName) { 75 | if (registered == 0 && received.find("383") != std::string::npos) 76 | return (1); 77 | else if (registered == 0 && received.find("433") != std::string::npos) { 78 | *botName += "_"; 79 | if (connectServiceBot(sockfd, pwd, *botName) == false) { 80 | return (-1); 81 | } 82 | } 83 | return (0); 84 | } 85 | 86 | // HANDLE MESSAGES FROM THE SERVER 87 | int handleMessage(const std::string &message, const int &fd) 88 | { 89 | std::string response; 90 | std::string command; 91 | std::string target; 92 | 93 | // ping 94 | if (message.find("PING") != std::string::npos) 95 | response = botPing(message); 96 | 97 | // other commands 98 | else if (message.find("PRIVMSG") != std::string::npos) 99 | { 100 | // Extract the target 101 | target = message.substr(1, message.find("PRIVMSG") - 1); 102 | // extract the command 103 | command = message.substr(message.find(':', message.find("PRIVMSG"))); 104 | command = command.substr(0, command.find("\r\n")); 105 | if (command.compare(":QUOTE") == 0){ 106 | // QUOTE 107 | response = botQuote(); 108 | } 109 | else if (command.compare(":TIME") == 0){ 110 | // TIME 111 | response = botTime(); 112 | } 113 | if (!response.empty()) 114 | response = std::string("PRIVMSG ").append(target) 115 | .append(":").append(response) 116 | .append("\r\n"); 117 | std::cout << "RES : " << response << std::endl; 118 | } 119 | // Send reply 120 | if (!response.empty()) 121 | { 122 | if (send(fd, response.c_str(), response.length(), 0) == -1) 123 | { 124 | perror("send"); 125 | return (-1); 126 | } 127 | } 128 | return (0); 129 | } 130 | 131 | int main(int argc, char *argv[]) 132 | { 133 | int sockfd = 0; 134 | int numbytes = 1; 135 | char buf[MAXDATASIZE + 1]; 136 | int port = PORT; 137 | // For connection: 138 | std::string botName = BOTNAME; 139 | std::string pwd = argv[2]; 140 | std::string received; 141 | int registered = 0; 142 | 143 | 144 | if (argc != 3 && argc != 4) { 145 | std::cerr << "usage: ./bot hostname password [PORT]" << std::endl; 146 | return (1); 147 | } 148 | else if (argc == 4) 149 | port = std::atol(argv[3]); 150 | 151 | 152 | if (setupClientBot(&sockfd, argv[1], port) == false) 153 | return (1); 154 | if (connectServiceBot(&sockfd, pwd, botName) == false) { 155 | closefd(sockfd); 156 | return (1); 157 | } 158 | while (1) { 159 | 160 | // clear buf here before another recv 161 | std::memset(buf, 0, MAXDATASIZE); 162 | if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1) { 163 | perror("recv"); 164 | return (1); 165 | } 166 | else if (numbytes > 0) { 167 | buf[numbytes] = '\0'; 168 | std::cout << "Received: " << buf << std::endl; 169 | } 170 | received = std::string(buf); 171 | if (handleMessage(received, sockfd) == -1) 172 | { 173 | close(sockfd); 174 | return (1); 175 | } 176 | // try to re-register if registration failed because nickname unavailable 177 | if (registered == 0) 178 | if ((registered = reconnect(received, registered, &sockfd, pwd, &botName)) == -1) { 179 | closefd(sockfd); 180 | return (1); 181 | } 182 | } 183 | closefd(sockfd); 184 | return (0); 185 | } 186 | -------------------------------------------------------------------------------- /srcs/commands/kick.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/channel.hpp" 2 | #include "../../includes/replies.hpp" 3 | #include "../../includes/utils.hpp" 4 | #include "../../includes/commands.hpp" 5 | 6 | /** 7 | * @brief Kick a user from a channel with or without a specific message. 8 | * 9 | * Errors handled: 10 | * - ERR_NEEDMOREPARAMS 11 | * - ERR_NOTONCHANNEL 12 | * - ERR_USERNOTINCHANNEL 13 | * - ERR_NOSUCHCHANNEL 14 | * - ERR_CHANOPRIVSNEEDED 15 | * 16 | */ 17 | 18 | int checkChannelExist(std::string channel, const int &fdUser, Server *server) 19 | { 20 | // channel must exist 21 | std::map::iterator itMap; 22 | itMap = server->_channelList.find(channel); 23 | if (itMap == server->_channelList.end()) 24 | { 25 | server->sendClient(fdUser, numericReply(server, fdUser, 26 | "403", ERR_NOSUCHCHANNEL(channel))); 27 | return (-1); 28 | } 29 | // current user must be in channel 30 | if (findUserOnChannel(itMap->second->getUsers(), 31 | server->getUserByFd(fdUser)) == false) 32 | { 33 | server->sendClient(fdUser, numericReply(server, fdUser, 34 | "442", ERR_NOTONCHANNEL(channel))); 35 | return (-2); 36 | } 37 | return (0); 38 | } 39 | 40 | int checkGeneralParameter(std::vector channel, Server *server, 41 | const int &fdUser) 42 | { 43 | // channelList must not be empty 44 | if (server->_channelList.empty() == true) 45 | { 46 | server->sendClient(fdUser, numericReply(server, fdUser, 47 | "403", ERR_NOSUCHCHANNEL(channel[0]))); 48 | return (-1); 49 | } 50 | return (0); 51 | } 52 | 53 | void oneChannelCase(std::string channel, std::vector user, 54 | std::string kickMessage, Server *server, const int &fdUser) 55 | { 56 | if (checkChannelExist(channel, fdUser, server) < 0) 57 | return; 58 | std::vector::iterator itVector = user.begin(); 59 | std::map::iterator itMap = server->_channelList.find(channel); 60 | 61 | for (; itVector != user.end(); itVector++) 62 | { 63 | // users on user list must be in channel 64 | if (findUserOnChannel(itMap->second->getUsers(), 65 | server->getUserByNickname(*itVector)) == false) 66 | { 67 | server->sendClient(fdUser, numericReply(server, fdUser, 68 | "441", ERR_USERNOTINCHANNEL(*itVector, channel))); 69 | return; 70 | } 71 | // check if users on user list are operators. 72 | // In that case current user must be an operator 73 | if (findUserOnChannel(itMap->second->_operators, 74 | server->getUserByNickname(*itVector)) == true && 75 | findUserOnChannel(itMap->second->_operators, 76 | server->getUserByFd(fdUser)) == false) 77 | 78 | { 79 | server->sendClient(fdUser, numericReply(server, fdUser, 80 | "482", ERR_CHANOPRIVSNEEDED(channel))); 81 | return; 82 | } 83 | // effectively kick user 84 | server->sendChannel(channel, clientReply(server, fdUser, 85 | "KICK " + channel + " " + *itVector + " :" + kickMessage)); 86 | itMap->second->removeUser(server->getUserByNickname(*itVector)); 87 | server->getUserByNickname(*itVector)->removeChannelJoined(channel); 88 | // Check if the user was operator and remove him from the operator list 89 | if (findUserOnChannel(itMap->second->_operators, 90 | server->getUserByFd(fdUser)) == true) 91 | itMap->second->removeOperator(server->getUserByFd(fdUser)); 92 | // Check if the user was invited and remove him from the invitee list 93 | if (findUserOnChannel(itMap->second->_invitees, 94 | server->getUserByFd(fdUser)) == true) 95 | itMap->second->removeInvitee(server->getUserByFd(fdUser)); 96 | 97 | 98 | } 99 | } 100 | 101 | void multipleChannelCase(std::vector channel, std::vector user, 102 | std::string kickMessage, Server *server, const int &fdUser) 103 | { 104 | if (channel.size() > user.size()) 105 | { 106 | server->sendClient(fdUser, numericReply(server, fdUser, 107 | "441", ERR_USERNOTINCHANNEL(user[0], channel[0]))); 108 | return; 109 | } 110 | else 111 | { 112 | server->sendClient(fdUser, numericReply(server, fdUser, 113 | "403", ERR_NOSUCHCHANNEL(channel[0]))); 114 | return; 115 | } 116 | std::vector::iterator itVector = channel.begin(); 117 | for (; itVector != channel.end(); itVector++) 118 | { 119 | oneChannelCase(*itVector, user, kickMessage, server, fdUser); 120 | } 121 | } 122 | 123 | void kick(const int &fdUser, const std::vector ¶meter, const std::string &, Server *server) 124 | { 125 | std::vector channel; 126 | std::vector user; 127 | std::string kickMessage; 128 | 129 | if (parameter.size() < 2) 130 | return (server->sendClient(fdUser, numericReply(server, fdUser, 131 | "461", ERR_NEEDMOREPARAMS(std::string("KICK"))))); 132 | channel = splitByComma(parameter[0]); 133 | if (parameter.size() > 1) 134 | user = splitByComma(parameter[1]); 135 | if (parameter.size() > 2) 136 | kickMessage = parameter[2]; 137 | if (checkGeneralParameter(channel, server, fdUser) < 0) 138 | return; 139 | if (channel.size() == 1) 140 | return (oneChannelCase(channel[0], user, kickMessage, server, fdUser)); 141 | else 142 | return (multipleChannelCase(channel, user, kickMessage, server, fdUser)); 143 | } 144 | -------------------------------------------------------------------------------- /srcs/commands/who.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../../includes/commands.hpp" 4 | #include "../../includes/utils.hpp" 5 | #include "../../includes/exceptions.hpp" 6 | #include "../../includes/parser.hpp" 7 | 8 | /** 9 | * @brief The WHO command is used by a client to generate a query which returns 10 | * a list of information which 'matches' the parameter given by 11 | * the client. 12 | * 13 | * Errors handled: 14 | * - ERR_NOSUCHSERVER 15 | * 16 | */ 17 | 18 | static std::string getCommonChannel(Server *srv, const int &fd1, const int &fd2) 19 | { 20 | std::deque channelsJoined1; 21 | std::deque channelsJoined2; 22 | std::deque::const_reverse_iterator it; 23 | std::deque::const_reverse_iterator itRes; 24 | User *user1 = srv->getUserByFd(fd1); 25 | User *user2 = srv->getUserByFd(fd2); 26 | 27 | channelsJoined1 = user1->getChannelsJoined(); 28 | for (it = channelsJoined1.rbegin(); it != channelsJoined1.rend(); ++it) 29 | { 30 | channelsJoined2 = user2->getChannelsJoined(); 31 | itRes = std::find(channelsJoined2.rbegin(), channelsJoined2.rend(), \ 32 | *it); 33 | if (itRes != channelsJoined2.rend()) 34 | return (*it); 35 | } 36 | return (std::string()); 37 | } 38 | 39 | static bool isOnTheSameChannel(Server *srv, const int &fd1, const int &fd2) 40 | { return (!getCommonChannel(srv, fd1, fd2).empty()); } 41 | 42 | static bool isUserChanOper(Server *srv, User *user, const std::string channel) 43 | { 44 | std::deque userList; 45 | std::deque::const_iterator it; 46 | 47 | if (channel.empty()) 48 | return (false); 49 | userList = srv->_channelList[channel]->_operators; 50 | if (std::find(userList.begin(), userList.end(), user) == userList.end()) 51 | return (false); 52 | return (true); 53 | } 54 | 55 | // Send information about users visible for me 56 | void who(const int &fd, const std::vector ¶ms, \ 57 | const std::string &, Server *srv) 58 | { 59 | std::string mask; 60 | std::string name; 61 | std::string channel; 62 | bool onlyOpers = false; 63 | std::deque usersList; 64 | std::deque allUsers; 65 | std::deque::iterator it; 66 | std::deque channelJoined; 67 | 68 | // COMMAND EXECUTION 69 | // Params 70 | if (params.size() > 0) 71 | { 72 | mask = params[0]; 73 | if (params.size() > 1 and params[1].compare("o") == 0) 74 | onlyOpers = true; 75 | } 76 | 77 | // 1. All visible users : same channel or non "i" 78 | allUsers = srv->getAllUsers(); 79 | for (it = allUsers.begin(); it != allUsers.end(); it++) 80 | { 81 | // Check if user is non invisible 82 | if ((*it)->hasMode(MOD_INVISIBLE) == false || (*it)->getFd() == fd) 83 | usersList.push_back(*it); 84 | else if (isOnTheSameChannel(srv, fd, (*it)->getFd()) == true) 85 | usersList.push_back(*it); 86 | } 87 | 88 | if (srv->_channelList.find(mask) != srv->_channelList.end()) { 89 | // 2. Is the match a channel ? Erase all users not in that channel 90 | for (it = usersList.begin(); it != usersList.end();) 91 | { 92 | channelJoined = (*it)->getChannelsJoined(); 93 | if (std::find(channelJoined.begin(), channelJoined.end(), mask) \ 94 | == channelJoined.end()) 95 | { 96 | it = usersList.erase(it); 97 | } 98 | else 99 | ++it; 100 | } 101 | // Set the channel in the results to be to channel asked 102 | channel = mask; 103 | } 104 | else { 105 | // 3. The mask is not a channel : Check the mask against users's Host, 106 | // Server, Real name, Nickname 107 | for (it = usersList.begin(); it != usersList.end();) 108 | { 109 | // Host 110 | if ((matchMask((*it)->getHostname().c_str(), mask.c_str()) == true) 111 | // Server 112 | || (matchMask(srv->getHostname().c_str(), mask.c_str()) == true) 113 | // Real name 114 | || (matchMask((*it)->getFullname().c_str(), mask.c_str()) == true) 115 | // Nickname 116 | || (matchMask((*it)->getNickname().c_str(), mask.c_str()) == true)) 117 | { 118 | ++it; 119 | continue; 120 | } 121 | else 122 | it = usersList.erase(it); 123 | } 124 | } 125 | 126 | if (onlyOpers == true) { 127 | // 4. If "o" option is set, apply oper option on the list 128 | for (it = usersList.begin(); it != usersList.end();) 129 | { 130 | // If user hasMode(MOD_OPER) == false > Remove 131 | if ((*it)->hasMode(MOD_OPER) == false) 132 | it = usersList.erase(it); 133 | else 134 | ++it; 135 | } 136 | } 137 | 138 | // 5. Loop on the resulting user list and send information about users 139 | for (it = usersList.begin(); it != usersList.end(); it++) 140 | { 141 | if (channel.empty()) 142 | channel = getCommonChannel(srv, fd, (*it)->getFd()); 143 | srv->sendClient(fd, \ 144 | numericReply(srv, fd, "352", RPL_WHOREPLY(\ 145 | channel, \ 146 | (*it)->getUsername(), \ 147 | (*it)->getHostname(), \ 148 | srv->getHostname(), \ 149 | (*it)->getNickname(), \ 150 | std::string("H"), \ 151 | ( (*it)->hasMode(MOD_OPER) ? std::string("*") : std::string() ), \ 152 | ( isUserChanOper(srv, *it, channel) ? std::string("@") 153 | : std::string() ), \ 154 | (*it)->getFullname()))); 155 | } 156 | if (params.size() > 0) 157 | name = params[0]; 158 | srv->sendClient(fd, numericReply(srv, fd, "315", RPL_ENDOFWHO(name))); 159 | } 160 | -------------------------------------------------------------------------------- /includes/Server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_HPP 2 | # define SERVER_HPP 3 | 4 | // Standard headers 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Custom headers 11 | #include "channel.hpp" 12 | #include "User.hpp" 13 | #include "commands.hpp" 14 | 15 | // Sockets & packets params 16 | #define MAX_CMD_LEN 512 17 | #define BACKLOG 10 18 | #define BUF_SIZE 4096 19 | #define MAX_EVENTS 50 20 | 21 | // Client check params 22 | #define PING_TIMEOUT 120 // in seconds 23 | #define PONG_TIMEOUT 20 // in seconds 24 | #define WAIT_TIMEOUT 3000 // in milliseconds 25 | #define KILLTIME 3600 // in seconds = 1h 26 | 27 | // Config files 28 | #define OPERCONF "conf/operhost.config" 29 | 30 | // Program infos 31 | #ifndef VERSION 32 | # define VERSION "1" 33 | #endif 34 | #ifndef VCOMMENT 35 | # define VCOMMENT "No comment" 36 | #endif 37 | #ifndef COMPILDATE 38 | # define COMPILDATE "Sun 25 Sep 1983 10:00:00 AM CEST" 39 | #endif 40 | #ifndef HOSTADDRESS 41 | # define HOSTADDRESS "42paris.fr" 42 | #endif 43 | 44 | 45 | // Utility structure 46 | struct Command 47 | { 48 | std::string command; 49 | std::string prefix; 50 | std::vector params; 51 | 52 | Command(std::string cmd, std::string prefix = std::string(), \ 53 | std::vector params = std::vector()); 54 | }; 55 | 56 | // Server class 57 | class Server 58 | { 59 | public: 60 | // member type 61 | typedef \ 62 | void (*CmdFunction)(const int &, \ 63 | const std::vector &, const std::string &, Server*); 64 | 65 | // Constructors & destructor 66 | Server(int port, std::string password, std::string name = "Gunther"); 67 | Server(Server const &src); 68 | ~Server(void){}; 69 | 70 | // Operator overload 71 | Server &operator=(Server const &rhs); 72 | 73 | // Getters 74 | int getPort(void) const; 75 | std::string getPassword(void) const; 76 | std::string getName(void) const; 77 | std::string getHostname(void) const; 78 | std::string getVersion(void) const; 79 | std::string getDate(void) const; 80 | User* getUserByFd(const int &fd) const; 81 | User* getUserByNickname(const std::string &nick) const; 82 | User* getUserByUsername(const std::string &user, \ 83 | const std::string &host = std::string()) const; 84 | std::deque getUsersByHostname(const std::string &hostname) const; 85 | std::deque getAllUsers(void) const; 86 | 87 | // Member functions 88 | void start(void); 89 | void sendClient(const int &fd, const std::string &message, \ 90 | const int &originFd = -1); 91 | void sendClient(const std::set &fds, \ 92 | const std::string &message, \ 93 | const int &originFd= -1); 94 | void broadcast(const std::string &message, \ 95 | const int &originFd = -1); 96 | void sendChannel(const std::string &channel, \ 97 | const std::string &message, \ 98 | const int &originFd = -1); 99 | void killConnection(const int &fd); 100 | 101 | // exceptions 102 | class socketException : public std::exception 103 | { public: virtual const char *what() const throw(); }; 104 | 105 | class bindException : public std::exception 106 | { public: virtual const char *what() const throw(); }; 107 | 108 | class pollException : public std::exception 109 | { public: virtual const char *what() const throw(); }; 110 | 111 | class pollWaitException : public std::exception 112 | { public: virtual const char *what() const throw(); }; 113 | 114 | class pollAddException : public std::exception 115 | { public: virtual const char *what() const throw(); }; 116 | 117 | class pollDelException : public std::exception 118 | { public: virtual const char *what() const throw(); }; 119 | 120 | class acceptException : public std::exception 121 | { public: virtual const char *what() const throw(); }; 122 | 123 | class passwordException : public std::exception 124 | { public: virtual const char *what() const throw(); }; 125 | 126 | class invalidFdException : public std::exception 127 | { public: virtual const char *what() const throw(); }; 128 | 129 | class invalidChannelException : public std::exception 130 | { public: virtual const char *what() const throw(); }; 131 | 132 | class sendException : public std::exception 133 | { public: virtual const char *what() const throw(); }; 134 | 135 | class readException : public std::exception 136 | { public: virtual const char *what() const throw(); }; 137 | 138 | class closeException : public std::exception 139 | { public: virtual const char *what() const throw(); }; 140 | 141 | std::map _channelList; 142 | std::map _cmdList; 143 | std::map _unavailableNicknames; 144 | 145 | private: 146 | // Cannot be default construct 147 | Server(void){}; 148 | 149 | // Private member functions 150 | void _initCommandList(void); 151 | void _createSocket(void); 152 | void _bindSocket(int sockfd, struct sockaddr_in *srv_addr); 153 | void _createPoll(int sockfd); 154 | int _pollWait(int pollfd, struct epoll_event **events, \ 155 | int max_events); 156 | void _acceptConnection(int sockfd, int pollfd); 157 | void _handleNewMessage(struct epoll_event event); 158 | void _executeCommands(int fd, std::vector cmds); 159 | void _pingClients(void); 160 | void _clearAll(void); 161 | 162 | // Member attributes 163 | int _port; 164 | std::string _password; 165 | std::string _name; 166 | std::string _hostname; 167 | std::string _version; 168 | std::string _date; 169 | 170 | int _pollfd; 171 | int _sockfd; 172 | 173 | std::map _userList; 174 | std::map _buffersByFd; 175 | 176 | }; 177 | 178 | #endif 179 | -------------------------------------------------------------------------------- /srcs/commands/join.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/channel.hpp" 2 | #include "../../includes/replies.hpp" 3 | #include "../../includes/utils.hpp" 4 | #include "../../includes/commands.hpp" 5 | 6 | /** 7 | * @brief Create or join an existing channel with or without key 8 | * 9 | * Mode supported: 10 | * - i 11 | * - b 12 | * - o 13 | * - k 14 | * 15 | * Errors handled: 16 | * - ERR_NEEDMOREPARAMS 17 | * - ERR_INVITEONLYCHAN 18 | * - ERR_NOSUCHCHANNEL 19 | * - ERR_BANNEDFROMCHAN 20 | * - ERR_BADCHANNELKEY 21 | * - ERR_USERONCHANNEL 22 | * 23 | */ 24 | 25 | void channelReply(Server *server, const int &fdUser, std::string channelName, 26 | std::vector parameters) 27 | { 28 | // Reply if the user successfully joined the channel 29 | // Use send for all the user of a channel (vector of fd) 30 | std::string event = clientReply(server, fdUser, "JOIN " + channelName); 31 | std::vector parameterTopic; 32 | 33 | parameterTopic.push_back(channelName); 34 | server->sendChannel(channelName, event); 35 | topic(fdUser, parameterTopic, channelName, server); 36 | names(fdUser, parameters, channelName, server); 37 | } 38 | 39 | void createChannel(std::string channel, const size_t &pos, 40 | std::vector key, User *currentUser, Server *server) 41 | { 42 | Channel *newChannel; 43 | std::vector::iterator it; 44 | 45 | if (key.empty() == false) 46 | { 47 | it = key.begin() + pos; 48 | if (key.size() > pos && (*it).compare("x") != 0) 49 | newChannel = new Channel(channel, *it, currentUser); 50 | else 51 | newChannel = new Channel(channel, currentUser); 52 | server->_channelList.insert(std::pair(channel, 53 | newChannel)); 54 | currentUser->addChannelJoined(channel); 55 | } 56 | else 57 | { 58 | newChannel = new Channel(channel, currentUser); 59 | server->_channelList.insert(std::pair(channel, 60 | newChannel)); 61 | currentUser->addChannelJoined(channel); 62 | } 63 | } 64 | 65 | int checkKey(size_t pos, std::vector key, 66 | std::map::iterator itMap, Server *server, 67 | const int &fdUser) 68 | { 69 | std::string keySetInChannel; 70 | std::vector::iterator it; 71 | std::string keyToCheck; 72 | 73 | keySetInChannel = itMap->second->getKey(); 74 | if (keySetInChannel.empty() == false && key.empty() == true) 75 | { 76 | server->sendClient(fdUser, numericReply(server, fdUser, 77 | "475", ERR_BADCHANNELKEY(itMap->second->getChannelName()))); 78 | return (-1); 79 | } 80 | if (key.empty() == true) 81 | return (0); 82 | it = key.begin() + pos; 83 | keyToCheck = *it; 84 | 85 | if (keyToCheck.empty() == false && keyToCheck.compare(keySetInChannel) != 0) 86 | { 87 | server->sendClient(fdUser, numericReply(server, fdUser, 88 | "475", ERR_BADCHANNELKEY(itMap->second->getChannelName()))); 89 | return (-1); 90 | } 91 | return (0); 92 | } 93 | 94 | int checkChannel(std::string channel, Server *server, const int &fdUser) 95 | { 96 | std::map::iterator itMap; 97 | 98 | if (channel.empty() == true) 99 | { 100 | server->sendClient(fdUser, numericReply(server, fdUser, "461", ERR_NEEDMOREPARAMS(std::string("JOIN")))); 101 | return (-1); 102 | } 103 | if (channel.size() > 50) 104 | { 105 | server->sendClient(fdUser, numericReply(server, fdUser, "403", ERR_NOSUCHCHANNEL(channel))); 106 | return (-2); 107 | } 108 | if (isChannel(channel) == false || channel.find(',') != std::string::npos) 109 | { 110 | server->sendClient(fdUser, numericReply(server, fdUser, "476", ERR_BADCHANMASK(channel))); 111 | return (-3); 112 | } 113 | if (server->_channelList.empty() == true) 114 | return (0); 115 | itMap = server->_channelList.find(channel); 116 | if (itMap == server->_channelList.end()) 117 | return (0); 118 | if (findUserOnChannel(itMap->second->_users, server->getUserByFd(fdUser)) == true) 119 | { 120 | server->sendClient(fdUser, numericReply(server, fdUser, 121 | "443", ERR_USERONCHANNEL(server->getUserByFd(fdUser)->getNickname(), channel))); 122 | return (-4); 123 | } 124 | return (0); 125 | } 126 | 127 | void addUserToChannel(std::map::iterator itMap, User *userToAdd) 128 | { 129 | itMap->second->addUser(userToAdd); 130 | userToAdd->addChannelJoined(itMap->second->getChannelName()); 131 | } 132 | 133 | void join(const int &fdUser, const std::vector ¶meter, const std::string &, Server *server) 134 | { 135 | // Get parameters of join 136 | std::vector channel; 137 | std::vector key; 138 | std::vector::iterator itChan; 139 | std::map::iterator itMap; 140 | 141 | if (parameter.size() == 0) 142 | return (server->sendClient(fdUser, numericReply(server, fdUser, 143 | "461", ERR_NEEDMOREPARAMS(std::string("JOIN"))))); 144 | channel = splitByComma(parameter[0]); 145 | if (parameter.size() > 1) 146 | key = splitByComma(parameter[1]); 147 | 148 | itChan = channel.begin(); 149 | for (; itChan != channel.end(); itChan++) 150 | { 151 | // Check channelname 152 | if (checkChannel(*itChan, server, fdUser) < 0) 153 | return; 154 | // Case where channel already exists 155 | if (server->_channelList.empty() == false) 156 | { 157 | itMap = server->_channelList.find(*itChan); 158 | if (itMap != server->_channelList.end()) 159 | { 160 | if (checkKey(itChan - channel.begin(), key, itMap, server, fdUser) < 0) 161 | return; 162 | if (itMap->second->hasMode(MOD_INVITE) == true && 163 | findUserOnChannel(itMap->second->_invitees, 164 | server->getUserByFd(fdUser)) == false) 165 | return (server->sendClient(fdUser, numericReply(server, fdUser, 166 | "473", ERR_INVITEONLYCHAN(*itChan)))); 167 | /* According to RFC 2811, "A user who is banned from a channel 168 | * and who carries an invitation 169 | * sent by a channel operator is allowed to join the channel" */ 170 | if (findBannedUserOnChannel(itMap->second->_bannedUsers, 171 | server->getUserByFd(fdUser)->getNickname()) == true 172 | && findUserOnChannel(itMap->second->_invitees, 173 | server->getUserByFd(fdUser)) == false) 174 | return (server->sendClient(fdUser, numericReply(server, fdUser, 175 | "474", ERR_BANNEDFROMCHAN(*itChan)))); 176 | addUserToChannel(itMap, server->getUserByFd(fdUser)); 177 | } 178 | // Case where channel doesn't exist 179 | else 180 | createChannel(*itChan, itChan - channel.begin(), key, 181 | server->getUserByFd(fdUser), server); 182 | channelReply(server, fdUser, *itChan, parameter); 183 | } 184 | // Case where no channel exists 185 | else 186 | { 187 | createChannel(*itChan, itChan - channel.begin(), key, 188 | server->getUserByFd(fdUser), server); 189 | channelReply(server, fdUser, *itChan, parameter); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /srcs/commands/whois.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../../includes/commands.hpp" 5 | #include "../../includes/utils.hpp" 6 | #include "../../includes/exceptions.hpp" 7 | #include "../../includes/parser.hpp" 8 | 9 | #ifndef SERVERINFO 10 | # define SERVERINFO "No info on this server :(" 11 | #endif 12 | 13 | /** 14 | * @brief This command is used to query information about particular user. 15 | * The server will answer this command with several numeric messages 16 | * indicating different statuses of each user which matches the mask (if 17 | * you are entitled to see them). 18 | * 19 | * Errors handled: 20 | * - ERR_NOSUCHSERVER 21 | * - ERR_NONICKNAMEGIVEN 22 | * - ERR_NOSUCHNICK 23 | * 24 | */ 25 | 26 | static std::string getCommonChannel(Server *srv, const int &fd1, const int &fd2) 27 | { 28 | std::deque channelsJoined1; 29 | std::deque channelsJoined2; 30 | std::deque::const_reverse_iterator it; 31 | std::deque::const_reverse_iterator itRes; 32 | User *user1 = srv->getUserByFd(fd1); 33 | User *user2 = srv->getUserByFd(fd2); 34 | 35 | channelsJoined1 = user1->getChannelsJoined(); 36 | for (it = channelsJoined1.rbegin(); it != channelsJoined1.rend(); ++it) 37 | { 38 | channelsJoined2 = user2->getChannelsJoined(); 39 | itRes = std::find(channelsJoined2.rbegin(), channelsJoined2.rend(), \ 40 | *it); 41 | if (itRes != channelsJoined2.rend()) 42 | return (*it); 43 | } 44 | return (std::string()); 45 | } 46 | 47 | static bool isOnTheSameChannel(Server *srv, const int &fd1, const int &fd2) 48 | { return (!getCommonChannel(srv, fd1, fd2).empty()); } 49 | 50 | static void splitMask(std::deque &masks, std::string mask) 51 | { 52 | // only one mask 53 | if (mask.find(',') == std::string::npos) 54 | { 55 | masks.push_back(mask); 56 | return; 57 | } 58 | 59 | // multiple masks 60 | while (mask.find(',') != std::string::npos) { 61 | masks.push_back(mask.substr(0, mask.find(','))); 62 | mask.erase(0, mask.find(',') + 1); 63 | } 64 | masks.push_back(mask); 65 | } 66 | 67 | static void addVisibleUser(Server *srv, const int &fd, \ 68 | std::deque &usersList, User *user) 69 | { 70 | if (isOnTheSameChannel(srv, fd, user->getFd())) 71 | usersList.push_back(user); 72 | else if (!user->hasMode(MOD_INVISIBLE)) 73 | usersList.push_back(user); 74 | } 75 | 76 | static std::string getChannelList(Server *srv, User *user) 77 | { 78 | std::deque chans = user->getChannelsJoined(); 79 | std::deque::const_iterator itChan; 80 | std::deque chanOps; 81 | std::string res; 82 | 83 | for (itChan = chans.begin(); itChan != chans.end(); ++itChan) 84 | { 85 | if (itChan != chans.begin()) 86 | res.append(" "); 87 | chanOps = srv->_channelList[*itChan]->_operators; 88 | if (std::find(chanOps.begin(), chanOps.end(), user) != chanOps.end()) 89 | res.append("@"); 90 | res.append(*itChan); 91 | } 92 | return (res); 93 | } 94 | 95 | // Send information about users visible for me 96 | void whois(const int &fd, const std::vector ¶ms, \ 97 | const std::string &, Server *srv) 98 | { 99 | // Loop and list management 100 | std::string mask; 101 | std::deque masks; 102 | std::deque::const_iterator itMasks; 103 | std::deque::iterator it; 104 | std::deque usersList; 105 | std::deque allUsers; 106 | // Infos to send back to requester 107 | std::stringstream ss; 108 | std::string nickname; 109 | std::string username; 110 | std::string realname; 111 | std::string hostname; 112 | std::string servername; 113 | std::string serverinfos; 114 | std::string channelList; 115 | int seconds; 116 | bool isOper; 117 | 118 | // COMMAND EXECUTION 119 | // Params 120 | try { 121 | if (params.size() == 0) 122 | throw nonicknamegivenException(); 123 | if (params.size() > 0) 124 | { 125 | if (params.size() > 1) 126 | { 127 | if (!matchMask(srv->getHostname().c_str(), params[0].c_str())) { 128 | throw nosuchserverException(params[0]); 129 | } 130 | mask = params[1]; 131 | } 132 | else 133 | mask = params[0]; 134 | } 135 | } 136 | // EXCEPTIONS THAT END THE COMMAND 137 | catch (nonicknamegivenException &e) {e.reply(srv, fd); return; } 138 | catch (nosuchserverException &e) {e.reply(srv, fd); return; } 139 | 140 | // 1. Split the mask parameter into a deque 141 | splitMask(masks, mask); 142 | 143 | allUsers = srv->getAllUsers(); 144 | 145 | // 2. Add users that fit the mask 146 | for (itMasks = masks.begin(); itMasks != masks.end(); ++itMasks) 147 | { 148 | if ((*itMasks).find('*') == std::string::npos 149 | && (*itMasks).find('?') == std::string::npos){ 150 | // The mask is a nickname ! 151 | try { 152 | for (it = allUsers.begin(); it != allUsers.end(); ++it) 153 | { 154 | if ((*it)->getNickname().compare(*itMasks) == 0) 155 | { 156 | addVisibleUser(srv, fd, usersList, *it); 157 | break; 158 | } 159 | } 160 | if (it == allUsers.end()) 161 | throw nosuchnickException(*itMasks); 162 | } 163 | catch (nosuchnickException &e) {e.reply(srv, fd);} 164 | } 165 | else { 166 | // The mask is ... a mask 167 | for (it = allUsers.begin(); it != allUsers.end(); ++it) 168 | { 169 | // Host 170 | if ((matchMask((*it)->getHostname().c_str(), (*itMasks).c_str())) 171 | // Server 172 | || (matchMask(srv->getHostname().c_str(), (*itMasks).c_str())) 173 | // Real name 174 | || (matchMask((*it)->getFullname().c_str(), (*itMasks).c_str())) 175 | // Nickname 176 | || (matchMask((*it)->getNickname().c_str(), (*itMasks).c_str()))) 177 | { 178 | addVisibleUser(srv, fd, usersList, *it); 179 | } 180 | } 181 | } 182 | } 183 | 184 | // 3. Loop on the resulting user list and send information about users 185 | for (it = usersList.begin(); it != usersList.end(); it++) 186 | { 187 | // Infos for each user 188 | nickname = (*it)->getNickname(); 189 | username = (*it)->getUsername(); 190 | realname = (*it)->getFullname(); 191 | hostname = (*it)->getHostname(); 192 | servername = srv->getHostname(); 193 | serverinfos = std::string(SERVERINFO); 194 | seconds = difftime(time(NULL), (*it)->getLastActivityTime()); 195 | ss << seconds; 196 | isOper = (*it)->hasMode(MOD_OPER); 197 | channelList = getChannelList(srv, (*it)); 198 | 199 | // Send RPL 200 | srv->sendClient(fd, numericReply(srv, fd, "311", \ 201 | RPL_WHOISUSER(nickname, username, hostname, realname))); 202 | srv->sendClient(fd, numericReply(srv, fd, "312", \ 203 | RPL_WHOISSERVER(nickname, servername, serverinfos))); 204 | srv->sendClient(fd, numericReply(srv, fd, "319", \ 205 | RPL_WHOISCHANNELS(nickname, channelList))); 206 | if (isOper) 207 | srv->sendClient(fd, numericReply(srv, fd, "313", \ 208 | RPL_WHOISOPERATOR(nickname))); 209 | srv->sendClient(fd, numericReply(srv, fd, "317", \ 210 | RPL_WHOISIDLE(nickname, ss.str()))); 211 | } 212 | srv->sendClient(fd, numericReply(srv, fd, "318", RPL_ENDOFWHOIS(mask))); 213 | } 214 | 215 | -------------------------------------------------------------------------------- /includes/replies.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RETURNCODE_HPP 2 | # define RETURNCODE_HPP 3 | 4 | #include 5 | # include 6 | 7 | #define USERMODES "a = away (not active); B = bot; i = invisible; w = wallops (not active); r = restricted; o = Server operator; s = Server's notice" 8 | #define CHANNELMODES "b = ban; i = invite only; k = channel's key; o = channel's operator rights" 9 | #define NAMESPECIALS ";[]`_^{|}\\" 10 | 11 | // LIST OF SERVER REPLIES 12 | 13 | // CONNECTION REGISTRATION 14 | 15 | // CONNECTION REGISTRATION REPLY 16 | #define RPL_WELCOME(nick, username, clientHost) ("Welcome to the Internet Relay Network " + nick + "!" + username + "@" + clientHost + "\r\n") // 001 17 | #define RPL_YOURHOST(serverName, version) ("Your host is " + serverName + ", running version " + version + "\r\n") // 002 18 | #define RPL_CREATED(date) ("This server was created " + date + "\r\n") // 003 19 | #define RPL_MYINFO(serverName, version, userModes, channelModes) (serverName + " " + version + " User modes: " + userModes + " Channel modes: " + channelModes + "\r\n") // 004 20 | // SERVICE MESSAGES 21 | #define RPL_YOURESERVICE(servicename) ("You are service " + servicename + "\r\n") //383 22 | 23 | // CHANNELS 24 | #define RPL_TOPIC(channelName, topic) (channelName + " :" + topic + "\r\n") // 332 25 | #define RPL_NOTOPIC(channelName) (channelName + " :No topic is set" + "\r\n") // 331 26 | #define RPL_NAMREPLY(channelName, nickName) (channelName + " :" + nickName + "\r\n") // 353 27 | #define RPL_ENDOFNAMES(channelName) (channelName + " :End of NAMES list" + "\r\n") // 366 28 | #define RPL_LIST(channelName, topic) (channelName + " " + topic + "\r\n") // 322 29 | #define RPL_LISTEND (":End of LIST\r\n") // 323 30 | #define RPL_BANLIST(channel, nickname) (channel + " " + nickname + "\r\n") // 323 31 | #define RPL_ENDOFBANLIST(channel) (":End of channel ban list\r\n") // 368 32 | #define RPL_CHANNELMODEIS(channel, mode, params) (channel + " " + mode + " " + params + "\r\n") // 324 33 | 34 | // NICK 35 | #define ERR_NONICKNAMEGIVEN (":No nickname given\r\n") // 431 36 | #define ERR_ERRONEUSNICKNAME(nick) (nick + " :Erroneous nickname" + "\r\n") // 432 37 | #define ERR_NICKNAMEINUSE(nick) (nick + " :Nickname is already in use" + "\r\n") // 433 38 | #define ERR_UNAVAILRESOURCE(nickOrChannel) (nickOrChannel + " :Nick/channel is temporarily unavailable" + "\r\n") // 437 39 | #define ERR_RESTRICTED (":Your connection is restricted!\r\n") // 484 40 | 41 | // PASS 42 | #define ERR_NEEDMOREPARAMS(command) (command + " :Not enough parameters" + "\r\n") // 461 43 | #define ERR_ALREADYREGISTRED (":Unauthorized command (already registered)\r\n") // 462 44 | 45 | // CHANNEL CONNECTION 46 | 47 | #define ERR_NOSUCHCHANNEL(channelName) (channelName + " :No such channel" + "\r\n") // 403 48 | #define ERR_TOOMANYCHANNELS(channelName) (channelName + " :You have joined too many channels" + "\r\n") // 405 49 | #define ERR_TOOMANYTARGETS(target, errorCode, AbortMsg) (target + " :" + errorCode + " recipients. "+ AbortMsg + "\r\n") // 407 50 | #define ERR_CHANNELISFULL(channel) (channel + " :Cannot join channel (+l)" + "\r\n") // 471 51 | #define ERR_INVITEONLYCHAN(channel) (channel + " :Cannot join channel (+i)" + "\r\n") // 473 52 | #define ERR_BANNEDFROMCHAN(channel) (channel + " :Cannot join channel (+b)" + "\r\n") // 474 53 | #define ERR_BADCHANNELKEY(channel) (channel + " :Cannot join channel (+k)" + "\r\n") // 475 54 | #define ERR_BADCHANMASK(channel) (channel + " :Bad Channel Mask" + "\r\n") // 476 55 | #define ERR_NOTONCHANNEL(channel) (channel + " :You're not on that channel" + "\r\n") // 442 56 | #define ERR_USERONCHANNEL(user, channel) (user + " " + channel + " :is already on channel" + "\r\n") // 443 57 | #define ERR_CHANOPRIVSNEEDED(channel) (channel + " :You're not channel operator" + "\r\n") // 482 58 | #define ERR_USERNOTINCHANNEL(nickname, channel) (nickname + " " + channel + " :They aren't on that channel" + "\r\n") // 441 59 | #define ERR_KEYSET(channel) (channel + " Channel key already set" + "\r\n") // 467 60 | #define ERR_UNKNOWNMODE(mode, channel) (mode + ":is unknown mode char to me for " + channel + "\r\n") // 472 61 | 62 | // OPER 63 | #define RPL_YOUREOPER (":You are now an IRC operator\r\n") // 381 64 | #define ERR_PASSWDMISMATCH (":Password incorrect\r\n") // 464 65 | #define ERR_NOOPERHOST (":No O-lines for your host\r\n") // 491 66 | 67 | // USER MODE 68 | #define RPL_UMODEIS(userModeStr) (userModeStr + "\r\n") // 221 69 | #define ERR_UMODEUNKNOWNFLAG (":Unknown MODE flag\r\n") // 501 70 | #define ERR_USERSDONTMATCH (":Cannot change mode for other users\r\n") // 502 71 | 72 | // MOTD 73 | #define ERR_NOMOTD (":MOTD File is missing\r\n") // 422 74 | #define RPL_MOTDSTART(server) (":- " + server + " Message of the day - \r\n") // 375 75 | #define RPL_MOTD(text) (":- " + text + "\r\n") // 372 76 | #define RPL_ENDOFMOTD (":End of MOTD command\r\n") // 376 77 | 78 | // SERVER COMMANDS 79 | #define RPL_VERSION(vers, debuglvl, server, comments) (vers + "." + debuglvl + " " + server + " :" + comments + "\r\n") // 351 80 | #define RPL_TIME(server, time) (server + " :" + time + "\r\n") // 391 81 | #define RPL_INFO(string) (":" + string + "\r\n") // 371 82 | #define RPL_ENDOFINFO (":End of INFO list\r\n") // 374 83 | 84 | // SENDING MESSAGES 85 | #define ERR_NORECIPIENT(command) (":No recipient given (" + command + ")\r\n") // 411 86 | #define ERR_NOTEXTTOSEND (":No text to send\r\n") // 412 87 | #define ERR_NOTOPLEVEL(mask) (mask + " :No toplevel domain specified\r\n") // 413 88 | #define ERR_WILDTOPLEVEL(mask) (mask + " :Wildcard in toplevel domain\r\n") // 414 89 | #define ERR_CANNOTSENDTOCHAN(channame) (channame + " :Cannot send to channel\r\n") // 404 90 | 91 | // WHO 92 | #define RPL_WHOREPLY(channel, username, host, server, nickname, presence, star, status, realname) (channel + " " + username + " " + host + " " + server + " " + nickname + " " + presence + star + status + " :0 " + realname + "\r\n") // 352 93 | #define RPL_ENDOFWHO(name) (name + " :End of WHO list\r\n") // 315 94 | 95 | // WHOIS 96 | #define RPL_WHOISUSER(nickname, username, hostname, realname) (nickname + " " + username + " " + hostname + " * :" + realname + "\r\n") // 311 97 | #define RPL_WHOISCHANNELS(nickname, channelslist) (nickname + " :" + channelslist + "\r\n") // 319 98 | #define RPL_WHOISIDLE(nickname, seconds) (nickname + " " + seconds + " :seconds idle\r\n") // 317 99 | #define RPL_WHOISSERVER(nickname, server, servinfos) (nickname + " " + server + " :" + servinfos + "\r\n") // 312 100 | #define RPL_WHOISOPERATOR(nickname) (nickname + " :is an IRC operator\r\n") // 313 101 | #define RPL_ENDOFWHOIS(nickname) (nickname + " :End of WHOIS list\r\n") // 318 102 | 103 | // OTHER ERRORS: 104 | #define ERR_NOSUCHNICK(nickname) (nickname + " :No such nick/channel" + "\r\n") // 401 105 | #define ERR_NOSUCHSERVER(servername) (servername + " :No such server" + "\r\n") // 402 106 | #define ERR_NOORIGIN (":No origin specified\r\n") // 409 107 | #define ERR_UNKNOWNCOMMAND(command) (command + " :Unknown command" + "\r\n") // 421 108 | #define ERR_NOTREGISTERED (":You have not registered\r\n") // 451 109 | #define ERR_NOPRIVILEGES (":Permission denied - You are not an IRC operator\r\n") // 481 110 | #define ERR_CANTKILLSERVER (":You can't kill a server!\r\n") // 483 111 | 112 | 113 | // LIST OF CLIENT ORIGINATED REPLIES 114 | 115 | //ADDITIONAL CLIENT REPLIES 116 | #define PING(origin) ("PING " + origin + "\r\n") 117 | #define PONG(origin) (":" + origin + " PONG " + origin + "\r\n") 118 | #define PRIVMSG(target, message) ("PRIVMSG " + target + " :" + message) 119 | #define CLIENT_QUIT(prefix, msg) (prefix + " " + msg + "\r\n") 120 | #define CLIENT_ERROR ("ERROR : \r\n") 121 | #define CLIENT_ERRORMSG(msg) ("ERROR :\"" + msg + "\"\r\n") 122 | #define ERRORMSG(msg) ("ERROR : :" + msg + "\r\n") 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /srcs/commands/notice.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../../includes/commands.hpp" 7 | #include "../../includes/replies.hpp" 8 | #include "../../includes/utils.hpp" 9 | #include "../../includes/exceptions.hpp" 10 | #include "../../includes/parser.hpp" 11 | 12 | #define MAX_TARGETS 100 13 | 14 | /** 15 | * @brief The NOTICE command is used similarly to PRIVMSG. The difference 16 | * between NOTICE and PRIVMSG is that automatic replies MUST NEVER be 17 | * sent in response to a NOTICE message. 18 | * 19 | * Errors handled (/!\ but not returned to client): 20 | * - ERR_NORECIPIENT 21 | * - ERR_NOTEXTTOSEND 22 | * - ERR_CANNOTSENDTOCHAN 23 | * - ERR_WILDTOPLEVEL 24 | * - ERR_TOOMANYTARGETS 25 | * - ERR_NOSUCHNICKERR_NOMOTD 26 | * 27 | */ 28 | 29 | /* ************************************************************************** */ 30 | /* STRUCTURE TO HOST CLIENT FD OR CHANNEL NAME AND TARGET OF THE MESSAGE */ 31 | /* ************************************************************************** */ 32 | struct Target { 33 | int fd; 34 | std::string channel; 35 | std::string target; 36 | 37 | Target(int fd, std::string target, std::string channel = std::string()) 38 | : fd(fd), channel(channel), target(target) {} 39 | }; 40 | 41 | /* ************************************************************************** */ 42 | /* UTILITY FUNCTIONS */ 43 | /* ************************************************************************** */ 44 | // Split the message by comma into multiple targets 45 | static std::map > splitTargets(std::string targets) 46 | { 47 | std::map > map; 48 | 49 | // only one target 50 | if (targets.find(',') == std::string::npos) 51 | { 52 | map[targets] = std::deque(); 53 | return (map); 54 | } 55 | 56 | // multiple targets 57 | std::string temp; 58 | std::istringstream stream(targets); 59 | while (std::getline(stream, temp, ',')) 60 | { 61 | transform(temp.begin(), temp.end(), temp.begin(), ::tolower); 62 | map[temp] = std::deque(); 63 | } 64 | return (map); 65 | } 66 | 67 | static bool isUserBannedOnChannel(Server *srv, const int &fd, \ 68 | const std::string &channel) 69 | { 70 | User *sender; 71 | std::deque bannedNicks; 72 | std::deque::const_iterator it; 73 | std::string senderNick; 74 | 75 | sender = srv->getUserByFd(fd); 76 | if (sender) 77 | { 78 | senderNick = sender->getNickname(); 79 | if (srv->_channelList.find(channel) != srv->_channelList.end()) 80 | { 81 | bannedNicks = srv->_channelList[channel]->_bannedUsers; 82 | for (it = bannedNicks.begin(); it != bannedNicks.end(); ++it) { 83 | if (senderNick.compare(*it) == 0) { 84 | return (true); 85 | } 86 | } 87 | } 88 | } 89 | return (false); 90 | } 91 | 92 | /* ************************************************************************** */ 93 | /* SPECIFIC FUNCTIONS TO EXTRACT USER FD, CHANNEL NAME OR TO COMPUTE MASK */ 94 | /* ************************************************************************** */ 95 | // CHANNEL 96 | static const std::string extractChannelName(const std::string str, Server *srv) 97 | { 98 | int pos = 0; 99 | std::string name; 100 | 101 | if (str[0] == '!') { 102 | // CHANNEL ID : we don't use it so we can just skip it 103 | for (int i = 0; i < 5; ++i) 104 | { 105 | if (!std::isdigit(str[i])) 106 | throw nosuchnickException(str); 107 | } 108 | pos = 6; 109 | } 110 | if (str.find(':') != std::string::npos) 111 | name = str.substr(pos, str.find(':') - pos); 112 | else 113 | name = str.substr(pos, std::string::npos); 114 | 115 | // Check if channel exists 116 | if (srv->_channelList.find(name) == srv->_channelList.end()) 117 | throw nosuchnickException(name); 118 | 119 | return (name); 120 | } 121 | 122 | // USER 123 | static int extractUserFd(const std::string str, Server *srv) 124 | { 125 | std::string user; 126 | std::string host; 127 | std::string servername; 128 | std::string nickname; 129 | User *resultUser = NULL; 130 | 131 | // string matching using 132 | if (str.find('%') != std::string::npos) { 133 | // USER % HOST 134 | user = str.substr(0, str.find('%')); 135 | host = str.substr(str.find('%') + 1); 136 | if (host.find('@') != std::string::npos) { 137 | // USER % HOST @ SERVERNAME 138 | servername = host.substr(host.find('@') + 1); 139 | host.erase(host.find('@')); 140 | } 141 | } 142 | else if (str.find('!') != std::string::npos) { 143 | // NICKNAME ! USER @ HOST 144 | nickname = str.substr(0, str.find('!')); 145 | user = str.substr(str.find('!') + 1); 146 | if (user.find('@') == std::string::npos) 147 | throw nosuchnickException(str); 148 | host = user.substr(user.find('@') + 1); 149 | user.erase(user.find('@')); 150 | } 151 | else if (str.find('@') != std::string::npos) { 152 | // USER @ SERVERNAME 153 | user = str.substr(0, str.find('@')); 154 | servername = str.substr(str.find('@') + 1); 155 | } 156 | else 157 | nickname = str; 158 | 159 | // User search and return 160 | if (!nickname.empty()) 161 | { 162 | resultUser = srv->getUserByNickname(nickname); 163 | if (resultUser == NULL) 164 | throw nosuchnickException(str); 165 | } 166 | else if (!user.empty() && !host.empty()) 167 | { 168 | resultUser = srv->getUserByUsername(user, host); 169 | if (resultUser == NULL) 170 | throw nosuchnickException(str); 171 | } 172 | else if (!user.empty()) 173 | { 174 | resultUser = srv->getUserByUsername(user); 175 | if (resultUser == NULL) 176 | throw nosuchnickException(str); 177 | } 178 | 179 | return (resultUser->getFd()); 180 | } 181 | 182 | // MASK 183 | static void computeMask(const int &fd, const std::string &str, Server *srv, \ 184 | std::deque &target) 185 | { 186 | std::deque userList; 187 | std::deque::const_iterator it; 188 | std::string mask; 189 | size_t toplevel; 190 | 191 | // Check user privileges : must be oper to use mask 192 | if (!srv->getUserByFd(fd)->hasMode(MOD_OPER)) 193 | throw noprivilegesException(); 194 | 195 | // Check the mask for exceptions 196 | mask = str.substr(1); 197 | toplevel = mask.find_last_of('.'); 198 | if (toplevel == std::string::npos) 199 | throw notoplevelException(mask); 200 | else if (mask.find('*', toplevel) != std::string::npos) 201 | throw wildtoplevelException(mask); 202 | 203 | if (str[0] == '$') { 204 | // Server mask 205 | if (matchMask(srv->getHostname().c_str(), \ 206 | str.substr(1).c_str()) == true) { 207 | userList = srv->getAllUsers(); 208 | for (it = userList.begin(); it != userList.end(); ++it) 209 | target.push_back(Target((*it)->getFd(), str)); 210 | } 211 | } 212 | else { 213 | // Host mask 214 | userList = srv->getAllUsers(); 215 | for (it = userList.begin(); it != userList.end(); ++it) 216 | { 217 | if (matchMask((*it)->getHostname().c_str(), \ 218 | str.substr(1).c_str()) == true) 219 | target.push_back(Target((*it)->getFd(), str)); 220 | } 221 | } 222 | } 223 | 224 | /* ************************************************************************** */ 225 | /* SPECIFIC FUNCTIONS TO CREATE A COLLECTION OF TARGETS FOR THE MAIN FUNCTION */ 226 | /* ************************************************************************** */ 227 | static void getTargetsFromString(const int &fd, const std::string &str, \ 228 | std::deque &target, Server *srv) 229 | { 230 | if (str[0] == '+' || str[0] == '&' || str[0] == '!' 231 | || (str[0] == '#' && str.find('.') == std::string::npos)) { 232 | // CHANNEL 233 | target.push_back(Target(-1, str, extractChannelName(str, srv))); 234 | } 235 | else if (str[0] == '$' 236 | || (str[0] == '#' && str.find('.') != std::string::npos)) { 237 | // MASK 238 | try { computeMask(fd, str, srv, target); } 239 | catch (noprivilegesException &e) { e.reply(srv, fd); } 240 | } 241 | else { 242 | // USER 243 | target.push_back(Target(extractUserFd(str, srv), str)); 244 | } 245 | } 246 | 247 | /* ************************************************************************** */ 248 | /* IRC COMMAND : NOTICE */ 249 | /* ************************************************************************** */ 250 | void notice(const int &fd, const std::vector ¶ms, \ 251 | const std::string &, Server *srv) 252 | { 253 | std::map > targets; 254 | std::map >::iterator it; 255 | std::string msgtarget; 256 | std::string message; 257 | std::deque target; 258 | std::deque::iterator itTarg; 259 | std::stringstream ss; 260 | 261 | // COMMAND EXECUTION 262 | try { 263 | // check nb of param 264 | if (params.size() == 0) 265 | throw norecipientException("NOTICE"); 266 | if (params.size() == 1) 267 | throw notexttosendException(); 268 | 269 | msgtarget = params[0]; 270 | message = params[1]; 271 | 272 | // Split targets into a map 273 | targets = splitTargets(msgtarget); 274 | 275 | // Too many targets ? 276 | if (targets.size() > MAX_TARGETS) 277 | throw toomanytargetsException(msgtarget, ss.str(), "Aborted."); 278 | 279 | } 280 | // EXCEPTIONS THAT END THE COMMAND 281 | catch (norecipientException &e) { return; } 282 | catch (notexttosendException &e) { return; } 283 | catch (toomanytargetsException &e) { return; } 284 | 285 | // Loop to SEND all messages 286 | for (it = targets.begin(); it != targets.end(); ++it) 287 | { 288 | try { 289 | getTargetsFromString(fd, it->first, it->second, srv); 290 | target = it->second; 291 | for (itTarg = target.begin(); itTarg != target.end(); ++itTarg) 292 | { 293 | // Check target validity here instead of upper 294 | if (itTarg->fd != -1) { 295 | // Send to user (we don't handle AWAY flag on user) 296 | srv->sendClient(itTarg->fd, \ 297 | clientReply(srv, fd, PRIVMSG(itTarg->target, message))); 298 | } 299 | else if (!itTarg->channel.empty()) { 300 | // Send to channel : no exception cannot send because 301 | // we do not handle the needed chan mode for this 302 | if (isUserBannedOnChannel(srv, fd, itTarg->channel)) 303 | throw cannotsendtochanException(itTarg->channel); 304 | srv->sendChannel(itTarg->channel, clientReply(srv, fd, \ 305 | PRIVMSG(itTarg->target, message)), fd); 306 | } 307 | } 308 | } 309 | // EXCEPTIONS THAT DON'T END THE COMMAND 310 | catch (nosuchnickException &e) {} 311 | catch (notoplevelException &e) {} 312 | catch (wildtoplevelException &e) {} 313 | catch (cannotsendtochanException &e) {} 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /srcs/commands/privmsg.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../../includes/commands.hpp" 7 | #include "../../includes/replies.hpp" 8 | #include "../../includes/utils.hpp" 9 | #include "../../includes/exceptions.hpp" 10 | #include "../../includes/parser.hpp" 11 | 12 | #define MAX_TARGETS 100 13 | 14 | /** 15 | * @brief PRIVMSG is used to send private messages between users, as well as to 16 | * send messages to channels. is usually the nickname of 17 | * the recipient of the message, or a channel name. 18 | * 19 | * Errors handled: 20 | * - ERR_NORECIPIENT 21 | * - ERR_NOTEXTTOSEND 22 | * - ERR_CANNOTSENDTOCHAN 23 | * - ERR_WILDTOPLEVEL 24 | * - ERR_TOOMANYTARGETS 25 | * - ERR_NOSUCHNICK 26 | * 27 | */ 28 | 29 | /* ************************************************************************** */ 30 | /* STRUCTURE TO HOST CLIENT FD OR CHANNEL NAME AND TARGET OF THE MESSAGE */ 31 | /* ************************************************************************** */ 32 | struct Target { 33 | int fd; 34 | std::string channel; 35 | std::string target; 36 | 37 | Target(int fd, std::string target, std::string channel = std::string()) 38 | : fd(fd), channel(channel), target(target) {} 39 | }; 40 | 41 | /* ************************************************************************** */ 42 | /* UTILITY FUNCTIONS */ 43 | /* ************************************************************************** */ 44 | // Split the message by comma into multiple targets 45 | static std::map > splitTargets(std::string targets) 46 | { 47 | std::map > map; 48 | 49 | // only one target 50 | if (targets.find(',') == std::string::npos) 51 | { 52 | map[targets] = std::deque(); 53 | return (map); 54 | } 55 | 56 | // multiple targets 57 | std::string temp; 58 | std::istringstream stream(targets); 59 | while (std::getline(stream, temp, ',')) 60 | { 61 | transform(temp.begin(), temp.end(), temp.begin(), ::tolower); 62 | map[temp] = std::deque(); 63 | } 64 | return (map); 65 | } 66 | 67 | static bool isUserBannedOnChannel(Server *srv, const int &fd, \ 68 | const std::string &channel) 69 | { 70 | User *sender; 71 | std::deque bannedNicks; 72 | std::deque::const_iterator it; 73 | std::string senderNick; 74 | 75 | sender = srv->getUserByFd(fd); 76 | if (sender) 77 | { 78 | senderNick = sender->getNickname(); 79 | if (srv->_channelList.find(channel) != srv->_channelList.end()) 80 | { 81 | bannedNicks = srv->_channelList[channel]->_bannedUsers; 82 | for (it = bannedNicks.begin(); it != bannedNicks.end(); ++it) { 83 | if (senderNick.compare(*it) == 0) { 84 | return (true); 85 | } 86 | } 87 | } 88 | } 89 | return (false); 90 | } 91 | 92 | /* ************************************************************************** */ 93 | /* SPECIFIC FUNCTIONS TO EXTRACT USER FD, CHANNEL NAME OR TO COMPUTE MASK */ 94 | /* ************************************************************************** */ 95 | // CHANNEL 96 | static const std::string extractChannelName(const std::string str, Server *srv) 97 | { 98 | int pos = 0; 99 | std::string name; 100 | 101 | if (str[0] == '!') { 102 | // CHANNEL ID : we don't use it so we can just skip it 103 | for (int i = 0; i < 5; ++i) 104 | { 105 | if (!std::isdigit(str[i])) 106 | throw nosuchnickException(str); 107 | } 108 | pos = 6; 109 | } 110 | if (str.find(':') != std::string::npos) 111 | name = str.substr(pos, str.find(':') - pos); 112 | else 113 | name = str.substr(pos, std::string::npos); 114 | 115 | // Check if channel exists 116 | if (srv->_channelList.find(name) == srv->_channelList.end()) 117 | throw nosuchnickException(name); 118 | 119 | return (name); 120 | } 121 | 122 | // USER 123 | static int extractUserFd(const std::string str, Server *srv) 124 | { 125 | std::string user; 126 | std::string host; 127 | std::string servername; 128 | std::string nickname; 129 | User *resultUser = NULL; 130 | 131 | // string matching using 132 | if (str.find('%') != std::string::npos) { 133 | // USER % HOST 134 | user = str.substr(0, str.find('%')); 135 | host = str.substr(str.find('%') + 1); 136 | if (host.find('@') != std::string::npos) { 137 | // USER % HOST @ SERVERNAME 138 | servername = host.substr(host.find('@') + 1); 139 | host.erase(host.find('@')); 140 | } 141 | } 142 | else if (str.find('!') != std::string::npos) { 143 | // NICKNAME ! USER @ HOST 144 | nickname = str.substr(0, str.find('!')); 145 | user = str.substr(str.find('!') + 1); 146 | if (user.find('@') == std::string::npos) 147 | throw nosuchnickException(str); 148 | host = user.substr(user.find('@') + 1); 149 | user.erase(user.find('@')); 150 | } 151 | else if (str.find('@') != std::string::npos) { 152 | // USER @ SERVERNAME 153 | user = str.substr(0, str.find('@')); 154 | servername = str.substr(str.find('@') + 1); 155 | } 156 | else 157 | nickname = str; 158 | 159 | // User search and return 160 | if (!nickname.empty()) 161 | { 162 | resultUser = srv->getUserByNickname(nickname); 163 | if (resultUser == NULL) 164 | throw nosuchnickException(str); 165 | } 166 | else if (!user.empty() && !host.empty()) 167 | { 168 | resultUser = srv->getUserByUsername(user, host); 169 | if (resultUser == NULL) 170 | throw nosuchnickException(str); 171 | } 172 | else if (!user.empty()) 173 | { 174 | resultUser = srv->getUserByUsername(user); 175 | if (resultUser == NULL) 176 | throw nosuchnickException(str); 177 | } 178 | 179 | return (resultUser->getFd()); 180 | } 181 | 182 | // MASK 183 | static void computeMask(const int &fd, const std::string &str, Server *srv, \ 184 | std::deque &target) 185 | { 186 | std::deque userList; 187 | std::deque::const_iterator it; 188 | std::string mask; 189 | size_t toplevel; 190 | 191 | // Check user privileges : must be oper to use mask 192 | if (!srv->getUserByFd(fd)->hasMode(MOD_OPER)) 193 | throw noprivilegesException(); 194 | 195 | // Check the mask for exceptions 196 | mask = str.substr(1); 197 | toplevel = mask.find_last_of('.'); 198 | if (toplevel == std::string::npos) 199 | throw notoplevelException(mask); 200 | else if (mask.find('*', toplevel) != std::string::npos) 201 | throw wildtoplevelException(mask); 202 | 203 | if (str[0] == '$') { 204 | // Server mask 205 | if (matchMask(srv->getHostname().c_str(), \ 206 | str.substr(1).c_str()) == true) { 207 | userList = srv->getAllUsers(); 208 | for (it = userList.begin(); it != userList.end(); ++it) 209 | target.push_back(Target((*it)->getFd(), str)); 210 | } 211 | } 212 | else { 213 | // Host mask 214 | userList = srv->getAllUsers(); 215 | for (it = userList.begin(); it != userList.end(); ++it) 216 | { 217 | if (matchMask((*it)->getHostname().c_str(), \ 218 | str.substr(1).c_str()) == true) 219 | target.push_back(Target((*it)->getFd(), str)); 220 | } 221 | } 222 | } 223 | 224 | /* ************************************************************************** */ 225 | /* SPECIFIC FUNCTIONS TO CREATE A COLLECTION OF TARGETS FOR THE MAIN FUNCTION */ 226 | /* ************************************************************************** */ 227 | static void getTargetsFromString(const int &fd, const std::string &str, \ 228 | std::deque &target, Server *srv) 229 | { 230 | if (str[0] == '+' || str[0] == '&' || str[0] == '!' 231 | || (str[0] == '#' && str.find('.') == std::string::npos)) { 232 | // CHANNEL 233 | target.push_back(Target(-1, str, extractChannelName(str, srv))); 234 | } 235 | else if (str[0] == '$' 236 | || (str[0] == '#' && str.find('.') != std::string::npos)) { 237 | // MASK 238 | try { computeMask(fd, str, srv, target); } 239 | catch (noprivilegesException &e) { e.reply(srv, fd); } 240 | } 241 | else { 242 | // USER 243 | target.push_back(Target(extractUserFd(str, srv), str)); 244 | } 245 | } 246 | 247 | /* ************************************************************************** */ 248 | /* IRC COMMAND : PRIVMSG */ 249 | /* ************************************************************************** */ 250 | void privmsg(const int &fd, const std::vector ¶ms, \ 251 | const std::string &, Server *srv) 252 | { 253 | std::map > targets; 254 | std::map >::iterator it; 255 | std::string msgtarget; 256 | std::string message; 257 | std::deque target; 258 | std::deque::iterator itTarg; 259 | std::stringstream ss; 260 | 261 | // COMMAND EXECUTION 262 | try { 263 | // check nb of param 264 | if (params.size() == 0) 265 | throw norecipientException("PRIVMSG"); 266 | if (params.size() == 1) 267 | throw notexttosendException(); 268 | 269 | msgtarget = params[0]; 270 | message = params[1]; 271 | 272 | // Split targets into a map 273 | targets = splitTargets(msgtarget); 274 | 275 | // Too many targets ? 276 | if (targets.size() > MAX_TARGETS) 277 | throw toomanytargetsException(msgtarget, ss.str(), "Aborted."); 278 | 279 | } 280 | // EXCEPTIONS THAT END THE COMMAND 281 | catch (norecipientException &e) {e.reply(srv, fd); return; } 282 | catch (notexttosendException &e) {e.reply(srv, fd); return; } 283 | catch (toomanytargetsException &e) {e.reply(srv, fd); return; } 284 | 285 | // Loop to SEND all messages 286 | for (it = targets.begin(); it != targets.end(); ++it) 287 | { 288 | try { 289 | getTargetsFromString(fd, it->first, it->second, srv); 290 | target = it->second; 291 | for (itTarg = target.begin(); itTarg != target.end(); ++itTarg) 292 | { 293 | // Check target validity here instead of upper 294 | if (itTarg->fd != -1) { 295 | // Send to user (we don't handle AWAY flag on user) 296 | srv->sendClient(itTarg->fd, \ 297 | clientReply(srv, fd, PRIVMSG(itTarg->target, message))); 298 | } 299 | else if (!itTarg->channel.empty()) { 300 | // Send to channel : no exception cannot send because 301 | // we do not handle the needed chan mode for this 302 | if (isUserBannedOnChannel(srv, fd, itTarg->channel)) 303 | throw cannotsendtochanException(itTarg->channel); 304 | srv->sendChannel(itTarg->channel, clientReply(srv, fd, \ 305 | PRIVMSG(itTarg->target, message)), fd); 306 | } 307 | } 308 | } 309 | // EXCEPTIONS THAT DON'T END THE COMMAND 310 | catch (nosuchnickException &e) {e.reply(srv, fd);} 311 | catch (notoplevelException &e) {e.reply(srv, fd);} 312 | catch (wildtoplevelException &e) {e.reply(srv, fd);} 313 | catch (cannotsendtochanException &e) {e.reply(srv, fd);} 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /srcs/commands/mode.cpp: -------------------------------------------------------------------------------- 1 | #include "../../includes/commands.hpp" 2 | #include "../../includes/utils.hpp" 3 | #include "../../includes/parser.hpp" 4 | 5 | /* ************************************************************************** */ 6 | /* USER */ 7 | /* ************************************************************************** */ 8 | 9 | std::string userModesToStr(User *user) 10 | { 11 | std::string modes; 12 | 13 | modes.append("+"); 14 | if (user->hasMode(MOD_AWAY)) 15 | modes.append("a"); 16 | if (user->hasMode(MOD_BOT)) 17 | modes.append("B"); 18 | if (user->hasMode(MOD_WALLOPS)) 19 | modes.append("w"); 20 | if (user->hasMode(MOD_INVISIBLE)) 21 | modes.append("i"); 22 | if (user->hasMode(MOD_OPER)) 23 | modes.append("o"); 24 | if (user->hasMode(MOD_SRVNOTICES)) 25 | modes.append("s"); 26 | if (user->hasMode(MOD_RESTRICTED)) 27 | modes.append("r"); 28 | return (modes); 29 | } 30 | 31 | void addModes(User *user, const std::string mode, int start, int stop) 32 | { 33 | for (int i = start; i < stop; i++) 34 | { 35 | switch (mode[i]) 36 | { 37 | case 'a': 38 | break; 39 | case 'w': 40 | user->addMode(MOD_WALLOPS); 41 | break; 42 | case 'i': 43 | user->addMode(MOD_INVISIBLE); 44 | break; 45 | case 'o': 46 | break; 47 | case 's': 48 | user->addMode(MOD_SRVNOTICES); 49 | break; 50 | case 'r': 51 | user->addMode(MOD_RESTRICTED); 52 | user->removeMode(MOD_OPER); 53 | break; 54 | } 55 | } 56 | } 57 | 58 | void removeModes(User *user, const std::string mode, int start, int stop) 59 | { 60 | for (int i = start; i < stop; i++) 61 | { 62 | switch (mode[i]) 63 | { 64 | case 'a': 65 | break; 66 | case 'w': 67 | user->removeMode(MOD_WALLOPS); 68 | break; 69 | case 'i': 70 | user->removeMode(MOD_INVISIBLE); 71 | break; 72 | case 'o': 73 | user->removeMode(MOD_OPER); 74 | break; 75 | case 's': 76 | user->removeMode(MOD_SRVNOTICES); 77 | break; 78 | case 'r': 79 | break; 80 | } 81 | } 82 | } 83 | 84 | void findPair(const std::string modes, unsigned int i, std::pair *pair) 85 | { 86 | pair->first = modes[i]; 87 | pair->second = i; 88 | 89 | for (i = i + 1; i < modes.size(); i++) 90 | { 91 | if (modes[i] == '+' || modes[i] == '-') 92 | break; 93 | else 94 | pair->second++; 95 | } 96 | return; 97 | } 98 | 99 | void handleAddRemoveModes(User *user, const std::string modes) 100 | { 101 | std::pair pair; 102 | 103 | for (unsigned int i = 0; i < modes.size(); i++) 104 | { 105 | if (modes[i] == '+' || modes[i] == '-') 106 | findPair(modes, i, &pair); 107 | if (modes[i] == '+' && pair.second > 0) 108 | addModes(user, modes, i, pair.second + 1); 109 | else if (modes[i] == '-' && pair.second > 0) 110 | removeModes(user, modes, i, pair.second + 1); 111 | } 112 | } 113 | 114 | void UserMode(const int &fd, const std::vector ¶ms, Server *srv) 115 | { 116 | User *user = srv->getUserByFd(fd); 117 | 118 | if (params.size() == 1 && params[0] == user->getNickname()) 119 | return (srv->sendClient(fd, numericReply(srv, fd, "221", 120 | RPL_UMODEIS(userModesToStr(user))))); 121 | else if (srv->getUserByNickname(params[0]) == 0) 122 | return (srv->sendClient(fd, numericReply(srv, fd, "401", 123 | ERR_NOSUCHNICK(params[0])))); 124 | else if (params[0] != user->getNickname()) 125 | return (srv->sendClient(fd, numericReply(srv, fd, "502", 126 | ERR_USERSDONTMATCH))); 127 | else if (params[1].find_first_not_of("+-aBiwros") != std::string::npos) 128 | return (srv->sendClient(fd, numericReply(srv, fd, "501", 129 | ERR_UMODEUNKNOWNFLAG))); 130 | else if (!params[1].empty()) 131 | handleAddRemoveModes(user, params[1]); 132 | } 133 | 134 | /* ************************************************************************** */ 135 | /* CHANNEL */ 136 | /* ************************************************************************** */ 137 | 138 | /** 139 | * @brief Add or remove a channel mode 140 | * 141 | * Mode supported: 142 | * - i = set the channel to invite only. Users won't be able to join an existing channel 143 | * if they are not invited by an operator on the channel 144 | * - b = ban an existing user on the channel. The user will be added to the ban list. 145 | * He won't be able to join unless the ban is removed 146 | * - o = give to a user the operator rights. This flag can only be set by an operator. 147 | * - k = add or remove a key from the channel. 148 | * Users won't be able to join the channel without this key 149 | * 150 | * Errors handled: 151 | * - ERR_NEEDMOREPARAMS 152 | * - ERR_USERNOTINCHANNEL 153 | * - ERR_KEYSET 154 | * - ERR_NOSUCHCHANNEL 155 | * - ERR_CHANOPRIVSNEEDED 156 | * - ERR_UNKNOWNMODE 157 | * 158 | */ 159 | 160 | void listBannedUser(const int &fdUser, Server *server, 161 | Channel *channel) 162 | { 163 | std::string nicknameList; 164 | std::deque listBannedUser = channel->_bannedUsers; 165 | std::deque::iterator itBannedUser; 166 | 167 | for (itBannedUser = listBannedUser.begin(); itBannedUser != listBannedUser.end(); 168 | itBannedUser++) 169 | nicknameList += *itBannedUser + "!*@* "; 170 | server->sendClient(fdUser, numericReply(server, fdUser, 171 | "367", RPL_BANLIST(channel->getChannelName(), nicknameList))); 172 | server->sendClient(fdUser, numericReply(server, fdUser, 173 | "368", RPL_ENDOFBANLIST(channel->getChannelName()))); 174 | } 175 | 176 | int checkUserExists(User *user, const std::vector params, 177 | const int &fd, Server *srv, int bannedList) 178 | { 179 | if (user == NULL && params.size() > 2) 180 | { 181 | srv->sendClient(fd, numericReply(srv, fd, "441", 182 | ERR_USERNOTINCHANNEL(params[2], params[0]))); 183 | return (-1); 184 | } 185 | else if (params.size() < 3 && bannedList == 0) 186 | return (-2); 187 | else if (params.size() < 3 && bannedList == 1) 188 | { 189 | listBannedUser(fd, srv, srv->_channelList.find(params[0])->second); 190 | return (-3); 191 | } 192 | return (0); 193 | } 194 | 195 | void handleChannelReply(const std::vector params, const int &fd, Server *srv) 196 | { 197 | std::string channel = params[0]; 198 | std::string mode = params[1]; 199 | std::string user; 200 | 201 | if (params.size() > 2) 202 | std::string user = params[2]; 203 | // Case where mode is only +i 204 | if (params.size() < 3) 205 | return(srv->sendChannel(channel, clientReply(srv, fd, "MODE " + 206 | channel + " " + mode))); 207 | // Case where mode is ban with a user as a third parameter 208 | if (mode.find('b') != std::string::npos 209 | && user.find('*') == std::string::npos) 210 | return(srv->sendChannel(channel, clientReply(srv, fd, "MODE " + 211 | channel + " " + mode + " " + user + "!*@*"))); 212 | // Case where mode is operator 213 | if (mode.find('o') != std::string::npos) 214 | return(srv->sendChannel(channel, clientReply(srv, fd, "MODE " + 215 | channel + " " + mode + " " + params[2]))); 216 | // Case where mode is key or ban with * 217 | return(srv->sendChannel(channel, clientReply(srv, fd, "MODE " + 218 | channel + " " + mode + " " + user))); 219 | } 220 | 221 | void addModesChannel(const std::vector params, int start, int stop, 222 | const int &fd, Server *srv) 223 | { 224 | Channel *channel = srv->_channelList.find(params[0])->second; 225 | User *user = NULL; 226 | 227 | if (params.size() > 2) 228 | user = srv->getUserByNickname(params[2]); 229 | 230 | for (int i = start; i < stop; i++) 231 | { 232 | switch (params[1][i]) 233 | { 234 | case 'i': 235 | channel->addMode(MOD_INVITE); 236 | break ; 237 | case 'k': 238 | // Check that a key was not already set 239 | if (channel->hasMode(MOD_KEY) == true) 240 | return (srv->sendClient(fd, numericReply(srv, fd, "467", 241 | ERR_KEYSET(params[0])))); 242 | channel->addMode(MOD_KEY); 243 | channel->setKey(params[2]); 244 | break ; 245 | case 'o': 246 | // Check that the user exists and a user was given in parameter 247 | if (checkUserExists(user, params, fd, srv, 0) < 0) 248 | return ; 249 | channel->addMode(MOD_OPERATOR); 250 | channel->addOperator(user); 251 | break ; 252 | case 'b': 253 | // Check that the user exists and a user was given in parameter 254 | if (checkUserExists(user, params, fd, srv, 1) < 0) 255 | return ; 256 | channel->addBannedUser(user->getNickname()); 257 | channel->addMode(MOD_BAN); 258 | break ; 259 | default: 260 | return (srv->sendClient(fd, numericReply(srv, fd, "472", 261 | ERR_UNKNOWNMODE(params[1], params[0])))); 262 | } 263 | } 264 | handleChannelReply(params, fd, srv); 265 | } 266 | 267 | void removeModesChannel(const std::vector params, int start, int stop, 268 | const int &fd, Server *srv) 269 | { 270 | Channel *channel = srv->_channelList.find(params[0])->second; 271 | User *user = NULL; 272 | 273 | if (params.size() > 2 && params[2].find('*') == std::string::npos) 274 | user = srv->getUserByNickname(params[2]); 275 | for (int i = start; i < stop; i++) 276 | { 277 | switch (params[1][i]) 278 | { 279 | case 'i': 280 | channel->removeMode(MOD_INVITE); 281 | channel->_invitees.clear(); 282 | break ; 283 | case 'k': 284 | channel->removeMode(MOD_KEY); 285 | channel->setKey(""); 286 | break ; 287 | case 'o': 288 | if (checkUserExists(user, params, fd, srv, 0) < 0) 289 | return ; 290 | channel->removeOperator(user); 291 | channel->removeMode(MOD_OPERATOR); 292 | break ; 293 | case 'b': 294 | if (checkUserExists(user, params, fd, srv, 1) < 0) 295 | return ; 296 | channel->removeBannedUser(user->getNickname()); 297 | channel->removeMode(MOD_BAN); 298 | break ; 299 | default: 300 | return (srv->sendClient(fd, numericReply(srv, fd, "472", 301 | ERR_UNKNOWNMODE(params[1], params[0])))); 302 | } 303 | } 304 | handleChannelReply(params, fd, srv); 305 | } 306 | 307 | void handleAddRemoveModesChannel(const int &fd, const std::vector params, 308 | Server *srv) 309 | { 310 | std::pair pair; 311 | std::string mode = params[1]; 312 | 313 | for (unsigned int i = 0; i < params[1].size(); i++) 314 | { 315 | if (mode[i] == '+' || mode[i] == '-') 316 | { 317 | findPair(params[1], i, &pair); 318 | } 319 | if (mode[i] == '+' && pair.second > 0) 320 | { 321 | addModesChannel(params, i + 1, pair.second + 1, fd, srv); 322 | } 323 | else if (mode[i] == '-' && pair.second > 0) 324 | { 325 | removeModesChannel(params, i + 1, pair.second + 1, fd, srv); 326 | } 327 | } 328 | } 329 | 330 | int checkChannelMode(const int &fd, const std::vector ¶ms, Server *srv) 331 | { 332 | std::map::iterator itChannel; 333 | 334 | // Check that channel list is not empty 335 | if (srv->_channelList.empty() == true) 336 | { 337 | srv->sendClient(fd, numericReply(srv, fd, "403", 338 | ERR_NOSUCHCHANNEL(params[0]))); 339 | return (-1); 340 | } 341 | itChannel = srv->_channelList.find(params[0]); 342 | // Check that channel exists 343 | if (itChannel == srv->_channelList.end()) 344 | { 345 | srv->sendClient(fd, numericReply(srv, fd, "403", 346 | ERR_NOSUCHCHANNEL(params[0]))); 347 | return (-2); 348 | } 349 | // Check that user is on channel 350 | if (findUserOnChannel(itChannel->second->_users, srv->getUserByFd(fd)) == false) 351 | { 352 | srv->sendClient(fd, numericReply(srv, fd, "441", 353 | ERR_USERNOTINCHANNEL(srv->getUserByFd(fd)->getNickname(), params[0]))); 354 | return (-3); 355 | } 356 | // Check that user is an operator 357 | if (findUserOnChannel(itChannel->second->_operators, srv->getUserByFd(fd)) == false 358 | && params.size() > 1 && params[1].compare("b") != 0) 359 | { 360 | srv->sendClient(fd, numericReply(srv, fd, "482", 361 | ERR_CHANOPRIVSNEEDED(params[0]))); 362 | return (-4); 363 | } 364 | return (0); 365 | } 366 | 367 | void ChannelMode(const int &fd, const std::vector ¶ms, Server *srv) 368 | { 369 | std::string mode = "+"; 370 | std::string modeParams; 371 | Channel *channel; 372 | 373 | if (checkChannelMode(fd, params, srv) < 0) 374 | return; 375 | if (params.size() > 1) 376 | handleAddRemoveModesChannel(fd, params, srv); 377 | else 378 | { 379 | channel = srv->_channelList.find(params[0])->second; 380 | if (channel->getKey().empty() == false) 381 | { 382 | mode += "k"; 383 | modeParams += channel->getKey(); 384 | } 385 | if (channel->hasMode(MOD_INVITE) == true) 386 | mode += "i"; 387 | return (srv->sendClient(fd, numericReply(srv, fd, 388 | "324", RPL_CHANNELMODEIS(params[0], mode, modeParams)))); 389 | } 390 | } 391 | 392 | /* ************************************************************************** */ 393 | /* MODE FUNCTION */ 394 | /* ************************************************************************** */ 395 | 396 | void mode(const int &fd, const std::vector ¶ms, const std::string &, 397 | Server *srv) 398 | { 399 | if (params.empty() || params[0].empty()) 400 | { 401 | return (srv->sendClient(fd, numericReply(srv, fd, "461", 402 | ERR_NEEDMOREPARAMS(std::string("MODE"))))); 403 | } 404 | if (isChannel(params[0]) == true) 405 | ChannelMode(fd, params, srv); 406 | else 407 | UserMode(fd, params, srv); 408 | return; 409 | } 410 | -------------------------------------------------------------------------------- /srcs/server/Server.cpp: -------------------------------------------------------------------------------- 1 | // Standard headers 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | // C Libraries 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | // Custom headers 20 | #include "../../includes/Server.hpp" 21 | #include "../../includes/channel.hpp" 22 | #include "../../includes/irc.hpp" 23 | #include "../../includes/utils.hpp" 24 | #include "../../includes/commands.hpp" 25 | 26 | #ifndef HOSTNAME 27 | # define HOSTNAME "localhost" 28 | #endif 29 | 30 | /* ************************************************************************** */ 31 | /* Constructors & destructor */ 32 | /* ************************************************************************** */ 33 | Server::Server(int port, std::string password, std::string name) 34 | : _port(port), _password(password), _name(name), _hostname(HOSTNAME), _version(VERSION), 35 | _pollfd(0), _sockfd(0) 36 | { 37 | time_t rawtime = time(NULL); 38 | struct tm *timeinfo; 39 | 40 | timeinfo = localtime(&rawtime); 41 | _date = std::string(asctime(timeinfo)); 42 | this->_initCommandList(); 43 | } 44 | 45 | Server::Server(Server const &src) 46 | { *this = src; } 47 | 48 | Command::Command(\ 49 | std::string cmd, std::string prefix, std::vector params) 50 | : command(cmd), prefix(prefix), params(params){} 51 | 52 | /* ************************************************************************** */ 53 | /* Operator overloads */ 54 | /* ************************************************************************** */ 55 | Server &Server::operator=(Server const &rhs) 56 | { 57 | if (this != &rhs) { 58 | this->_port = rhs._port; 59 | this->_password = rhs._password; 60 | this->_name = rhs._name; 61 | this->_userList = rhs._userList; 62 | this->_channelList = rhs._channelList; 63 | this->_cmdList = rhs._cmdList; 64 | this->_hostname = rhs._hostname; 65 | this->_version = rhs._version; 66 | this->_date = rhs._date; 67 | } 68 | return (*this); 69 | } 70 | 71 | /* ************************************************************************** */ 72 | /* Getters */ 73 | /* ************************************************************************** */ 74 | int Server::getPort(void) const 75 | { return this->_port; } 76 | 77 | std::string Server::getPassword(void) const 78 | { return this->_password; } 79 | 80 | std::string Server::getName(void) const 81 | { return this->_name; } 82 | 83 | std::string Server::getHostname(void) const 84 | { return this->_hostname; } 85 | 86 | std::string Server::getVersion(void) const 87 | { return this->_version; } 88 | 89 | std::string Server::getDate(void) const 90 | { return this->_date; } 91 | 92 | User* Server::getUserByFd(const int &fd) const 93 | { 94 | std::map::const_iterator ite = this->_userList.end(); 95 | 96 | if (this->_userList.find(fd) != ite) 97 | return (this->_userList.find(fd)->second); 98 | return (NULL); 99 | } 100 | 101 | User* Server::getUserByNickname(const std::string &nick) const 102 | { 103 | std::map::const_iterator it; 104 | 105 | for (it = this->_userList.begin(); it != this->_userList.end(); ++it) 106 | { 107 | if (it->second->getNickname() == nick) 108 | return (it->second); 109 | } 110 | return (NULL); 111 | } 112 | 113 | std::deque Server::getUsersByHostname(const std::string &hostname) const 114 | { 115 | std::map::const_iterator it; 116 | std::deque result; 117 | 118 | for (it = this->_userList.begin(); it != this->_userList.end(); ++it) 119 | { 120 | if (it->second->getHostname() == hostname) 121 | result.push_back(it->second); 122 | } 123 | return (result); 124 | } 125 | 126 | User* Server::getUserByUsername(const std::string &user, \ 127 | const std::string &host) const 128 | { 129 | std::map::const_iterator it; 130 | 131 | for (it = this->_userList.begin(); it != this->_userList.end(); ++it) 132 | { 133 | if (it->second->getUsername() == user) 134 | { 135 | if (!host.empty()) 136 | { 137 | if (it->second->getHostname() == host) 138 | return (it->second); 139 | else 140 | return (NULL); 141 | } 142 | return (it->second); 143 | } 144 | } 145 | return (NULL); 146 | } 147 | 148 | std::deque Server::getAllUsers(void) const 149 | { 150 | std::map::const_iterator it; 151 | std::deque users; 152 | 153 | for (it = this->_userList.begin(); it != this->_userList.end(); ++it) 154 | users.push_back(it->second); 155 | return (users); 156 | } 157 | 158 | /* ************************************************************************** */ 159 | /* Private member functions */ 160 | /* ************************************************************************** */ 161 | // CREATE SOCKET 162 | void Server::_createSocket(void) 163 | { 164 | int sockfd; 165 | 166 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 167 | if (sockfd == -1) 168 | throw Server::socketException(); 169 | if (fcntl(sockfd, F_SETFL, O_NONBLOCK) == -1) { 170 | throw Server::socketException(); 171 | } 172 | this->_sockfd = sockfd; 173 | } 174 | 175 | // BIND SOCKET 176 | void Server::_bindSocket(int sockfd, struct sockaddr_in *srv_addr) 177 | { 178 | // binding to ip address + port 179 | srv_addr->sin_family = AF_INET; // host byte order 180 | srv_addr->sin_port = htons(this->_port); 181 | srv_addr->sin_addr.s_addr = INADDR_ANY; 182 | memset(&(srv_addr->sin_zero), 0, 8); // fill remaining space with 0 183 | if (bind(sockfd, reinterpret_cast(srv_addr), \ 184 | sizeof(struct sockaddr)) == -1) 185 | throw Server::bindException(); 186 | 187 | // mark the socket as passive and set a max connection backlog 188 | if (listen(sockfd, BACKLOG) == -1) 189 | throw Server::bindException(); 190 | } 191 | 192 | // CREATE POLL 193 | void Server::_createPoll(int sockfd) 194 | { 195 | int pollfd; 196 | struct epoll_event ev; 197 | 198 | // epoll creation 199 | pollfd = epoll_create1(0); 200 | if (pollfd == -1) 201 | throw Server::pollException(); 202 | 203 | // adding current fd to epoll interest list so we can loop on it to accept 204 | // connections 205 | memset(&ev, 0, sizeof(struct epoll_event)); 206 | ev.events = EPOLLIN; 207 | ev.data.fd = sockfd; 208 | if (epoll_ctl(pollfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) { 209 | throw Server::pollException(); 210 | } 211 | this->_pollfd = pollfd; 212 | } 213 | 214 | // WAIT POLL 215 | int Server::_pollWait(int pollfd, struct epoll_event **events, int max_events) 216 | { 217 | int nfds; 218 | 219 | nfds = epoll_wait(pollfd, *events, max_events, WAIT_TIMEOUT); 220 | if (nfds == -1) 221 | throw Server::pollWaitException(); 222 | return (nfds); 223 | } 224 | 225 | // ACCEPT CONNECTION 226 | void Server::_acceptConnection(int sockfd, int pollfd) 227 | { 228 | socklen_t sin_size; 229 | struct epoll_event ev; 230 | int newfd; 231 | struct sockaddr_in client_addr; 232 | 233 | 234 | // accept the connect request 235 | memset(&client_addr, 0, sizeof(struct sockaddr_in)); 236 | memset(&sin_size, 0, sizeof(socklen_t)); 237 | newfd = accept(sockfd, reinterpret_cast(&client_addr), \ 238 | &sin_size); 239 | if (newfd == -1) 240 | throw Server::acceptException(); 241 | 242 | // Ask getsockname to fill in this socket's local address 243 | getsockname(newfd, reinterpret_cast(&client_addr), &sin_size); 244 | // create a new empty user 245 | this->_userList[newfd] = new User(newfd, inet_ntoa(client_addr.sin_addr)); 246 | 247 | // add the new fd to the poll 248 | memset(&ev, 0, sizeof(struct epoll_event)); 249 | ev.events = EPOLLIN | EPOLLET; 250 | ev.data.fd = newfd; 251 | if (epoll_ctl(pollfd, EPOLL_CTL_ADD, newfd, &ev) == -1) 252 | throw Server::pollAddException(); 253 | } 254 | 255 | // HANDLE NEW MESSAGE 256 | void clearCmds(std::vector cmds) { 257 | std::vector::iterator cmdsIt = cmds.begin(); 258 | std::vector::iterator cmdsIte = cmds.end(); 259 | for (; cmdsIt != cmdsIte; cmdsIt++) { 260 | delete &(*cmdsIt); 261 | //cmds.erase(cmdsIt); 262 | } 263 | } 264 | 265 | void Server::_handleNewMessage(struct epoll_event event) 266 | { 267 | char buf[BUF_SIZE]; 268 | ssize_t ret; 269 | std::string mess; 270 | std::vector< std::string > cmd_strings; 271 | std::vector cmds; 272 | 273 | // Fill a buffer with the message 274 | memset(buf, 0, BUF_SIZE); 275 | ret = recv(event.data.fd, buf, BUF_SIZE, 0); 276 | if (ret == -1) 277 | { 278 | printError("Read error: ", 1, true); 279 | return; 280 | } 281 | buf[ret] = '\0'; 282 | 283 | // Adding current buf to fd's buffer on server 284 | this->_buffersByFd[event.data.fd].append(buf); 285 | 286 | // Split the commands in a vector and appending/clearing the buffer 287 | // Non blocking in case of not ok message. 288 | try { cmd_strings = splitBy(_buffersByFd[event.data.fd], "\r\n", 289 | &(_buffersByFd[event.data.fd])); } 290 | catch (std::runtime_error &e) { 291 | // Send an error to the client and kill the connection 292 | this->sendClient(event.data.fd, ERRORMSG(std::string(e.what()))); 293 | this->killConnection(event.data.fd); 294 | return; 295 | } 296 | 297 | // Split all commands in a vector of t_command (CMD / PARAM) 298 | try { splitCmds(cmd_strings, &cmds); } 299 | catch (std::runtime_error &e) {printError(e.what(), 1, false); } 300 | 301 | // Display of commands received by the server (temporary) 302 | #ifdef VERBOSE 303 | displayCommands(cmds); 304 | #endif 305 | // execute all commands in the vector 306 | this->_executeCommands(event.data.fd, cmds); 307 | //clearCmds(cmds); 308 | } 309 | 310 | // INIT COMMAND LIST OF THE SERVER 311 | void Server::_initCommandList(void) // functions to complete 312 | { 313 | this->_cmdList["CAP"] = ∩ 314 | this->_cmdList["DIE"] = ¨ 315 | this->_cmdList["PASS"] = &pass; 316 | this->_cmdList["NICK"] = &nick; 317 | this->_cmdList["USER"] = &user; 318 | this->_cmdList["MODE"] = &mode; 319 | this->_cmdList["OPER"] = &oper; 320 | this->_cmdList["KILL"] = &kill; 321 | this->_cmdList["JOIN"] = &join; 322 | this->_cmdList["PART"] = ∂ 323 | this->_cmdList["INVITE"] = &invite; 324 | this->_cmdList["KICK"] = &kick; 325 | this->_cmdList["TOPIC"] = &topic; 326 | this->_cmdList["LIST"] = &list; 327 | this->_cmdList["NAMES"] = &names; 328 | this->_cmdList["PING"] = &ping; 329 | this->_cmdList["PONG"] = &pong; 330 | this->_cmdList["QUIT"] = &quit; 331 | this->_cmdList["MOTD"] = &motd; 332 | this->_cmdList["VERSION"] = &version; 333 | this->_cmdList["TIME"] = &time; 334 | this->_cmdList["INFO"] = &info; 335 | this->_cmdList["PRIVMSG"] = &privmsg; 336 | this->_cmdList["NOTICE"] = ¬ice; 337 | this->_cmdList["WHO"] = &who; 338 | this->_cmdList["WHOIS"] = &whois; 339 | this->_cmdList["SERVICE"] = &service; 340 | } 341 | 342 | // EXECUTE RECEIVED COMMANDS 343 | void Server::_executeCommands(const int fd, std::vector cmds) 344 | { 345 | std::vector::iterator it; 346 | std::map::iterator it_cmd; 347 | std::string result; 348 | CmdFunction exec_command; 349 | std::string reply_str; 350 | User *user; 351 | 352 | // for each command in the message 353 | for (it = cmds.begin(); it < cmds.end(); ++it) 354 | { 355 | // search if it is in the known commands list of the server 356 | std::transform(it->command.begin(), it->command.end(), 357 | it->command.begin(), ::toupper); 358 | it_cmd = this->_cmdList.find(it->command); 359 | if (it_cmd != this->_cmdList.end()) 360 | { 361 | // execute the command 362 | exec_command = it_cmd->second; 363 | user = this->getUserByFd(fd); 364 | // update client timers 365 | user->setLastActivityTime(); 366 | if (user->getAuthenticated() || isAuthenticationCmd(it_cmd->first)){ 367 | try { exec_command(fd, it->params, it->prefix, this); } 368 | // send exception 369 | catch (Server::invalidFdException &e) 370 | { printError(e.what(), 1, false); } 371 | } 372 | } 373 | else // the command is unknown, send something to the client 374 | { 375 | try { this->sendClient(fd, \ 376 | numericReply(this, fd, "421", ERR_UNKNOWNCOMMAND(it->command)));} 377 | catch (Server::invalidFdException &e) 378 | { printError(e.what(), 1, false); } 379 | } 380 | } 381 | } 382 | 383 | // PING CLIENTS 384 | void Server::_pingClients(void) 385 | { 386 | int userFd; 387 | User *user; 388 | double seconds; 389 | std::map::iterator it; 390 | std::stringstream ss; 391 | 392 | // loop on every active connection 393 | for (it = this->_userList.begin(); it != this->_userList.end();) 394 | { 395 | userFd = it->first; 396 | user = it->second; 397 | // Check user's last activity time 398 | seconds = difftime(time(NULL), user->getLastActivityTime()); 399 | // case 1 : send a PING command if > 120sec 400 | if (user->getStatus() == ST_ALIVE && seconds > PING_TIMEOUT) 401 | { 402 | try { this->sendClient(userFd, PING(this->_hostname)); } 403 | catch (Server::invalidFdException &e) 404 | { printError(e.what(), 1, false); } 405 | user->setStatus(ST_PINGED); 406 | user->setPingTime(); 407 | ++it; 408 | } 409 | else if (user->getStatus() == ST_PINGED) 410 | { 411 | seconds = difftime(time(NULL), user->getPingTime()); 412 | // case 2 : PONG timeout > Kill the client 413 | if (seconds > PONG_TIMEOUT) 414 | { 415 | // Move the iterator to the next user before removing user 416 | ++it; 417 | // Send en error to the client 418 | ss << PONG_TIMEOUT; 419 | this->sendClient(userFd, ERRORMSG(std::string("Ping timeout: ") 420 | .append(ss.str()).append(" seconds"))); 421 | // Kill connection 422 | this->killConnection(userFd); 423 | } 424 | } 425 | else 426 | ++it; 427 | } 428 | } 429 | 430 | // CLEAR USERS/CHANNELS AND FDS 431 | 432 | void Server::_clearAll(void) { 433 | 434 | // delete channels 435 | if (!this->_channelList.empty()) { 436 | std::map::iterator chanIt = _channelList.begin(); 437 | std::map::iterator chanIte = _channelList.end(); 438 | for (; chanIt != chanIte; chanIt++) 439 | delete chanIt->second; 440 | } 441 | 442 | // delete users 443 | if (!this->_userList.empty()) { 444 | std::map::iterator userIt = this->_userList.begin(); 445 | std::map::iterator userIte = this->_userList.end(); 446 | for (; userIt != userIte; userIt++) { 447 | // epoll delete on fds 448 | try { epoll_ctl(this->_pollfd, EPOLL_CTL_DEL, (userIt->second)->getFd(), NULL); } 449 | catch (Server::pollDelException &e) 450 | { printError(e.what(), 1, true); } 451 | // close fd for each user & delete user 452 | if (userIt->second) { 453 | if (close((userIt->second)->getFd()) == -1) { 454 | printError("Close error : ", 1, true); 455 | return; 456 | } 457 | delete userIt->second; 458 | } 459 | } 460 | } 461 | 462 | // close epoll functional fds 463 | if (this->_sockfd) 464 | if (close(this->_sockfd) == -1) { 465 | printError("Close error : ", 1, true); 466 | return; 467 | } 468 | if (this->_pollfd) 469 | if (close(this->_pollfd) == -1) { 470 | printError("Close error : ", 1, true); 471 | return; 472 | } 473 | } 474 | 475 | /* ************************************************************************** */ 476 | /* Member functions */ 477 | /* ************************************************************************** */ 478 | // START 479 | void Server::start(void) 480 | { 481 | // socket 482 | struct sockaddr_in srv_addr; 483 | // epoll 484 | int nfds; 485 | struct epoll_event events[MAX_EVENTS]; 486 | struct epoll_event *events_tmp; 487 | 488 | try { 489 | // socket creation and param ----------------------------------------- / 490 | this->_createSocket(); 491 | 492 | // binding to ip address + port and switch to passive mode ----------- / 493 | this->_bindSocket(this->_sockfd, &srv_addr); 494 | 495 | // poll creation ----------------------------------------------------- / 496 | this->_createPoll(this->_sockfd); 497 | 498 | // server loop ------------------------------------------------------- / 499 | while (1) 500 | { 501 | // poll wait ----------------------------------------------------- / 502 | events_tmp = &(events[0]); 503 | nfds = this->_pollWait(this->_pollfd, &events_tmp, MAX_EVENTS); 504 | 505 | // loop on ready fds --------------------------------------------- / 506 | for (int i = 0; i < nfds; ++i) 507 | { 508 | // handle new connection requests ---------------------------- / 509 | if (events[i].data.fd == this->_sockfd) 510 | this->_acceptConnection(this->_sockfd, this->_pollfd); 511 | else // new message from existing connection ----------------- / 512 | this->_handleNewMessage(events[i]); 513 | } 514 | 515 | // send a PING to fds that seems inactive ------------------------ / 516 | this->_pingClients(); 517 | } 518 | } 519 | // EXCEPTIONS THAT END THE SERVER 520 | catch (Server::socketException &e){ printError(e.what(), 1, true); return;} 521 | catch (Server::bindException &e){ _clearAll(); printError(e.what(), 1, true); return; } 522 | catch (Server::pollException &e){ _clearAll(); printError(e.what(), 1, true); return; } 523 | catch (Server::pollAddException &e){ _clearAll(); printError(e.what(), 1, true); return;} 524 | catch (Server::pollWaitException &e){ _clearAll(); 525 | printError("\nServer is shutting down\n", 1, false); return; } 526 | catch (Server::pollDelException &e){ _clearAll(); printError(e.what(), 1, true); return;} 527 | catch (Server::acceptException &e){ _clearAll(); printError(e.what(), 1, true); return; } 528 | catch (Server::readException &e){ _clearAll(); printError(e.what(), 1, true); return; } 529 | catch (Server::closeException &e){ _clearAll(); printError(e.what(), 1, true); return; } 530 | } 531 | 532 | // KILL CONNECTION 533 | 534 | void clearUser(Server *srv, User *user) { 535 | 536 | std::map::iterator chanIt = srv->_channelList.begin(); 537 | std::map::iterator chanIte = srv->_channelList.end(); 538 | for (; chanIt != chanIte; chanIt++) { 539 | std::deque::iterator userIt = ((chanIt->second)->_users).begin(); 540 | std::deque::iterator userIte = ((chanIt->second)->_users).end(); 541 | for (; userIt != userIte; userIt++) { 542 | if (user == *userIt) 543 | ((chanIt->second)->_users).erase(userIt); 544 | } 545 | } 546 | } 547 | 548 | void Server::killConnection(const int &fd) 549 | { 550 | std::map::iterator it; 551 | 552 | // check if fd/user exists using the userlist ---------------------------- / 553 | it = this->_userList.find(fd); 554 | if (it == this->_userList.end()) { 555 | throw Server::invalidFdException(); 556 | } 557 | 558 | // fd exists, clean all -------------------------------------------------- / 559 | // delete user and remove pair from list -------------------------------- / 560 | clearUser(this, it->second); 561 | delete it->second; 562 | this->_userList.erase(fd); 563 | 564 | // remove user's fd from the poll --------------------------------------- / 565 | if (epoll_ctl(this->_pollfd, EPOLL_CTL_DEL, fd, NULL) == -1) 566 | throw Server::pollDelException(); 567 | // close the socket ----------------------------------------------------- / 568 | if (close(fd) == -1) { 569 | throw Server::closeException(); 570 | } 571 | } 572 | 573 | // SEND CLIENT (ONE FD) 574 | void Server::sendClient(const int &fd, const std::string &message, \ 575 | const int &originFd) 576 | { 577 | if (originFd != -1 && originFd == fd) 578 | return; 579 | if (this->_userList.find(fd) == this->_userList.end()) 580 | throw Server::invalidFdException(); 581 | if (send(fd, message.c_str(), message.length(), MSG_NOSIGNAL) == -1) 582 | { clearUser(this, this->getUserByFd(fd)); } 583 | } 584 | 585 | // SEND CLIENT (MULTIPLE FDS) 586 | void Server::sendClient(const std::set &fds, \ 587 | const std::string &message, \ 588 | const int &originFd) 589 | { 590 | std::set::const_iterator it; 591 | 592 | for (it = fds.begin(); it != fds.end(); ++it) 593 | this->sendClient(*it, message, originFd); 594 | } 595 | 596 | // SEND CHANNEL 597 | void Server::sendChannel(const std::string &channel, \ 598 | const std::string &message, \ 599 | const int &originFd) 600 | { 601 | std::map::const_iterator itChannel; 602 | std::deque::const_iterator itUsers; 603 | std::deque userList; 604 | 605 | itChannel = this->_channelList.find(channel); 606 | if (itChannel == this->_channelList.end()) 607 | throw Server::invalidChannelException(); 608 | userList = itChannel->second->getUsers(); 609 | for (itUsers = userList.begin(); itUsers != userList.end(); ++itUsers) { 610 | if (((*itUsers)->getFd() != 0)) { 611 | this->sendClient((*itUsers)->getFd(), message, originFd); 612 | } 613 | } 614 | } 615 | 616 | // BROADCAST 617 | void Server::broadcast(const std::string &message,const int &originFd) 618 | { 619 | std::map::const_iterator it; 620 | std::set fds; 621 | 622 | for (it = this->_userList.begin(); it != this->_userList.end(); ++it) 623 | { 624 | fds.insert(it->first); 625 | this->sendClient(fds, message, originFd); 626 | } 627 | } 628 | 629 | /* ************************************************************************** */ 630 | /* Exceptions */ 631 | /* ************************************************************************** */ 632 | const char *Server::socketException::what() const throw() 633 | { return ("Socket creation or mode error: "); } 634 | 635 | const char *Server::bindException::what() const throw() 636 | { return ("Bind error: "); } 637 | 638 | const char *Server::pollException::what() const throw() 639 | { return ("Poll error: "); } 640 | 641 | const char *Server::pollWaitException::what() const throw() 642 | { return ("Poll wail error: "); } 643 | 644 | const char *Server::pollAddException::what() const throw() 645 | { return ("Poll add error: "); } 646 | 647 | const char *Server::pollDelException::what() const throw() 648 | { return ("Poll del error: "); } 649 | 650 | const char *Server::acceptException::what() const throw() 651 | { return ("Accept error: "); } 652 | 653 | const char *Server::passwordException::what() const throw() 654 | { return ("Password error: please provide a correct password"); } 655 | 656 | const char *Server::invalidFdException::what() const throw() 657 | { return ("Fd error: please provide a valid fd"); } 658 | 659 | const char *Server::invalidChannelException::what() const throw() 660 | { return ("Invalid Channel: please provide a valid channel"); } 661 | 662 | const char *Server::sendException::what() const throw() 663 | { return ("Send error: "); } 664 | 665 | const char *Server::readException::what() const throw() 666 | { return ("Read error: "); } 667 | 668 | const char *Server::closeException::what() const throw() 669 | { return ("Close error: "); } 670 | --------------------------------------------------------------------------------