push
This commit is contained in:
parent
f221c92fec
commit
f1a7e3e8f7
|
|
@ -0,0 +1,101 @@
|
|||
*This project has been created as part of the 42 curriculum by lfirmin, jle-neze.*
|
||||
|
||||
## Description
|
||||
|
||||
ft_irc is an IRC server written in C++ 98. It allows multiple clients to connect simultaneously, authenticate, join channels, and communicate in real time using the IRC protocol over TCP/IP. The server is built around a single `poll()` call with non-blocking I/O, handling all connections without forking.
|
||||
|
||||
## Instructions
|
||||
|
||||
### Compilation
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
### Execution
|
||||
|
||||
```
|
||||
./ircserv <port> <password>
|
||||
```
|
||||
|
||||
- `port` : the port the server listens on (e.g. 6667)
|
||||
- `password` : the connection password required by IRC clients
|
||||
|
||||
### Connecting with irssi
|
||||
|
||||
```
|
||||
irssi
|
||||
/connect localhost <port> <password>
|
||||
```
|
||||
|
||||
### Connecting with netcat (for testing)
|
||||
|
||||
```
|
||||
nc -C localhost <port>
|
||||
```
|
||||
|
||||
Then send commands manually:
|
||||
```
|
||||
PASS <password>
|
||||
NICK <nickname>
|
||||
USER <username> 0 * :<realname>
|
||||
JOIN #<channel>
|
||||
PRIVMSG #<channel> :<message>
|
||||
```
|
||||
|
||||
## Available Commands
|
||||
|
||||
### Registration
|
||||
|
||||
| Command | Usage | Description |
|
||||
|---------|-------|-------------|
|
||||
| `PASS` | `PASS <password>` | Authenticate with the server password |
|
||||
| `NICK` | `NICK <nickname>` | Set or change your nickname |
|
||||
| `USER` | `USER <username> 0 * :<realname>` | Set your username and realname |
|
||||
| `QUIT` | `QUIT` | Disconnect from the server |
|
||||
| `PING` | `PING <token>` | Ping the server, replies with PONG |
|
||||
|
||||
### Channels
|
||||
|
||||
| Command | Usage | Description |
|
||||
|---------|-------|-------------|
|
||||
| `JOIN` | `JOIN #<channel> [key]` | Join or create a channel |
|
||||
| `PART` | `PART #<channel>` | Leave a channel |
|
||||
| `PRIVMSG` | `PRIVMSG #<channel> :<message>` | Send a message to a channel |
|
||||
| `PRIVMSG` | `PRIVMSG <nick> :<message>` | Send a private message to a user |
|
||||
|
||||
### Operator Commands
|
||||
|
||||
| Command | Usage | Description |
|
||||
|---------|-------|-------------|
|
||||
| `KICK` | `KICK #<channel> <nick>` | Eject a user from the channel |
|
||||
| `INVITE` | `INVITE <nick> #<channel>` | Invite a user to the channel |
|
||||
| `TOPIC` | `TOPIC #<channel>` | View the channel topic |
|
||||
| `TOPIC` | `TOPIC #<channel> :<topic>` | Change the channel topic |
|
||||
| `MODE` | `MODE #<channel> +i` | Set channel invite-only |
|
||||
| `MODE` | `MODE #<channel> -i` | Remove invite-only |
|
||||
| `MODE` | `MODE #<channel> +t` | Restrict TOPIC to operators |
|
||||
| `MODE` | `MODE #<channel> -t` | Remove TOPIC restriction |
|
||||
| `MODE` | `MODE #<channel> +k <key>` | Set a channel password |
|
||||
| `MODE` | `MODE #<channel> -k` | Remove the channel password |
|
||||
| `MODE` | `MODE #<channel> +o <nick>` | Give operator privilege to a user |
|
||||
| `MODE` | `MODE #<channel> -o <nick>` | Remove operator privilege from a user |
|
||||
| `MODE` | `MODE #<channel> +l <limit>` | Set a user limit on the channel |
|
||||
| `MODE` | `MODE #<channel> -l` | Remove the user limit |
|
||||
|
||||
## Resources
|
||||
|
||||
- [RFC 1459 - IRC Protocol](https://datatracker.ietf.org/doc/html/rfc1459)
|
||||
- [RFC 2812 - IRC Client Protocol](https://datatracker.ietf.org/doc/html/rfc2812)
|
||||
- [Beej's Guide to Network Programming](https://beej.us/guide/bgnet/)
|
||||
- [irssi documentation](https://irssi.org/documentation/)
|
||||
|
||||
### AI Usage
|
||||
|
||||
Claude (claude.ai) was used during this project to:
|
||||
- Guide the step-by-step implementation of the Server class (socket setup, poll loop, client handling)
|
||||
- Explain IRC protocol concepts and numeric error codes
|
||||
- Help structure the Channel class and its methods
|
||||
- Debug compilation errors and logic issues
|
||||
|
||||
All generated content was reviewed, understood and typed manually by the authors before being integrated into the project.
|
||||
|
|
@ -6,16 +6,19 @@
|
|||
# include <map>
|
||||
# include <sys/socket.h>
|
||||
# include <netinet/in.h>
|
||||
# include <netinet/tcp.h>
|
||||
# include <arpa/inet.h>
|
||||
# include <poll.h>
|
||||
# include <fcntl.h>
|
||||
# include <unistd.h>
|
||||
# include "ParseBuffer.hpp"
|
||||
# include <iostream>
|
||||
# include <stdexcept>
|
||||
# include <cstring>
|
||||
# include <cerrno>
|
||||
# include "CommandValidator.hpp"
|
||||
# include "Channel.hpp"
|
||||
# include <cstdlib>
|
||||
|
||||
struct ConnectedUser
|
||||
{
|
||||
|
|
|
|||
316
srcs/Server.cpp
316
srcs/Server.cpp
|
|
@ -36,6 +36,7 @@ void Server::start(void)
|
|||
pollfd.events = POLLIN;
|
||||
pollfd.revents = 0;
|
||||
_fds.push_back(pollfd);
|
||||
std::cout << "FT_IRC server startded on " << _port << " Port." << std::endl << "The password is \"" << _password << "\"." << std::endl;
|
||||
}
|
||||
|
||||
ConnectedUser::ConnectedUser(void) : fd(-1), passOk(false), nickOk(false), userOk(false)
|
||||
|
|
@ -109,6 +110,8 @@ void Server::acceptNew(void)
|
|||
if (newFd == -1)
|
||||
throw std::runtime_error(strerror(errno));
|
||||
fcntl(newFd, F_SETFL, O_NONBLOCK);
|
||||
int flag = 1;
|
||||
setsockopt(newFd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
|
||||
struct pollfd pfd;
|
||||
pfd.fd = newFd;
|
||||
pfd.events = POLLIN;
|
||||
|
|
@ -191,6 +194,20 @@ void Server::dispatch(int fd, const IrcMessage &msg)
|
|||
handlePing(fd, msg);
|
||||
else if (cmd == "QUIT")
|
||||
handleQuit(fd, msg);
|
||||
else if (cmd == "JOIN")
|
||||
handleJoin(fd, msg);
|
||||
else if (cmd == "PART")
|
||||
handlePart(fd, msg);
|
||||
else if (cmd == "PRIVMSG")
|
||||
handlePrivmsg(fd, msg);
|
||||
else if (cmd == "KICK")
|
||||
handleKick(fd, msg);
|
||||
else if (cmd == "INVITE")
|
||||
handleInvite(fd, msg);
|
||||
else if (cmd == "TOPIC")
|
||||
handleTopic(fd, msg);
|
||||
else if (cmd == "MODE")
|
||||
handleMode(fd, msg);
|
||||
}
|
||||
|
||||
void Server::handlePass(int fd, const IrcMessage &msg)
|
||||
|
|
@ -243,4 +260,303 @@ void Server::handleQuit(int fd, const IrcMessage &msg)
|
|||
{
|
||||
removeUser(fd);
|
||||
(void)msg;
|
||||
}
|
||||
|
||||
void Server::broadcastToChannel(const std::string &chanName, const std::string &msg, int exceptFd)
|
||||
{
|
||||
Channel &chan = _channels[chanName];
|
||||
const std::vector<int> &members = chan.getMembers();
|
||||
for (std::vector<int>::const_iterator it = members.begin(); it != members.end(); ++it)
|
||||
{
|
||||
if (*it != exceptFd)
|
||||
sendReply(*it, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handleJoin(int fd, const IrcMessage &msg)
|
||||
{
|
||||
std::string chanName = msg.param(0);
|
||||
if (!chanName.empty() && chanName[0] != '#')
|
||||
{
|
||||
sendReply(fd, "403 " + chanName + " :No such channel\r\n");
|
||||
return ;
|
||||
}
|
||||
if (_channels.find(chanName) == _channels.end())
|
||||
_channels.insert(std::make_pair(chanName, Channel(chanName)));
|
||||
Channel &chan = _channels[chanName];
|
||||
if (chan.isInviteOnly() && !chan.isInvited(fd))
|
||||
{
|
||||
sendReply(fd, "473 " + chanName + " :Cannot join channel (+i)\r\n");
|
||||
return ;
|
||||
}
|
||||
if (chan.getKey() != "" && (msg.paramCount() < 2 || msg.param(1) != chan.getKey()))
|
||||
{
|
||||
sendReply(fd, "475 " + chanName + " :Cannot join channel (+k)\r\n");
|
||||
return ;
|
||||
}
|
||||
if (chan.getUserLimit() > 0 && chan.memberCount() >= chan.getUserLimit())
|
||||
{
|
||||
sendReply(fd, "471 " + chanName + " :Cannot join channel (+l)\r\n");
|
||||
return ;
|
||||
}
|
||||
if (chan.hasMember(fd))
|
||||
return ;
|
||||
chan.addMember(fd);
|
||||
if (chan.memberCount() == 1)
|
||||
chan.addOperator(fd);
|
||||
std::string msg_add = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost JOIN " + chanName + "\r\n";
|
||||
broadcastToChannel(chanName, msg_add, -1);
|
||||
if (chan.getTopic() != "")
|
||||
sendReply(fd, "332 " + _users[fd].nick + " " + chanName + " :" + chan.getTopic() + "\r\n");
|
||||
}
|
||||
|
||||
void Server::handlePart(int fd, const IrcMessage &msg)
|
||||
{
|
||||
std::string chanName = msg.param(0);
|
||||
if (_channels.find(chanName) == _channels.end())
|
||||
{
|
||||
sendReply(fd, "403 " + chanName + " :No such channel\r\n");
|
||||
return ;
|
||||
}
|
||||
Channel &chan = _channels[chanName];
|
||||
if (!chan.hasMember(fd))
|
||||
{
|
||||
sendReply(fd, "442 " + chanName + " :You're not on that channel\r\n");
|
||||
return ;
|
||||
}
|
||||
std::string msg_rm = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost PART " + chanName + "\r\n";
|
||||
broadcastToChannel(chanName, msg_rm, -1);
|
||||
chan.removeMember(fd);
|
||||
if (chan.memberCount() == 0)
|
||||
_channels.erase(chanName);
|
||||
}
|
||||
|
||||
void Server::handlePrivmsg(int fd, const IrcMessage &msg)
|
||||
{
|
||||
std::string target = msg.param(0);
|
||||
std::string text = msg.param(1);
|
||||
std::string mess = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost PRIVMSG " + target + " :" + text + "\r\n";
|
||||
if (target[0] == '#')
|
||||
{
|
||||
if (_channels.find(target) == _channels.end())
|
||||
{
|
||||
sendReply(fd, "403 " + target + " :No such channel\r\n");
|
||||
return ;
|
||||
}
|
||||
Channel &chan = _channels[target];
|
||||
if (!chan.hasMember(fd))
|
||||
{
|
||||
sendReply(fd, "404 " + target + " :Cannot send to channel\r\n");
|
||||
return ;
|
||||
}
|
||||
broadcastToChannel(target, mess, fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (std::map<int, ConnectedUser>::iterator it = _users.begin(); it != _users.end(); ++it)
|
||||
{
|
||||
if (it->second.nick == target)
|
||||
{
|
||||
sendReply(it->first, mess);
|
||||
return;
|
||||
}
|
||||
}
|
||||
sendReply(fd, "401 " + target + " :No such nick\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handleKick(int fd, const IrcMessage &msg)
|
||||
{
|
||||
std::string chanName = msg.param(0);
|
||||
std::string targetNick = msg.param(1);
|
||||
if (_channels.find(chanName) == _channels.end())
|
||||
{
|
||||
sendReply(fd, "403 " + chanName + " :No such channel\r\n");
|
||||
return ;
|
||||
}
|
||||
Channel &chan = _channels[chanName];
|
||||
if (!chan.hasMember(fd))
|
||||
{
|
||||
sendReply(fd, "442 " + chanName + " :You're not on that channel\r\n");
|
||||
return ;
|
||||
}
|
||||
if (!chan.isOperator(fd))
|
||||
{
|
||||
sendReply(fd, "482 " + chanName + " :You're not channel operator\r\n");
|
||||
return ;
|
||||
}
|
||||
std::map<int, ConnectedUser>::iterator it = _users.begin();
|
||||
int targetFd = -1;
|
||||
for (; it != _users.end(); it++)
|
||||
{
|
||||
if (it->second.nick == targetNick)
|
||||
{
|
||||
targetFd = it->first;
|
||||
break ;
|
||||
}
|
||||
}
|
||||
if (targetFd == -1)
|
||||
{
|
||||
sendReply(fd, "401 " + targetNick + " :No such nick\r\n");
|
||||
return ;
|
||||
}
|
||||
if (!chan.hasMember(targetFd))
|
||||
{
|
||||
sendReply(fd, "441 " + targetNick + " " + chanName + " :They aren't on that channel\r\n");
|
||||
return ;
|
||||
}
|
||||
std::string kick_msg = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost KICK " + chanName + " " + targetNick + "\r\n";
|
||||
broadcastToChannel(chanName, kick_msg, -1);
|
||||
chan.removeMember(targetFd);
|
||||
}
|
||||
|
||||
void Server::handleInvite(int fd, const IrcMessage &msg)
|
||||
{
|
||||
std::string targetNick = msg.param(0);
|
||||
std::string chanName = msg.param(1);
|
||||
if (_channels.find(chanName) == _channels.end())
|
||||
{
|
||||
sendReply(fd, "403 " + chanName + " :No such channel\r\n");
|
||||
return ;
|
||||
}
|
||||
Channel &chan = _channels[chanName];
|
||||
if (!chan.hasMember(fd))
|
||||
{
|
||||
sendReply(fd, "442 " + chanName + " :You're not on that channel\r\n");
|
||||
return ;
|
||||
}
|
||||
if (!chan.isOperator(fd))
|
||||
{
|
||||
sendReply(fd, "482 " + chanName + " :You're not channel operator\r\n");
|
||||
return ;
|
||||
}
|
||||
std::map<int, ConnectedUser>::iterator it = _users.begin();
|
||||
int targetFd = -1;
|
||||
for (; it != _users.end(); it++)
|
||||
{
|
||||
if (it->second.nick == targetNick)
|
||||
{
|
||||
targetFd = it->first;
|
||||
break ;
|
||||
}
|
||||
}
|
||||
if (targetFd == -1)
|
||||
{
|
||||
sendReply(fd, "401 " + targetNick + " :No such nick\r\n");
|
||||
return ;
|
||||
}
|
||||
chan.addInvited(targetFd);
|
||||
std::string invite_msg = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost INVITE " + targetNick + " " + chanName + "\r\n";
|
||||
sendReply(targetFd, invite_msg);
|
||||
}
|
||||
|
||||
void Server::handleTopic(int fd, const IrcMessage &msg)
|
||||
{
|
||||
std::string chanName = msg.param(0);
|
||||
if (_channels.find(chanName) == _channels.end())
|
||||
{
|
||||
sendReply(fd, "403 " + chanName + " :No such channel\r\n");
|
||||
return ;
|
||||
}
|
||||
Channel &chan = _channels[chanName];
|
||||
if (!chan.hasMember(fd))
|
||||
{
|
||||
sendReply(fd, "442 " + chanName + " :You're not on that channel\r\n");
|
||||
return ;
|
||||
}
|
||||
if (msg.paramCount() == 1)
|
||||
{
|
||||
if (chan.getTopic() != "")
|
||||
sendReply(fd, "332 " + _users[fd].nick + " " + chanName + " :" + chan.getTopic() + "\r\n");
|
||||
else
|
||||
sendReply(fd, "331 " + _users[fd].nick + " " + chanName + " :No topic is set\r\n");
|
||||
return ;
|
||||
}
|
||||
if (chan.isTopicRestricted() && !chan.isOperator(fd))
|
||||
{
|
||||
sendReply(fd, "482 " + chanName + " :You're not channel operator\r\n");
|
||||
return ;
|
||||
}
|
||||
chan.setTopic(msg.param(1));
|
||||
std::string bd_msg = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost TOPIC " + chanName + " :" + chan.getTopic() + "\r\n";
|
||||
broadcastToChannel(chanName, bd_msg, -1);
|
||||
}
|
||||
|
||||
void Server::handleMode(int fd, const IrcMessage &msg)
|
||||
{
|
||||
std::string chanName = msg.param(0);
|
||||
std::string modeStr = msg.param(1);
|
||||
if (chanName.empty() || chanName[0] != '#')
|
||||
return ;
|
||||
if (_channels.find(chanName) == _channels.end())
|
||||
{
|
||||
sendReply(fd, "403 " + chanName + " :No such channel\r\n");
|
||||
return ;
|
||||
}
|
||||
Channel &chan = _channels[chanName];
|
||||
if (!chan.hasMember(fd))
|
||||
{
|
||||
sendReply(fd, "442 " + chanName + " :You're not on that channel\r\n");
|
||||
return ;
|
||||
}
|
||||
if (!chan.isOperator(fd))
|
||||
{
|
||||
sendReply(fd, "482 " + chanName + " :You're not channel operator\r\n");
|
||||
return ;
|
||||
}
|
||||
bool adding = (modeStr[0] == '+');
|
||||
char mode = modeStr[1];
|
||||
if (mode == 'i')
|
||||
chan.setInviteOnly(adding);
|
||||
else if (mode == 't')
|
||||
chan.setTopicRestricted(adding);
|
||||
else if (mode == 'k')
|
||||
if (adding)
|
||||
if (msg.paramCount() >= 3)
|
||||
chan.setKey(msg.param(2));
|
||||
else
|
||||
return ;
|
||||
else
|
||||
chan.setKey("");
|
||||
else if (mode == 'o')
|
||||
{
|
||||
if (msg.paramCount() >= 3)
|
||||
{
|
||||
std::map<int, ConnectedUser>::iterator it = _users.begin();
|
||||
int targetFd = -1;
|
||||
for (; it != _users.end(); it++)
|
||||
{
|
||||
if (it->second.nick == msg.param(2))
|
||||
{
|
||||
targetFd = it->first;
|
||||
break ;
|
||||
}
|
||||
}
|
||||
if (targetFd == -1)
|
||||
{
|
||||
sendReply(fd, "401 " + msg.param(2) + " :No such nick\r\n");
|
||||
return ;
|
||||
}
|
||||
if (adding)
|
||||
chan.addOperator(targetFd);
|
||||
else
|
||||
chan.removeOperator(targetFd);
|
||||
}
|
||||
else
|
||||
return ;
|
||||
}
|
||||
else if (mode == 'l')
|
||||
{
|
||||
if (adding)
|
||||
{
|
||||
if (msg.paramCount() >= 3)
|
||||
chan.setUserLimit((size_t)atoi(msg.param(2).c_str()));
|
||||
else
|
||||
return ;
|
||||
}
|
||||
else
|
||||
chan.setUserLimit(0);
|
||||
}
|
||||
std::string mod_msg = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost MODE " + chanName + " " + modeStr + "\r\n";
|
||||
broadcastToChannel(chanName, mod_msg, -1);
|
||||
}
|
||||
Loading…
Reference in New Issue