647 lines
18 KiB
C++
647 lines
18 KiB
C++
#include "Server.hpp"
|
|
|
|
extern volatile sig_atomic_t g_stop;
|
|
|
|
void Server::start(void)
|
|
{
|
|
_serverFd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (_serverFd == -1)
|
|
throw std::runtime_error(strerror(errno));
|
|
int opt = 1;
|
|
if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1)
|
|
{
|
|
close(_serverFd);
|
|
throw std::runtime_error(strerror(errno));
|
|
}
|
|
if (fcntl(_serverFd, F_SETFL, O_NONBLOCK) == -1)
|
|
{
|
|
close(_serverFd);
|
|
throw std::runtime_error(strerror(errno));
|
|
}
|
|
struct sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(_port);
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
if (bind(_serverFd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
|
|
{
|
|
close(_serverFd);
|
|
throw std::runtime_error(strerror(errno));
|
|
}
|
|
if (listen(_serverFd, SOMAXCONN) == -1)
|
|
{
|
|
close(_serverFd);
|
|
throw std::runtime_error(strerror(errno));
|
|
}
|
|
struct pollfd pollfd;
|
|
pollfd.fd = _serverFd;
|
|
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 << "Ctrl+c for shutdown the server" << std::endl;
|
|
}
|
|
|
|
ConnectedUser::ConnectedUser(void) : fd(-1), passOk(false), nickOk(false), userOk(false)
|
|
{
|
|
}
|
|
|
|
ConnectedUser::ConnectedUser(int fd) : fd(fd), passOk(false), nickOk(false), userOk(false)
|
|
{
|
|
}
|
|
|
|
bool ConnectedUser::isRegistered(void) const
|
|
{
|
|
return (passOk && nickOk && userOk);
|
|
}
|
|
|
|
Server::Server(int port, const std::string &password)
|
|
{
|
|
_port = port;
|
|
_password = password;
|
|
_serverFd = -1;
|
|
}
|
|
|
|
Server::Server(const Server &other)
|
|
{
|
|
*this = other;
|
|
}
|
|
|
|
Server &Server::operator=(const Server &other)
|
|
{
|
|
if (this != &other)
|
|
{
|
|
_serverFd = other._serverFd;
|
|
_port = other._port;
|
|
_password = other._password;
|
|
_fds = other._fds;
|
|
_users = other._users;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
Server::~Server(void)
|
|
{
|
|
for (size_t i = 0; i < _fds.size(); i++)
|
|
{
|
|
close(_fds[i].fd);
|
|
}
|
|
}
|
|
|
|
void Server::sendReply(int fd, const std::string &msg)
|
|
{
|
|
size_t total = 0;
|
|
while (total < msg.size())
|
|
{
|
|
ssize_t s = send(fd, msg.c_str() + total, msg.size() - total, 0);
|
|
if (s <= 0)
|
|
return ;
|
|
total += static_cast<size_t>(s);
|
|
}
|
|
}
|
|
|
|
void Server::removeUser(int fd)
|
|
{
|
|
std::map<std::string, Channel>::iterator it = _channels.begin();
|
|
while (it != _channels.end())
|
|
{
|
|
it->second.removeMember(fd);
|
|
it->second.removeOperator(fd);
|
|
it->second.removeInvited(fd);
|
|
if (it->second.memberCount() == 0)
|
|
_channels.erase(it++);
|
|
else
|
|
++it;
|
|
}
|
|
close(fd);
|
|
_users.erase(fd);
|
|
for (size_t i = 0; i < _fds.size(); i++)
|
|
{
|
|
if (_fds[i].fd == fd)
|
|
{
|
|
_fds.erase(_fds.begin() + i);
|
|
break ;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Server::acceptNew(void)
|
|
{
|
|
int newFd = accept(_serverFd, NULL, NULL);
|
|
if (newFd == -1)
|
|
return ;
|
|
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;
|
|
pfd.revents = 0;
|
|
_fds.push_back(pfd);
|
|
_users.insert(std::make_pair(newFd, ConnectedUser(newFd)));
|
|
}
|
|
|
|
void Server::handleRecv(int fd)
|
|
{
|
|
char buf[512];
|
|
int n;
|
|
n = recv(fd, buf, sizeof(buf), 0);
|
|
if (n == 0)
|
|
{
|
|
removeUser(fd);
|
|
return ;
|
|
}
|
|
else if (n == -1)
|
|
{
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
|
return ;
|
|
else
|
|
{
|
|
removeUser(fd);
|
|
return ;
|
|
}
|
|
}
|
|
_users[fd].parseBuffer.append(buf, n);
|
|
std::vector<IrcMessage> messages = _users[fd].parseBuffer.extractMessages();
|
|
for (size_t i = 0; i < messages.size(); i++)
|
|
{
|
|
if (_users.find(fd) == _users.end())
|
|
break ;
|
|
dispatch(fd, messages[i]);
|
|
}
|
|
}
|
|
|
|
void Server::run(void)
|
|
{
|
|
while (!g_stop)
|
|
{
|
|
int ret = poll(_fds.data(), _fds.size(), -1);
|
|
if (ret == -1)
|
|
break ;
|
|
for (size_t i = 0; i < _fds.size(); i++)
|
|
{
|
|
if (i == 0 && _fds[0].revents & POLLIN)
|
|
acceptNew();
|
|
else if (_fds[i].revents & (POLLHUP | POLLERR))
|
|
{
|
|
int fd = _fds[i].fd;
|
|
removeUser(fd);
|
|
i--;
|
|
}
|
|
else if (_fds[i].revents & POLLIN)
|
|
{
|
|
size_t before = _fds.size();
|
|
handleRecv(_fds[i].fd);
|
|
if (_fds.size() < before)
|
|
i--;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void Server::dispatch(int fd, const IrcMessage &msg)
|
|
{
|
|
if (!msg.isValid())
|
|
return ;
|
|
const std::string &cmd = msg.getCommand();
|
|
if (CommandValidator::needsRegistration(cmd) && !_users[fd].isRegistered())
|
|
{
|
|
sendReply(fd, "451 :You have not registered\r\n");
|
|
return ;
|
|
}
|
|
else if (!CommandValidator::hasEnoughParams(msg))
|
|
{
|
|
sendReply(fd, "461 " + cmd + " :Not enough parameters\r\n");
|
|
return ;
|
|
}
|
|
else if (cmd == "PASS")
|
|
handlePass(fd, msg);
|
|
else if (cmd == "NICK")
|
|
handleNick(fd, msg);
|
|
else if (cmd == "USER")
|
|
handleUser(fd, msg);
|
|
else if (cmd == "PING")
|
|
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)
|
|
{
|
|
if (msg.param(0) == _password)
|
|
_users[fd].passOk = true;
|
|
else
|
|
{
|
|
sendReply(fd, "464 :Password incorrect\r\n");
|
|
removeUser(fd);
|
|
}
|
|
}
|
|
|
|
static bool isNickChar(char c, bool first)
|
|
{
|
|
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
|
|
return (true);
|
|
if (c == '[' || c == ']' || c == '\\' || c == '`'
|
|
|| c == '_' || c == '^' || c == '{' || c == '|' || c == '}')
|
|
return (true);
|
|
if (!first && ((c >= '0' && c <= '9') || c == '-'))
|
|
return (true);
|
|
return (false);
|
|
}
|
|
|
|
static bool isValidNick(const std::string &nick)
|
|
{
|
|
if (nick.empty())
|
|
return (false);
|
|
for (size_t i = 0; i < nick.size(); ++i)
|
|
{
|
|
if (!isNickChar(nick[i], i == 0))
|
|
return (false);
|
|
}
|
|
return (true);
|
|
}
|
|
|
|
void Server::handleNick(int fd, const IrcMessage &msg)
|
|
{
|
|
std::string newNick = msg.param(0);
|
|
if (!isValidNick(newNick))
|
|
{
|
|
sendReply(fd, "432 * " + newNick + " :Erroneous nickname\r\n");
|
|
return ;
|
|
}
|
|
std::map<int, ConnectedUser>::iterator it = _users.begin();
|
|
for (; it != _users.end(); it++)
|
|
{
|
|
if (it->second.nick == newNick && it->first != fd)
|
|
{
|
|
sendReply(fd, "433 * " + newNick + " :Nickname is already in use\r\n");
|
|
return ;
|
|
}
|
|
}
|
|
bool wasRegistered = _users[fd].isRegistered();
|
|
std::string oldNick = _users[fd].nick;
|
|
_users[fd].nick = newNick;
|
|
_users[fd].nickOk = true;
|
|
if (wasRegistered && oldNick != newNick)
|
|
{
|
|
std::string notice = ":" + oldNick + "!" + _users[fd].username
|
|
+ "@localhost NICK " + newNick + "\r\n";
|
|
std::map<int, bool> seen;
|
|
seen[fd] = true;
|
|
for (std::map<std::string, Channel>::iterator cit = _channels.begin();
|
|
cit != _channels.end(); ++cit)
|
|
{
|
|
if (!cit->second.hasMember(fd))
|
|
continue ;
|
|
const std::vector<int> &mem = cit->second.getMembers();
|
|
for (size_t i = 0; i < mem.size(); ++i)
|
|
seen[mem[i]] = true;
|
|
}
|
|
for (std::map<int, bool>::iterator sit = seen.begin(); sit != seen.end(); ++sit)
|
|
sendReply(sit->first, notice);
|
|
}
|
|
}
|
|
|
|
void Server::handleUser(int fd, const IrcMessage &msg)
|
|
{
|
|
if (_users[fd].isRegistered() == true)
|
|
return ;
|
|
_users[fd].username = msg.param(0);
|
|
_users[fd].realname = msg.param(3);
|
|
_users[fd].userOk = true;
|
|
if (_users[fd].isRegistered())
|
|
{
|
|
sendReply(fd, ":server 001 " + _users[fd].nick + " :Welcome to the IRC server\r\n");
|
|
sendReply(fd, ":server 002 " + _users[fd].nick + " :Your host is server\r\n");
|
|
}
|
|
}
|
|
|
|
void Server::handlePing(int fd, const IrcMessage &msg)
|
|
{
|
|
sendReply(fd, "PONG :" + msg.param(0) + "\r\n");
|
|
}
|
|
|
|
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);
|
|
chan.removeOperator(fd);
|
|
chan.removeInvited(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);
|
|
chan.removeOperator(targetFd);
|
|
chan.removeInvited(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 (modeStr.size() < 2 || (modeStr[0] != '+' && modeStr[0] != '-'))
|
|
return ;
|
|
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);
|
|
} |