ft_irc is an IRC (Internet Relay Chat) server written in C++98, following RFC 2812. It handles multiple clients simultaneously using poll(), supports channel management, user authentication, operator privileges, and channel modes. A bonus moderation bot (ModBot) is included for automated spam and bad-word detection.
ft_irc/
├── Makefile
├── main.cpp
├── include/
│ ├── Server.hpp
│ ├── Client.hpp
│ ├── Channel.hpp
│ ├── Replies.hpp
│ ├── commands.hpp
│ └── Bot_bonus.hpp
├── src/
│ ├── Server.cpp
│ ├── Client.cpp
│ ├── Channel.cpp
│ └── commands/
│ ├── Join.cpp
│ ├── Part.cpp
│ ├── Kick.cpp
│ ├── Invite.cpp
│ ├── Mode.cpp
│ ├── Topic.cpp
│ ├── Quit.cpp
│ ├── Privmsg.cpp
│ └── Notice.cpp
└── Bot/
├── main_bonus.cpp
└── Bot_bonus.cpp
| File | Summary |
|---|---|
Server.cpp/.hpp |
Core server: socket setup, poll() event loop, client/channel management, command parsing and dispatching |
Client.cpp/.hpp |
Client state: nickname, username, registration status, message buffer, joined channels list |
Channel.cpp/.hpp |
Channel state: members, operators, invite list, modes (+i/+t/+k/+l/+o), broadcasting |
Replies.hpp |
IRC numeric reply macros (RPL/ERR) and message format macros (JOIN, KICK, PART, etc.) |
commands.hpp |
Command function prototypes |
commands/*.cpp |
One file per IRC command (JOIN, PART, KICK, INVITE, MODE, TOPIC, QUIT, PRIVMSG, NOTICE) |
Bot_bonus.cpp/.hpp |
Moderation bot: spam detection, bad-word filtering, !kick and !help commands |
Build using make in the root directory, run the server, connect the clients and there you are connected to an IRC server! The following sections show the features available and how to use them.
make./ircserv <port> <password>There are multiple ways and tools to connect a client to a server, either by using netcat, irssi, weechat or hexchat. We will go with irssi since it's highly configurable and fast.
irssiThen inside irssi
/connect localhost <port> <password> nickname
make bonus
./ircbot <ip> <port> <password> "#channel1,#channel2"The bot automatically joins the specified channels and moderates them by detecting spam (repeated messages) and bad words. Users can also use !help and !kick <nick> (operators only).
The commands are text-based instructions starting with a forward slash (/) used in IRC clients to manage connections, join channels, and interact with users.
| Command | Description |
|---|---|
PASS <password> |
Authenticate with the server |
NICK <nick> |
Set or change your nickname |
USER <user> 0 * :<realname> |
Set username and real name |
JOIN <#channel> [key] |
Join a channel (create it if it doesn't exist) |
JOIN 0 |
Leave all channels |
PART <#channel> [reason] |
Leave a channel |
PRIVMSG <target> :<message> |
Send a message to a user or channel |
NOTICE <target> :<message> |
Like PRIVMSG but no error replies |
TOPIC <#channel> [:<topic>] |
View or set channel topic |
KICK <#channel> <nick> [reason] |
Kick a user (operators only) |
INVITE <nick> <#channel> |
Invite a user to a channel |
MODE <#channel> <+/-modes> [args] |
Set channel modes |
PING <token> |
Keepalive — server replies with PONG |
QUIT [reason] |
Disconnect from the server |
Channel modes restrict permissions for users or clients.
| Mode | Description |
|---|---|
+i / -i |
Invite-only |
+t / -t |
Only operators can change topic |
+k <key> / -k |
Set/remove channel password |
+l <n> / -l |
Set/remove user limit |
+o <nick> / -o <nick> |
Grant/revoke operator status |
This guide details the exact step-by-step logic required for the 8 core ft_irc commands. This is a pure logic reference with no code, structured as decision trees and flowcharts to guide your implementation.
- Server: Find Channel, Create new Channel.
- Channel: Check Mode (+i, +k, +l), Check Members count, Check Invites list, Add Member, Add Operator, Remove from Invites, Get Topic, Broadcast message.
- Client: Check if registered, Add Channel to joined list, Send Replies.
- Initial Check:
- Is the User fully registered?
- ❌ No: Send
ERR_NOTREGISTEREDto Client. Stop processing this command. - ✅ Yes: Continue.
- ❌ No: Send
- Is the User fully registered?
- Parsing:
- Split the given
<channel>string by commas. - Split the given
<key>string by commas. - Loop through each parsed channel name and its corresponding key (if any).
- Split the given
- Channel Exists?
- For the current channel name in the loop, does a Channel object exist on the Server?
- ❌ No (Channel doesn't exist):
- Server creates a new Channel object with the given name and key (if provided).
- Add the User to this new Channel as a Member.
- Promote the User to an Operator for this new Channel.
- Add the Channel's name to the User's list of joined channels.
- Proceed to Step 5 (Broadcast & Replies).
- ✅ Yes (Channel exists): Continue to Step 4 (Mode Checks).
- ❌ No (Channel doesn't exist):
- For the current channel name in the loop, does a Channel object exist on the Server?
- Mode Checks (For Existing Channels):
- Check +i (Invite-Only): Is the Channel Invite-Only?
- ✅ Yes: Is the User on the Channel's Invite list?
- ❌ No: Send
ERR_INVITEONLYCHANto Client. Skip to the next channel in the loop. - ✅ Yes: Continue.
- ❌ No: Send
- ✅ Yes: Is the User on the Channel's Invite list?
- Check +k (Key Required): Does the Channel require a password?
- ✅ Yes: Does the provided key match the Channel's password?
- ❌ No: Send
ERR_BADCHANNELKEYto Client. Skip to the next channel in the loop. - ✅ Yes: Continue.
- ❌ No: Send
- ✅ Yes: Does the provided key match the Channel's password?
- Check +l (User Limit): Does the Channel have a user limit?
- ✅ Yes: Is the current number of members >= the limit?
- ✅ Yes (Full): Send
ERR_CHANNELISFULLto Client. Skip to the next channel in the loop.
- ✅ Yes (Full): Send
- ❌ No / Not Full: Continue.
- ✅ Yes: Is the current number of members >= the limit?
- Success (Passed all checks):
- Add the User to the Channel as a Member.
- Remove the User from the Channel's Invite list (if they were on it).
- Add the Channel's name to the User's list of joined channels.
- Check +i (Invite-Only): Is the Channel Invite-Only?
- Broadcast & Replies:
- Broadcast to all members in the Channel (including the new User):
:<user_prefix> JOIN <channel_name> - Does the Channel have a Topic set?
- ✅ Yes: Send
RPL_TOPICto the joined User.
- ✅ Yes: Send
- Send
RPL_NAMREPLY(List of all members in the channel, prefixing operators with@) to the joined User. - Send
RPL_ENDOFNAMESto the joined User.
- Broadcast to all members in the Channel (including the new User):
- Server: Find Channel, Delete Channel.
- Channel: Check if User is a member, Remove Member, Remove Operator, Broadcast message, Check if Channel is empty.
- Client: Remove Channel from joined list, Send Replies.
- Initial Check:
- Is the User fully registered?
- ❌ No: Ignore or send Error. Stop.
- ✅ Yes: Continue.
- Is the User fully registered?
- Parsing:
- Split the given
<channel>string by commas. - Extract the
<reason>text (if provided). - Loop through each parsed channel name.
- Split the given
- Channel Validation:
- For the current channel name in the loop, does the Channel exist on the Server?
- ❌ No: Send
ERR_NOSUCHCHANNELto Client. Skip to the next channel. - ✅ Yes: Continue.
- ❌ No: Send
- For the current channel name in the loop, does the Channel exist on the Server?
- Member Validation:
- Is the User a member of this Channel?
- ❌ No: Send
ERR_NOTONCHANNELto Client. Skip to the next channel. - ✅ Yes: Continue.
- ❌ No: Send
- Is the User a member of this Channel?
- Execution:
- Broadcast to all members in the Channel (including the departing User):
:<user_prefix> PART <channel_name> :<reason> - Remove the User from the Channel's Member list.
- Remove the User from the Channel's Operator list (if they were one).
- Remove the Channel's name from the User's list of joined channels.
- Broadcast to all members in the Channel (including the departing User):
- Cleanup:
- Is the Channel now completely empty (0 members)?
- ✅ Yes: Server deletes the Channel object entirely from memory.
- Is the Channel now completely empty (0 members)?
- Server: Find Channels, Delete Channels, Remove Client, Close Socket.
- Channel: Broadcast message, Remove Member, Remove Operator, Remove Invite, Check if Empty.
- Client: Provide list of joined Channels, Send Error/Message (optional).
- Preparation:
- Extract the
<Quit message>(if not provided, default to a generic "Client Quit"). - Get the complete list of all Channels the User is currently joined to.
- Extract the
- Disconnecting from Channels:
- Loop through the User's list of joined Channels.
- For each Channel:
- Broadcast to all other members in the Channel:
:<user_prefix> QUIT :<Quit message> - Remove the User from the Channel's Member list.
- Remove the User from the Channel's Operator list (if they were one).
- Remove the User from the Channel's Invite list (if they were invited).
- Cleanup: Is the Channel now empty?
- ✅ Yes: Server deletes the Channel object entirely.
- Broadcast to all other members in the Channel:
- Server Cleanup:
- Close the User's network socket (file descriptor).
- Remove the User object entirely from the Server's tracked clients.
- Server: Find Channel, Find Target Client.
- Channel: Check if Sender is member, Check if Sender is Operator, Check if Target is member, Broadcast message, Remove Target Member, Remove Target Operator.
- Client (Target): Remove Channel from joined list. Send Replies.
- Channel Validation:
- Does the specified
<channel>exist on the Server?- ❌ No: Send
ERR_NOSUCHCHANNELto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Does the specified
- Sender Validation:
- Is the Sender a member of this Channel?
- ❌ No: Send
ERR_NOTONCHANNELto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Is the Sender an Operator of this Channel?
- ❌ No: Send
ERR_CHANOPRIVSNEEDEDto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Is the Sender a member of this Channel?
- Target Validation:
- Does the specified
<user>exist on the Server?- ❌ No: Send
ERR_NOSUCHNICKto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Is the Target
<user>a member of this Channel?- ❌ No: Send
ERR_USERNOTINCHANNELto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Does the specified
- Execution:
- Extract the
<comment>(if empty, default to the Target's own nickname). - Broadcast to all members in the Channel (including the Target):
:<sender_prefix> KICK <channel_name> <target_nickname> :<comment> - Remove the Target from the Channel's Member list.
- Remove the Target from the Channel's Operator list (if they were one).
- Remove the Channel's name from the Target's list of joined channels.
- Extract the
- Server: Find Target Client, Find Channel.
- Channel: Check if Sender is member, Check if Target is member, Check Mode (+i), Check if Sender is Operator, Add Target to Invite list.
- Target Validation:
- Does the Target
<nickname>exist on the Server?- ❌ No: Send
ERR_NOSUCHNICKto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Does the Target
- Channel Validation:
- Does the specified
<channel>exist on the Server?- ❌ No: Send
ERR_NOSUCHCHANNELto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Does the specified
- State Validation:
- Is the Sender a member of this Channel?
- ❌ No: Send
ERR_NOTONCHANNELto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Is the Target already a member of this Channel?
- ✅ Yes: Send
ERR_USERONCHANNELto Sender. Stop. - ❌ No: Continue.
- ✅ Yes: Send
- Is the Sender a member of this Channel?
- Mode & Privilege Validation:
- Is the Channel Mode set to Invite-Only (+i)?
- ✅ Yes: Is the Sender an Operator of this Channel?
- ❌ No: Send
ERR_CHANOPRIVSNEEDEDto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- ❌ No (+i is not set): Continue.
- ✅ Yes: Is the Sender an Operator of this Channel?
- Is the Channel Mode set to Invite-Only (+i)?
- Execution:
- Add the Target User to the Channel's Invite list.
- Send
RPL_INVITINGto the Sender. - Send the direct INVITE message to the Target User:
:<sender_prefix> INVITE <target_nickname> :<channel_name>
- Server: Find Channel.
- Channel: Check if Sender is member, Check Mode (+t), Check if Sender is Operator, Get Topic, Set Topic, Broadcast message.
- Channel & Sender Validation:
- Does the specified
<channel>exist on the Server?- ❌ No: Send
ERR_NOSUCHCHANNELto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Is the Sender a member of this Channel?
- ❌ No: Send
ERR_NOTONCHANNELto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Does the specified
- Determine Action (Read vs Write):
- Was a
<topic>argument provided in the command?- ❌ No (Read Action):
- Does the Channel currently have a topic set (not empty)?
- ❌ No: Send
RPL_NOTOPICto Sender. Stop. - ✅ Yes: Send
RPL_TOPICto Sender. Stop.
- ❌ No: Send
- Does the Channel currently have a topic set (not empty)?
- ✅ Yes (Write Action): Continue to Step 3.
- ❌ No (Read Action):
- Was a
- Privilege Validation (Write Action):
- Is the Channel Mode set to Topic-Restricted (+t)?
- ✅ Yes: Is the Sender an Operator of this Channel?
- ❌ No: Send
ERR_CHANOPRIVSNEEDEDto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- ❌ No (+t is not set): Continue.
- ✅ Yes: Is the Sender an Operator of this Channel?
- Is the Channel Mode set to Topic-Restricted (+t)?
- Execution (Write Action):
- Wait: Is the
<topic>argument exactly ":" (an empty string but explicitly cleared)?- ✅ Yes: Clear the Channel's topic.
- ❌ No: Set the Channel's topic to the provided string. Update the "Topic Setter" metadata to the Sender's nickname.
- Broadcast to all members in the Channel:
:<sender_prefix> TOPIC <channel_name> :<new_topic>
- Wait: Is the
(Focusing strictly on Channel Modes: i, t, k, o, l)
- Server: Find Channel, Find Target Client (for +o/-o).
- Channel: Check if Sender Operator, Apply/Remove Modes (+i, +t, +k, +l, +o), Validate limits/passwords, Broadcast applied changes.
- Target Validation:
- Is the
<target>a Channel (starts with#or&)?- ❌ No: Send
RPL_UMODEISor ignore (since User Modes aren't strictly required by the subject). Stop. - ✅ Yes: Does the Channel exist?
- ❌ No: Send
ERR_NOSUCHCHANNELto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- ❌ No: Send
- Is the
- Determine Action (Read vs Write):
- Was a
<modestring>provided?- ❌ No (Read Action): Send
RPL_CHANNELMODEIS(listing current modes: e.g.,+it) to Sender. Stop. - ✅ Yes (Write Action): Continue.
- ❌ No (Read Action): Send
- Was a
- Privilege Validation (Write Action):
- Is the Sender an Operator of this Channel?
- ❌ No: Send
ERR_CHANOPRIVSNEEDEDto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Is the Sender an Operator of this Channel?
- Processing the Modestring:
- Initialize state:
Adding = True(because it usually starts with+but could be-). - Loop character by character through the
<modestring>:- Is the character
+? -> SetAdding = True. - Is the character
-? -> SetAdding = False. - Is the character
i(Invite Only)?Adding = True: Set Channel to Invite-Only.Adding = False: Remove Invite-Only.
- Is the character
t(Topic Restricted)?Adding = True: Set Channel to Topic-Restricted.Adding = False: Remove Topic-Restricted.
- Is the character
k(Password/Key)?Adding = True: Was an<argument>provided for this mode?- ✅ Yes: Set Channel Password to the argument. Enable Key-Required mode.
- ❌ No: Ignore or send Error (Missing Parameter).
Adding = False: Remove Key-Required mode. Clear the password.
- Is the character
l(User Limit)?Adding = True: Was an<argument>provided, and is it a valid positive number?- ✅ Yes: Set Channel Limit to the number. Enable Limit-Required mode.
- ❌ No: Ignore or send Error (Invalid/Missing Parameter).
Adding = False: Remove Limit-Required mode.
- Is the character
o(Operator Privilege)?- Was an
<argument>(target nickname) provided?- ❌ No: Ignore or send Error (Missing Parameter).
- ✅ Yes: Does the Target User exist and are they in the Channel?
- ❌ No: Ignore or send Error.
- ✅ Yes:
Adding = True: Add Target User to Channel's Operator list.Adding = False: Remove Target User from Channel's Operator list.
- Was an
- Is the character anything else? -> Send
ERR_UNKNOWNMODE.
- Is the character
- Initialize state:
- Execution & Broadcast:
- Construct a string containing ONLY the modes and arguments that were successfully applied/changed.
- Broadcast to all members in the Channel:
:<sender_prefix> MODE <channel_name> <applied_modestring> <applied_arguments>
- Server: Find Target Channel, Find Target Client.
- Channel: Check if Sender is member, Broadcast to all except Sender.
- Client (Target): Send raw message to socket.
- Validation:
- Was
<text>provided?- ❌ No: Send
ERR_NOTEXTTOSENDto Sender. Stop.
- ❌ No: Send
- Was
<target>provided?- ❌ No: Send
ERR_NORECIPIENTto Sender. Stop.
- ❌ No: Send
- Was
- Determine Target Type:
- Does
<target>start with#or&?- ✅ Yes (Channel Target):
- Does the Channel exist on the Server?
- ❌ No: Send
ERR_NOSUCHCHANNELto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Is the Sender a member of this Channel?
- ❌ No: Send
ERR_CANNOTSENDTOCHANto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Execution: Broadcast
:<sender_prefix> PRIVMSG <channel_name> :<text>to every member in the Channel EXCEPT for the Sender themselves.
- Does the Channel exist on the Server?
- ❌ No (User Target):
- Does the Target User exist on the Server?
- ❌ No: Send
ERR_NOSUCHNICKto Sender. Stop. - ✅ Yes: Continue.
- ❌ No: Send
- Execution: Send directly to the Target User's socket:
:<sender_prefix> PRIVMSG <target_nickname> :<text>
- Does the Target User exist on the Server?
- ✅ Yes (Channel Target):
- Does
To effectively implement these logic trees in the 42 ft_irc project, strict adherence to C++98 memory models and specific STL (Standard Template Library) containers is required. This section explains why we structure memory the way we do, specifically regarding Maps vs Vectors and raw Pointers (*).
The most critical architectural question in ft_irc is: Who owns a Client, and how do Channels interact with them?
-
Server Ownership (The Single Point of Truth):
- The
Serverobject definitively "owns" theClientinstances. - When a new user connects via
accept(), the Server creates aClientobject and stores it in astd::map<int, Client>, mapping their file descriptor (fd) to the actualClientobject data. -
Why
std::maphere? Becausefds are unique but not strictly sequential (e.g., sockets 4, 5, 8 might be active). A Map provides$O(\log N)$ access time byfd, which is extremely fast whenpoll()returns an activefdand we need to find exactly which Client is talking.
- The
-
Channel References (
Client*):- The
Channelclass keeps lists of its members usingstd::vector<Client*>,std::map<int, Client*>, orstd::set<Client*>. -
Why Pointers (
*)?-
Memory Duplication: If a Channel stored
Clientobjects directly (by value), it would create copies of the client. If the Client later changed their nickname, the Server's main list would update, but the Channel's copy wouldn't! - Shared State: Using a pointer means the Channel holds a reference to the exact memory address of the Client owned by the Server. When the Client changes their nickname, all Channels pointing to that Client instantly see the new name.
-
Performance: Copying entire
Clientobjects (which contain large string buffers) is incredibly slow. Copying a pointer (which is just an 8-byte memory address) is instantaneous.
-
Memory Duplication: If a Channel stored
- The
When a Channel needs to store its members, operators, or invites, which STL container should you choose?
-
Logic: Map uses a Key-Value pair. Key =
int(the client's network file descriptor,fd), Value =Client*(pointer to the client object). -
Why it's perfect here:
-
Lookups ($O(\log N)$): When processing a command, you frequently need to answer: "Is User X in this Channel?" With a Map, finding a user by
fdor nickname (if Key =std::string) is instantly efficient. -
Removing Members ($O(\log N)$): When a user types
PART,std::map::erase()is computationally fast and does not force the rest of the elements in memory to shift around.
-
Lookups ($O(\log N)$): When processing a command, you frequently need to answer: "Is User X in this Channel?" With a Map, finding a user by
- Logic: A dynamic array holding pointers sequentially.
-
Why it's risky here:
- Lookups ($O(N)$): If a Channel is huge (e.g., 500 members), answering "Is User X in this Channel?" requires checking every element one by one from begin() to end().
-
Removing Members ($O(N)$): If a user types
PARTand they happen to be at the very beginning of the vector,std::vector::erase()forces every other member in the array to physically shift down one slot in memory. This is computationally expensive. - When to use it: Vectors are only ideal when you rarely erase, frequently iterate over everything in order, and memory cache predictability is paramount (which isn't strictly necessary for low-throughput IRC).
- Logic: A collection of unique elements, automatically ordered.
-
Why it's perfect here:
-
Uniqueness Guarantee: A user cannot be an Operator twice. They are either an Operator or they aren't. A
std::setinherently rejects duplicate entries. -
Lookups ($O(\log N)$): Checking if a Client is an Operator using
std::set::find()is much faster than searching through a Vector.
-
Uniqueness Guarantee: A user cannot be an Operator twice. They are either an Operator or they aren't. A
Because ft_irc uses C++98, you do not have smart pointers (std::shared_ptr) or modern range-based loops. You are manually tracking memory through Iterator loops.
The Fatal Mistake:
// DANGEROUS: Do NOT do this
for (std::map<int, Client*>::iterator it = members.begin(); it != members.end(); ++it) {
if (it->second->getNickname() == "badguy") {
members.erase(it); // BOOM! Segfault/Undefined Behavior.
}
}The Logic Rule: When you erase() an element that an iterator is currently pointing to, that iterator is immediately invalidated. The loop tries to do ++it in the next cycle, but the memory it points to is already gone.
The C++98 Safe Logic Pattern:
// SAFE: C++98 Standard Map Erase Pattern
std::map<int, Client*>::iterator it = members.begin();
while (it != members.end()) {
if (it->second->getNickname() == "badguy") {
members.erase(it++); // Magic trick: Increment iterator BEFORE erase takes effect (post-increment)
} else {
++it;
}
}(Note: If using std::vector, the logic changes slightly as vector::erase() returns the next valid iterator, whereas C++98 map::erase() returns void).