ft_irc - non-blocking IRC server in C++98
  • C++ 98.1%
  • Makefile 1.9%
Find a file
2026-07-25 13:29:01 +02:00
src part 2026-07-25 13:29:01 +02:00
.gitignore modified .gitignore 2026-07-17 15:26:44 +02:00
Makefile Check if there is a space in password and refuse if there is any 2026-07-25 11:57:51 +02:00
README.md merged with Bot !hello, !status, !explain <COMMAND>, and with README.md 2026-07-24 13:47:42 +02:00

This project has been created as part of the 42 curriculum by clao, rdinis, vicli.

ft_irc

An IRC server written in C++98, built from scratch as part of the 42 curriculum. ft_irc implements the core Internet Relay Chat protocol over non-blocking TCP sockets, handled entirely through a single poll() loop, and can be driven by any standard IRC client.


Description

ft_irc implements the server side of the IRC protocol: clients connect over TCP, authenticate with a shared password, register a nickname and username, and can then join channels, exchange private messages, and use channel-operator commands. All I/O is non-blocking and multiplexed through one poll() call — no forking, no per-client threads, no blocking read/recv/write/send outside of poll()'s readiness notifications.

Supported commands

  • RegistrationPASS, NICK, USER, CAP, PING, QUIT
  • MessagingPRIVMSG (to a user or a #channel), WHOIS
  • ChannelsJOIN (with optional key), LIST
  • Channel operator toolsKICK, INVITE, TOPIC, MODE (+i invite-only, +t topic lock, +k key, +o operator, +l user limit)

Bonus features

  • Bot — a genuine registered IRC client (Bot), reachable only by private message, that helps a connecting user understand the server:
    • !hello — greeting plus the full list of bot commands and server IRC commands
    • !status — the asker's live channel memberships and operator status
    • !explain <COMMAND> — usage syntax for a given IRC command, plus live, personalized context where relevant (e.g. how many channels are currently open, or which channels the asker currently operates)

Instructions

Requirements

  • A C++98-compatible compiler (c++)
  • POSIX sockets (Linux or macOS)

Building

make

Compiles with -Wall -Wextra -Werror -std=c++98. Also supports clean, fclean, and re.

Running

./ircserv <port> <password>
  • port — the TCP port to listen on
  • password — the connection password every client must supply via PASS before registering

The server shuts down gracefully on Ctrl+C (SIGINT), closing its listening socket rather than terminating abruptly.

Connecting

Any standard IRC client works. For example, with irssi:

/connect -network mynet 127.0.0.1 <port> <password>
/nick yourname

Once registered, /join #channel, /msg Bot !hello, /msg nickname hello, etc. all work as they would against any IRC server.

Testing raw protocol behavior

nc -C 127.0.0.1 <port>

Useful for testing partial/fragmented input directly, since the server explicitly reassembles TCP packets before parsing a line.


Approach

Event loop

A single poll() call multiplexes every file descriptor the server owns: the listening socket, every connected client, and the bot's own outgoing "dial" socket (see below). Each iteration checks POLLIN/POLLOUT per fd and dispatches to the matching handler — acceptNewClient, handleClientRecv, handleClientSend — with no blocking calls anywhere in the loop.

Command parsing and dispatch

Incoming lines are parsed into a Command (name, positional args, trailing message) and dispatched through a std::map<std::string, CommandHandler*> — one small handler class per IRC command, each implementing a shared execute(Command const&, CommandContext const&) interface. CommandContext bundles the sender, the live client map, and the channel manager so handlers never need global state.

Registration gating

Commands are split into "exempt" (PASS, NICK, USER, CAP, PING, QUIT) and everything else. Any non-exempt command from an unregistered client is rejected with 451 before it ever reaches its handler — registration is enforced in one central place (Server::executeCommand), not scattered across every command's own logic.

Channels

Each channel tracks its member list, its operator list, invited users, topic (with setter/time), password, user limit, and mode flags (itkol) in a single t_data struct, keyed by channel name in one std::map. Channel-scoped commands (KICK, INVITE, TOPIC, MODE, JOIN, LIST) all live on the Channel class, which owns this map and exposes the operations needed to mutate it safely.

The bot: self-connect mechanism

The bot is not a special-cased fake client — it is a real, second TCP connection the server makes to itself at startup, riding through the exact same accept()/poll()/Client machinery any external IRC client uses. Concretely:

  • _bot_dial_fd — a non-blocking socket the server connect()s to its own listening port. Once connected, the server sends PASS/NICK Bot/USER Bot ... down this socket, registering "Bot" as an ordinary client.
  • The accepted sideacceptNewClient() sees this connection arrive like any other, producing a normal Client object identified as Bot in _clients. This accepted side only ever sends — every reply Bot gives is queued as an outgoing PRIVMSG on _bot_dial_fd, which loops back through the server's own registration and command-dispatch pipeline exactly as if a human had typed it.
  • handleBotDialRecv — the only place incoming bot-directed messages are actually read: it reassembles TCP fragments into full lines and hands each one to handleBotCommand, which recognizes !-prefixed bot commands and dispatches to the matching handler.
  • !explain's design — each explainable command is a t_explainEntry (static usage text plus an optional function pointer for live context) stored in a std::map<std::string, t_explainEntry>, built once at startup. Commands that share the same kind of live context (e.g. KICK/INVITE/TOPIC/MODE, all wanting "which channels is the asker currently op in?") point at the same shared context function rather than duplicating the lookup logic.

Resources

References

Protocol specification

Non-blocking I/O and poll()

  • man poll, man fcntl, man socket (POSIX manual pages)

How AI was used

Claude (claude.ai) was used throughout this project as a Socratic learning partner and code reviewer, primarily for the bot bonus feature — not to generate solutions outright, but to guide design decisions and catch bugs through targeted questions and review passes.

Specifically, Claude was used for:

  • Designing the bot's self-connect architecture and the !explain command's data structure through guided discussion.
  • General code review passes, catching correctness and safety issues across the codebase
  • Cross-checking command behavior (e.g. numeric replies) against RFC 2812

All code was written by the team; Claude's role was limited to explanation, design discussion, and pointing out specific bugs for us to fix ourselves. Every suggestion was tested against a real IRC client and nc, and understood before being committed.