#include "Server.hpp" 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; } 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) { send(fd, msg.c_str(), msg.size(), 0); } void Server::removeUser(int fd) { 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) 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; 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 messages = _users[fd].parseBuffer.extractMessages(); for (size_t i = 0; i < messages.size(); i++) dispatch(fd, messages[i]); } void Server::run(void) { while (true) { 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) handleRecv(_fds[i].fd); } } } 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); } } void Server::handleNick(int fd, const IrcMessage &msg) { std::string newNick = msg.param(0); std::map::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 ; } } _users[fd].nick = newNick; _users[fd].nickOk = true; } 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 &members = chan.getMembers(); for (std::vector::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::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::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::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::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); }