commit d345c1127df9fa11d10ec352b44bce18743cb068 Author: Ladebeze66 Date: Thu Jun 6 14:16:52 2024 +0200 Initial commit diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..c2098a2 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "linux-gcc-x64", + "includePath": [ + "${workspaceFolder}/**" + ], + "compilerPath": "/usr/bin/gcc", + "cStandard": "${default}", + "cppStandard": "${default}", + "intelliSenseMode": "linux-gcc-x64", + "compilerArgs": [ + "" + ] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1f13c6c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "C/C++ Runner: Debug Session", + "type": "cppdbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "externalConsole": false, + "cwd": "/home/fgras-ca/Bureau/ft_irc/ft_irc/src", + "program": "/home/fgras-ca/Bureau/ft_irc/ft_irc/src/build/Debug/outDebug", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cddcf37 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,111 @@ +{ + "C_Cpp_Runner.cCompilerPath": "gcc", + "C_Cpp_Runner.cppCompilerPath": "g++", + "C_Cpp_Runner.debuggerPath": "gdb", + "C_Cpp_Runner.cStandard": "", + "C_Cpp_Runner.cppStandard": "", + "C_Cpp_Runner.msvcBatchPath": "", + "C_Cpp_Runner.useMsvc": false, + "C_Cpp_Runner.warnings": [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wformat=2", + "-Wcast-align", + "-Wconversion", + "-Wsign-conversion", + "-Wnull-dereference" + ], + "C_Cpp_Runner.msvcWarnings": [ + "/W4", + "/permissive-", + "/w14242", + "/w14287", + "/w14296", + "/w14311", + "/w14826", + "/w44062", + "/w44242", + "/w14905", + "/w14906", + "/w14263", + "/w44265", + "/w14928" + ], + "C_Cpp_Runner.enableWarnings": true, + "C_Cpp_Runner.warningsAsError": false, + "C_Cpp_Runner.compilerArgs": [], + "C_Cpp_Runner.linkerArgs": [], + "C_Cpp_Runner.includePaths": [], + "C_Cpp_Runner.includeSearch": [ + "*", + "**/*" + ], + "C_Cpp_Runner.excludeSearch": [ + "**/build", + "**/build/**", + "**/.*", + "**/.*/**", + "**/.vscode", + "**/.vscode/**" + ], + "C_Cpp_Runner.useAddressSanitizer": false, + "C_Cpp_Runner.useUndefinedSanitizer": false, + "C_Cpp_Runner.useLeakSanitizer": false, + "C_Cpp_Runner.showCompilationTime": false, + "C_Cpp_Runner.useLinkTimeOptimization": false, + "C_Cpp_Runner.msvcSecureNoWarnings": false, + "files.associations": { + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "random": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "typeinfo": "cpp", + "ctime": "cpp", + "map": "cpp", + "set": "cpp", + "fstream": "cpp" +} +} \ No newline at end of file diff --git a/en.subject (2).pdf b/en.subject (2).pdf new file mode 100644 index 0000000..e744cc1 --- /dev/null +++ b/en.subject (2).pdf @@ -0,0 +1,227 @@ + ft_irc + + Internet Relay Chat + + Summary: + This project is about creating your own IRC server. + You will use an actual IRC client to connect to your server and test it. +Internet is ruled by solid standards protocols that allow connected computers to interact + + with each other. + It’s always a good thing to know. + + Version: 8 + Contents + +I Introduction 2 + +II General rules 3 + +III Mandatory Part 4 + + III.1 Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 + + III.2 For MacOS only . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 + + III.3 Test example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 + +IV Bonus part 7 + +V Submission and peer-evaluation 8 + + 1 + Chapter I +Introduction + +Internet Relay Chat or IRC is a text-based communication protocol on the Internet. +It offers real-time messaging that can be either public or private. Users can exchange +direct messages and join group channels. + + IRC clients connect to IRC servers in order to join channels. IRC servers are connected +together to form a network. + + 2 + Chapter II +General rules + + • Your program should not crash in any circumstances (even when it runs out of + memory), and should not quit unexpectedly. + If it happens, your project will be considered non-functional and your grade will be + 0. + + • You have to turn in a Makefile which will compile your source files. It must not + relink. + + • Your Makefile must at least contain the rules: + $(NAME), all, clean, fclean and re. + + • Compile your code with c++ and the flags -Wall -Wextra -Werror + • Your code must comply with the C++ 98 standard. Then, it should still compile + + if you add the flag -std=c++98. + • Try to always develop using the most C++ features you can (for example, choose + + over ). You are allowed to use C functions, but always prefer + their C++ versions if possible. + • Any external library and Boost libraries are forbidden. + + 3 + Chapter III +Mandatory Part + +Program name ircserv +Turn in files Makefile, *.{h, hpp}, *.cpp, *.tpp, *.ipp, +Makefile an optional configuration file +Arguments NAME, all, clean, fclean, re +External functs. port: The listening port + password: The connection password +Libft authorized Everything in C++ 98. +Description socket, close, setsockopt, getsockname, + getprotobyname, gethostbyname, getaddrinfo, + freeaddrinfo, bind, connect, listen, accept, htons, + htonl, ntohs, ntohl, inet_addr, inet_ntoa, send, + recv, signal, sigaction, lseek, fstat, fcntl, poll + (or equivalent) + n/a + An IRC server in C++ 98 + +You have to develop an IRC server in C++ 98. + +You mustn’t develop a client. +You mustn’t handle server-to-server communication. + +Your executable will be run as follows: + ./ircserv + +• port: The port number on which your IRC server will be listening to for incoming + IRC connections. + +• password: The connection password. It will be needed by any IRC client that tries + to connect to your server. + +Even if poll() is mentionned in the subject and the evaluation scale, +you can use any equivalent such as select(), kqueue(), or epoll(). + + 4 + ft_irc Internet Relay Chat + +III.1 Requirements + + • The server must be capable of handling multiple clients at the same time and never + hang. + + • Forking is not allowed. All I/O operations must be non-blocking. + + • Only 1 poll() (or equivalent) can be used for handling all these operations (read, + write, but also listen, and so forth). + + Because you have to use non-blocking file descriptors, it is + possible to use read/recv or write/send functions with no poll() + (or equivalent), and your server wouldn’t be blocking. + But it would consume more system resources. + + Thus, if you try to read/recv or write/send in any file descriptor + + without using poll() (or equivalent), your grade will be 0. + +• Several IRC clients exist. You have to choose one of them as a reference. Your + reference client will be used during the evaluation process. + +• Your reference client must be able to connect to your server without encountering + any error. + +• Communication between client and server has to be done via TCP/IP (v4 or v6). + +• Using your reference client with your server must be similar to using it with any + official IRC server. However, you only have to implement the following features: + + ◦ You must be able to authenticate, set a nickname, a username, join a channel, + send and receive private messages using your reference client. + + ◦ All the messages sent from one client to a channel have to be forwarded to + every other client that joined the channel. + + ◦ You must have operators and regular users. + + ◦ Then, you have to implement the commands that are specific to channel + operators: + ∗ KICK - Eject a client from the channel + ∗ INVITE - Invite a client to a channel + ∗ TOPIC - Change or view the channel topic + ∗ MODE - Change the channel’s mode: + · i: Set/remove Invite-only channel + · t: Set/remove the restrictions of the TOPIC command to channel + operators + · k: Set/remove the channel key (password) + · o: Give/take channel operator privilege + + 5 + ft_irc Internet Relay Chat + · l: Set/remove the user limit to channel + + • Of course, you are expected to write a clean code. + +III.2 For MacOS only + +Since MacOS doesn’t implement write() the same way as other Unix +OSes, you are allowed to use fcntl(). +You must use file descriptors in non-blocking mode in order to get a +behavior similar to the one of other Unix OSes. + +However, you are allowed to use fcntl() only as follows: +fcntl(fd, F_SETFL, O_NONBLOCK); +Any other flag is forbidden. + +III.3 Test example + +Verify absolutely every possible error and issue (receiving partial data, low bandwidth, +and so forth). + + To ensure that your server correctly processes everything that you send to it, the +following simple test using nc can be done: + + \$> nc 127.0.0.1 6667 + com^Dman^Dd + \$> + + Use ctrl+D to send the command in several parts: ’com’, then ’man’, then ’d\n’. + + In order to process a command, you have to first aggregate the received packets in +order to rebuild it. + +6 + Chapter IV +Bonus part + +Here are the extra features you can add to your IRC server so it looks even more like and +actual IRC server: + + • Handle file transfer. + • A bot. + + The bonus part will only be assessed if the mandatory part is + PERFECT. Perfect means the mandatory part has been integrally done + and works without malfunctioning. If you have not passed ALL the + mandatory requirements, your bonus part will not be evaluated at all. + + 7 + Chapter V +Submission and peer-evaluation + +Turn in your assignment in your Git repository as usual. Only the work inside your repos- +itory will be evaluated during the defense. Don’t hesitate to double check the names of +your files to ensure they are correct. + + You are encouraged to create test programs for your project even though they won’t +be submitted and won’t be graded. Those tests could be especially useful to test +your server during defense, but also your peer’s if you have to evaluate another ft_irc +one day. Indeed, you are free to use whatever tests you need during the evaluation process. + + Your reference client will be used during the evaluation process. + + 16D85ACC441674FBA2DF65190663F432222F81AA0248081A7C1C1823F7A96F0B74495 + 15056E97427E5B22F07132659EC8D88B574BD62C94BB654D5835AAD889B014E078705 + 709F6E02 + + 8 + diff --git a/ft_irc3/.vscode/c_cpp_properties.json b/ft_irc3/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..c2098a2 --- /dev/null +++ b/ft_irc3/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "linux-gcc-x64", + "includePath": [ + "${workspaceFolder}/**" + ], + "compilerPath": "/usr/bin/gcc", + "cStandard": "${default}", + "cppStandard": "${default}", + "intelliSenseMode": "linux-gcc-x64", + "compilerArgs": [ + "" + ] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/ft_irc3/.vscode/launch.json b/ft_irc3/.vscode/launch.json new file mode 100644 index 0000000..670f779 --- /dev/null +++ b/ft_irc3/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "C/C++ Runner: Debug Session", + "type": "cppdbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "externalConsole": false, + "cwd": "/home/fgras-ca/Bureau/ft_irc3/src", + "program": "/home/fgras-ca/Bureau/ft_irc3/src/build/Debug/outDebug", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/ft_irc3/.vscode/settings.json b/ft_irc3/.vscode/settings.json new file mode 100644 index 0000000..08b6cd5 --- /dev/null +++ b/ft_irc3/.vscode/settings.json @@ -0,0 +1,120 @@ +{ + "files.associations": { + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "map": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "random": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "typeinfo": "cpp", + "list": "cpp", + "chrono": "cpp", + "condition_variable": "cpp", + "ctime": "cpp", + "set": "cpp", + "ratio": "cpp", + "mutex": "cpp", + "semaphore": "cpp", + "stop_token": "cpp", + "thread": "cpp", + "cinttypes": "cpp" + }, + "C_Cpp_Runner.cCompilerPath": "gcc", + "C_Cpp_Runner.cppCompilerPath": "g++", + "C_Cpp_Runner.debuggerPath": "gdb", + "C_Cpp_Runner.cStandard": "", + "C_Cpp_Runner.cppStandard": "", + "C_Cpp_Runner.msvcBatchPath": "", + "C_Cpp_Runner.useMsvc": false, + "C_Cpp_Runner.warnings": [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wformat=2", + "-Wcast-align", + "-Wconversion", + "-Wsign-conversion", + "-Wnull-dereference" + ], + "C_Cpp_Runner.msvcWarnings": [ + "/W4", + "/permissive-", + "/w14242", + "/w14287", + "/w14296", + "/w14311", + "/w14826", + "/w44062", + "/w44242", + "/w14905", + "/w14906", + "/w14263", + "/w44265", + "/w14928" + ], + "C_Cpp_Runner.enableWarnings": true, + "C_Cpp_Runner.warningsAsError": false, + "C_Cpp_Runner.compilerArgs": [], + "C_Cpp_Runner.linkerArgs": [], + "C_Cpp_Runner.includePaths": [], + "C_Cpp_Runner.includeSearch": [ + "*", + "**/*" + ], + "C_Cpp_Runner.excludeSearch": [ + "**/build", + "**/build/**", + "**/.*", + "**/.*/**", + "**/.vscode", + "**/.vscode/**" + ], + "C_Cpp_Runner.useAddressSanitizer": false, + "C_Cpp_Runner.useUndefinedSanitizer": false, + "C_Cpp_Runner.useLeakSanitizer": false, + "C_Cpp_Runner.showCompilationTime": false, + "C_Cpp_Runner.useLinkTimeOptimization": false, + "C_Cpp_Runner.msvcSecureNoWarnings": false +} \ No newline at end of file diff --git a/ft_irc3/Makefile b/ft_irc3/Makefile new file mode 100644 index 0000000..8f65f1c --- /dev/null +++ b/ft_irc3/Makefile @@ -0,0 +1,46 @@ +# **************************************************************************** # +# # +# ::: :::::::: # +# Makefile :+: :+: :+: # +# +:+ +:+ +:+ # +# By: fgras-ca +#+ +:+ +#+ # +# +#+#+#+#+#+ +#+ # +# Created: 2024/05/15 12:13:50 by fgras-ca #+# #+# # +# Updated: 2024/05/15 12:47:49 by fgras-ca ### ########.fr # +# # +# **************************************************************************** # + +CXX = g++ +CXXFLAGS = -Wall -Wextra -Werror -std=c++98 +LDFLAGS = -pthread + +SRC_DIR = src +INC_DIR = includes +OBJ_DIR = obj +LOG_DIR = logs + +SRCS = $(wildcard $(SRC_DIR)/*.cpp) +OBJS = $(SRCS:$(SRC_DIR)/%.cpp=$(OBJ_DIR)/%.o) + +NAME = ircserv + +all: $(NAME) + +$(NAME): $(OBJS) + $(CXX) $(CXXFLAGS) -I$(INC_DIR) -o $@ $^ $(LDFLAGS) + mkdir -p $(LOG_DIR) + touch $(LOG_DIR)/irc_server.log + +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp + mkdir -p $(OBJ_DIR) + $(CXX) $(CXXFLAGS) -I$(INC_DIR) -c $< -o $@ + +clean: + rm -rf $(OBJ_DIR) $(NAME) + +fclean: clean + rm -rf $(LOG_DIR) + +re: fclean all + +.PHONY: all clean fclean re diff --git a/ft_irc3/badwords.txt b/ft_irc3/badwords.txt new file mode 100644 index 0000000..ad3e7dc --- /dev/null +++ b/ft_irc3/badwords.txt @@ -0,0 +1,50 @@ +fuck +Fuck +fucking +fucking +Shit +shit +Shitty +shitty +Bullshit +bullshit +shithead +Shithead +Scumbag +scumbag +asshole +Asshole +Cocksucker +cocksucker +Dickhead +dickhead +Suck my dick +suck my dick! +Ass +ass +Bastard +Bastard +connard +Connard +Va te faire foutre +va te faire foutre +ferme ta gueule +Ferme ta gueule +Pute +pute +Salope +salope +trou du cul +Trou du cul +sac à merde +Sac à merde +casse-couilles +Casse-couilles +tête de bite +Tête de bite +bite +bite +pétasse +Pétasse +Fini à la pisse +fini à la pisse \ No newline at end of file diff --git a/ft_irc3/cmd b/ft_irc3/cmd new file mode 100644 index 0000000..e69de29 diff --git a/ft_irc3/includes/AdditionalCommands.hpp b/ft_irc3/includes/AdditionalCommands.hpp new file mode 100644 index 0000000..d5a13c7 --- /dev/null +++ b/ft_irc3/includes/AdditionalCommands.hpp @@ -0,0 +1,48 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* AdditionalCommands.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/21 18:09:05 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 18:48:32 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef ADDITIONALCOMMANDS_HPP +#define ADDITIONALCOMMANDS_HPP + +#include "Client.hpp" +#include "Server.hpp" +#include "Channel.hpp" +#include "CommandHandler.hpp" +#include "Utils.hpp" +#include "RPL.hpp" +#include "Who.hpp" +#include "InviteHandler.hpp" +#include "TopicHandler.hpp" +#include "KickHandler.hpp" + +#include +#include +#include + +class Server; +class Client; +class Channel; +class AdditionalCommands +{ + private: + Server *_server; + + public: + AdditionalCommands(Server *server); + void processCommand(Client *client, const std::string &command); + void broadcastChannelList(Client *client, Server *server); + void handlePartCommand(Server *server, Client *client, const std::string &command); + void handleNickCommand(Server *server, Client *client, const std::string &command); + void handlePrivmsgCommand(Server *server, Client *client, const std::string &command); +}; + +#endif diff --git a/ft_irc3/includes/BotFilter.hpp b/ft_irc3/includes/BotFilter.hpp new file mode 100644 index 0000000..c3c58de --- /dev/null +++ b/ft_irc3/includes/BotFilter.hpp @@ -0,0 +1,45 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* BotFilter.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/06/06 11:44:44 by fgras-ca #+# #+# */ +/* Updated: 2024/06/06 13:41:41 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef BOTFILTER_HPP +#define BOTFILTER_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include "Server.hpp" +#include "Client.hpp" +#include "Channel.hpp" + +class BotFilter +{ +public: + BotFilter(Server *server); + void loadBadWords(const std::string &fileName); + bool checkMessage(Client *client, Channel *channel, const std::string &message); + +private: + Server *_server; + std::vector _badWords; + std::map _warnings; + + bool containsBadWords(const std::string &message); + void warnClient(Client *client, Channel *channel); + void kickClient(Client *client, Channel *channel); +}; + +#endif diff --git a/ft_irc3/includes/Channel.hpp b/ft_irc3/includes/Channel.hpp new file mode 100644 index 0000000..605618d --- /dev/null +++ b/ft_irc3/includes/Channel.hpp @@ -0,0 +1,81 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Channel.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 12:41:35 by fgras-ca #+# #+# */ +/* Updated: 2024/06/04 16:12:23 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef CHANNEL_HPP +#define CHANNEL_HPP + +#include +#include +#include +#include +#include + +#include "RPL.hpp" +#include "Client.hpp" +#include "Server.hpp" + +class Server; +class Client; + +class Channel +{ + public: + Channel(const std::string &name); + ~Channel(); + + const std::string &getName() const; + void addClient(Client *client); + void removeClient(Client *client); + bool isEmpty() const; + const std::vector &getClients() const; + void addOperator(Client *client); + bool isOperator(Client *client) const; + void removeOperator(Client *client); // Ajouté + bool hasClient(Client *client) const; + void broadcast(const std::string &message, Client *_client, Server *_server); + bool isBanned(Client *client) const; + bool isFull() const; + bool isInviteOnly() const; + bool isInvited(Client *client) const; + bool checkKey(const std::string &key) const; + const std::string &getTopic() const; + const std::string &getTopicSetter() const; + time_t getTopicTime() const; + void setTopic(const std::string &topic, const std::string &setter); + std::string getKey(); + + void setClientLimit(size_t limit); + size_t getClientLimit() const; + void setInviteOnly(bool inviteOnly); + void setKey(const std::string &key); + void setTopicProtection(bool protection); + std::string getModes() const; + bool getTopicProtection() const; + void addInvitedClient(Client* client); + + private: + std::string _name; + std::vector _clients; + std::vector _operators; + std::set _bannedClients; + std::set _invitedClients; + std::string _key; + std::string _topic; + std::string _topicSetter; + time_t _topicTime; + size_t _clientLimit; + bool _inviteOnly; + bool _topicProtection; + +}; + +#endif diff --git a/ft_irc3/includes/Client.hpp b/ft_irc3/includes/Client.hpp new file mode 100644 index 0000000..f52845a --- /dev/null +++ b/ft_irc3/includes/Client.hpp @@ -0,0 +1,68 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Client.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 12:15:42 by fgras-ca #+# #+# */ +/* Updated: 2024/06/06 13:23:23 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef CLIENT_HPP +#define CLIENT_HPP + +#include +#include + +class Client +{ +public: + Client(int fd, const std::string &nickname, const std::string &user, const std::string &host, const std::string &password, const std::string &realname); + + int getFd() const; + const std::string &getNickname() const; + void setNickname(const std::string &nickname); + const std::string &getUser() const; + void setUser(const std::string &user); + const std::string &getHost() const; + void setHost(const std::string &host); + const std::string &getPassword() const; + void setPassword(const std::string &password); + const std::string &getRealName() const; + void setRealName(const std::string &realname); + bool isAuthenticated() const; + void authenticate(); + bool isOperator() const; + void setOperator(bool isOperator); + + // Ajout des méthodes pour la gestion du statut "away" + bool isAway() const; + const std::string &getAwayMessage() const; + void setAwayMessage(const std::string &message); + void setAway(bool away); + std::string getKey() const; + void setkey(const std::string &key); + char buffer[1024]; + char buffer2[1024]; + void joinChannel(const std::string &channel); + void leaveChannel(const std::string &channel); + const std::set &getChannels() const; + +private: + int _fd; + std::string _nickname; + std::string _user; + std::string _host; + std::string _password; + std::string _realname; + bool _authenticated; + bool _operator; + bool _away; + std::string _awayMessage; + std::string _key; + std::set _channels; +}; + +#endif diff --git a/ft_irc3/includes/ClientManager.hpp b/ft_irc3/includes/ClientManager.hpp new file mode 100644 index 0000000..0844e76 --- /dev/null +++ b/ft_irc3/includes/ClientManager.hpp @@ -0,0 +1,56 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* ClientManager.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 18:30:07 by fgras-ca #+# #+# */ +/* Updated: 2024/06/06 13:01:05 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef CLIENTMANAGER_HPP +#define CLIENTMANAGER_HPP + +#include "Client.hpp" +#include "Channel.hpp" +#include "Server.hpp" +#include "CommandHandler.hpp" +#include "RPL.hpp" +#include "Utils.hpp" +#include "BotFilter.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Server; +class Client; +class BotFilter; + +class ClientManager +{ + public: + ClientManager(Server *server); + void acceptClient(); + void handleClientNext(int client_fd, char * buffer, int bytes_received); + void handleClient(int client_fd); + void removeClient(int client_fd); + + private: + Server *_server; + BotFilter *_botFilter; +}; + +#endif diff --git a/ft_irc3/includes/CommandHandler.hpp b/ft_irc3/includes/CommandHandler.hpp new file mode 100644 index 0000000..3ca3a75 --- /dev/null +++ b/ft_irc3/includes/CommandHandler.hpp @@ -0,0 +1,62 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* CommandHandler.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 18:14:12 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 18:47:51 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef COMMANDHANDLER_HPP +#define COMMANDHANDLER_HPP + +#include "Utils.hpp" +#include "Server.hpp" +#include "Client.hpp" +#include "Channel.hpp" +#include "Who.hpp" +#include "AdditionalCommands.hpp" +#include "RPL.hpp" +#include "Join.hpp" +#include "Welcome.hpp" +#include "ModeHandler.hpp" + +#include +#include +#include + +class Server; +class Client; +class Channel; +class WhoHandler; +class AdditionalCommands; +class ModeHandler; + +class CommandHandler +{ +private: + Server *_server; + AdditionalCommands *_additionalCommands; + ModeHandler *_modeHandler; + + +public: + CommandHandler(Server *server); + bool isValidNickname(const std::string& nickname); + bool isNicknameInUse(const std::string& nickname); + void handleNick(Client* client, const std::vector& tokens); + + void handleUser(Client* client, const std::vector& tokens); + void handlePingCommand(Client* client, const std::vector& tokens); + void handleCommand(Client *client, const std::string &command); + void handleCapCommand(Client* client, const std::vector& tokens); + void handlePassCommand(Client* client, const std::vector& tokens); + void handleQuitCommand(Client* client, const std::vector& tokens); + void handleErrorCommand(Client* client, const std::string &message); + +}; + +#endif \ No newline at end of file diff --git a/ft_irc3/includes/InviteHandler.hpp b/ft_irc3/includes/InviteHandler.hpp new file mode 100644 index 0000000..6d74f64 --- /dev/null +++ b/ft_irc3/includes/InviteHandler.hpp @@ -0,0 +1,40 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* InviteHandler.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/30 13:01:50 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 18:51:00 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef INVITEHANDLER_HPP +#define INVITEHANDLER_HPP + +#include +#include +#include + +#include "Server.hpp" +#include "Client.hpp" +#include "Channel.hpp" +#include "RPL.hpp" +#include "Utils.hpp" + +class Server; +class Client; +class Channel; + +class InviteHandler +{ + public: + InviteHandler(Server* server); + void handleInviteCommand(Client* client, const std::string& command); + + private: + Server* _server; +}; + +#endif diff --git a/ft_irc3/includes/Join.hpp b/ft_irc3/includes/Join.hpp new file mode 100644 index 0000000..c44393a --- /dev/null +++ b/ft_irc3/includes/Join.hpp @@ -0,0 +1,40 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Join.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/21 19:51:08 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 18:51:10 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef JOIN_HPP +#define JOIN_HPP + +#include +#include "Client.hpp" +#include "Server.hpp" +#include "Channel.hpp" +#include "RPL.hpp" +#include "Utils.hpp" +#include "Server.hpp" +#include "Channel.hpp" +#include "Client.hpp" + +#include +#include +#include +#include + +class JoinHandler +{ + public: + void handleJoinCommand(Client *client, const std::string &channelName, Server *server); + void sendJoinSuccess(Client *client, Channel *channel, Server *server); + std::string getUsersList(Channel *channel); + +}; + +#endif diff --git a/ft_irc3/includes/KickHandler.hpp b/ft_irc3/includes/KickHandler.hpp new file mode 100644 index 0000000..7c00a4d --- /dev/null +++ b/ft_irc3/includes/KickHandler.hpp @@ -0,0 +1,35 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* KickHandler.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/06/01 16:59:42 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 18:51:26 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef KICKHANDLER_HPP +#define KICKHANDLER_HPP + +#include "Server.hpp" +#include "Client.hpp" +#include "Channel.hpp" +#include "Utils.hpp" + +#include +#include +#include + +class KickHandler +{ + public: + KickHandler(Server* server); + void handleKickCommand(Client* client, const std::string& command); + + private: + Server* _server; +}; + +#endif diff --git a/ft_irc3/includes/ModeHandler.hpp b/ft_irc3/includes/ModeHandler.hpp new file mode 100644 index 0000000..593def9 --- /dev/null +++ b/ft_irc3/includes/ModeHandler.hpp @@ -0,0 +1,52 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* ModeHandler.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/30 11:12:57 by fgras-ca #+# #+# */ +/* Updated: 2024/06/04 14:00:47 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef MODEHANDLER_HPP +#define MODEHANDLER_HPP + +#include +#include +#include +#include +#include + +#include "Client.hpp" +#include "Channel.hpp" +#include "Server.hpp" +#include "RPL.hpp" +#include "Utils.hpp" + +class Server; +class Client; +class Channel; + +class ModeHandler +{ + public: + ModeHandler(Server* server); + void handleModeCommand(Client* client, const std::string& command); + + private: + Server* _server; + + void handleUserMode(Client* client, const std::vector& tokens); + void handleChannelMode(Client* client, const std::vector& tokens); + + void setChannelMode(Client *client, Channel* channel, const std::string& mode, bool adding, const std::string& argument); + void applyModeL(Client *client, Channel* channel, bool adding, const std::string& argument); + void applyModeI(Client *client, Channel* channel, bool adding); + void applyModeK(Client *client, Channel* channel, bool adding, const std::string& argument); + void applyModeT(Channel* channel, bool adding); + void applyModeO(Client *client, Channel* channel, bool adding, const std::string& argument); +}; + +#endif diff --git a/ft_irc3/includes/RPL.hpp b/ft_irc3/includes/RPL.hpp new file mode 100644 index 0000000..9632dd1 --- /dev/null +++ b/ft_irc3/includes/RPL.hpp @@ -0,0 +1,552 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* RPL.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/19 15:12:47 by fgras-ca #+# #+# */ +/* Updated: 2024/06/06 12:28:50 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef RPL_HPP +#define RPL_HPP + +#include +#include + +#include "Client.hpp" +#include "Utils.hpp" +#include "Channel.hpp" + +#define SERVER_NAME "IRC_Server" +#define SERVER_VERSION "1.0" +#define USER_MODES "None" +#define CHANNEL_MODES "l" +#define CHANNEL_MODES_WITH_PARAMS "l" + + +#define CLIENT_FD(client) (client->getFd()) +#define CLIENT_NICK(client) ((client)->getNickname()) +#define CLIENT_USER(client) ((client)->getUser()) +#define CLIENT_HOST(client) ((client)->getHost()) +#define CLIENT_REALNAME(client) ((client)->getRealName()) + +inline std::string RPL_WELCOME(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 001 " << CLIENT_NICK(client) + << " :Welcome to the Internet Relay Network " << CLIENT_NICK(client) + << "!" << CLIENT_USER(client) << "@" << CLIENT_HOST(client) << "\r\n"; + return oss.str(); +} + +inline std::string RPL_YOURHOST(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 002 " << CLIENT_NICK(client) + << " :Your host is " << SERVER_NAME << ", running version " << SERVER_VERSION << "\r\n"; + return oss.str(); +} + +inline std::string RPL_CREATED(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 003 " << CLIENT_NICK(client) + << " :This server was created " << __DATE__ << "\r\n"; + return oss.str(); +} + +inline std::string RPL_MYINFO(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 004 " << CLIENT_NICK(client) << " " + << SERVER_NAME << " " << SERVER_VERSION << " " + << USER_MODES << " " << CHANNEL_MODES << " " + << CHANNEL_MODES_WITH_PARAMS << "\r\n"; + return oss.str(); +} + +inline std::string RPL_ISUPPORT(Client* client, const std::string& tokens) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 005 " << CLIENT_NICK(client) + << " " << tokens << " :are supported by this server\r\n"; + return oss.str(); +} + +inline std::string RPL_UMODEIS(Client* client, const std::string& modes) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 221 " << CLIENT_NICK(client) << " :" << modes << "\r\n"; + return oss.str(); +} + +inline std::string RPL_AWAY(Client *client, const std::string& target, const std::string& message) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 301 " << CLIENT_NICK(client) << " " << target << " :" << message << "\r\n"; + return oss.str(); +} + +inline std::string RPL_WHOISUSER(Client *client, Client* target) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 311 " << CLIENT_NICK(client) << " " << CLIENT_NICK(target) << " " + << CLIENT_USER(target) << " " << CLIENT_HOST(target) << " * :" << CLIENT_REALNAME(target) << "\r\n"; + return oss.str(); +} + +inline std::string RPL_WHOISSERVER(Client *client, const std::string& targetNick, const std::string& serverInfo) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 312 " << CLIENT_NICK(client) << " " << targetNick << " " << SERVER_NAME + << " :" << serverInfo << "\r\n"; + return oss.str(); +} + +inline std::string RPL_ENDOFWHO(Client *client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 315 " << CLIENT_NICK(client) << " " << channel << " :End of /WHO list.\r\n"; + return oss.str(); +} + +inline std::string RPL_ENDOFWHOIS(Client *client, const std::string& targetNick) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 318 " << CLIENT_NICK(client) << " " << targetNick << " :End of /WHOIS list.\r\n"; + return oss.str(); +} + +inline std::string RPL_LIST(Client *client, const std::string& channel, int numVisible, const std::string& topic) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 322 " << CLIENT_NICK(client) << " " << channel << " " << numVisible << " :" << topic << "\r\n"; + return oss.str(); +} + +inline std::string RPL_LISTEND(Client *client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 323 " << CLIENT_NICK(client) << " :End of /LIST\r\n"; + return oss.str(); +} + +inline std::string RPL_CHANNELMODEIS(int clientFd, const std::string& channel, const std::string& mode) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 324 " << clientFd << " " << channel << " " << mode << "\r\n"; + return oss.str(); +} + +inline std::string RPL_CREATIONTIME(Client *client, const std::string& channel, time_t creationTime) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 329 " << CLIENT_NICK(client) << " " << channel << " " << creationTime << "\r\n"; + return oss.str(); +} + +inline std::string RPL_NOTOPIC(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 331 " << CLIENT_NICK(client) << " " << channel << " :No topic is set\r\n"; + return oss.str(); +} + +inline std::string RPL_TOPIC(Client* client, const std::string& channel, const std::string& topic) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 332 " << CLIENT_NICK(client) << " " << channel << " :" << topic << "\r\n"; + return oss.str(); +} + +inline std::string RPL_TOPICWHOTIME(Client* client, const std::string& channel, const std::string& setter, time_t setTime) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 333 " << CLIENT_NICK(client) << " " << channel << " " << setter << " " << setTime << "\r\n"; + return oss.str(); +} + +inline std::string RPL_INVITELIST(Client *client, const std::string& channel, const std::string& inviteMask) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 336 " << CLIENT_NICK(client) << " " << channel << " " << inviteMask << "\r\n"; + return oss.str(); +} + +inline std::string RPL_ENDOFINVITELIST(Client *client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 337 " << CLIENT_NICK(client) << " " << channel << " :End of channel invite list\r\n"; + return oss.str(); +} + +inline std::string RPL_INVITING(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 341 " << CLIENT_NICK(client) << " " << CLIENT_NICK(client) << " " << channel << "\r\n"; + return oss.str(); +} + +inline std::string RPL_EXCEPTLIST(Client *client, const std::string& channel, const std::string& exceptionMask) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 348 " << CLIENT_NICK(client) << " " << channel << " " << exceptionMask << "\r\n"; + return oss.str(); +} + +inline std::string RPL_ENDOFEXCEPTLIST(Client *client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 349 " << CLIENT_NICK(client) << " " << channel << " :End of channel exception list\r\n"; + return oss.str(); +} + +inline std::string RPL_NAMREPLY(Client* client, const std::string& channel, const std::string& users) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 353 " << CLIENT_NICK(client) << " = " << channel << " :" << users << "\r\n"; + return oss.str(); +} + +inline std::string RPL_WHOREPLY(const std::string& channel, Client* target) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 352 " << CLIENT_NICK(target) << " " << channel << " " + << CLIENT_USER(target) << " " << CLIENT_HOST(target) << " " << SERVER_NAME << " " + << CLIENT_NICK(target) << " H :0 " << CLIENT_REALNAME(target) << "\r\n"; + return oss.str(); +} + +inline std::string RPL_ENDOFNAMES(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 366 " << CLIENT_NICK(client) << " " << channel << " :End of /NAMES list\r\n"; + return oss.str(); +} + +inline std::string RPL_BANLIST(Client *client, const std::string& channel, const std::string& banMask) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 367 " << CLIENT_NICK(client) << " " << channel << " " << banMask << "\r\n"; + return oss.str(); +} + +inline std::string RPL_ENDOFBANLIST(Client *client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 368 " << CLIENT_NICK(client) << " " << channel << " :End of channel ban list\r\n"; + return oss.str(); +} + +inline std::string RPL_MOTD(Client* client, const std::string& line) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 372 " << CLIENT_NICK(client) + << " :- " << line << "\r\n"; + return oss.str(); +} + +inline std::string RPL_MOTDSTART(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 375 " << CLIENT_NICK(client) + << " :- " << SERVER_NAME << " Message of the day - \r\n"; + return oss.str(); +} + +inline std::string RPL_ENDOFMOTD(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 376 " << CLIENT_NICK(client) + << " :End of /MOTD command.\r\n"; + return oss.str(); +} + +inline std::string ERR_NOSUCHNICK(Client *client, const std::string& nick) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 401 " << CLIENT_NICK(client) << " " << nick << " :No such nick/channel\r\n"; + return oss.str(); +} + +inline std::string ERR_NOSUCHCHANNEL(Client *client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 403 " << CLIENT_NICK(client) << " " << channel << " :No such channel\r\n"; + return oss.str(); +} + +inline std::string ERR_CANNOTSENDTOCHAN(Client *client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 404 " << CLIENT_NICK(client) << " " << channel << " :Cannot send to channel\r\n"; + return oss.str(); +} + +inline std::string ERR_TOOMANYCHANNELS(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 405 " << CLIENT_NICK(client) << " " << channel << " :You have joined too many channels\r\n"; + return oss.str(); +} + +// RPL Error Messages +inline std::string ERR_NOORIGIN(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 409 " << CLIENT_NICK(client) << " :No origin specified\r\n"; + return oss.str(); +} + +inline std::string ERR_NORECIPIENT(Client *client, const std::string& command) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 411 " << CLIENT_NICK(client) << " :No recipient given (" << command << ")\r\n"; + return oss.str(); +} + +inline std::string ERR_NOTEXTTOSEND(Client *client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 412 " << CLIENT_NICK(client) << " :No text to send\r\n"; + return oss.str(); +} + +inline std::string ERR_UNKNOWNCOMMAND(Client* client, const std::string& command) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 421 " << CLIENT_NICK(client) << " " << command << " :Unknown command\r\n"; + return oss.str(); +} + +inline std::string ERR_NOMOTD(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 422 " << CLIENT_NICK(client) + << " :MOTD File is missing\r\n"; + return oss.str(); +} + +inline std::string ERR_NONICKNAMEGIVEN(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 431 " << CLIENT_NICK(client) << " :No nickname given\r\n"; + return oss.str(); +} + +inline std::string ERR_ERRONEUSNICKNAME(Client* client, const std::string& nickname) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 432 " << CLIENT_NICK(client) << " " << nickname << " :Erroneous nickname\r\n"; + return oss.str(); +} + +inline std::string ERR_NICKNAMEINUSE(Client* client, const std::string& nickname) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 433 " << CLIENT_NICK(client) << " " << nickname << " :Nickname is already in use\r\n"; + return oss.str(); +} + +inline std::string ERR_USERNOTINCHANNEL(Client *client, const std::string& nick, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 441 " << CLIENT_NICK(client) << " " << nick << " " << channel + << " :They aren't on that channel\r\n"; + return oss.str(); +} + +inline std::string ERR_NOTONCHANNEL(Client *client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 442 " << CLIENT_NICK(client) << " " << channel << " :You're not on that channel\r\n"; + return oss.str(); +} + +// Ajoutez cette fonction pour ERR_USERONCHANNEL +inline std::string ERR_USERONCHANNEL(Client* client, const std::string& nick, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 443 " << CLIENT_NICK(client) << " " << nick << " " << channel + << " :is already on channel\r\n"; + return oss.str(); +} + +inline std::string ERR_NOTREGISTERED(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 451 " << CLIENT_NICK(client) << " :You have not registered\r\n"; + return oss.str(); +} + +//RPL Password +// RPL Error Messages +inline std::string ERR_NEEDMOREPARAMS(Client* client, const std::string& command) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 461 " << CLIENT_NICK(client) << " " << command << " :Not enough parameters\r\n"; + return oss.str(); +} + +inline std::string ERR_ALREADYREGISTERED(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 462 " << CLIENT_NICK(client) << " :You may not reregister\r\n"; + return oss.str(); +} + +inline std::string ERR_PASSWDMISMATCH(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 464 " << CLIENT_NICK(client) << " :Password incorrect\r\n"; + return oss.str(); +} + +inline std::string ERR_INVALIDKEY(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 467 " << CLIENT_NICK(client) << " " << channel << " :Invalid key\r\n"; + return oss.str(); +} + +inline std::string ERR_KEYSET(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 467 " << CLIENT_NICK(client) << " " << channel + << " :Channel key already set\r\n"; + return oss.str(); +} + +inline std::string ERR_LINKSET(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 469 " << CLIENT_NICK(client) << " " << channel + << " :No key set\r\n"; + return oss.str(); +} + +inline std::string ERR_CHANNELISFULL(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 471 " << CLIENT_NICK(client) << " " << channel << " :Cannot join channel (channel is full)\r\n"; + return oss.str(); +} + +inline std::string ERR_UNKNOWNMODE(Client* client, char mode, const std::string& channelName) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 472 " << CLIENT_NICK(client) << " " << mode << " :is unknown mode char to me for " << channelName << "\r\n"; + return oss.str(); +} + +inline std::string ERR_INVITEONLYCHAN(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 473 " << CLIENT_NICK(client) << " " << channel << " :Cannot join channel (invite only)\r\n"; + return oss.str(); +} + +inline std::string ERR_BANNEDFROMCHAN(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 474 " << CLIENT_NICK(client) << " " << channel << " :Cannot join channel (banned)\r\n"; + return oss.str(); +} + +inline std::string ERR_BADCHANNELKEY(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 475 " << CLIENT_NICK(client) << " " << channel << " :Cannot join channel (incorrect key)\r\n"; + return oss.str(); +} + +inline std::string ERR_BADCHANMASK(Client* client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 476 " << CLIENT_NICK(client) << " " << channel << " :Bad channel mask\r\n"; + return oss.str(); +} + +inline std::string RPL_NOCHANMODES(Client *client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 477 " << CLIENT_NICK(client) << " " << channel << " :Channel doesn't support modes\r\n"; + return oss.str(); +} + +inline std::string ERR_CHANOPRIVSNEEDED(Client *client, const std::string& channel) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 482 " << CLIENT_NICK(client) << " " << channel << " :You're not channel operator\r\n"; + return oss.str(); +} + +inline std::string ERR_UMODEUNKNOWNFLAG(Client *client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 501 " << CLIENT_NICK(client) << " :Unknown MODE flag\r\n"; + return oss.str(); +} + +inline std::string ERR_USERSDONTMATCH(Client *client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 502 " << CLIENT_NICK(client) << " :Cannot change mode for other users\r\n"; + return oss.str(); +} + +inline std::string ERR_INVALIDMODEPARAM(Client* client, const std::string& param) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " 696 " << CLIENT_NICK(client) << " " << param << " :Invalid mode parameter\r\n"; + return oss.str(); +} + +inline std::string RPL_PONG(const std::string& token) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " PONG " << SERVER_NAME << " " << token << "\r\n"; + return oss.str(); +} + +inline std::string RPL_CAP(Client *client, const std::string& subcommand, const std::string& capabilities) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " CAP " << CLIENT_NICK(client) << " " << subcommand << " :" << capabilities << "\r\n"; + return oss.str(); +} + +inline std::string RPL_PASSACCEPTED(Client* client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " NOTICE " << CLIENT_NICK(client) + << " :Password accepted!\r\n"; + return oss.str(); +} + +inline std::string RPL_CAPEND(Client *client) +{ + std::ostringstream oss; + oss << ":" << SERVER_NAME << " CAP " << CLIENT_NICK(client) << " END\r\n"; + return oss.str(); +} + +inline std::string MODEACCEPTMESSAGE(Client *client, std::string channel, const std::string& mode) +{ + std::ostringstream oss; + oss << ":" << client->getNickname() << " MODE " << channel << " " << mode << " :" << "\r\n"; + return oss.str(); +} + + +inline std::string BOTMESSAGE(Client *client, std::string channel, const std::string& message) +{ + std::ostringstream oss; + oss << ":" << client->getNickname() << " WARNING " << channel << " " << message << " :" << "\r\n"; + return oss.str(); +} + +#endif diff --git a/ft_irc3/includes/Server.hpp b/ft_irc3/includes/Server.hpp new file mode 100644 index 0000000..e361aad --- /dev/null +++ b/ft_irc3/includes/Server.hpp @@ -0,0 +1,91 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Server.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 12:15:13 by fgras-ca #+# #+# */ +/* Updated: 2024/06/06 12:04:58 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef SERVER_HPP +#define SERVER_HPP + +#include "Utils.hpp" +#include "Client.hpp" +#include "Channel.hpp" +#include "ClientManager.hpp" +#include "CommandHandler.hpp" +#include "AdditionalCommands.hpp" +#include "RPL.hpp" +#include "ModeHandler.hpp" +#include "TopicHandler.hpp" +#include "BotFilter.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Client; +class Channel; +class ClientManager; +class CommandHandler; +class AdditionalCommands; +class ModeHandler; +class TopicHandler; +class BotFilter; + +class Server +{ + public: + Server(int port, const std::string &password); + ~Server(); + void run(); + void log(const std::string &message, const std::string &color = "\033[0m"); + void sendToClient(int client_fd, const std::string &message); + + std::map &getChannels(); + std::map &getClients(); + const std::string &getPassword() const; + void broadcast(const std::string &message); + Client* getClientByName(const std::string &name); + Channel* getChannelByName(const std::string &name); + void disconnectClient(int clientFd); + bool MatchFd(const pollfd& pfd, int clientFd); + void removePollFd(int clientFd); + + protected: + int _server_fd; + int _port; + std::string _password; + std::map _clients; + std::map _channels; + std::vector _poll_fds; + ClientManager *_clientManager; + CommandHandler *_commandHandler; + ModeHandler *_modeHandler; + TopicHandler *_topicHandler; + BotFilter *_botFilter; + + friend class ClientManager; + friend class CommandHandler; + friend class ModeHandler; + friend class TopicHandler; + + private: + void initServer(); + void handleServerCommands(); +}; + +#endif diff --git a/ft_irc3/includes/TopicHandler.hpp b/ft_irc3/includes/TopicHandler.hpp new file mode 100644 index 0000000..b92ee40 --- /dev/null +++ b/ft_irc3/includes/TopicHandler.hpp @@ -0,0 +1,38 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* TopicHandler.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/30 17:04:33 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 19:02:10 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef TOPIC_HANDLER_HPP +#define TOPIC_HANDLER_HPP + +#include +#include +#include +#include "Server.hpp" +#include "Client.hpp" +#include "Channel.hpp" +#include "Utils.hpp" + + +class TopicHandler +{ + public: + TopicHandler(Server* server); + void handleTopicCommand(Client* client, const std::string& command); + + private: + void viewTopic(Client* client, Channel* channel); + void changeTopic(Client* client, Channel* channel, const std::string& topic); + + Server* _server; +}; + +#endif diff --git a/ft_irc3/includes/Utils.hpp b/ft_irc3/includes/Utils.hpp new file mode 100644 index 0000000..31d0e71 --- /dev/null +++ b/ft_irc3/includes/Utils.hpp @@ -0,0 +1,30 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Utils.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 12:16:02 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 15:13:40 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef UTILS_HPP +#define UTILS_HPP + +#include +#include +#include + +#define RESET "\033[0m" +#define RED "\033[1;31m" +#define GREEN "\033[1;32m" +#define YELLOW "\033[1;33m" +#define BLUE "\033[1;34m" +#define MAGENTA "\033[1;35m" +#define CYAN "\033[1;36m" + +std::vector split(const std::string &input, const std::string &delimiters); + +#endif diff --git a/ft_irc3/includes/Welcome.hpp b/ft_irc3/includes/Welcome.hpp new file mode 100644 index 0000000..aa3f330 --- /dev/null +++ b/ft_irc3/includes/Welcome.hpp @@ -0,0 +1,29 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Welcome.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/21 19:53:17 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 19:02:40 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef WELCOME_HPP +#define WELCOME_HPP + +#include "Client.hpp" +#include "Server.hpp" +#include "Utils.hpp" + +#include + +class WelcomeHandler +{ + public: + void sendWelcomeMessages(Client *client, Server *server); + void sendMotd(Client *client, Server *server); +}; + +#endif diff --git a/ft_irc3/includes/Who.hpp b/ft_irc3/includes/Who.hpp new file mode 100644 index 0000000..f32f32a --- /dev/null +++ b/ft_irc3/includes/Who.hpp @@ -0,0 +1,38 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Who.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/17 16:08:48 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 19:03:06 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#ifndef WHO_HPP +#define WHO_HPP + +#include "Server.hpp" +#include "Client.hpp" +#include "Channel.hpp" +#include "Utils.hpp" +#include "RPL.hpp" + +#include +#include + +class Server; +class WhoHandler +{ + public: + WhoHandler(Server *server); + void handleWhoCommand(Client *client, const std::string &command); + void handleWhoisCommand(Client *client, const std::string &command); + + private: + Server *_server; + +}; + +#endif diff --git a/ft_irc3/motd.txt b/ft_irc3/motd.txt new file mode 100644 index 0000000..ff24924 --- /dev/null +++ b/ft_irc3/motd.txt @@ -0,0 +1,9 @@ + +░▒▓████████▓▒░▒▓████████▓▒░▒▓█▓▒░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓███████▓▒░ +░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ +░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ +░▒▓██████▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░▒▓███████▓▒░░▒▓█▓▒░ ░▒▓████████▓▒░░▒▓██████▓▒░ +░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░ +░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░ +░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░▒▓████████▓▒░ + \ No newline at end of file diff --git a/ft_irc3/src/AdditionalCommands.cpp b/ft_irc3/src/AdditionalCommands.cpp new file mode 100644 index 0000000..6c9740c --- /dev/null +++ b/ft_irc3/src/AdditionalCommands.cpp @@ -0,0 +1,188 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* AdditionalCommands.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/16 15:27:29 by fgras-ca #+# #+# */ +/* Updated: 2024/06/04 13:55:09 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "AdditionalCommands.hpp" + +AdditionalCommands::AdditionalCommands(Server *server) : _server(server) {} + +void AdditionalCommands::processCommand(Client *client, const std::string &command) +{ + if (command.find("PART") == 0) + { + handlePartCommand(_server, client, command); + } + else if (command.find("PRIVMSG") == 0) + { + handlePrivmsgCommand(_server, client, command); + } + else if (command.find("WHO") == 0) + { + WhoHandler whoHandler(_server); + whoHandler.handleWhoCommand(client, command); + } + else if (command.find("WHOIS") == 0) + { + WhoHandler whoHandler(_server); + whoHandler.handleWhoisCommand(client, command); + } + else if (command.find("INVITE") == 0) + { + InviteHandler inviteHandler(_server); + inviteHandler.handleInviteCommand(client, command); + } + else if (command.find("TOPIC") == 0) + { + TopicHandler topichandler(_server); + topichandler.handleTopicCommand(client, command); + } + else if (command.find("LIST") == 0) + { + broadcastChannelList(client, _server); + } + else if (command.find("KICK") == 0) + { + KickHandler kickHandler(_server); + kickHandler.handleKickCommand(client, command); + } + else + { + _server->sendToClient(client->getFd(), ERR_UNKNOWNCOMMAND(client, command)); + _server->log("Message from client " + client->getNickname() + ": " + command, MAGENTA); + } +} + +void AdditionalCommands::broadcastChannelList(Client *client, Server *server) +{ + std::map &channels = server->getChannels(); + for (std::map::iterator it = channels.begin(); it != channels.end(); ++it) + { + server->sendToClient(client->getFd(), RPL_LIST(client, it->first, it->second->getClients().size(), it->second->getTopic())); + } + server->sendToClient(client->getFd(), RPL_LISTEND(client)); +} + +// Fonction pour gérer la commande PART +void AdditionalCommands::handlePartCommand(Server *server, Client *client, const std::string &command) +{ + std::istringstream iss(command); + std::string cmd, channelNames; + iss >> cmd >> channelNames; + + if (channelNames.empty()) + { + server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "PART")); + return; + } + + std::vector channels = split(channelNames, ","); + std::map &channelMap = server->getChannels(); + + for (size_t i = 0; i < channels.size(); ++i) + { + std::string &channelName = channels[i]; + if (channelMap.find(channelName) == channelMap.end()) + { + server->sendToClient(client->getFd(), ERR_NOSUCHCHANNEL(client, channelName)); + continue; + } + + Channel *channel = channelMap[channelName]; + if (!channel->hasClient(client)) { + server->sendToClient(client->getFd(), ERR_NOTONCHANNEL(client, channelName)); + continue; + } + + channel->removeClient(client); + + std::ostringstream partMsg; + partMsg << ":" << client->getNickname() << " PART " << channelName << "\r\n"; + server->sendToClient(client->getFd(), partMsg.str()); + + if (channel->isEmpty()) + { + delete channel; + channelMap.erase(channelName); + } + + server->log("Client " + client->getNickname() + " left channel " + channelName, MAGENTA); + } +} + +void AdditionalCommands::handlePrivmsgCommand(Server *server, Client *client, const std::string &command) +{ + std::istringstream iss(command); + std::string cmd, target, message; + iss >> cmd >> target; + getline(iss, message); + + // Enlever le ':' initial dans le message si présent + if (!message.empty() && message[0] == ':') + message = message.substr(1); + + if (target.empty()) + { + server->sendToClient(client->getFd(), ERR_NORECIPIENT(client, "PRIVMSG")); + return; + } + + if (message.empty()) + { + server->sendToClient(client->getFd(), ERR_NOTEXTTOSEND(client)); + return; + } + + std::map &channels = server->getChannels(); + + if (channels.find(target) != channels.end()) + { + Channel *channel = channels[target]; + + if (channel->isBanned(client)) + { + server->sendToClient(client->getFd(), ERR_CANNOTSENDTOCHAN(client, target)); + return; + } + + std::vector channelClients = channel->getClients(); + + for (size_t i = 0; i < channelClients.size(); ++i) + { + if (channelClients[i] != client) + { + std::stringstream privMsg; + privMsg << ":" << client->getNickname() << " PRIVMSG " << target << message << "\r\n"; + server->sendToClient(channelClients[i]->getFd(), privMsg.str()); + } + } + } + else + { + Client *targetClient = server->getClientByName(target); + + if (targetClient) + { + std::stringstream privMsg; + privMsg << ":" << client->getNickname() << " PRIVMSG " << target << message << "\r\n"; + server->sendToClient(targetClient->getFd(), privMsg.str()); + + if (targetClient->isAway()) + { + server->sendToClient(client->getFd(), RPL_AWAY(client, targetClient->getNickname(), targetClient->getAwayMessage())); + } + } + else + { + // Si la cible n'est ni un canal ni un utilisateur existant, envoyer un message d'erreur + server->sendToClient(client->getFd(), ERR_NOSUCHNICK(client, target)); + } + } +} diff --git a/ft_irc3/src/BotFilter.cpp b/ft_irc3/src/BotFilter.cpp new file mode 100644 index 0000000..53e89b4 --- /dev/null +++ b/ft_irc3/src/BotFilter.cpp @@ -0,0 +1,70 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* BotFilter.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/06/06 11:45:43 by fgras-ca #+# #+# */ +/* Updated: 2024/06/06 13:37:24 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "BotFilter.hpp" + +BotFilter::BotFilter(Server *server) : _server(server) {} + +void BotFilter::loadBadWords(const std::string &fileName) +{ + std::ifstream file(fileName.c_str()); + std::string line; + while (std::getline(file, line)) + { + _badWords.push_back(line); + } +} + +bool BotFilter::checkMessage(Client *client, Channel *channel, const std::string &message) +{ + if (containsBadWords(message)) + { + warnClient(client, channel); + return true; + } + return false; +} + +bool BotFilter::containsBadWords(const std::string &message) +{ + for (std::vector::iterator it = _badWords.begin(); it != _badWords.end(); ++it) + { + if (message.find(*it) != std::string::npos) + { + return true; + } + } + return false; +} + +void BotFilter::warnClient(Client* client, Channel *channel) +{ + int fd = client->getFd(); + _warnings[fd] += 1; + if (_warnings[fd] >= 3) + { + _server->sendToClient(fd, BOTMESSAGE(client, channel->getName(), "You have been kicked from the server for inappropriate language.\r\n")); + _server->disconnectClient(fd); + } + else + { + std::ostringstream oss; + oss << "Warning: Inappropriate language detected. You have " << (3 - _warnings[fd]) << " warnings left before you are kicked.\r\n"; + _server->sendToClient(fd, BOTMESSAGE(client, channel->getName(), oss.str())); + } +} + +void BotFilter::kickClient(Client *client, Channel *channel) +{ + _server->sendToClient(client->getFd(), BOTMESSAGE(client, channel->getName(), "You have been kicked for inappropriate language.\r\n")); + _server->disconnectClient(client->getFd()); +} diff --git a/ft_irc3/src/Channel.cpp b/ft_irc3/src/Channel.cpp new file mode 100644 index 0000000..a462fa4 --- /dev/null +++ b/ft_irc3/src/Channel.cpp @@ -0,0 +1,172 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Channel.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 12:42:57 by fgras-ca #+# #+# */ +/* Updated: 2024/06/04 16:12:22 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "Channel.hpp" + +Channel::Channel(const std::string &name) + : _name(name), _clients(), _operators(), _bannedClients(), _invitedClients(), _key(""), _topic(""), _topicSetter(""), _topicTime(time(NULL)), _clientLimit(50), _inviteOnly(false), _topicProtection(false) {} + +Channel::~Channel() {} + +const std::string &Channel::getName() const +{ + return _name; +} + +void Channel::addClient(Client *client) +{ + _clients.push_back(client); +} + +void Channel::removeClient(Client *client) +{ + _clients.erase(std::remove(_clients.begin(), _clients.end(), client), _clients.end()); +} + +bool Channel::isEmpty() const +{ + return _clients.empty(); +} + +const std::vector &Channel::getClients() const +{ + return _clients; +} + +void Channel::addOperator(Client *client) +{ + _operators.push_back(client); +} + +bool Channel::isOperator(Client *client) const +{ + return std::find(_operators.begin(), _operators.end(), client) != _operators.end(); +} + +void Channel::removeOperator(Client *client) +{ + _operators.erase(std::remove(_operators.begin(), _operators.end(), client), _operators.end()); +} + +bool Channel::hasClient(Client *client) const +{ + return std::find(_clients.begin(), _clients.end(), client) != _clients.end(); +} + +void Channel::broadcast(const std::string &message, Client *_client, Server *_server) +{ + for (std::vector::iterator it = _clients.begin(); it != _clients.end(); ++it) + { + if (*it != _client) + { + // Send message to each client except the sender + _server->sendToClient((*it)->getFd(), message); + } + } +} + +bool Channel::isBanned(Client *client) const +{ + return _bannedClients.find(client) != _bannedClients.end(); +} + +bool Channel::isFull() const +{ + return _clients.size() >= _clientLimit; +} + +bool Channel::isInviteOnly() const +{ + return _inviteOnly; +} + +bool Channel::isInvited(Client *client) const +{ + return _invitedClients.find(client) != _invitedClients.end(); +} + +bool Channel::checkKey(const std::string &key) const +{ + return _key.empty() || _key == key; +} + +const std::string &Channel::getTopic() const +{ + return _topic; +} + +const std::string &Channel::getTopicSetter() const +{ + return _topicSetter; +} + +time_t Channel::getTopicTime() const +{ + return _topicTime; +} + +void Channel::setTopic(const std::string &topic, const std::string &setter) +{ + _topic = topic; + _topicSetter = setter; + _topicTime = std::time(NULL); +} + +void Channel::setClientLimit(size_t limit) +{ + _clientLimit = limit; +} + +size_t Channel::getClientLimit() const +{ + return _clientLimit; +} + +void Channel::setInviteOnly(bool inviteOnly) +{ + _inviteOnly = inviteOnly; +} + +void Channel::setKey(const std::string &key) +{ + _key = key; +} + +std::string Channel::getKey() +{ + return _key; +} + +void Channel::setTopicProtection(bool protection) +{ + _topicProtection = protection; +} + +void Channel::addInvitedClient(Client* client) +{ + _invitedClients.insert(client); +} + +std::string Channel::getModes() const +{ + std::string modes; + if (_inviteOnly) modes += 'i'; + if (!_key.empty()) modes += 'k'; + if (_clientLimit > 0) modes += 'l'; + if (_topicProtection) modes += 't'; + return modes; +} + +bool Channel::getTopicProtection() const +{ + return _topicProtection; +} \ No newline at end of file diff --git a/ft_irc3/src/Client.cpp b/ft_irc3/src/Client.cpp new file mode 100644 index 0000000..6bbc569 --- /dev/null +++ b/ft_irc3/src/Client.cpp @@ -0,0 +1,136 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Client.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 12:17:42 by fgras-ca #+# #+# */ +/* Updated: 2024/06/06 13:23:32 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "Client.hpp" + +Client::Client(int fd, const std::string &nickname, const std::string &user, const std::string &host, const std::string &password, const std::string &realname) + : _fd(fd), _nickname(nickname), _user(user), _host(host), _password(password), _realname(realname), _authenticated(false), _operator(false), _away(false) {} + +int Client::getFd() const +{ + return _fd; +} + +const std::string &Client::getNickname() const +{ + return _nickname; +} + +void Client::setNickname(const std::string &nickname) +{ + _nickname = nickname; +} + +const std::string &Client::getUser() const +{ + return _user; +} + +void Client::setUser(const std::string &user) +{ + _user = user; +} + +const std::string &Client::getHost() const +{ + return _host; +} + +void Client::setHost(const std::string &host) +{ + _host = host; +} + +const std::string &Client::getPassword() const +{ + return _password; +} + +void Client::setPassword(const std::string &password) +{ + _password = password; +} + +const std::string &Client::getRealName() const +{ + return _realname; +} + +void Client::setRealName(const std::string &realname) +{ + _realname = realname; +} + +bool Client::isAuthenticated() const +{ + return _authenticated; +} + +void Client::authenticate() +{ + _authenticated = true; +} + +bool Client::isOperator() const +{ + return _operator; +} + +void Client::setOperator(bool isOperator) +{ + _operator = isOperator; +} + +bool Client::isAway() const +{ + return _away; +} + +const std::string &Client::getAwayMessage() const +{ + return _awayMessage; +} + +void Client::setAwayMessage(const std::string &message) +{ + _awayMessage = message; +} + +void Client::setAway(bool away) +{ + _away = away; +} + +std::string Client::getKey() const +{ + return _key; +} + +void Client::setkey(const std::string &key) +{ + _key = key; +} + +void Client::joinChannel(const std::string &channel) +{ + _channels.insert(channel); +} + +void Client::leaveChannel(const std::string &channel) +{ + _channels.erase(channel); +} + +const std::set &Client::getChannels() const +{ + return _channels; +} \ No newline at end of file diff --git a/ft_irc3/src/ClientManager.cpp b/ft_irc3/src/ClientManager.cpp new file mode 100644 index 0000000..d03b8cd --- /dev/null +++ b/ft_irc3/src/ClientManager.cpp @@ -0,0 +1,173 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* ClientManager.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 18:32:23 by fgras-ca #+# #+# */ +/* Updated: 2024/06/06 14:09:52 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "ClientManager.hpp" + +ClientManager::ClientManager(Server *server) + : _server(server) +{ + _botFilter = new BotFilter(server); + _botFilter->loadBadWords("badwords.txt"); +} + +void ClientManager::acceptClient() +{ + int client_fd = accept(_server->_server_fd, NULL, NULL); + if (client_fd < 0) + { + _server->log("Failed to accept client.", RED); + return; + } + + Client *newClient = new Client(client_fd, "", "", "", "", ""); + _server->_clients[client_fd] = newClient; + struct pollfd client_pollfd; + client_pollfd.fd = client_fd; + client_pollfd.events = POLLIN; + _server->_poll_fds.push_back(client_pollfd); + + std::stringstream ss; + ss << "Client attempting connection: " << client_fd; + _server->log(ss.str(), YELLOW); +} + +void ClientManager::handleClient(int client_fd) +{ + Client* client = _server->getClients()[client_fd]; + std::memset(client->buffer, 0, sizeof(client->buffer)); + int bytes_received = recv(client_fd, client->buffer, sizeof(client->buffer), 0); + + if (bytes_received <= 0) + { + std::ostringstream oss; + oss << "Client disconnected: " << client_fd; + _server->log(oss.str(), RED); + removeClient(client_fd); + return; + } + + std::cout << "Received data (" << bytes_received << " bytes): " << client->buffer << std::endl; + + if (std::string(client->buffer).find('\n') != std::string::npos) + { + strcat(client->buffer2, client->buffer); + std::string fullMessage(client->buffer2); + + // Log the combined message + std::cout << "Complete message: " << fullMessage << std::endl; + + bool messageAllowed = true; + + if (!client->getChannels().empty()) + { + for (std::set::const_iterator it = client->getChannels().begin(); it != client->getChannels().end(); ++it) + { + const std::string& channelName = *it; + Channel* channel = _server->getChannelByName(channelName); + if (channel && !_botFilter->checkMessage(client, channel, fullMessage)) + { + messageAllowed = false; + break; + } + } + } + + if (messageAllowed) + { + handleClientNext(client_fd, client->buffer2, fullMessage.size()); + } + + std::memset(client->buffer2, 0, sizeof(client->buffer2)); + } + else + { + strcat(client->buffer2, client->buffer); + } +} + + + +void ClientManager::handleClientNext(int client_fd, char * buffer, int bytes_received) +{ + std::string message(buffer, bytes_received); + std::ostringstream oss; + oss << "Received from client " << client_fd << ": " << message; + _server->log(oss.str(), BLUE); + + Client* client = _server->getClients()[client_fd]; + if (!client) + { + std::ostringstream oss; + oss << "Client not found for fd: " << client_fd; + _server->log(oss.str(), RED); + return; + } + + std::istringstream message_stream(message); + std::string line; + + while (std::getline(message_stream, line)) + { + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + line.erase(std::remove(line.begin(), line.end(), '\n'), line.end()); + + if (!line.empty()) + { + std::ostringstream oss; + oss << "Processing command from client " << client_fd << ": " << line; + _server->log(oss.str(), BLUE); + _server->_commandHandler->handleCommand(client, line); + } + } +} + +void ClientManager::removeClient(int clientFd) +{ + Client* client = _server->_clients[clientFd]; + if (client) + { + _server->log("Removing client: " + client->getNickname(), YELLOW); + + std::map::iterator it = _server->_channels.begin(); + while (it != _server->_channels.end()) + { + it->second->removeClient(client); + if (it->second->isEmpty()) + { + delete it->second; + std::map::iterator it_to_delete = it++; + _server->_channels.erase(it_to_delete); + } + else + { + ++it; + } + } + delete client; + _server->_clients.erase(clientFd); + } + + std::vector::iterator it_poll = _server->_poll_fds.begin(); + while (it_poll != _server->_poll_fds.end()) + { + if (it_poll->fd == clientFd) + { + _server->_poll_fds.erase(it_poll); + break; + } + ++it_poll; + } + + std::stringstream ss; + ss << "Client disconnected: " << clientFd; + _server->log(ss.str(), YELLOW); +} diff --git a/ft_irc3/src/CommandHandler.cpp b/ft_irc3/src/CommandHandler.cpp new file mode 100644 index 0000000..ad1a3a4 --- /dev/null +++ b/ft_irc3/src/CommandHandler.cpp @@ -0,0 +1,305 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* CommandHandler.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 18:26:34 by fgras-ca #+# #+# */ +/* Updated: 2024/06/05 09:57:55 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "CommandHandler.hpp" + +CommandHandler::CommandHandler(Server *server) + : _server(server), _additionalCommands(new AdditionalCommands(server)), _modeHandler(new ModeHandler(server)) + { + // Ensure that _server is not null + if (!_server) + { + std::cerr << "Server pointer is null in CommandHandler constructor." << std::endl; + exit(EXIT_FAILURE); + } + } + +void CommandHandler::handleCommand(Client* client, const std::string& command) +{ + std::vector tokens = split(command, " \n\r\t"); + + if (tokens.empty()) + { + return; + } + + std::string commandType = tokens[0]; + if (commandType == "CAP") + { + handleCapCommand(client, tokens); + } + else if (commandType == "PASS") + { + handlePassCommand(client, tokens); + } + else if (commandType == "NICK") + { + handleNick(client, tokens); + } + else if (commandType == "USER") + { + handleUser(client, tokens); + } + else if (commandType == "QUIT") + { + handleQuitCommand(client, tokens); + } + else if (commandType == "PING") + { + handlePingCommand(client, tokens); + } + else if (commandType == "ERROR") + { + if (tokens.size() > 1) + { + handleErrorCommand(client, tokens[1]); + } + } + else if (commandType == "MODE") + { + _modeHandler->handleModeCommand(client, command); + } + else if (commandType == "JOIN") + { + std::string joinParams = command.substr(command.find(" ") + 1); + JoinHandler joinHandler; + joinHandler.handleJoinCommand(client, joinParams, _server); + } + else + { + if (!client->isAuthenticated()) + { + _server->sendToClient(client->getFd(), ERR_NOTREGISTERED(client)); + _server->log("Client " + client->getNickname() + " attempted to send a command before registering.", RED); + } + else + { + _additionalCommands->processCommand(client, command); + } + } +} + +void CommandHandler::handleCapCommand(Client* client, const std::vector& tokens) +{ + if (tokens.size() < 2) + { + _server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "CAP")); + return; + } + + std::string subcommand = tokens[1]; + std::string capabilities = "multi-prefix extended-join account-notify batch invite-notify"; + + if (subcommand == "LS") + { + _server->sendToClient(client->getFd(), RPL_CAP(client, "LS", capabilities)); + } + else if (subcommand == "LIST") + { + _server->sendToClient(client->getFd(), RPL_CAP(client, "LIST", capabilities)); + } + else if (subcommand == "REQ") + { + if (tokens.size() < 3) + { + _server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "CAP")); + return; + } + std::string requestedCapabilities = tokens[2]; + _server->sendToClient(client->getFd(), RPL_CAP(client, "ACK", requestedCapabilities)); + } + else if (subcommand == "END") + { + _server->sendToClient(client->getFd(), RPL_CAPEND(client)); + } + else + { + _server->sendToClient(client->getFd(), ERR_UNKNOWNCOMMAND(client, "CAP")); + } +} + +void CommandHandler::handlePassCommand(Client* client, const std::vector& tokens) +{ + if (tokens.size() < 2) + { + _server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "PASS")); + return; + } + + if (client->isAuthenticated()) + { + _server->sendToClient(client->getFd(), ERR_ALREADYREGISTERED(client)); + return; + } + + if (tokens[1] == _server->_password) + { + client->setPassword(tokens[1]); + _server->sendToClient(client->getFd(), ":server NOTICE * :Password accepted\r\n"); + _server->log("Client " + client->getNickname() + " provided correct password.", GREEN); + } else { + _server->sendToClient(client->getFd(), ERR_PASSWDMISMATCH(client)); + _server->log("Client " + client->getNickname() + " failed authentication password.", RED); + } +} + +bool CommandHandler::isValidNickname(const std::string& nickname) +{ + if (nickname.empty() || nickname[0] == '#' || nickname[0] == ':' || nickname.find(' ') != std::string::npos) + { + return false; + } + return true; +} + +bool CommandHandler::isNicknameInUse(const std::string& nickname) +{ + for (std::map::iterator it = _server->_clients.begin(); it != _server->_clients.end(); ++it) + { + if (it->second->getNickname() == nickname) + { + return true; + } + } + return false; +} + +void CommandHandler::handleNick(Client* client, const std::vector& tokens) +{ + if (tokens.size() < 2) + { + _server->sendToClient(client->getFd(), ERR_NONICKNAMEGIVEN(client)); + return; + } + + std::string newNick = tokens[1]; + + if (!isValidNickname(newNick)) + { + _server->sendToClient(client->getFd(), ERR_ERRONEUSNICKNAME(client, newNick)); + return; + } + + if (isNicknameInUse(newNick)) + { + _server->sendToClient(client->getFd(), ERR_NICKNAMEINUSE(client, newNick)); + return; + } + + std::string oldNick = client->getNickname(); + client->setNickname(newNick); + + std::string nickMessage = ":" + oldNick + " NICK " + newNick + "\r\n"; + for (std::map::iterator it = _server->_clients.begin(); it != _server->_clients.end(); ++it) + { + if (it->second->isAuthenticated()) + _server->sendToClient(it->second->getFd(), nickMessage); + } + + std::ostringstream oss; + oss << "Client " << client->getFd() << " changed nickname from " << oldNick << " to " << newNick; + _server->log(oss.str(), GREEN); +} + + +void CommandHandler::handleUser(Client* client, const std::vector& tokens) +{ + if (tokens.size() < 5) + { + _server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "USER")); + + std::ostringstream oss; + oss << "Client " << client->getFd() << ": USER command failed - not enough parameters."; + _server->log(oss.str(), RED); + + return; + } + + if (client->isAuthenticated()) + { + _server->sendToClient(client->getFd(), ERR_ALREADYREGISTERED(client)); + + std::ostringstream oss; + oss << "Client " << client->getFd() << ": USER command failed - already registered."; + _server->log(oss.str(), RED); + + return; + } + + std::string username = tokens[1]; + std::string realname = tokens[4].substr(1); + + client->setUser(username); + client->setRealName(realname); + + std::ostringstream oss; + oss << "Client " << client->getFd() << ": USER command set username to " << username << " and real name to " << realname; + _server->log(oss.str(), BLUE); + + if (client->getPassword() == _server->_password && !client->getNickname().empty()) + { + client->authenticate(); + WelcomeHandler welcomeHandler; + welcomeHandler.sendWelcomeMessages(client, _server); + _server->log("Client " + client->getNickname() + " authenticated.", GREEN); + } + else + { + std::ostringstream oss; + oss << "Client " << client->getFd() << ": USER command failed - authentication conditions not met."; + _server->log(oss.str(), RED); + } +} + +void CommandHandler::handlePingCommand(Client* client, const std::vector& tokens) +{ + if (tokens.size() < 2) + { + _server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "PING")); + return; + } + + std::string serverName = tokens[1]; + std::string pongMessage = ":" + serverName + " PONG " + serverName + "\r\n"; + _server->sendToClient(client->getFd(), pongMessage); +} + + +void CommandHandler::handleQuitCommand(Client* client, const std::vector& tokens) +{ + std::string reason = "Quit: "; + if (tokens.size() > 1) + { + reason += tokens[1]; + } + + std::string quitMessage = ":" + client->getNickname() + " QUIT :" + reason + "\r\n"; + + std::map::iterator it; + for (it = _server->getChannels().begin(); it != _server->getChannels().end(); ++it) + { + if (it->second->hasClient(client)) + { + it->second->broadcast(quitMessage, client, _server); + it->second->removeClient(client); + } + } + + _server->sendToClient(client->getFd(), "ERROR :" + reason + "\r\n"); + _server->disconnectClient(client->getFd()); +} + +void CommandHandler::handleErrorCommand(Client* client, const std::string &message) +{ + _server->sendToClient(client->getFd(), "ERROR :" + message + "\r\n"); + _server->disconnectClient(client->getFd()); +} diff --git a/ft_irc3/src/InviteHandler.cpp b/ft_irc3/src/InviteHandler.cpp new file mode 100644 index 0000000..2b66b05 --- /dev/null +++ b/ft_irc3/src/InviteHandler.cpp @@ -0,0 +1,67 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* InviteHandler.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/30 13:02:09 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 19:11:22 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "InviteHandler.hpp" + +InviteHandler::InviteHandler(Server* server) : _server(server) {} + +void InviteHandler::handleInviteCommand(Client* client, const std::string& command) +{ + std::istringstream iss(command); + std::string cmd, nickname, channelName; + iss >> cmd >> nickname >> channelName; + + if (nickname.empty() || channelName.empty()) + { + _server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "INVITE")); + return; + } + + Channel* channel = _server->getChannelByName(channelName); + if (!channel) + { + _server->sendToClient(client->getFd(), ERR_NOSUCHCHANNEL(client, channelName)); + return; + } + + if (!channel->hasClient(client)) + { + _server->sendToClient(client->getFd(), ERR_NOTONCHANNEL(client, channelName)); + return; + } + + if (!channel->isOperator(client) && channel->isInviteOnly()) + { + _server->sendToClient(client->getFd(), ERR_CHANOPRIVSNEEDED(client, channelName)); + return; + } + + Client* targetClient = _server->getClientByName(nickname); + if (!targetClient) + { + _server->sendToClient(client->getFd(), ERR_NOSUCHNICK(client, nickname)); + return; + } + + if (channel->hasClient(targetClient)) + { + _server->sendToClient(client->getFd(), ERR_USERONCHANNEL(client, nickname, channelName)); + return; + } + + channel->addInvitedClient(targetClient); + _server->sendToClient(client->getFd(), RPL_INVITING(client, channel->getName())); + + std::ostringstream inviteMsg; + inviteMsg << ":" << client->getNickname() << " INVITE " << nickname << " " << channelName << "\r\n"; + _server->sendToClient(targetClient->getFd(), inviteMsg.str()); +} diff --git a/ft_irc3/src/Join.cpp b/ft_irc3/src/Join.cpp new file mode 100644 index 0000000..8c10ce0 --- /dev/null +++ b/ft_irc3/src/Join.cpp @@ -0,0 +1,167 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Join.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/21 19:51:31 by fgras-ca #+# #+# */ +/* Updated: 2024/05/21 20:13:30 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "Join.hpp" + +void JoinHandler::handleJoinCommand(Client* client, const std::string& params, Server* server) +{ + std::map& channels = server->getChannels(); + Channel* channel; + + server->log("Received JOIN command with params: " + params, RED); + + // Split params into channel names and keys + std::vector parts = split(params, " "); + server->log("Split parts: ", RED); + for (size_t i = 0; i < parts.size(); ++i) + { + std::ostringstream oss; + oss << "Part " << i << ": " << parts[i]; + server->log(oss.str(), RED); + } + if (parts.empty()) + { + server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "JOIN")); + return; + } + + std::string channelNames = parts[0]; + std::string keys = (parts.size() > 1) ? parts[1] : ""; + + server->log("Split params: channelNames = " + channelNames + ", keys = " + keys, RED); + + std::vector channelList = split(channelNames, ","); + std::vector keyList = split(keys, ","); + + server->log("channelList: ", RED); + for (size_t i = 0; i < channelList.size(); ++i) + { + server->log(channelList[i], RED); + } + + server->log("keyList: ", RED); + for (size_t i = 0; i < keyList.size(); ++i) + { + server->log(keyList[i], RED); + } + + for (size_t i = 0; i < channelList.size(); ++i) + { + const std::string& channelName = channelList[i]; + server->log("Processing channel: " + channelName, RED); + + if (channelName.empty()) + { + server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "JOIN")); + return; + } + + if (channelName[0] == '0' && channelName.size() == 1) + { + // Leave all channels + return; + } + + if (channelName[0] != '#' && channelName[0] != '&') + { + server->sendToClient(client->getFd(), ERR_NOSUCHCHANNEL(client, channelName)); + return; + } + + if (channels.find(channelName) == channels.end()) + { + channel = new Channel(channelName); + channels[channelName] = channel; + channel->addOperator(client); // Set client as operator for new channel + } + else + { + channel = channels[channelName]; + } + + if (channel->isBanned(client)) + { + server->sendToClient(client->getFd(), ERR_BANNEDFROMCHAN(client, channelName)); + return; + } + + if (channel->isFull()) + { + server->sendToClient(client->getFd(), ERR_CHANNELISFULL(client, channelName)); + return; + } + + if (channel->isInviteOnly() && !channel->isInvited(client)) + { + server->sendToClient(client->getFd(), ERR_INVITEONLYCHAN(client, channelName)); + return; + } + + // Extract the key for this channel, if provided + std::string key = (i < keyList.size()) ? keyList[i] : ""; + server->log("Using key: " + key + " for channel: " + channelName, RED); + + if (!channel->checkKey(key)) + { + server->sendToClient(client->getFd(), ERR_BADCHANNELKEY(client, channelName)); + return; + } + + channel->addClient(client); + sendJoinSuccess(client, channel, server); + } +} + +void JoinHandler::sendJoinSuccess(Client* client, Channel* channel, Server* server) +{ + std::vector clients = channel->getClients(); + for (std::vector::iterator it = clients.begin(); it != clients.end(); ++it) + { + std::string joinMsg = ":" + client->getNickname() + " JOIN " + channel->getName() + "\r\n"; + server->sendToClient((*it)->getFd(), joinMsg); + } + + if (!channel->getTopic().empty()) + { + server->sendToClient(client->getFd(), RPL_TOPIC(client, channel->getName(), channel->getTopic())); + server->sendToClient(client->getFd(), RPL_TOPICWHOTIME(client, channel->getName(), channel->getTopicSetter(), channel->getTopicTime())); + } + else + { + server->sendToClient(client->getFd(), RPL_NOTOPIC(client, channel->getName())); + } + + std::ostringstream oss; + oss << RPL_CHANNELMODEIS(client->getFd(), channel->getName(), channel->getModes()); + oss << RPL_CREATIONTIME(client, channel->getName(), channel->getTopicTime()); + server->sendToClient(client->getFd(), oss.str()); + + std::string usersList = getUsersList(channel); + server->sendToClient(client->getFd(), usersList); +} + +std::string JoinHandler::getUsersList(Channel* channel) +{ + std::vector clients = channel->getClients(); + std::string users; + for (std::vector::iterator it = clients.begin(); it != clients.end(); ++it) + { + if (channel->isOperator((*it))) + users += "@"; + users += (*it)->getNickname() + " "; + } + + std::ostringstream oss; + oss << RPL_NAMREPLY(clients[0], channel->getName(), users); + oss << RPL_ENDOFNAMES(clients[0], channel->getName()); + return oss.str(); +} diff --git a/ft_irc3/src/KickHandler.cpp b/ft_irc3/src/KickHandler.cpp new file mode 100644 index 0000000..5015c2c --- /dev/null +++ b/ft_irc3/src/KickHandler.cpp @@ -0,0 +1,68 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* KickHandler.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/06/01 17:00:31 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 19:12:13 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "KickHandler.hpp" + + + +KickHandler::KickHandler(Server* server) : _server(server) {} + +void KickHandler::handleKickCommand(Client* client, const std::string& command) +{ + std::vector tokens = split(command, " "); + if (tokens.size() < 3) + { + _server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "KICK")); + return; + } + + std::string channelName = tokens[1]; + std::string targetNickname = tokens[2]; + std::string comment = (tokens.size() > 3) ? tokens[3] : "Kicked by operator"; + + Channel* channel = _server->getChannelByName(channelName); + if (!channel) + { + _server->sendToClient(client->getFd(), ERR_NOSUCHCHANNEL(client, channelName)); + return; + } + + if (!channel->hasClient(client)) + { + _server->sendToClient(client->getFd(), ERR_NOTONCHANNEL(client, channelName)); + return; + } + + if (!channel->isOperator(client)) + { + _server->sendToClient(client->getFd(), ERR_CHANOPRIVSNEEDED(client, channelName)); + return; + } + + Client* targetClient = _server->getClientByName(targetNickname); + if (!targetClient) + { + _server->sendToClient(client->getFd(), ERR_NOSUCHNICK(client, targetNickname)); + return; + } + + if (!channel->hasClient(targetClient)) + { + _server->sendToClient(client->getFd(), ERR_USERNOTINCHANNEL(client, targetNickname, channelName)); + return; + } + + channel->removeClient(targetClient); + std::string kickMessage = ":" + client->getNickname() + " KICK " + channelName + " " + targetNickname + " :" + comment + "\r\n"; + channel->broadcast(kickMessage, NULL, _server); + _server->sendToClient(targetClient->getFd(), kickMessage); +} diff --git a/ft_irc3/src/ModeHandler.cpp b/ft_irc3/src/ModeHandler.cpp new file mode 100644 index 0000000..c28f350 --- /dev/null +++ b/ft_irc3/src/ModeHandler.cpp @@ -0,0 +1,277 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* ModeHandler.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/30 11:13:08 by fgras-ca #+# #+# */ +/* Updated: 2024/06/04 16:12:50 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "ModeHandler.hpp" + +ModeHandler::ModeHandler(Server* server) : _server(server) {} + +void ModeHandler::handleModeCommand(Client* client, const std::string& command) +{ + _server->log("Received MODE command: " + command, BLUE); + std::vector tokens = split(command, " \n\r\t"); + + if (tokens.size() < 2) + { + _server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "MODE")); + _server->log("MODE command error: Need more parameters", RED); + return; + } + + std::string target = tokens[1]; + if (target[0] == '#' || target[0] == '&') + { + _server->log("Handling channel mode for target: " + target, GREEN); + handleChannelMode(client, tokens); + } + else + { + _server->log("Handling user mode for target: " + target, GREEN); + handleUserMode(client, tokens); + } +} + +void ModeHandler::handleUserMode(Client* client, const std::vector& tokens) +{ + _server->log("Entered handleUserMode", MAGENTA); + std::string target = tokens[1]; + _server->log("User mode target: " + target, CYAN); + if (target != client->getNickname()) + { + _server->sendToClient(client->getFd(), ERR_USERSDONTMATCH(client)); + _server->log("User mode error: Users don't match", RED); + return; + } + + if (tokens.size() == 2) + { + _server->sendToClient(client->getFd(), RPL_UMODEIS(client, "")); + _server->log("Sent user modes to client", GREEN); + } + else + { + std::string modeString = tokens[2]; + _server->log("Updating user modes: " + modeString, BLUE); + } +} + +void ModeHandler::handleChannelMode(Client* client, const std::vector& tokens) +{ + _server->log("Entered handleChannelMode", MAGENTA); + std::string channelName = tokens[1]; + _server->log("Channel mode target: " + channelName, CYAN); + Channel* channel = _server->getChannelByName(channelName); + if (!channel) + { + _server->sendToClient(client->getFd(), ERR_NOSUCHCHANNEL(client, channelName)); + _server->log("Channel mode error: No such channel " + channelName, RED); + return; + } + + if (tokens.size() == 2) + { + std::ostringstream oss; + oss << RPL_CHANNELMODEIS(client->getFd(), channel->getName(), channel->getModes()); + oss << RPL_CREATIONTIME(client, channel->getName(), channel->getTopicTime()); + _server->sendToClient(client->getFd(), oss.str()); + _server->log("Sent channel modes to client", GREEN); + return; + } + + if (!channel->isOperator(client)) + { + _server->sendToClient(client->getFd(), ERR_CHANOPRIVSNEEDED(client, channelName)); + _server->log("Channel mode error: Channel operator privileges needed for " + channelName, RED); + return; + } + + std::string modeString = tokens[2]; + bool adding = true; + size_t argIndex = 3; + + for (size_t i = 0; i < modeString.length(); ++i) + { + char mode = modeString[i]; + if (mode == '+') + { + adding = true; + _server->log("Adding mode: " + std::string(1, mode), GREEN); + } else if (mode == '-') + { + adding = false; + _server->log("Removing mode: " + std::string(1, mode), RED); + } else if (mode == 'b' || mode == 'l' || mode == 'i' || mode == 'k' || mode == 't' || mode == 'o') + { + std::string argument; + if (argIndex < tokens.size()) + { + argument = tokens[argIndex++]; + } + setChannelMode(client, channel, std::string(1, mode), adding, argument); + } + else + { + _server->sendToClient(client->getFd(), ERR_UNKNOWNMODE(client, mode, channelName)); + _server->log("Unknown mode: " + std::string(1, mode), RED); + } + } +} + +void ModeHandler::setChannelMode(Client *client, Channel* channel, const std::string& mode, bool adding, const std::string& argument) +{ + _server->log("Setting channel mode: " + mode + " Adding: " + (adding ? "true" : "false") + " Argument: " + argument, GREEN); + if (mode == "l") + { + applyModeL(client, channel, adding, argument); + } + else if (mode == "i") + { + applyModeI(client, channel, adding); + } + else if (mode == "k") + { + applyModeK(client, channel, adding, argument); + } + else if (mode == "t") + { + applyModeT(channel, adding); + } + else if (mode == "o") + applyModeO(client, channel, adding, argument); +} + +void ModeHandler::applyModeL(Client *client, Channel* channel, bool adding, const std::string& argument) +{ + if (adding) + { + int limit = std::atoi(argument.c_str()); + if (limit <= 0) + { + _server->sendToClient(client->getFd(), ERR_INVALIDMODEPARAM(client, argument)); + _server->log("Invalid limit for mode +l: " + argument, RED); + return; + } + std::ostringstream oss; + oss << limit; + _server->log("Applying mode L: Setting limit to " + oss.str(), GREEN); + channel->setClientLimit(limit); + } + else + { + _server->log("Applying mode L: Removing limit", RED); + channel->setClientLimit(0); + } +} + +void ModeHandler::applyModeI(Client *client, Channel *channel, bool adding) +{ + std::string modeChange; + bool isAlreadySet; + + modeChange = adding ? "+i" : "-i"; + isAlreadySet = channel->isInviteOnly() == adding; + if (!isAlreadySet) + { + _server->sendToClient(client->getFd(), MODEACCEPTMESSAGE(client, channel->getName(), modeChange)); + _server->log("Applying mode I: " + std::string(adding ? "Setting invite-only" : "Removing invite-only"), GREEN); + channel->setInviteOnly(adding); + } +} + +void ModeHandler::applyModeK(Client *client, Channel *channel, bool adding, const std::string &argument) +{ + bool isAlreadyProtected; + + isAlreadyProtected = !channel->getKey().empty(); + if (adding) + { + if (isAlreadyProtected) + { + _server->sendToClient(client->getFd(), ERR_KEYSET(client, channel->getName())); + _server->log("Mode +k error: Channel already has a key set", RED); + return; + } + if (argument.empty()) + { + _server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "MODE +k")); + _server->log("Mode +k error: No key provided", RED); + return; + } + if (argument.find(' ') != std::string::npos) + { + _server->sendToClient(client->getFd(), ERR_INVALIDMODEPARAM(client, argument)); + _server->log("Invalid key for mode +k: contains spaces", RED); + return; + } + _server->sendToClient(client->getFd(), MODEACCEPTMESSAGE(client, channel->getName(), "+k " + argument)); + _server->log("Applying mode K: Setting key to " + argument, GREEN); + channel->setKey(argument); + } + else + { + if (!isAlreadyProtected) + { + _server->sendToClient(client->getFd(), ERR_LINKSET(client, channel->getName())); + _server->log("Mode -k error: No key to remove", RED); + return; + } + _server->sendToClient(client->getFd(), MODEACCEPTMESSAGE(client, channel->getName(), "-k")); + _server->log("Applying mode K: Removing key", RED); + channel->setKey(""); + } +} + +void ModeHandler::applyModeT(Channel* channel, bool adding) +{ + std::string modeChange; + bool isAlreadySet; + + modeChange = adding ? "+t" : "-t"; + isAlreadySet = channel->isInviteOnly() == adding; + if (!isAlreadySet) + { + _server->log("Applying mode T: " + std::string(adding ? "Setting topic protection" : "Removing topic protection"), GREEN); + channel->setTopicProtection(adding); + } +} + +void ModeHandler::applyModeO(Client* client, Channel* channel, bool adding, const std::string& argument) +{ + Client* targetClient = _server->getClientByName(argument); + if (!targetClient) + { + _server->sendToClient(client->getFd(), ERR_NOSUCHNICK(client, argument)); + _server->log("Mode o error: No such client " + argument, RED); + return; + } + + if (!channel->hasClient(targetClient)) + { + _server->sendToClient(client->getFd(), ERR_USERNOTINCHANNEL(client, argument, channel->getName())); + _server->log("Mode o error: Client " + argument + " not in channel " + channel->getName(), RED); + return; + } + + if (adding) + { + _server->log("Applying mode o: Adding operator " + argument, GREEN); + channel->addOperator(targetClient); + std::string message = ":" + client->getNickname() + " MODE " + channel->getName() + " +o " + targetClient->getNickname() + "\r\n"; + channel->broadcast(message, NULL, _server); + } + else + { + _server->log("Applying mode o: Removing operator " + argument, RED); + channel->removeOperator(targetClient); + std::string message = ":" + client->getNickname() + " MODE " + channel->getName() + " -o " + targetClient->getNickname() + "\r\n"; + channel->broadcast(message, NULL, _server); + } +} \ No newline at end of file diff --git a/ft_irc3/src/Server.cpp b/ft_irc3/src/Server.cpp new file mode 100644 index 0000000..aaaf0ca --- /dev/null +++ b/ft_irc3/src/Server.cpp @@ -0,0 +1,252 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Server.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 12:17:12 by fgras-ca #+# #+# */ +/* Updated: 2024/06/06 13:00:25 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "Server.hpp" + +Server::Server(int port, const std::string &password) + : _port(port), _password(password), _clientManager(new ClientManager(this)), _commandHandler(new CommandHandler(this)), _modeHandler(new ModeHandler(this)), _topicHandler(new TopicHandler(this)) +{ + initServer(); + _botFilter = new BotFilter(this); + _botFilter->loadBadWords("badwords.txt"); +} + +Server::~Server() +{ + delete _clientManager; + delete _commandHandler; + delete _topicHandler; + delete _botFilter; + + for (std::map::iterator it = _clients.begin(); it != _clients.end(); ++it) + { + delete it->second; + } + for (std::map::iterator it = _channels.begin(); it != _channels.end(); ++it) + { + delete it->second; + } + close(_server_fd); +} + +void Server::initServer() +{ + _server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (_server_fd == -1) + { + log("Failed to create socket.", RED); + exit(EXIT_FAILURE); + } + + int opt = 1; + if (setsockopt(_server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) + { + log("Failed to set socket options.", RED); + close(_server_fd); + exit(EXIT_FAILURE); + } + + struct sockaddr_in server_addr; + std::memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(_port); + + if (bind(_server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) + { + log("Failed to bind socket.", RED); + close(_server_fd); + exit(EXIT_FAILURE); + } + + if (listen(_server_fd, SOMAXCONN) == -1) + { + log("Failed to listen on socket.", RED); + close(_server_fd); + exit(EXIT_FAILURE); + } + + log("Server initialized.", GREEN); +} + +void Server::run() +{ + struct pollfd server_pollfd; + server_pollfd.fd = _server_fd; + server_pollfd.events = POLLIN; + server_pollfd.revents = 0; + _poll_fds.push_back(server_pollfd); + + + struct pollfd stdin_pollfd; + stdin_pollfd.fd = STDIN_FILENO; + stdin_pollfd.events = POLLIN; + stdin_pollfd.revents = 0; + _poll_fds.push_back(stdin_pollfd); + + while (true) + { + int poll_count = poll(&_poll_fds[0], _poll_fds.size(), -1); + if (poll_count == -1) + { + log("Poll error.", RED); + exit(EXIT_FAILURE); + } + + for (size_t i = 0; i < _poll_fds.size(); ++i) + { + if (_poll_fds[i].revents & POLLIN) + { + if (_poll_fds[i].fd == _server_fd) + { + _clientManager->acceptClient(); + } + else if (_poll_fds[i].fd == STDIN_FILENO) + { + handleServerCommands(); + } + else + { + _clientManager->handleClient(_poll_fds[i].fd); + } + } + } + } +} + + +std::map &Server::getChannels() +{ + return _channels; +} + +const std::string &Server::getPassword() const +{ + return _password; +} + +void Server::handleServerCommands() +{ + std::string command; + std::getline(std::cin, command); + + if (command == "quit") + { + log("Server shutting down.", YELLOW); + exit(EXIT_SUCCESS); + } + else if (command == "channels") + { + log("Listing all channels:", BLUE); + for (std::map::iterator it = _channels.begin(); it != _channels.end(); ++it) + { + log(it->first, BLUE); + } + } + else if (command == "clients") + { + log("Listing all clients:", BLUE); + for (std::map::iterator it = _clients.begin(); it != _clients.end(); ++it) + { + log(it->second->getNickname(), BLUE); + } + } + else + { + log("Unknown server command.", RED); + } +} + +void Server::log(const std::string &message, const std::string &color) +{ + std::cout << color << message << std::endl; +} + +void Server::sendToClient(int client_fd, const std::string &message) +{ + int result = send(client_fd, message.c_str(), message.length(), 0); + if (result < 0) + { + std::stringstream ss; + ss << "Failed to send message to client " << client_fd; + log(ss.str(), RED); + } + else + { + std::stringstream ss; + ss << "Sent message to client " << client_fd << ": " << message; + log(ss.str(), YELLOW); + } +} +std::map &Server::getClients() +{ + return _clients; +} + +void Server::broadcast(const std::string &message) +{ + for (std::map::iterator it = _clients.begin(); it != _clients.end(); ++it) + { + sendToClient(it->first, message); + } +} + +Client* Server::getClientByName(const std::string &name) +{ + for (std::map::iterator it = _clients.begin(); it != _clients.end(); ++it) + { + if (it->second->getNickname() == name) + { + return it->second; + } + } + return NULL; +} + +Channel* Server::getChannelByName(const std::string &name) +{ + std::map::iterator it = _channels.find(name); + if (it != _channels.end()) + { + return it->second; + } + return NULL; +} + +bool Server::MatchFd(const pollfd& pfd, int clientFd) +{ + return pfd.fd == clientFd; +} + +void Server::removePollFd(int clientFd) +{ + for (std::vector::iterator it = _poll_fds.begin(); it != _poll_fds.end(); ++it) + { + if (MatchFd(*it, clientFd)) + { + _poll_fds.erase(it); + break; + } + } +} + +void Server::disconnectClient(int clientFd) +{ + close(clientFd); + + _clients.erase(clientFd); + removePollFd(clientFd); + + std::ostringstream oss; + oss << "Client disconnected: " << clientFd; + log(oss.str(), YELLOW); +} \ No newline at end of file diff --git a/ft_irc3/src/TopicHandler.cpp b/ft_irc3/src/TopicHandler.cpp new file mode 100644 index 0000000..f6fd3da --- /dev/null +++ b/ft_irc3/src/TopicHandler.cpp @@ -0,0 +1,96 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* TopicHandler.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/30 17:04:58 by fgras-ca #+# #+# */ +/* Updated: 2024/06/01 19:17:25 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "TopicHandler.hpp" + +TopicHandler::TopicHandler(Server* server) : _server(server) {} + +void TopicHandler::handleTopicCommand(Client* client, const std::string& command) +{ + _server->log("Received TOPIC command: " + command, "\033[1;34m"); + std::vector tokens = split(command, " \n\r\t"); + + if (tokens.size() < 2) + { + _server->sendToClient(client->getFd(), ERR_NEEDMOREPARAMS(client, "TOPIC")); + _server->log("TOPIC command error: Need more parameters", RED); + return; + } + + std::string channelName = tokens[1]; + Channel* channel = _server->getChannelByName(channelName); + if (!channel) + { + _server->sendToClient(client->getFd(), ERR_NOSUCHCHANNEL(client, channelName)); + _server->log("TOPIC command error: No such channel " + channelName, RED); + return; + } + + if (!channel->hasClient(client)) + { + _server->sendToClient(client->getFd(), ERR_NOTONCHANNEL(client, channelName)); + _server->log("TOPIC command error: Client not on channel " + channelName, RED); + return; + } + + if (tokens.size() == 2) + { + viewTopic(client, channel); + } + else + { + size_t topicStart = command.find(" :"); + std::string topic = (topicStart != std::string::npos) ? command.substr(topicStart + 2) : ""; + changeTopic(client, channel, topic); + } +} + +void TopicHandler::viewTopic(Client* client, Channel* channel) +{ + if (channel->getTopic().empty()) + { + _server->sendToClient(client->getFd(), RPL_NOTOPIC(client, channel->getName())); + _server->log("No topic set for channel " + channel->getName(), GREEN); + } + else + { + _server->sendToClient(client->getFd(), RPL_TOPIC(client, channel->getName(), channel->getTopic())); + _server->sendToClient(client->getFd(), RPL_TOPICWHOTIME(client, channel->getName(), channel->getTopicSetter(), channel->getTopicTime())); + _server->log("Sent topic info for channel " + channel->getName(), GREEN); + } +} + +void TopicHandler::changeTopic(Client* client, Channel* channel, const std::string& topic) +{ + if (channel->getTopicProtection() && !channel->isOperator(client)) + { + _server->sendToClient(client->getFd(), ERR_CHANOPRIVSNEEDED(client, channel->getName())); + _server->log("TOPIC command error: Channel operator privileges needed for " + channel->getName(), RED); + return; + } + + if (topic == channel->getTopic()) + { + _server->sendToClient(client->getFd(), RPL_TOPIC(client, channel->getName(), channel->getTopic())); + _server->log("TOPIC unchanged for channel " + channel->getName(), GREEN); + return; + } + + channel->setTopic(topic, client->getNickname()); + std::vector clients = channel->getClients(); + for (std::vector::iterator it = clients.begin(); it != clients.end(); ++it) + { + _server->sendToClient((*it)->getFd(), ":" + client->getNickname() + " TOPIC " + channel->getName() + " :" + topic + "\r\n"); + } + + _server->log("Changed topic for channel " + channel->getName() + " to: " + topic, GREEN); +} diff --git a/ft_irc3/src/Utils.cpp b/ft_irc3/src/Utils.cpp new file mode 100644 index 0000000..133e6cd --- /dev/null +++ b/ft_irc3/src/Utils.cpp @@ -0,0 +1,37 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Utils.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 12:17:59 by fgras-ca #+# #+# */ +/* Updated: 2024/05/16 18:17:21 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "Utils.hpp" + +std::vector split(const std::string &input, const std::string &delimiters) +{ + std::vector result; + size_t startPos = 0; + size_t foundPos = input.find_first_of(delimiters, startPos); + + while (foundPos != std::string::npos) + { + if (foundPos != startPos) + { + result.push_back(input.substr(startPos, foundPos - startPos)); + } + startPos = foundPos + 1; + foundPos = input.find_first_of(delimiters, startPos); + } + + if (startPos < input.length()) + { + result.push_back(input.substr(startPos)); + } + + return result; +} diff --git a/ft_irc3/src/Welcome.cpp b/ft_irc3/src/Welcome.cpp new file mode 100644 index 0000000..28cb40a --- /dev/null +++ b/ft_irc3/src/Welcome.cpp @@ -0,0 +1,44 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Welcome.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/21 17:53:52 by fgras-ca #+# #+# */ +/* Updated: 2024/05/30 16:43:14 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "Welcome.hpp" + +void WelcomeHandler::sendWelcomeMessages(Client *client, Server *server) +{ + server->sendToClient(client->getFd(), RPL_WELCOME(client)); + server->sendToClient(client->getFd(), RPL_YOURHOST(client)); + server->sendToClient(client->getFd(), RPL_CREATED(client)); + server->sendToClient(client->getFd(), RPL_MYINFO(client)); + server->sendToClient(client->getFd(), RPL_ISUPPORT(client, "i/t/k/o/l")); + + sendMotd(client, server); +} + +void WelcomeHandler::sendMotd(Client *client, Server *server) +{ + std::ifstream motdFile("motd.txt"); + if (motdFile.is_open()) + { + std::string line; + server->sendToClient(client->getFd(), RPL_MOTDSTART(client)); + while (std::getline(motdFile, line)) + { + server->sendToClient(client->getFd(), RPL_MOTD(client, line)); + } + server->sendToClient(client->getFd(), RPL_ENDOFMOTD(client)); + motdFile.close(); + } + else + { + server->sendToClient(client->getFd(), ERR_NOMOTD(client)); + } +} diff --git a/ft_irc3/src/Who.cpp b/ft_irc3/src/Who.cpp new file mode 100644 index 0000000..34b22f6 --- /dev/null +++ b/ft_irc3/src/Who.cpp @@ -0,0 +1,71 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Who.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/17 16:09:20 by fgras-ca #+# #+# */ +/* Updated: 2024/05/30 16:56:03 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "Who.hpp" + +WhoHandler::WhoHandler(Server *server) + : _server(server) +{ +} + +void WhoHandler::handleWhoCommand(Client *client, const std::string &command) +{ + std::istringstream iss(command); + std::string cmd, mask; + iss >> cmd >> mask; + + std::map &channels = _server->getChannels(); + if (channels.find(mask) != channels.end()) + { + Channel *channel = channels[mask]; + std::vector channelClients = channel->getClients(); + + for (size_t i = 0; i < channelClients.size(); ++i) + { + _server->sendToClient(client->getFd(), RPL_WHOREPLY(mask, channelClients[i])); + } + } + else + { + Client *targetClient = _server->getClientByName(mask); + if (targetClient) + { + _server->sendToClient(client->getFd(), RPL_WHOREPLY("*", targetClient)); + } + } + + _server->sendToClient(client->getFd(), RPL_ENDOFWHO(client, mask)); +} + +void WhoHandler::handleWhoisCommand(Client *client, const std::string &command) +{ + std::istringstream iss(command); + std::string cmd, target; + iss >> cmd >> target; + + if (target.empty()) + { + _server->sendToClient(client->getFd(), ERR_NONICKNAMEGIVEN(client)); + return; + } + + Client *targetClient = _server->getClientByName(target); + if (!targetClient) + { + _server->sendToClient(client->getFd(), ERR_NOSUCHNICK(client, target)); + return; + } + + _server->sendToClient(client->getFd(), RPL_WHOISUSER(client, targetClient)); + _server->sendToClient(client->getFd(), RPL_WHOISSERVER(client, target, "IRC server info")); + _server->sendToClient(client->getFd(), RPL_ENDOFWHOIS(client, target)); +} diff --git a/ft_irc3/src/main.cpp b/ft_irc3/src/main.cpp new file mode 100644 index 0000000..88862d5 --- /dev/null +++ b/ft_irc3/src/main.cpp @@ -0,0 +1,33 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* main.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: fgras-ca +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/05/15 12:16:41 by fgras-ca #+# #+# */ +/* Updated: 2024/05/15 12:49:07 by fgras-ca ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "Server.hpp" +#include "Utils.hpp" +#include +#include + +int main(int argc, char *argv[]) +{ + if (argc != 3) + { + std::cerr << RED << "Usage: " << argv[0] << " " << RESET << std::endl; + return EXIT_FAILURE; + } + + int port = std::atoi(argv[1]); + std::string password = argv[2]; + + Server server(port, password); + server.run(); + + return EXIT_SUCCESS; +} diff --git a/ft_irc3/target b/ft_irc3/target new file mode 100644 index 0000000..e69de29 diff --git a/notessujet b/notessujet new file mode 100644 index 0000000..019fe59 --- /dev/null +++ b/notessujet @@ -0,0 +1,321 @@ +FT_IRC + +Ce projet consiste à créer votre propre serveur IRC (Internet Relay Chat). Vous utiliserez un client IRC actuel pour vous connecter à votre serveur et le tester. L'Internet est régi par des protocoles de standards solides qui permettent aux ordinateurs connectés de communiquer entre eux. C'est toujours bénéfique de connaître ces protocoles. + +Chapitre I - Introduction + +L'Internet Relay Chat, ou IRC, est un protocole de communication basé sur le texte sur Internet. Il offre des messageries en temps réel qui peuvent être publiques ou privées. Les utilisateurs peuvent échanger des messages directs et rejoindre des canaux de groupe. Les clients IRC se connectent aux serveurs IRC pour rejoindre ces canaux. Les serveurs IRC sont connectés entre eux pour former un réseau. + + La mise en place d'un serveur IRC implique plusieurs étapes clés que nous pouvons explorer brièvement : + +1. Initialisation du serveur +Création d'un socket serveur : C'est le premier point de contact entre votre serveur et les clients IRC. Vous utilisez la fonction socket() pour créer un socket. +Liaison du socket : Utilisez la fonction bind() pour associer le socket à une adresse IP et un numéro de port spécifiques. +Écoute des connexions entrantes : Avec listen(), votre serveur se met à écouter sur le port spécifié, prêt à accepter les connexions client. + +2. Gestion des connexions client +Acceptation des connexions : La fonction accept() permet au serveur d'accepter une connexion entrante d'un client. +Gestion multithread ou multiplexage : Pour gérer plusieurs clients en même temps, utilisez le multithreading (avec std::thread en C++11 ou plus) ou le multiplexage des entrées/sorties (par exemple, select() ou poll()). + +3. Interprétation des commandes IRC +Réception des messages : Les messages des clients sont reçus en continu. Chaque message doit être analysé pour identifier la commande (par exemple, JOIN, NICK, PRIVMSG). +Exécution des commandes : En fonction de la commande, le serveur exécutera différentes actions, comme rejoindre un canal ou envoyer un message à un autre utilisateur. + +4. Envoi de réponses et de messages +Formatage des réponses : Les réponses aux commandes doivent être formatées selon les spécifications du protocole IRC, puis envoyées au client concerné. +Diffusion : Pour les messages comme ceux d’un canal IRC, le serveur doit envoyer le message à tous les utilisateurs présents dans le canal. + +5. Gestion des erreurs et de la stabilité +Traitement des exceptions : Assurez-vous de gérer les exceptions pour éviter que le serveur ne se termine de manière inattendue. +Surveillance des ressources : Surveillez l'utilisation de la mémoire et les performances pour éviter les dépassements de mémoire et les fuites. + +6. Fermeture et nettoyage +Déconnexion propre : Lorsqu'un client se déconnecte ou lors de la fermeture du serveur, assurez-vous de fermer proprement tous les sockets et de libérer toutes les ressources utilisées. +Voici un petit exemple de code pour initialiser un socket serveur en C++ : + +#include +#include +#include +#include + +int main() { + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + std::cerr << "Erreur de création de socket" << std::endl; + return 1; + } + + sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(6667); // Port standard pour IRC + + if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + std::cerr << "Erreur de liaison" << std::endl; + return 1; + } + + listen(sockfd, 5); + std::cout << "Serveur IRC en écoute sur le port 6667" << std::endl; + + // Ajouter ici le code pour accepter les connexions et traiter les clients + + return 0; +} + +Chapitre II - Règles générales + +Votre programme ne doit pas planter dans aucune circonstance (même en cas de pénurie de mémoire), et ne doit pas se terminer de manière inattendue. Si cela arrive, votre projet sera considéré comme non fonctionnel et votre note sera de 0. +Vous devez fournir un Makefile qui compilera vos fichiers sources. Ce Makefile ne doit pas recompiler les objets déjà compilés. +Votre Makefile doit contenir au minimum les règles : $(NAME), all, clean, fclean et re. +Compilez votre code avec g++ et les drapeaux -Wall -Wextra -Werror. +Votre code doit être conforme à la norme C++ 98. Il doit toujours être compilable si vous ajoutez le drapeau -std=c++98. +Essayez d'utiliser le plus possible les fonctionnalités de C++ (par exemple, préférez à ). Vous êtes autorisé à utiliser des fonctions C, mais préférez toujours leurs versions C++ si possible. +L'utilisation de toute bibliothèque externe et des bibliothèques Boost est interdite. + +Chapitre III - Partie Obligatoire + +Description du Programme +Nom du programme : ircserv +Fichiers à rendre : Makefile, *.{h, hpp}, *.cpp, *.tpp, *.ipp, un fichier de configuration optionnel +Règles du Makefile : NAME, all, clean, fclean, re +Arguments du programme : + +port : Le port d'écoute pour les connexions entrantes IRC. +password : Le mot de passe de connexion, nécessaire pour tout client IRC tentant de se connecter au serveur. + +Fonctions externes autorisées +Toutes ces fonctions doivent être utilisées conformément à la norme C++ 98 : + +Fonctions de gestion de socket +Bibliothèque +close() : Ferme un descripteur de fichier, donc le socket, libérant toutes les ressources. +Bibliothèque +socket() : Crée un point de communication (socket) et renvoie un descripteur de fichier. +setsockopt() : Configure les options du socket, permettant de modifier son comportement à divers niveaux. +bind() : Associe une adresse locale à un socket, spécifiant le port et l'adresse IP pour les connexions entrantes. +connect() : Initie une connexion sur un socket spécifié à une adresse distante. +listen() : Écoute les connexions sur un socket spécifique. +accept() : Accepte une connexion entrante sur un socket en écoute, créant un nouveau socket connecté pour cette connexion. +send() : Envoie des données à la connexion spécifiée par le descripteur de socket. +recv() : Reçoit des données d'une connexion spécifiée par le descripteur de socket. + +Fonctions de manipulation de données réseau +Bibliothèque +htons() : Convertit un short (16 bits) de l'ordre des octets de l'hôte à celui du réseau. +htonl() : Convertit un long (32 bits) de l'ordre des octets de l'hôte à celui du réseau. +ntohs() : Convertit un short (16 bits) de l'ordre des octets du réseau à celui de l'hôte. +ntohl() : Convertit un long (32 bits) de l'ordre des octets du réseau à celui de l'hôte. +Bibliothèque +inet_addr() : Convertit une chaîne de caractères représentant une adresse IPv4 en un entier approprié. +inet_ntoa() : Convertit une adresse IP de sa forme numérique en une chaîne de caractères. + +Fonctions de résolution de noms +Bibliothèque +getprotobyname() : Renvoie des informations sur un protocole spécifique (par exemple "tcp") identifié par son nom. +gethostbyname() : Renvoie des informations sur un hôte, y compris son adresse IP, identifié par son nom. +getaddrinfo() : Fournit des informations d'adresse pour configurer des connexions socket. +freeaddrinfo() : Libère la mémoire allouée par getaddrinfo(). + +Fonctions de signalisation et multiplexage +Bibliothèque +signal() : Permet de gérer des signaux, tels que SIGINT, en définissant la fonction appelée lors de la réception d'un signal. +sigaction() : Permet de gérer des signaux de manière plus précise que signal(), offrant plus de contrôle sur le comportement du signal. +poll() : Attends un événement sur un ensemble de descripteurs de fichiers, très utile pour gérer plusieurs connexions. +Bibliothèque + +Fonctions de manipulation de fichiers et contrôles +lseek() : Positionne le descripteur de fichier (par exemple, pour revenir au début d'un fichier). +Bibliothèque et +fstat() : Obtient les informations d'état d'un fichier ouvert. +Bibliothèque +fcntl() : Manipule les propriétés d'un fichier ouvert, comme le blocage ou le comportement non-blocant de l'opération. +Ces fonctions offrent une base solide pour gérer les sockets, les connexions réseau, la conversion d'adresses, la gestion des signaux, et la manipulation basique des fichiers, tous essentiels pour construire un serveur IRC robuste. + +(Notez que d'autres fonctions comme select(), epoll() et kqueue() peuvent venir de différentes bibliothèques, notamment , et pour kqueue() si vous utilisez un système comme BSD ou macOS.) + +Même si pool() est mentionné dans le sujet, vous pouvez utiliser n'importe quel équivalent comme select(), kqueue(), ou epoll(). + +Exemple de démarrage du serveur IRC +Voici un exemple de code simplifié montrant comment initialiser un serveur en écoutant sur un port et en utilisant un mot de passe pour authentifier les clients. Ce code est purement illustratif pour vous donner une idée de la structure : + +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + if (argc != 3) { + std::cerr << "Usage: ./ircserv " << std::endl; + return 1; + } + + int port = std::atoi(argv[1]); + const char* password = argv[2]; + + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + std::cerr << "Could not create socket" << std::endl; + return 1; + } + + sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(port); + + if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + std::cerr << "Bind failed" << std::endl; + close(sockfd); + return 1; + } + + listen(sockfd, 5); + std::cout << "Server listening on port " << port << std::endl; + + // Ajout d'une boucle ici pour accepter les connexions et vérifier le mot de passe + + close(sockfd); + return 0; +} + +Chapitre III.1 - Exigences de votre serveur IRC + +Gestion des clients + +Multiclient : Le serveur doit être capable de gérer plusieurs clients simultanément sans jamais se bloquer. +Opérations non-bloquantes : Toutes les opérations d'entrée/sortie doivent être non-bloquantes. L'utilisation de fork() n'est pas autorisée. +Utilisation unique de poll() : Seul un appel à poll() (ou équivalent comme select(), epoll()) peut être utilisé pour gérer toutes les opérations (lecture, écriture, écoute, etc.). Bien que les opérations de lecture/écriture puissent être réalisées sans poll() pour rendre les descripteurs de fichiers non-bloquants, cela consommerait plus de ressources système. Si poll() n'est pas utilisé là où nécessaire, la note sera de 0. + +Choix et compatibilité du client IRC + +Client de référence : Vous devez choisir un client IRC spécifique comme référence, qui sera utilisé pendant le processus d'évaluation pour tester votre serveur. +Compatibilité : Votre client de référence doit pouvoir se connecter à votre serveur sans rencontrer d'erreurs. +Communication : La communication entre le client et le serveur doit se faire via TCP/IP (IPv4 ou IPv6). + +Fonctionnalités requises + +Authentification et gestion de l'utilisateur : Les utilisateurs doivent pouvoir s'authentifier, définir un pseudonyme (nickname), un nom d'utilisateur (username), rejoindre un canal, envoyer et recevoir des messages privés. +Gestion des canaux : Tous les messages envoyés à un canal doivent être transmis à tous les autres clients présents dans ce canal. +Utilisateurs et opérateurs : Votre serveur doit distinguer les opérateurs de canal des utilisateurs réguliers. + +Commandes spécifiques aux opérateurs de canal : +KICK : Expulser un client du canal. +INVITE : Inviter un client à rejoindre un canal. +TOPIC : Modifier ou afficher le sujet du canal. +MODE : Changer le mode du canal : +i : Activer/désactiver le canal sur invitation seulement. +t : Activer/désactiver les restrictions de la commande TOPIC aux opérateurs de canal. +k : Définir/supprimer la clé du canal (mot de passe). +o : Donner/retirer les privilèges d'opérateur de canal. +l : Définir/supprimer la limite d'utilisateurs dans le canal. + +Qualité du code + +Code propre : Un code bien organisé, clair et maintenable est attendu. + +Conseils de mise en œuvre +Architecture : Pensez à une architecture modulaire où chaque fonctionnalité (gestion des utilisateurs, gestion des canaux, traitement des commandes) est isolée pour faciliter le développement et la maintenance. +Tests : Développez des tests unitaires pour chaque composant pour assurer la fiabilité de votre serveur. +Sécurité : Soyez vigilant avec la gestion de la mémoire et la validation des entrées pour éviter les failles de sécurité. +Ces exigences définissent clairement les fonctionnalités que votre serveur doit implémenter et comment il doit être conçu pour interagir efficacement avec les clients IRC. + +Chapitre III.3 - Exemple de Test +Objectif du test +L'objectif de ce test est de vérifier la robustesse de votre serveur IRC en simulant des conditions réseau défavorables telles que la réception de données partielles ou une bande passante limitée. Ce type de test est crucial pour s'assurer que votre serveur peut gérer des situations où les données envoyées par les clients n'arrivent pas en une seule pièce mais plutôt en fragments. + +Description du test +Le test utilise l'outil nc (Netcat), qui est un utilitaire permettant de lire et écrire des données à travers des connexions réseau. Il est utilisé ici pour envoyer des commandes à votre serveur en plusieurs parties, pour simuler un client IRC envoyant des données de manière fragmentée. + +Procédure de test +1.Ouverture de la connexion : + +$> nc 127.0.0.1 6667 + +Cette commande ouvre une connexion TCP sur le port 6667 à l'adresse localhost (127.0.0.1), où votre serveur IRC est censé écouter. + +2.Envoi de commandes fragmentées : + +Tapez com puis appuyez sur Ctrl+D pour envoyer cette partie de la commande. +Tapez ensuite man et appuyez de nouveau sur Ctrl+D. +Enfin, tapez d\n et appuyez une dernière fois sur Ctrl+D pour terminer l'envoi. +Ctrl+D est utilisé ici pour simuler la fin de l'envoi de données, poussant Netcat à envoyer les données sans fermer la connexion. Cela permet de simuler l'envoi de données en morceaux, comme cela pourrait se produire dans des conditions de réseau réel avec des délais ou des interruptions. +Traitement des données fragmentées +Agrégation des paquets : Votre serveur doit être capable de reconstruire la commande complète à partir de ces fragments. Cela signifie que vous devez implémenter une logique pour stocker temporairement les fragments de données reçus jusqu'à ce que la commande soit complètement reçue et prête à être traitée. +Traitement de la commande : Une fois la commande entièrement reconstituée, votre serveur devrait la traiter comme il le ferait pour toute commande reçue en une seule fois. +Importance du test +Ce test est essentiel pour vérifier que votre serveur gère correctement les aspects de la transmission TCP tels que la segmentation des données et les délais de transmission, qui sont des réalités courantes dans les communications sur Internet. Il garantit que les commandes sont correctement reçues et traitées même lorsque les données arrivent en plusieurs morceaux. + +Ce type de test est crucial pour assurer la fiabilité et la robustesse de votre serveur dans des conditions réseau réalistes. Il est conseillé de réaliser des tests supplémentaires pour d'autres scénarios d'erreur possibles et de s'assurer que votre serveur est capable de gérer toutes sortes de situations sans planter ou sans perdre de données. + +Utilisation de Netcat + +Écouter sur un port spécifique +Pour démarrer Netcat en mode écoute (serveur) sur un port spécifique : + +nc -l 1234 + +Ceci configure Netcat pour écouter sur le port 1234. + +Se connecter à un serveur +Pour utiliser Netcat comme client et se connecter à un serveur : + +nc [adresse IP ou nom de domaine] [port] + +Par exemple, pour se connecter à un serveur local sur le port 1234 : + +nc 127.0.0.1 1234 + +Transférer un fichier +Netcat peut être utilisé pour transférer des fichiers entre un serveur et un client. + +Sur le serveur (réception) : + +nc -l 1234 > fichier_de_sortie.txt + +Sur le client (envoi) : + +nc [adresse IP du serveur] 1234 < fichier_à_envoyer.txt + +Chat simple +Netcat peut être utilisé pour créer un chat basique en réseau entre deux machines. + +Sur la machine A (serveur) : + +nc -l 1234 + +Sur la machine B (client) : + +nc [adresse IP de A] 1234 + +Après ces commandes, chaque texte entré dans la console de Netcat sera envoyé à l'autre partie. + +Chapitre IV - Partie Bonus + +Si vous avez complété avec succès toutes les exigences obligatoires de votre projet de serveur IRC et que tout fonctionne parfaitement, vous pouvez envisager d'ajouter des fonctionnalités supplémentaires pour enrichir votre serveur. Voici quelques suggestions pour la partie bonus : + +1. Gestion du transfert de fichiers + +Implémentation de DCC (Direct Client-to-Client) : Le protocole DCC permet aux utilisateurs de votre serveur IRC de transférer des fichiers directement entre eux. Cette fonctionnalité peut être complexe, car elle implique la gestion de connexions peer-to-peer en plus des communications client-serveur habituelles. +Sécurité et validation : Assurez-vous que le transfert de fichiers est sécurisé et que les fichiers sont correctement validés pour éviter le transfert de malwares. + +2. Création d'un bot IRC + +Fonctionnalités du bot : Le bot pourrait offrir des services comme la gestion des commandes administratives, la fourniture d'informations en temps réel (météo, nouvelles), des jeux, ou des rappels. +Interaction avec les utilisateurs : Le bot devrait être capable de répondre aux commandes des utilisateurs et d'effectuer des actions spécifiques basées sur ces commandes. +Automatisation : Le bot pourrait également surveiller les conversations dans les canaux pour modérer les discussions ou notifier les opérateurs de certains événements. + +Points à considérer pour les fonctionnalités bonus +Intégrité de la partie obligatoire : Avant de vous lancer dans le développement des fonctionnalités bonus, assurez-vous que toutes les fonctionnalités obligatoires sont complètement fonctionnelles et exemptes de bugs. Les fonctionnalités bonus ne seront évaluées que si la partie obligatoire est jugée "parfaite". +Documentation et tests : Tout comme pour les fonctionnalités obligatoires, documentez bien vos fonctionnalités bonus et assurez-vous qu'elles sont bien testées. Les tests devraient couvrir non seulement les cas d'utilisation normaux mais aussi les cas d'erreur et les limites de performance. + +Conseils de développement +Modularité : Essayez de garder votre code aussi modulaire que possible. Cela vous permettra d'ajouter des fonctionnalités bonus sans perturber le fonctionnement de base de votre serveur. +Scalabilité : Pensez à la scalabilité de votre serveur avec ces nouvelles fonctionnalités. Assurez-vous que l'ajout de fonctionnalités comme le transfert de fichiers et un bot n'impactera pas négativement la performance générale du serveur. +Conformité aux standards : Même pour les fonctionnalités bonus, tenez-vous aux standards IRC et aux bonnes pratiques de programmation pour garantir que votre serveur peut interagir correctement avec d'autres clients et serveurs IRC. + +Ces fonctionnalités bonus peuvent grandement enrichir l'expérience utilisateur sur votre serveur IRC, le rendant plus attractif et plus utile pour vos utilisateurs. + +Dans le cadre de notre projet nous utiliserons hexcat comme reference de client irc. diff --git a/plan b/plan new file mode 100644 index 0000000..ca570f5 --- /dev/null +++ b/plan @@ -0,0 +1,84 @@ +Plan d'action pour le développement + +Étape 1: Configuration de l'environnement de développement + +Installation des outils : Assurez-vous d'avoir un compilateur C++ qui supporte C++98, comme g++. +Structure de base du projet : Créez un répertoire de projet avec des sous-répertoires pour les fichiers sources (src), les en-têtes (include), et les objets (build). + +Étape 2: Conception de l'architecture de base + +Définition des modules : Identifiez les principaux composants du serveur, tels que la gestion des connexions réseau, le traitement des commandes IRC, et la gestion des utilisateurs et des canaux. +Création des fichiers : +main.cpp : Le point d'entrée du programme. +Server.cpp et Server.hpp : Gérer la logique du serveur, les sockets, et les connexions. +Client.cpp et Client.hpp : Gérer les informations et interactions spécifiques aux clients. +Channel.cpp et Channel.hpp : Gérer les opérations liées aux canaux. +CommandHandler.cpp et CommandHandler.hpp : Interpréter et exécuter les commandes IRC. +Utilities.cpp et Utilities.hpp : Fonctions utilitaires, comme la manipulation de chaînes et le logging. + +Étape 3: Implémentation des fonctionnalités de base + +Gestion des sockets : Écrivez le code pour créer, lier, et écouter les sockets. +Connexion des clients : Implémentez la logique pour accepter les nouvelles connexions des clients. +Réception et envoi de messages : Codez la réception des commandes des clients et l'envoi de réponses. + +Étape 4: Gestion des commandes et des canaux + +Traitement des commandes : Développez le traitement des commandes telles que JOIN, NICK, PRIVMSG. +Gestion des canaux : Implémentez la logique pour créer, modifier, et gérer les canaux. + +Étape 5: Tests et débogage + +Tests unitaires : Écrivez des tests pour chaque composant. +Tests d'intégration : Testez le fonctionnement global du serveur avec des clients IRC. +Débogage : Corrigez les bugs identifiés lors des tests. + +Étape 6: Préparation pour les fonctionnalités bonus + +Architecture extensible : Assurez-vous que votre conception permet facilement l'ajout de nouvelles fonctionnalités, comme le transfert de fichiers ou un bot IRC. +Documentation : Documentez les points d'extension pour les fonctionnalités bonus. + +Étape 7: Documentation et finalisation + +Documentation du code : Assurez-vous que chaque fonction et module est bien documenté. +Nettoyage du code : Revoyez votre code pour vous assurer qu'il est propre et bien organisé. +Intégration des fonctionnalités bonus +Planification conditionnelle : Les fonctionnalités bonus ne doivent être abordées que si la partie obligatoire est parfaitement fonctionnelle et testée. +Modularité : Gardez les modules bonus séparés et veillez à ce qu'ils ne perturbent pas le fonctionnement de base. + +Arborescence du pojet: + +irc_server/ +│ +├── src/ +│ ├── main.cpp # Point d'entrée du programme. +│ ├── Server.cpp # Implémentation des fonctionnalités du serveur. +│ ├── Client.cpp # Gestion des clients. +│ ├── Channel.cpp # Gestion des canaux. +│ ├── CommandHandler.cpp # Traitement et exécution des commandes IRC. +│ ├── Utilities.cpp # Fonctions utilitaires. +│ ├── FileTransfer.cpp # Gestion du transfert de fichiers (bonus). +│ └── Bot.cpp # Logique du bot IRC (bonus). +│ +├── include/ +│ ├── Server.hpp # Déclarations pour la gestion du serveur. +│ ├── Client.hpp # Déclarations pour la gestion des clients. +│ ├── Channel.hpp # Déclarations pour la gestion des canaux. +│ ├── CommandHandler.hpp # Déclarations pour le traitement des commandes. +│ ├── Utilities.hpp # Déclarations pour les fonctions utilitaires. +│ ├── FileTransfer.hpp # Déclarations pour la gestion du transfert de fichiers (bonus). +│ └── Bot.hpp # Déclarations pour la logique du bot IRC (bonus). +│ +├── build/ +│ └── (fichiers objets et binaires générés lors de la compilation) +│ +├── tests/ +│ ├── server_tests.cpp # Tests unitaires pour le serveur. +│ ├── client_tests.cpp # Tests pour les fonctionnalités client. +│ ├── channel_tests.cpp # Tests pour les canaux. +│ ├── file_transfer_tests.cpp # Tests pour le transfert de fichiers (bonus). +│ └── bot_tests.cpp # Tests pour le bot IRC (bonus). +│ +├── Makefile # Makefile pour compiler le projet. +│ +└── README.md # Instructions de base et documentation.