Skip to content

HMSTR63/IRC_42

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Description

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.

Project Structure

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

Instructions

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.

Build

make

Run the server

./ircserv <port> <password>

Connect the Clients

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.

irssi

Then inside irssi

/connect localhost <port> <password> nickname

Bonus: Moderation Bot

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).

Supported Commands

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 (MODE)

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

ft_irc: Pure Logic Trees for Commands

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.


1. JOIN (JOIN <channel>{,<channel>} [<key>{,<key>}])

Required State & Actions

  • 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.

Logic Tree

  1. Initial Check:
    • Is the User fully registered?
      • No: Send ERR_NOTREGISTERED to Client. Stop processing this command.
      • Yes: Continue.
  2. 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).
  3. Channel Exists?
    • For the current channel name in the loop, does a Channel object exist on the Server?
      • No (Channel doesn't exist):
        1. Server creates a new Channel object with the given name and key (if provided).
        2. Add the User to this new Channel as a Member.
        3. Promote the User to an Operator for this new Channel.
        4. Add the Channel's name to the User's list of joined channels.
        5. Proceed to Step 5 (Broadcast & Replies).
      • Yes (Channel exists): Continue to Step 4 (Mode Checks).
  4. 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_INVITEONLYCHAN to Client. Skip to the next channel in the loop.
        • Yes: Continue.
    • Check +k (Key Required): Does the Channel require a password?
      • Yes: Does the provided key match the Channel's password?
        • No: Send ERR_BADCHANNELKEY to Client. Skip to the next channel in the loop.
        • Yes: Continue.
    • Check +l (User Limit): Does the Channel have a user limit?
      • Yes: Is the current number of members >= the limit?
        • Yes (Full): Send ERR_CHANNELISFULL to Client. Skip to the next channel in the loop.
      • No / Not Full: Continue.
    • Success (Passed all checks):
      1. Add the User to the Channel as a Member.
      2. Remove the User from the Channel's Invite list (if they were on it).
      3. Add the Channel's name to the User's list of joined channels.
  5. 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_TOPIC to the joined User.
    • Send RPL_NAMREPLY (List of all members in the channel, prefixing operators with @) to the joined User.
    • Send RPL_ENDOFNAMES to the joined User.

2. PART (PART <channel>{,<channel>} [<reason>])

Required State & Actions

  • 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.

Logic Tree

  1. Initial Check:
    • Is the User fully registered?
      • No: Ignore or send Error. Stop.
      • Yes: Continue.
  2. Parsing:
    • Split the given <channel> string by commas.
    • Extract the <reason> text (if provided).
    • Loop through each parsed channel name.
  3. Channel Validation:
    • For the current channel name in the loop, does the Channel exist on the Server?
      • No: Send ERR_NOSUCHCHANNEL to Client. Skip to the next channel.
      • Yes: Continue.
  4. Member Validation:
    • Is the User a member of this Channel?
      • No: Send ERR_NOTONCHANNEL to Client. Skip to the next channel.
      • Yes: Continue.
  5. 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.
  6. Cleanup:
    • Is the Channel now completely empty (0 members)?
      • Yes: Server deletes the Channel object entirely from memory.

3. QUIT (QUIT [<Quit message>])

Required State & Actions

  • 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).

Logic Tree

  1. 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.
  2. Disconnecting from Channels:
    • Loop through the User's list of joined Channels.
    • For each Channel:
      1. Broadcast to all other members in the Channel: :<user_prefix> QUIT :<Quit message>
      2. Remove the User from the Channel's Member list.
      3. Remove the User from the Channel's Operator list (if they were one).
      4. Remove the User from the Channel's Invite list (if they were invited).
      5. Cleanup: Is the Channel now empty?
        • Yes: Server deletes the Channel object entirely.
  3. Server Cleanup:
    • Close the User's network socket (file descriptor).
    • Remove the User object entirely from the Server's tracked clients.

4. KICK (KICK <channel> <user> [<comment>])

Required State & Actions

  • 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.

Logic Tree

  1. Channel Validation:
    • Does the specified <channel> exist on the Server?
      • No: Send ERR_NOSUCHCHANNEL to Sender. Stop.
      • Yes: Continue.
  2. Sender Validation:
    • Is the Sender a member of this Channel?
      • No: Send ERR_NOTONCHANNEL to Sender. Stop.
      • Yes: Continue.
    • Is the Sender an Operator of this Channel?
      • No: Send ERR_CHANOPRIVSNEEDED to Sender. Stop.
      • Yes: Continue.
  3. Target Validation:
    • Does the specified <user> exist on the Server?
      • No: Send ERR_NOSUCHNICK to Sender. Stop.
      • Yes: Continue.
    • Is the Target <user> a member of this Channel?
      • No: Send ERR_USERNOTINCHANNEL to Sender. Stop.
      • Yes: Continue.
  4. 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.

5. INVITE (INVITE <nickname> <channel>)

Required State & Actions

  • 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.

Logic Tree

  1. Target Validation:
    • Does the Target <nickname> exist on the Server?
      • No: Send ERR_NOSUCHNICK to Sender. Stop.
      • Yes: Continue.
  2. Channel Validation:
    • Does the specified <channel> exist on the Server?
      • No: Send ERR_NOSUCHCHANNEL to Sender. Stop.
      • Yes: Continue.
  3. State Validation:
    • Is the Sender a member of this Channel?
      • No: Send ERR_NOTONCHANNEL to Sender. Stop.
      • Yes: Continue.
    • Is the Target already a member of this Channel?
      • Yes: Send ERR_USERONCHANNEL to Sender. Stop.
      • No: Continue.
  4. Mode & Privilege Validation:
    • Is the Channel Mode set to Invite-Only (+i)?
      • Yes: Is the Sender an Operator of this Channel?
        • No: Send ERR_CHANOPRIVSNEEDED to Sender. Stop.
        • Yes: Continue.
      • No (+i is not set): Continue.
  5. Execution:
    • Add the Target User to the Channel's Invite list.
    • Send RPL_INVITING to the Sender.
    • Send the direct INVITE message to the Target User: :<sender_prefix> INVITE <target_nickname> :<channel_name>

6. TOPIC (TOPIC <channel> [<topic>])

Required State & Actions

  • Server: Find Channel.
  • Channel: Check if Sender is member, Check Mode (+t), Check if Sender is Operator, Get Topic, Set Topic, Broadcast message.

Logic Tree

  1. Channel & Sender Validation:
    • Does the specified <channel> exist on the Server?
      • No: Send ERR_NOSUCHCHANNEL to Sender. Stop.
      • Yes: Continue.
    • Is the Sender a member of this Channel?
      • No: Send ERR_NOTONCHANNEL to Sender. Stop.
      • Yes: Continue.
  2. 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_NOTOPIC to Sender. Stop.
          • Yes: Send RPL_TOPIC to Sender. Stop.
      • Yes (Write Action): Continue to Step 3.
  3. 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_CHANOPRIVSNEEDED to Sender. Stop.
        • Yes: Continue.
      • No (+t is not set): Continue.
  4. 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>

7. MODE (MODE <target> [<modestring> [<mode arguments>...]])

(Focusing strictly on Channel Modes: i, t, k, o, l)

Required State & Actions

  • 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.

Logic Tree

  1. Target Validation:
    • Is the <target> a Channel (starts with # or &)?
      • No: Send RPL_UMODEIS or ignore (since User Modes aren't strictly required by the subject). Stop.
      • Yes: Does the Channel exist?
        • No: Send ERR_NOSUCHCHANNEL to Sender. Stop.
        • Yes: Continue.
  2. 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.
  3. Privilege Validation (Write Action):
    • Is the Sender an Operator of this Channel?
      • No: Send ERR_CHANOPRIVSNEEDED to Sender. Stop.
      • Yes: Continue.
  4. 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 +? -> Set Adding = True.
      • Is the character -? -> Set Adding = 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.
      • Is the character anything else? -> Send ERR_UNKNOWNMODE.
  5. 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>

8. PRIVMSG (PRIVMSG <target> <text>)

Required State & Actions

  • 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.

Logic Tree

  1. Validation:
    • Was <text> provided?
      • No: Send ERR_NOTEXTTOSEND to Sender. Stop.
    • Was <target> provided?
      • No: Send ERR_NORECIPIENT to Sender. Stop.
  2. Determine Target Type:
    • Does <target> start with # or &?
      • Yes (Channel Target):
        1. Does the Channel exist on the Server?
          • No: Send ERR_NOSUCHCHANNEL to Sender. Stop.
          • Yes: Continue.
        2. Is the Sender a member of this Channel?
          • No: Send ERR_CANNOTSENDTOCHAN to Sender. Stop.
          • Yes: Continue.
        3. Execution: Broadcast :<sender_prefix> PRIVMSG <channel_name> :<text> to every member in the Channel EXCEPT for the Sender themselves.
      • No (User Target):
        1. Does the Target User exist on the Server?
          • No: Send ERR_NOSUCHNICK to Sender. Stop.
          • Yes: Continue.
        2. Execution: Send directly to the Target User's socket: :<sender_prefix> PRIVMSG <target_nickname> :<text>

9. Developer's Guide: C++98 Core Architecture & STL Usage

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 (*).

A. The Core Ownership Model: Client vs Client*

The most critical architectural question in ft_irc is: Who owns a Client, and how do Channels interact with them?

  1. Server Ownership (The Single Point of Truth):

    • The Server object definitively "owns" the Client instances.
    • When a new user connects via accept(), the Server creates a Client object and stores it in a std::map<int, Client>, mapping their file descriptor (fd) to the actual Client object data.
    • Why std::map here? Because fds are unique but not strictly sequential (e.g., sockets 4, 5, 8 might be active). A Map provides $O(\log N)$ access time by fd, which is extremely fast when poll() returns an active fd and we need to find exactly which Client is talking.
  2. Channel References (Client*):

    • The Channel class keeps lists of its members using std::vector<Client*>, std::map<int, Client*>, or std::set<Client*>.
    • Why Pointers (*)?
      • Memory Duplication: If a Channel stored Client objects 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 Client objects (which contain large string buffers) is incredibly slow. Copying a pointer (which is just an 8-byte memory address) is instantaneous.

B. STL Container Choices in Channels

When a Channel needs to store its members, operators, or invites, which STL container should you choose?

1. std::map<int, Client*> (The Ideal Member List)

  • 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 fd or 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.

2. std::vector<Client*> (Use with Caution!)

  • 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 PART and 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).

3. std::set<Client*> (The Operator/Invite List)

  • 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::set inherently 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.

C. The C++98 Iterator Invalidation Danger

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).

Resources

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors