commit c15ff7d6e752acfe41ce2495d63e8cfca68a1944 Author: Xamora Date: Tue Aug 27 17:56:32 2024 +0200 Push project diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d701cdc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# https://EditorConfig.org +root = true + +[*.{cpp,hpp}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bc46561 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build/ +/ircserv \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4e7025c --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +MAKEFLAGS += -j + +CXX := c++ +LD := $(CXX) +CXXFLAGS := -g -O0 -Wall -Wextra -Werror -std=c++98 -MMD + +OBJ := $(patsubst src/%.cpp,build/%.o,$(wildcard src/*.cpp)) +DEP := $(patsubst %.o,%.d,$(OBJ)) +NAME := ircserv + +all: $(NAME) + +$(NAME): $(OBJ) + @printf 'LD %s\n' "$@" + @$(LD) -o $(NAME) $(OBJ) + +build/%.o: src/%.cpp + @printf 'CXX %s\n' "$@" + @mkdir -p $(@D) + @$(CXX) $(CXXFLAGS) -c -o $@ $< + +clean: + @printf 'RM build\n' + @rm -rf build/ + +fclean: + @printf 'RM build %s\n' "$(NAME)" + @rm -rf build/ $(NAME) + +re: + @make --no-print-directory fclean + @make --no-print-directory all + +run: $(NAME) + clear + @./$(NAME) 6969 michel + +gdb: $(NAME) + clear + @gdb --args ./$(NAME) 6969 michel + +.PHONY: all clean fclean re run gdb + +-include $(DEP) diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..8a2f408 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,3 @@ +-Wall +-Wextra +-std=c++98 diff --git a/src/Channel.cpp b/src/Channel.cpp new file mode 100644 index 0000000..ffc13fd --- /dev/null +++ b/src/Channel.cpp @@ -0,0 +1,443 @@ +#include "Channel.hpp" +#include "errcodes.hpp" +#include "Server.hpp" +#include "log.hpp" +#include "split.hpp" +#include +#include + +Channel::Channel(std::string const& name, Client& client) : + _clients(), + _op_clients(), + _name(name), + _topic(), + _fInviteOnly(false), + _fTopicRestric(false), + _keyword(), + _userLimit(0) +{ + _op_clients.insert(&client); + if (addClient(client)) { + return; + } +} + +Channel::Channel(Channel const& src) : + _clients(src._clients), + _op_clients(src._clients), + _name(src._name), + _topic(src._topic), + _fInviteOnly(src._fInviteOnly), + _fTopicRestric(src._fTopicRestric), + _keyword(src._keyword), + _userLimit(src._userLimit) +{} + +bool +Channel::addClient(Client& client) +{ + if (_clients.insert(&client).second) { + std::set::iterator it = _clients.begin(); + for (; it != _clients.end(); ++it) { + (*it)->sendMsg(client.nick, "JOIN", _name); + } + if (!_topic.empty()) { + if (client.sendCode(RPL_TOPIC, _name, _topic)) { + removeClient(client); + return false; + } + } + std::string names; + for (it = _clients.begin(); it != _clients.end(); ++it) { + if (isOp(**it)) { + names += '@'; + } + names += (*it)->nick + ' '; + } + if (client.sendCode(RPL_NAMREPLY, _name, names)) { + removeClient(client); + return false; + } + return true; + } + return false; +} + +bool +Channel::addOp(Client& client) +{ + if (_clients.find(&client) == _clients.end()) { + /* client to op not in channel */ + return false; + } + if (_op_clients.insert(&client).second) { + /* client is now op */ + LOG << client.nick << " is op" << std::endl; + _op_clients.insert(&client); + return true; + } + /* cringe shit lol */ + return false; +} + +bool +Channel::removeClient(Client& client) +{ + removeOp(client); + return (_clients.erase(&client) == 1); +} + +bool +Channel::removeOp(Client &client) +{ + return (_op_clients.erase(&client) == 1); +} + +bool +Channel::partClient(Client& client, std::string const& reason) +{ + if (_clients.find(&client) == _clients.end()) { + return false; + } + + std::set::iterator it; + for (it = _clients.begin(); it != _clients.end(); ++it) { + (*it)->sendMsg(client.nick, "PART " + _name, reason); + } + return removeClient(client); +} + +bool +Channel::kickClient(Client& client, Client& victim, std::string const& reason) +{ + if (_clients.find(&client) == _clients.end()) { + return false; + } + + std::set::iterator it; + for (it = _clients.begin(); it != _clients.end(); ++it) { + (*it)->sendMsg(client.nick, "KICK " + _name + ' ' + victim.nick, reason); + } + return removeClient(victim); +} + +size_t +Channel::numClients() const +{ + return _clients.size(); +} + +int +Channel::sendMessage(std::string const& msg, Client const* client) const +{ + std::string const sender = client ? client->nick : ""; + std::set::iterator it = _clients.begin(); + for (; it != _clients.end(); ++it) { + if (client == *it) { + continue; + } + (*it)->sendMsg(sender, "PRIVMSG " + _name, msg); + } + return 0; +} + +int +Channel::sendNotice(std::string const& msg, Client const& client) const +{ + std::set::iterator it = _clients.begin(); + for (; it != _clients.end(); ++it) { + if (client == **it) { + continue; + } + (*it)->sendMsg(client.nick, "NOTICE " + _name, msg); + } + return 0; +} + +int +Channel::sendTopic(Client& client) const +{ + if (!isOnChannel(client)) { + return 0; + } + + if (_topic.empty()) { + return client.sendCode(RPL_NOTOPIC, _name, "No topic is set"); + } else { + return client.sendCode(RPL_TOPIC, _name, _topic); + } +} + +int +Channel::setTopic(Client& client, std::string const& newTopic) +{ + if (_fTopicRestric && !isOp(client)) { + return client.sendCode(ERR_CHANOPRIVSNEEDED, _name, + "You're not channel operator"); + } + _topic = newTopic; + return sendTopic(client); +} + +int +Channel::setMode(Server& server, Client& client, string_vector const& args) +{ + if (!isOnChannel(client)) { + return client.sendCode(ERR_NOTONCHANNEL, _name, + "You're not on that channel"); + } + if (!isOp(client)) { + return client.sendCode(ERR_CHANOPRIVSNEEDED, _name, + "You're not channel operator"); + } + + bool sign = true; + size_t argToCheck = 2; + std::string const& modes = args[1]; + + string_vector update(1); + + for (size_t i = 0; i < modes.length(); ++i) { + const char c = modes[i]; + if (c == 'o' || ((c == 'k' || c == 'l') && sign)) { + if (args.size() <= argToCheck) { + continue; + } + } + + switch (c) { + case '-': + sign = false; + if (update[0].length() && + (update[0][update[0].length() - 1] == '-' || + update[0][update[0].length() - 1] == '+')) + { + update[0].erase(update[0].length() - 1); + } + update[0].push_back(c); + break; + case '+': + sign = true; + if (update[0].length() && + (update[0][update[0].length() - 1] == '-' || + update[0][update[0].length() - 1] == '+')) + { + update[0].erase(update[0].length() - 1); + } + update[0].push_back(c); + break; + case 'i': + if (_fInviteOnly != sign) { + update[0].push_back(c); + } + _fInviteOnly = sign; + break; + case 't': + if (_fInviteOnly != sign) { + update[0].push_back(c); + } + _fTopicRestric = sign; + break; + case 'o': { + Client* user = server.findClient(args[argToCheck++]); + if (!user) { + if (client.sendCode(ERR_NOSUCHNICK, args[argToCheck - 1], "No such nick (UwU~)")) { + return -1; + } + break; + } + if (!isOnChannel(*user)) { + if (client.sendCode(ERR_USERNOTINCHANNEL, args[argToCheck - 1] + ' ' + _name, + "They aren't on that channel")) + { + return -1; + } + break; + } + if (sign) { + if (!isOp(*user)) { + update[0].push_back(c); + update.push_back(args[argToCheck - 1]); + } + addOp(*user); + } else { + if (isOp(*user)) { + update[0].push_back(c); + update.push_back(args[argToCheck - 1]); + } + removeOp(*user); + } + break; + } + case 'k': + _keyword = sign ? args[argToCheck++] : ""; + update[0].push_back(c); + if (sign) { + update.push_back(args[argToCheck - 1]); + } + break; + case 'l': + if (sign) { + const int n = std::atoi(args[argToCheck++].c_str()); + if (n > 0) { + update[0].push_back(c); + update.push_back(args[argToCheck - 1]); + _userLimit = n; + } + } else { + if (_userLimit) { + update[0].push_back(c); + } + _userLimit = 0; + } + break; + default: { + const char cs[] = {c, 0}; + if (client.sendCode(ERR_UNKNOWNMODE, cs, + "is unknown mode char to " + _name)) + { + return -1; + } + break; + } + } + } + if (update[0].length() && + (update[0][update[0].length() - 1] == '-' || + update[0][update[0].length() - 1] == '+')) + { + update[0].erase(update[0].length() - 1); + } + + if (!update[0].length()) { + return 0; + } + for (size_t i = 1; i < update.size(); ++i) { + update[0] += ' ' + update[i]; + } + + std::set::iterator it = _clients.begin(); + for (; it != _clients.end(); ++it) { + (*it)->sendMsg(client.nick, "MODE " + _name + " " + update[0], ""); + } + return 0; +} + +int +Channel::sendMode(Client& client) +{ + string_vector modes(1); + if (_fInviteOnly) { + modes[0] += 'i'; + } + if (_fTopicRestric) { + modes[0] += 't'; + } + if (!_keyword.empty()) { + modes[0] += 'k'; + if (isOnChannel(client)) { + modes.push_back(_keyword); + } + } + if (_userLimit != 0) { + modes[0] += 'l'; + std::stringstream str; + str << _userLimit; + modes.push_back(str.str()); + } + + std::string out = _name + " +" + modes[0]; + for (size_t i = 1; i < modes.size(); ++i) { + out += ' ' + modes[i]; + } + return client.sendCode(RPL_CHANNELMODEIS, out, ""); +} + +std::string const& +Channel::getName() const +{ + return _name; +} + +size_t +Channel::getUserLimit() const +{ + return _userLimit; +} + +bool +Channel::isInviteOnly() const +{ + return _fInviteOnly; +} + + std::string const& + Channel::getKeyword() const +{ + return _keyword; +} + +bool +Channel::areOnChannel(Client& c1, Client& c2) const +{ + if (_clients.find(&c1) != _clients.end()) { + if (_clients.find(&c2) != _clients.end()) { + return true; + } + } + return false; +} + +bool +Channel::isOnChannel(Client& c1) const +{ + if (_clients.find(&c1) != _clients.end()) { + return true; + } + return false; +} + +bool +Channel::isOp(Client &client) const +{ + return (_op_clients.find(&client) != _op_clients.end()); +} + +bool +Channel::isChannelEmpty() const +{ + if (_clients.empty() && _op_clients.empty()) { + return true; + } + return false; +} + +Channel& +Channel::operator=(Channel const& rhs) +{ + if (this == &rhs) { + return *this; + } + + _clients = rhs._clients; + _op_clients = rhs._op_clients; + _topic = rhs._topic; + _fInviteOnly = rhs._fInviteOnly; + _fTopicRestric = rhs._fTopicRestric; + _keyword = rhs._keyword; + _userLimit = rhs._userLimit; + + return *this; +} + +bool +Channel::operator==(Channel const& rhs) const +{ + return _name == rhs._name; +} + +bool +Channel::operator==(std::string const& name) const +{ + return _name == name; +} diff --git a/src/Channel.hpp b/src/Channel.hpp new file mode 100644 index 0000000..da473e3 --- /dev/null +++ b/src/Channel.hpp @@ -0,0 +1,57 @@ +#pragma once +#include "Client.hpp" +#include "Server.hpp" +#include "split.hpp" +#include +#include + +class Client; +class Server; + +class Channel { +private: + std::set _clients; + std::set _op_clients; + std::string const _name; + std::string _topic; + + //Channel modes + bool _fInviteOnly; + bool _fTopicRestric; + std::string _keyword; + size_t _userLimit; +public: + Channel(std::string const& name, Client& client); + Channel(Channel const& src); + + bool addClient(Client& client); + bool addOp(Client& client); + bool removeClient(Client& client); + bool removeOp(Client& client); + bool partClient(Client& client, std::string const& reason = ""); + bool kickClient(Client& client, Client& victim, std::string const& reason); + size_t numClients() const; + + int sendMessage(std::string const& msg, Client const* client = NULL) const; + int sendNotice(std::string const& msg, Client const& client) const; + + int sendTopic(Client& client) const; + int setTopic(Client& client, std::string const& newTopic); + + int setMode(Server& server, Client& client, string_vector const& args); + int sendMode(Client& client); + + std::string const& getName() const; + size_t getUserLimit() const; + bool isInviteOnly() const; + std::string const& getKeyword() const; + bool areOnChannel(Client& c1, Client& c2) const; + bool isOnChannel(Client& c1) const; + bool isOp(Client& client) const; + + bool isChannelEmpty() const; + + Channel& operator=(Channel const& rhs); + bool operator==(Channel const& rhs) const; + bool operator==(std::string const& name) const; +}; diff --git a/src/Client.cpp b/src/Client.cpp new file mode 100644 index 0000000..a980f90 --- /dev/null +++ b/src/Client.cpp @@ -0,0 +1,209 @@ +#include "Client.hpp" +#include "Server.hpp" +#include "error.hpp" +#include "log.hpp" +#include +#include + +Client::Client(Server& server): + _server(server), + _addrsize(sizeof(_addr)), + _addr(), + _fd(-1), + _buffer(), + _pass(false), + _registered(false), + _deleted(false), + _welcomed(false), + _channelsInvitedTo(), + nick(""), + username(""), + realname("") + {} + +Client::~Client() +{ + if (_deleted) { + return; + } + _deleted = true; + if (_server.isLive()) { + _server.removeClient(*this); + } + if (_fd >= 0) { + close(_fd); + LOG << "client disconnected" << std::endl; + _fd = -1; + } +} + +int +Client::init(int servfd) +{ + _fd = accept(servfd, &_addr, &_addrsize); + if (_fd < 0) { + error("accept"); + return -1; + } + return 0; +} + +int +Client::getFd() const +{ + return _fd; +} + +int +Client::recv() +{ + char buf[4096] = {}; + const int rv = ::recv(getFd(), buf, sizeof(buf) - 1, 0); + if (rv < 0) { + error("recv"); + _server.removeClient(*this); + return -1; + } + if (rv == 0) { + /* disconnection */ + _server.quitClient(*this); + return 1; + } + + _buffer += std::string(buf); + return 0; +} + +bool +Client::queuedMsg() const +{ + return (_buffer.find("\r\n") != std::string::npos); +} + +std::string +Client::pollMsg() +{ + const std::size_t div = _buffer.find("\r\n"); + assert(div != std::string::npos); + + const std::string msg = _buffer.substr(0, div); + _buffer = _buffer.substr(div + 2); + + return msg; +} + +int +Client::sendCode(const std::string& code, const std::string& msg) +{ + const std::string out = code + " :" + msg + "\r\n"; + if (::send(_fd, out.c_str(), out.length(), 0) != (ssize_t)out.length()) { + LOG << "send failed cringe kekw" << std::endl; + _server.removeClient(*this); + return -1; + } + return 0; +} + +int +Client::sendCode(const std::string& code, const std::string& msg0, const std::string& msg1) +{ + const std::string out = code + " " + msg0 + " :" + msg1 + "\r\n"; + if (::send(_fd, out.c_str(), out.length(), 0) != (ssize_t)out.length()) { + LOG << "send failed cringe kekw" << std::endl; + _server.removeClient(*this); + return -1; + } + return 0; +} + +void +Client::fatalSendCode(const std::string &code, const std::string &msg) +{ + if (!sendCode(code, msg)) { + _server.removeClient(*this); + } +} + +int +Client::sendMsg(std::string const& sender, std::string const& cmd, + std::string const& trailing) +{ + std::string const out = ':' + sender + ' ' + cmd + " :" + trailing + "\r\n"; + if (::send(_fd, out.c_str(), out.length(), 0) != (ssize_t)out.length()) { + LOG << "send failed cringe kekw" << std::endl; + _server.removeClient(*this); + return -1; + } + return 0; +} + +int +Client::sendRaw(std::string const& msg) +{ + if (::send(_fd, msg.c_str(), msg.length(), 0) != (ssize_t)msg.length()) { + LOG << "send failed cringe kekw" << std::endl; + _server.removeClient(*this); + return -1; + } + return 0; +} + +bool +Client::getPass() const +{ + return _pass; +} + +void +Client::setPass() +{ + _pass = true; +} + +bool +Client::getRegistered() const +{ + return _registered; +} + +void +Client::setRegistered() +{ + _registered = true; +} + +bool +Client::getWelcomed() const +{ + return _welcomed; +} + +void +Client::setWelcomed() +{ + _welcomed = true; +} + +void +Client::addInvite(Channel const& channel) +{ + _channelsInvitedTo.insert(channel.getName()); +} + +bool +Client::isInvited(Channel const& channel) +{ + return _channelsInvitedTo.erase(channel.getName()); +} + +bool +Client::operator==(Client const& rhs) const +{ + return this == &rhs; +} + +bool +Client::operator==(std::string const& nick) const +{ + return this->nick == nick; +} diff --git a/src/Client.hpp b/src/Client.hpp new file mode 100644 index 0000000..d5f945f --- /dev/null +++ b/src/Client.hpp @@ -0,0 +1,58 @@ +#pragma once +#include +#include +#include +#include "Channel.hpp" + +class Channel; +class Server; + +class Client { +private: + Server& _server; + unsigned int _addrsize; + struct sockaddr _addr; + int _fd; + std::string _buffer; + bool _pass; + bool _registered; + bool _deleted; + bool _welcomed; + + std::set _channelsInvitedTo; +public: + std::string nick; + std::string username; + std::string realname; + + Client(Server& _server); + ~Client(); + + int init(int servfd); + int getFd() const; + int recv(); + bool queuedMsg() const; + std::string pollMsg(); + + int sendCode(const std::string& code, const std::string& msg); + int sendCode(const std::string& code, const std::string& msg0, const std::string& msg1); + void fatalSendCode(const std::string& code, const std::string& msg); + int sendMsg(std::string const& sender, std::string const& cmd, + std::string const& trailing); + int sendRaw(std::string const& msg); + + bool getPass() const; + void setPass(); + + bool getRegistered() const; + void setRegistered(); + + bool getWelcomed() const; + void setWelcomed(); + + void addInvite(Channel const& channel); + bool isInvited(Channel const& channel); + + bool operator==(Client const& rhs) const; + bool operator==(std::string const& nick) const; +}; diff --git a/src/Message.cpp b/src/Message.cpp new file mode 100644 index 0000000..80bc592 --- /dev/null +++ b/src/Message.cpp @@ -0,0 +1,80 @@ +#include "Message.hpp" +#include "Client.hpp" +#include "log.hpp" +#include "split.hpp" +#include "errcodes.hpp" +#include + +Message::Message(Client& client, const std::string& raw) : _client(client), _raw(raw), _cmd(), _args() +{ + if (_raw == "") { + return; + } + + string_vector major_split = split(_raw, " :"); + + while (major_split.size() > 2) { + size_t size = major_split.size(); + major_split[size - 2] += " :" + major_split[size - 1]; + major_split.pop_back(); + } + + _args = split(major_split[0], " ", true); + if (major_split.size() == 2) { + _args.push_back(major_split[1]); + } + + if (!_args.empty()) { + _cmd = _args[0]; + _args.erase(_args.begin()); + } +} + +const std::string& +Message::getRaw() const +{ + return _raw; +} + +const std::string& +Message::getCmd() const +{ + return _cmd; +} + +int +Message::expectArgs(unsigned int count) const +{ + if (getArgs().size() < count) { + LOG << _cmd << " expects " << count << " parameters" << std::endl; + if (_client.sendCode(ERR_NEEDMOREPARAMS, _cmd, "Not enough parameters")) { + return -1; + } + return 1; + } + return 0; +} + +const string_vector& +Message::getArgs() const +{ + return _args; +} + +Client& +Message::getClient() const +{ + return _client; +} + +std::ostream& +operator<<(std::ostream &os, const Message& msg) +{ + os << "CMD: '" << msg.getCmd() << '\'' << std::endl; + os << "ARGS: "; + string_vector const& args = msg.getArgs(); + for (size_t i = 0; i < args.size(); ++i) { + os << '\'' << args[i] << "'\t"; + } + return os; +} diff --git a/src/Message.hpp b/src/Message.hpp new file mode 100644 index 0000000..6cb683a --- /dev/null +++ b/src/Message.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include "split.hpp" + +class Client; + +class Message { +private: + Client& _client; + const std::string _raw; + std::string _cmd; + string_vector _args; +public: + Message(Client& _client, const std::string& raw); + + const std::string& getRaw() const; + const std::string& getCmd() const; + int expectArgs(unsigned int count) const; + const string_vector& getArgs() const; + Client& getClient() const; +}; + +std::ostream& operator<<(std::ostream &os, const Message& msg); diff --git a/src/Server.INVITE.cpp b/src/Server.INVITE.cpp new file mode 100644 index 0000000..4517d2f --- /dev/null +++ b/src/Server.INVITE.cpp @@ -0,0 +1,48 @@ +#include "Server.hpp" +#include "errcodes.hpp" +#include + +int +Server::_cmdINVITE(Message const& msg) +{ + if (int rv = msg.expectArgs(2)) { + return rv == -1 ? rv : 0; + } + + Client& client = msg.getClient(); + + std::list::iterator guest = std::find(_clients.begin(), + _clients.end(), + msg.getArgs()[0]); + std::vector::iterator channel = std::find(_channels.begin(), + _channels.end(), + msg.getArgs()[1]); + if (guest == _clients.end()) { + return client.sendCode(ERR_NOSUCHNICK, msg.getArgs()[0], "No such nick"); + } + if (channel == _channels.end()) { + goto send_invite_msg; + } + if (!channel->isOnChannel(client)) { + return client.sendCode(ERR_NOTONCHANNEL, msg.getArgs()[1], + "You're not on that channel"); + } + if (channel->isOnChannel(*guest)) { + return client.sendCode(ERR_USERONCHANNEL, + msg.getArgs()[0] + ' ' + msg.getArgs()[1], + "is already on channel"); + } + if (channel->isInviteOnly() && !channel->isOp(client)) { + return client.sendCode(ERR_CHANOPRIVSNEEDED, msg.getArgs()[1], + "You're not channel operator"); + } + + guest->addInvite(*channel); +send_invite_msg: + guest->sendMsg(client.nick, + "INVITE " + msg.getArgs()[0] + ' ' + msg.getArgs()[1], + ""); + return client.sendCode(RPL_INVITING, + msg.getArgs()[1] + ' ' + msg.getArgs()[0], + ""); +} diff --git a/src/Server.JOIN.cpp b/src/Server.JOIN.cpp new file mode 100644 index 0000000..66102fd --- /dev/null +++ b/src/Server.JOIN.cpp @@ -0,0 +1,65 @@ +#include "Server.hpp" +#include "errcodes.hpp" +#include + +int +Server::_cmdJOIN(Message const& msg) +{ + static std::string const charCheck(" \a\r\n"); + + if (int rv = msg.expectArgs(1)) { + return rv == -1 ? rv : 0; + } + + Client& client = msg.getClient(); + + string_vector const& chansToJoin = split(msg.getArgs()[0], ",", true); + string_vector keywords(chansToJoin.size()); + if (msg.getArgs().size() > 1) { + keywords = split(msg.getArgs()[1], ",", false); + } + + for (size_t i = 0; i < chansToJoin.size(); ++i) { + if (chansToJoin[i][0] != '&' && chansToJoin[i][0] != '#') { + if (client.sendCode(ERR_NOSUCHCHANNEL, chansToJoin[i], "No such channel")) { + return 1; + } + continue; + } + for (size_t j = 1; j < chansToJoin[i].length(); ++j) { + if (charCheck.find(chansToJoin[i][j]) != charCheck.npos) { + if (client.sendCode(ERR_NOSUCHCHANNEL, chansToJoin[i], "No such channel")) { + return 1; + } + goto loop_skip; + } + } + { + std::vector::iterator it = std::find(_channels.begin(), + _channels.end(), chansToJoin[i]); + if (it == _channels.end()) { + it = _channels.insert(_channels.end(), Channel(chansToJoin[i], client)); + if (it->isChannelEmpty()) { + _channels.erase(it); + } + } else if (it->getUserLimit() && it->getUserLimit() >= it->numClients()) { + if (client.sendCode(ERR_CHANNELISFULL, chansToJoin[i], "Cannot join channel (+l)")) { + return 1; + } + goto loop_skip; + } else if (it->isInviteOnly() && !client.isInvited(*it)) { + if (client.sendCode(ERR_INVITEONLYCHAN, chansToJoin[i], "Cannot join channel (+i)")) { + return 1; + } + } else if (!it->getKeyword().empty() && keywords[i] != it->getKeyword()) { + if (client.sendCode(ERR_BADCHANNELKEY, chansToJoin[i], "Cannot join channel (+k)")) { + return 1; + } + } else { + it->addClient(client); + } + } +loop_skip:; + } + return 0; +} diff --git a/src/Server.KICK.cpp b/src/Server.KICK.cpp new file mode 100644 index 0000000..b2a86d4 --- /dev/null +++ b/src/Server.KICK.cpp @@ -0,0 +1,67 @@ +#include "Server.hpp" +#include "split.hpp" +#include "errcodes.hpp" +#include +#include + +int +Server::_cmdKICK(Message const& msg) +{ + if (int rv = msg.expectArgs(2)) { + return rv == -1 ? rv : 0; + } + Client& client = msg.getClient(); + + std::string reason; + if (msg.getArgs().size() > 2) { + reason = msg.getArgs()[2]; + } else { + reason = client.nick; + } + string_vector const& chans = split(msg.getArgs()[0], ","); + string_vector const& users = split(msg.getArgs()[1], ","); + + if (users.size() != chans.size()) { + client.sendCode(ERR_NEEDMOREPARAMS, "KICK", "Not enough parameters"); + return 0; + } + for (size_t i = 0; i < users.size(); ++i) { + std::list::iterator itClient; + std::vector::iterator itChannel; + + itClient = std::find(_clients.begin(), _clients.end(), users[i]); + if (itClient == _clients.end()) { + client.sendCode(ERR_NOSUCHNICK, users[i], "No such nick/channel"); + continue; + } + + itChannel = std::find(_channels.begin(), _channels.end(), chans[i]); + if (itChannel == _channels.end()) { + client.sendCode(ERR_NOSUCHCHANNEL, chans[i], "No such channel"); + continue; + } + + if (!itChannel->isOnChannel(client)) { + client.sendCode(ERR_NOTONCHANNEL, chans[i], "You're not on that channel"); + continue; + } + if (!itChannel->isOnChannel(*itClient)) { + client.sendCode(ERR_USERNOTINCHANNEL, users[i] + " " + chans[i], + "They aren't on that channel"); + continue; + } + if (!itChannel->isOp(client)) { + client.sendCode(ERR_CHANOPRIVSNEEDED, chans[i], "You're not channel operator"); + continue; + } + itChannel->kickClient(client, *itClient, reason); + if (itChannel->isChannelEmpty()) { + _channels.erase(itChannel); + std::list::iterator itC; + for (itC = _clients.begin(); itC != _clients.end(); ++itC) { + itC->isInvited(*itChannel); + } + } + } + return 0; +} diff --git a/src/Server.MODE.cpp b/src/Server.MODE.cpp new file mode 100644 index 0000000..5b02660 --- /dev/null +++ b/src/Server.MODE.cpp @@ -0,0 +1,27 @@ +#include "Server.hpp" +#include "errcodes.hpp" +#include "split.hpp" +#include + +int +Server::_cmdMODE(Message const& msg) +{ + if (int rv = msg.expectArgs(1)) { + return rv == -1 ? rv : 0; + } + + Client& client = msg.getClient(); + string_vector const& args = msg.getArgs(); + + std::vector::iterator itChannel; + itChannel = std::find(_channels.begin(), _channels.end(), args[0]); + if (itChannel == _channels.end()) { + return client.sendCode(ERR_NOSUCHCHANNEL, msg.getArgs()[0], + "No such channel"); + } + if (msg.getArgs().size() > 1) { + return itChannel->setMode(*this, client, args); + } else { + return itChannel->sendMode(client); + } +} diff --git a/src/Server.MOTD.cpp b/src/Server.MOTD.cpp new file mode 100644 index 0000000..f47b83c --- /dev/null +++ b/src/Server.MOTD.cpp @@ -0,0 +1,96 @@ +#include "Server.hpp" +#include "errcodes.hpp" + +int +Server::_cmdMOTD(const Message& msg) +{ + static const char *const motd[] = { + "- We're no strangers to love", + "- You know the rules and so do I", + "- A full commitment's what I'm thinking of", + "- You wouldn't get this from any other guy", + "- ", + "- I just wanna tell you how I'm feeling", + "- Gotta make you understand", + "- ", + "- Never gonna give you up", + "- Never gonna let you down", + "- Never gonna run around and desert you", + "- Never gonna make you cry", + "- Never gonna say goodbye", + "- Never gonna tell a lie and hurt you", + "- ", + "- We've known each other for so long", + "- Your heart's been aching, but", + "- You're too shy to say it", + "- Inside, we both know what's been going on", + "- We know the game and we're gonna play it", + "- ", + "- And if you ask me how I'm feeling", + "- Don't tell me you're too blind to see", + "- ", + "- Never gonna give you up", + "- Never gonna let you down", + "- Never gonna run around and desert you", + "- Never gonna make you cry", + "- Never gonna say goodbye", + "- Never gonna tell a lie and hurt you", + "- ", + "- Never gonna give you up", + "- Never gonna let you down", + "- Never gonna run around and desert you", + "- Never gonna make you cry", + "- Never gonna say goodbye", + "- Never gonna tell a lie and hurt you", + "- ", + "- (Ooh, give you up)", + "- (Ooh, give you up)", + "- Never gonna give, never gonna give", + "- (Give you up)", + "- Never gonna give, never gonna give", + "- (Give you up)", + "- ", + "- We've known each other for so long", + "- Your heart's been aching, but", + "- You're too shy to say it", + "- Inside, we both know what's been going on", + "- We know the game and we're gonna play it", + "- ", + "- I just wanna tell you how I'm feeling", + "- Gotta make you understand", + "- ", + "- Never gonna give you up", + "- Never gonna let you down", + "- Never gonna run around and desert you", + "- Never gonna make you cry", + "- Never gonna say goodbye", + "- Never gonna tell a lie and hurt you", + "- ", + "- Never gonna give you up", + "- Never gonna let you down", + "- Never gonna run around and desert you", + "- Never gonna make you cry", + "- Never gonna say goodbye", + "- Never gonna tell a lie and hurt you", + "- ", + "- Never gonna give you up", + "- Never gonna let you down", + "- Never gonna run around and desert you", + "- Never gonna make you cry", + "- Never gonna say goodbye", + "- Never gonna tell a lie and hurt you" + }; + + if (msg.getClient().sendCode(RPL_MOTDSTART, "- Message of the day -")) { + return -1; + } + for (std::size_t i = 0; i < sizeof(motd) / sizeof(*motd); i++) { + if (msg.getClient().sendCode(RPL_MOTD, motd[i])) { + return -1; + } + } + if (msg.getClient().sendCode(RPL_ENDOFMOTD, "End of MOTD command")) { + return -1; + } + return 0; +} diff --git a/src/Server.NICK.cpp b/src/Server.NICK.cpp new file mode 100644 index 0000000..9dcf97c --- /dev/null +++ b/src/Server.NICK.cpp @@ -0,0 +1,42 @@ +#include "Server.hpp" +#include "errcodes.hpp" + +int +Server::_cmdNICK(Message const& msg) +{ + if (int rv = msg.expectArgs(1)) { + return rv == -1 ? rv : 0; + } + + Client& client = msg.getClient(); + static std::string const special("[]\\`_^{|}"); + + std::string const& nick = msg.getArgs()[0]; + + std::list::iterator it = _clients.begin(); + for (; it != _clients.end(); ++it) { + if (client == *it) { + continue; + } + if (nick == it->nick) { + return client.sendCode(ERR_NICKNAMEINUSE, nick, "Nickname is already in use"); + } + } + if (nick.length() > 9 || nick.length() < 1 || + (!std::isalpha(nick[0]) && special.find(nick[0]) == special.npos)) + { + return client.sendCode(ERR_ERRONEUSNICKNAME, nick, "Erroneous nickname"); + } + for (size_t i = 1; i < nick.length(); ++i) { + if (!std::isdigit(nick[i]) && !std::isalpha(nick[i]) && + special.find(nick[i]) == special.npos && nick[i] != '-') + { + return client.sendCode(ERR_ERRONEUSNICKNAME, nick, "Erroneous nickname"); + } + } + client.nick = nick; + if (client.getRegistered() && !client.getWelcomed()) { + return _welcomeClient(client); + } + return 0; +} diff --git a/src/Server.NOTICE.cpp b/src/Server.NOTICE.cpp new file mode 100644 index 0000000..0cf765e --- /dev/null +++ b/src/Server.NOTICE.cpp @@ -0,0 +1,30 @@ +#include "Server.hpp" +#include "Message.hpp" + +int +Server::_cmdNOTICE(Message const& msg) +{ + if (msg.getArgs().size() < 2) { + return 0; + } + + Client& client = msg.getClient(); + + std::list::iterator it; + for (it = _clients.begin(); it != _clients.end(); ++it) { + if (it->nick == msg.getArgs()[0]) { + if (it->sendMsg(client.nick, "NOTICE " + it->nick, msg.getArgs()[1]) + && it->nick == client.nick) { + return 1; + } + return 0; + } + } + for (size_t i = 0; i < _channels.size(); ++i) { + if (_channels[i].getName() == msg.getArgs()[0]) { + _channels[i].sendNotice(msg.getArgs()[1], client); + return 0; + } + } + return 0; +} diff --git a/src/Server.PART.cpp b/src/Server.PART.cpp new file mode 100644 index 0000000..28a55b1 --- /dev/null +++ b/src/Server.PART.cpp @@ -0,0 +1,43 @@ +#include "Server.hpp" +#include "split.hpp" +#include "errcodes.hpp" +#include + +int +Server::_cmdPART(Message const& msg) +{ + if (int rv = msg.expectArgs(1)) { + return rv == -1 ? rv : 0; + } + + Client& client = msg.getClient(); + + std::string const& reason = (msg.getArgs().size() > 1) ? msg.getArgs()[1] : ""; + + string_vector const& channels = split(msg.getArgs()[0], ",", true); + for (size_t i = 0; i < channels.size(); ++i) { + std::vector::iterator it; + it = std::find(_channels.begin(), _channels.end(), channels[i]); + if (it == _channels.end()) { + if (client.sendCode(ERR_NOSUCHCHANNEL, channels[i], "No such channel")) { + return 1; + } + continue; + } + if (!it->isOnChannel(client)) { + if (client.sendCode(ERR_NOTONCHANNEL, channels[i], "You're not on that channel")) { + return 1; + } + continue; + } + it->partClient(client, reason); + if (it->isChannelEmpty()) { + _channels.erase(it); + std::list::iterator itC; + for (itC = _clients.begin(); itC != _clients.end(); ++itC) { + itC->isInvited(*it); + } + } + } + return 0; +} diff --git a/src/Server.PING.cpp b/src/Server.PING.cpp new file mode 100644 index 0000000..c6e8a2a --- /dev/null +++ b/src/Server.PING.cpp @@ -0,0 +1,15 @@ +#include "Server.hpp" + +int +Server::_cmdPING(Message const& msg) +{ + Client& client = msg.getClient(); + string_vector const& args = msg.getArgs(); + + std::string out = "PONG"; + for (size_t i = 0; i < args.size(); ++i) { + out += ' ' + args[i]; + } + out += "\r\n"; + return client.sendRaw(out); +} diff --git a/src/Server.PRIVMSG.cpp b/src/Server.PRIVMSG.cpp new file mode 100644 index 0000000..36b0368 --- /dev/null +++ b/src/Server.PRIVMSG.cpp @@ -0,0 +1,34 @@ +#include "Server.hpp" +#include "split.hpp" +#include "errcodes.hpp" + +int +Server::_cmdPRIVMSG(Message const& msg) +{ + string_vector const& args = msg.getArgs(); + Client& client = msg.getClient(); + + if (args.empty()) { + return client.sendCode(ERR_NORECIPIENT, ":No recipient given (PRIVMSG)"); + } + if (args.size() < 2) { + return client.sendCode(ERR_NOTEXTTOSEND, ":No text to send"); + } + string_vector const& recipients = split(args[0], ",", true); + for (size_t i = 0; i < recipients.size(); ++i) { + std::list::iterator itClient; + for (itClient = _clients.begin(); itClient != _clients.end(); ++itClient) { + if (itClient->nick == recipients[i]) { + itClient->sendMsg(client.nick, "PRIVMSG " + itClient->nick, args[1]); + continue; + } + } + for (size_t j = 0; j < _channels.size(); ++j) { + if (_channels[j].getName() == recipients[i]) { + _channels[j].sendMessage(args[1], &client); + continue; + } + } + } + return 0; +} diff --git a/src/Server.QUIT.cpp b/src/Server.QUIT.cpp new file mode 100644 index 0000000..e16d0b2 --- /dev/null +++ b/src/Server.QUIT.cpp @@ -0,0 +1,37 @@ +#include "Server.hpp" +#include "errcodes.hpp" + +int +Server::_cmdQUIT(Message const& msg) +{ + Client& client = msg.getClient(); + std::string quit; + if (msg.getArgs().empty()) { + quit = client.username; + } else { + quit = msg.getArgs()[0]; + } + std::list::iterator it; + for (it = _clients.begin(); it != _clients.end(); ++it) { + for (size_t i = 0; i < _channels.size(); ++i) { + if (_channels[i].areOnChannel(client, *it)) { + it->sendMsg(client.nick, "QUIT", quit); + goto next_client; + } + } +next_client:; + } + for (size_t i = 0; i < _channels.size(); ++i) { + _channels[i].removeClient(client); + if (_channels[i].isChannelEmpty()) { + _channels.erase(_channels.begin() + i); + + std::list::iterator itC; + for (itC = _clients.begin(); itC != _clients.end(); ++itC) { + itC->isInvited(_channels[i]); + } + } + } + removeClient(client); + return 1; +} diff --git a/src/Server.TOPIC.cpp b/src/Server.TOPIC.cpp new file mode 100644 index 0000000..41d38f6 --- /dev/null +++ b/src/Server.TOPIC.cpp @@ -0,0 +1,30 @@ +#include "Server.hpp" +#include "split.hpp" +#include "errcodes.hpp" +#include + +int +Server::_cmdTOPIC(Message const& msg) +{ + if (int rv = msg.expectArgs(1)) { + return rv == -1 ? rv : 0; + } + + Client& client = msg.getClient(); + string_vector const& args = msg.getArgs(); + + std::vector::iterator itChannel; + itChannel = std::find(_channels.begin(), _channels.end(), args[0]); + if (itChannel == _channels.end()) { + return client.sendCode(ERR_NOSUCHCHANNEL, args[0], "No such channel"); + } + if (!itChannel->isOnChannel(client)) { + return client.sendCode(ERR_NOTONCHANNEL, args[0], + "You're not on that channel"); + } + if (args.size() < 2) { + return itChannel->sendTopic(client); + } else { + return itChannel->setTopic(client, args[1]); + } +} diff --git a/src/Server.USER.cpp b/src/Server.USER.cpp new file mode 100644 index 0000000..7a3a49b --- /dev/null +++ b/src/Server.USER.cpp @@ -0,0 +1,24 @@ +#include "Server.hpp" +#include "errcodes.hpp" + +int +Server::_cmdUSER(Message const& msg) +{ + if (int rv = msg.expectArgs(4)) { + return rv == -1 ? rv : 0; + } + + Client& client = msg.getClient(); + + if (client.getRegistered()) { + return client.sendCode(ERR_ALREADYREGISTRED); + } + + client.username = msg.getArgs()[0]; + client.realname = msg.getArgs()[3]; + client.setRegistered(); + if (!client.nick.empty()) { + return _welcomeClient(client); + } + return 0; +} diff --git a/src/Server.cpp b/src/Server.cpp new file mode 100644 index 0000000..ef894a4 --- /dev/null +++ b/src/Server.cpp @@ -0,0 +1,302 @@ +#include "Server.hpp" +#include "Client.hpp" +#include "Message.hpp" +#include "error.hpp" +#include "errcodes.hpp" +#include "log.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +Server::Server(int port, const std::string& keyword) : + _port(port), + _keyword(keyword), + _efd(-1), + _socket(-1), + _dead(false), + _clients(), + _channels() +{} + +Server::~Server() +{ + _dead = true; + + if (_socket >= 0) { + close(_socket); + _socket = -1; + } + + if (_efd >= 0) { + close(_efd); + _socket = -1; + } +} + +int +Server::init() +{ + _efd = epoll_create('U'+'w'+'U'); + if (_efd < 0) { + error("epoll_create"); + return -1; + } + + _socket = socket(AF_INET, SOCK_STREAM, 0); + if (_socket < 0) { + error("socket"); + return -1; + } + + int option = 1; + if (setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) < 0) { + error("setsockopt"); + return -1; + } + + struct sockaddr_in addr = {}; + addr.sin_family = AF_INET; + addr.sin_port = htons(_port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + if (bind(_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + error("bind"); + return -1; + } + + if (listen(_socket, 128) < 0) { + error("listen"); + return -1; + } + + return _addFd(_socket); +} + +int +Server::_addFd(int fd) const +{ + epoll_event event = {}; + event.data.fd = fd; + event.events = EPOLLIN | EPOLLET; + if (epoll_ctl(_efd, EPOLL_CTL_ADD, fd, &event)) { + error("epoll_ctl"); + return -1; + } + return 0; +} + +int +Server::wait() +{ + epoll_event event = {}; + const int ev = epoll_wait(_efd, &event, 1, -1); + if (ev < 0) { + error("epoll_wait"); + return -1; + } + + const int fd = event.data.fd; + if (fd == _socket) { + if (_accept(fd)) { + return -1; + } + return 0; + } + + return _receiveMsg(fd); +} + +int +Server::_accept(int fd) +{ + _clients.push_back(Client(*this)); + Client& client = _clients.back(); + + if (client.init(fd)) { + _clients.pop_back(); + return -1; + } + + return _addFd(client.getFd()); +} + +int +Server::_receiveMsg(int fd) +{ + std::list::iterator client; + for (client = _clients.begin(); client != _clients.end(); ++client) { + if (client->getFd() == fd) { + break; + } + } + if (client == _clients.end()) { + LOG << "no client has this fd" << std::endl; + return -1; + } + + if (client->recv()) { + return 0; + } + + while (_processMsg(*client) == 0) {} + + return 0; +} + +int +Server::_processMsg(Client &client) +{ + if (!client.queuedMsg()) { + return 1; + } + + const Message msg(client, client.pollMsg()); + LOG << msg << std::endl; + + if (msg.getCmd().empty()) { + return 0; + } + + const std::string& cmd = msg.getCmd(); + + if (!client.getPass()) { + if (cmd != "PASS" || msg.getArgs().empty() || msg.getArgs()[0] != _keyword) { + client.fatalSendCode(ERR_PASSWDMISMATCH); + return 1; + } + client.setPass(); + return 0; + } + + if (cmd == "PASS") { + return client.sendCode(ERR_ALREADYREGISTRED); + } + + if (cmd == "NICK") { + return _cmdNICK(msg); + } + if (cmd == "USER") { + return _cmdUSER(msg); + } + + if (!client.getRegistered() || client.nick.empty()) { + return client.sendCode(ERR_NOTREGISTERED); + } + + if (cmd == "QUIT") { + return _cmdQUIT(msg); + } + if (cmd == "PART") { + return _cmdPART(msg); + } + + if (cmd == "JOIN") { + return _cmdJOIN(msg); + } + + if (cmd == "PRIVMSG") { + return _cmdPRIVMSG(msg); + } + + if (cmd == "NOTICE") { + return _cmdNOTICE(msg); + } + + if (cmd == "KICK") { + return _cmdKICK(msg); + } + + if (cmd == "TOPIC") { + return _cmdTOPIC(msg); + } + + if (cmd == "PING") { + return _cmdPING(msg); + } + + if (cmd == "MODE") { + return _cmdMODE(msg); + } + + if (cmd == "INVITE") { + return _cmdINVITE(msg); + } + + if (cmd == "MOTD") { + return _cmdMOTD(msg); + } + + + client.sendCode(ERR_UNKNOWNCOMMAND, cmd, "Unknown command"); + return 1; +} + +void +Server::removeClient(Client &client) +{ + _clients.remove(client); +} + +void +Server::quitClient(Client& client) +{ + std::list::iterator it; + for (it = _clients.begin(); it != _clients.end(); ++it) { + if (client == *it) { + continue; + } + for (size_t i = 0; i < _channels.size(); ++i) { + if (_channels[i].areOnChannel(client, *it)) { + it->sendMsg(client.nick, "QUIT", "Client closed connection"); + goto next_client; + } + } +next_client:; + } + for (size_t i = 0; i < _channels.size(); ++i) { + _channels[i].removeClient(client); + if (_channels[i].isChannelEmpty()) { + _channels.erase(_channels.begin() + i); + std::list::iterator itC; + for (itC = _clients.begin(); itC != _clients.end(); ++itC) { + itC->isInvited(_channels[i]); + } + } + } + removeClient(client); +} + +Client* +Server::findClient(const std::string& nick) +{ + std::list::iterator it = std::find(_clients.begin(), _clients.end(), nick); + if (it != _clients.end()) { + return &*it; + } + return NULL; +} + +int +Server::_welcomeClient(Client& client) +{ + if (client.getWelcomed()) { + return 0; + } + + if (client.sendCode(RPL_WELCOME, client.nick, "Welcome to our IRC server bozo")) { + return -1; + } + client.setWelcomed(); + return 0; +} + +bool +Server::isLive() const +{ + return !_dead; +} diff --git a/src/Server.hpp b/src/Server.hpp new file mode 100644 index 0000000..d778124 --- /dev/null +++ b/src/Server.hpp @@ -0,0 +1,57 @@ +#pragma once +#include "Client.hpp" +#include "Message.hpp" +#include "Channel.hpp" +#include +#include +#include + +class Client; +class Channel; + +class Server { +private: + const int _port; + const std::string _keyword; + + int _efd; + int _socket; + bool _dead; + + std::list _clients; + std::vector _channels; + + int _addFd(int fd) const; + int _accept(int fd); + int _receiveMsg(int fd); + int _processMsg(Client &client); + + int _cmdNICK(Message const& msg); + int _cmdUSER(Message const& msg); + int _cmdQUIT(Message const& msg); + int _cmdPART(Message const& msg); + int _cmdJOIN(Message const& msg); + int _cmdPRIVMSG(Message const& msg); + int _cmdNOTICE(Message const& msg); + int _cmdKICK(Message const& msg); + int _cmdMOTD(Message const& msg); + int _cmdTOPIC(Message const& msg); + int _cmdPING(Message const& msg); + int _cmdMODE(Message const& msg); + int _cmdINVITE(Message const& msg); + + int _welcomeClient(Client& client); + +public: + Server(int port, const std::string& password); + ~Server(); + + int init(); + int wait(); + + void removeClient(Client& client); + void quitClient(Client& client); + Client *findClient(const std::string& nick); + + bool isLive() const; +}; diff --git a/src/errcodes.hpp b/src/errcodes.hpp new file mode 100644 index 0000000..cfb288f --- /dev/null +++ b/src/errcodes.hpp @@ -0,0 +1,96 @@ +#pragma once +#define ERR_NOSUCHNICK "401" +#define ERR_NOSUCHSERVER "402" +#define ERR_NOSUCHCHANNEL "403" +#define ERR_CANNOTSENDTOCHAN "404" +#define ERR_TOOMANYCHANNELS "405" +#define ERR_WASNOSUCHNICK "406" +#define ERR_TOOMANYTARGETS "407" +#define ERR_NOSUCHSERVICE "408" +#define ERR_NOORIGIN "409" +#define ERR_NORECIPIENT "411" +#define ERR_NOTEXTTOSEND "412" +#define ERR_NOTOPLEVEL "413" +#define ERR_WILDTOPLEVEL "414" +#define ERR_BADMASK "415" +#define ERR_UNKNOWNCOMMAND "421" +#define ERR_NOMOTD "422" +#define ERR_NOADMININFO "423" +#define ERR_FILEERROR "424" +#define ERR_NONICKNAMEGIVEN "431" +#define ERR_ERRONEUSNICKNAME "432" +#define ERR_NICKNAMEINUSE "433" +#define ERR_NICKCOLLISION "436" +#define ERR_UNAVAILRESOURCE "437" +#define ERR_USERNOTINCHANNEL "441" +#define ERR_NOTONCHANNEL "442" +#define ERR_USERONCHANNEL "443" +#define ERR_NOLOGIN "444" +#define ERR_SUMMONDISABLED "445" +#define ERR_USERSDISABLED "446" +#define ERR_NOTREGISTERED "451", "You have not registered" +#define ERR_NEEDMOREPARAMS "461" +#define ERR_ALREADYREGISTRED "462", "Unauthorized command (already registered)" +#define ERR_NOPERMFORHOST "463" +#define ERR_PASSWDMISMATCH "464", "Password incorrect" +#define ERR_YOUREBANNEDCREEP "465" +#define ERR_YOUWILLBEBANNED "466" +#define ERR_KEYSET "467" +#define ERR_CHANNELISFULL "471" +#define ERR_UNKNOWNMODE "472" +#define ERR_INVITEONLYCHAN "473" +#define ERR_BANNEDFROMCHAN "474" +#define ERR_BADCHANNELKEY "475" +#define ERR_BADCHANMASK "476" +#define ERR_NOCHANMODES "477" +#define ERR_BANLISTFULL "478" +#define ERR_ILLEGALCHANNAME "479" +#define ERR_NOPRIVILEGES "481" +#define ERR_CHANOPRIVSNEEDED "482" +#define ERR_CANTKILLSERVER "483" +#define ERR_RESTRICTED "484" +#define ERR_UNIQOPPRIVSNEEDED "485" +#define ERR_NOOPERHOST "491" +#define ERR_UMODEUNKNOWNFLAG "501" +#define ERR_USERSDONTMATCH "502" +#define RPL_WELCOME "001" +#define RPL_YOURHOST "002" +#define RPL_CREATED "003" +#define RPL_MYINFO "004" +#define RPL_BOUNCE "005" +#define RPL_USERHOST "302" +#define RPL_ISON "303" +#define RPL_AWAY "301" +#define RPL_UNAWAY "305" +#define RPL_NOWAWAY "306" +#define RPL_WHOISUSER "311" +#define RPL_WHOISSERVER "312" +#define RPL_WHOISOPERATOR "313" +#define RPL_WHOISIDLE "317" +#define RPL_ENDOFWHOIS "318" +#define RPL_WHOISCHANNELS "319" +#define RPL_WHOWASUSER "314" +#define RPL_ENDOFWHOWAS "369" +#define RPL_LISTSTART "321" +#define RPL_LIST "322" +#define RPL_LISTEND "323" +#define RPL_UNIQOPIS "325" +#define RPL_CHANNELMODEIS "324" +#define RPL_NOTOPIC "331" +#define RPL_TOPIC "332" +#define RPL_INVITING "341" +#define RPL_SUMMONING "342" +#define RPL_INVITELIST "346" +#define RPL_ENDOFINVITELIST "347" +#define RPL_EXCEPTLIST "348" +#define RPL_ENDOFEXCEPTLIST "349" +#define RPL_VERSION "351" +#define RPL_WHOREPLY "352" +#define RPL_ENDOFWHO "315" +#define RPL_NAMREPLY "353" +#define RPL_LINKS "364" +#define RPL_ENDOFNAMES "366" +#define RPL_MOTD "372" +#define RPL_MOTDSTART "375" +#define RPL_ENDOFMOTD "376" +#define RPL_UMODEIS "221" diff --git a/src/error.cpp b/src/error.cpp new file mode 100644 index 0000000..68d696e --- /dev/null +++ b/src/error.cpp @@ -0,0 +1,9 @@ +#include "error.hpp" +#include "log.hpp" +#include +#include + +void error(const char* msg) +{ + LOG << msg << ": " << std::strerror(errno) << std::endl; +} \ No newline at end of file diff --git a/src/error.hpp b/src/error.hpp new file mode 100644 index 0000000..1e6640a --- /dev/null +++ b/src/error.hpp @@ -0,0 +1,3 @@ +#pragma once + +void error(const char* msg); diff --git a/src/log.hpp b/src/log.hpp new file mode 100644 index 0000000..291a984 --- /dev/null +++ b/src/log.hpp @@ -0,0 +1,4 @@ +#pragma once +#include + +#define LOG std::cerr diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..b455419 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include "log.hpp" +#include "Server.hpp" + +static void sigint_handler(int sig); +static int main_loop = true; + +int +main(int argc, char **argv) +{ + if (argc != 3) { + LOG << "usage: " << argv[0] << " " << std::endl; + return 1; + } + + char *pEnd; + long port = std::strtol(argv[1], &pEnd, 10); + if (errno == ERANGE || *pEnd || port < 0 || port > 0xffff) { + LOG << "incorrect port" << std::endl; + return 1; + } + + Server serv(port, argv[2]); + + if (serv.init()) { + LOG << "omegasadge :(" << std::endl; + return 1; + } + + signal(SIGINT, sigint_handler); + while (main_loop && !serv.wait()) {} + + return 0; +} + +static void +sigint_handler(int) +{ + main_loop = false; +} diff --git a/src/split.cpp b/src/split.cpp new file mode 100644 index 0000000..9d4ed5f --- /dev/null +++ b/src/split.cpp @@ -0,0 +1,30 @@ +#include "split.hpp" + +string_vector +split(std::string str, const std::string &sep, bool ignore_empty) +{ + std::vector vec; + + bool end_sep = (sep.length() <= str.length() && + str.substr(str.length() - sep.length()) == sep); + + for (;;) { + const size_t find = str.find(sep); + if (find == std::string::npos) { + break; + } + std::string const bit = str.substr(0, find); + if (!ignore_empty || !bit.empty()) { + vec.push_back(bit); + } + str.erase(0, find + sep.length()); + } + + if (!str.empty()) { + vec.push_back(str); + } + if (!ignore_empty && end_sep) { + vec.push_back(""); + } + return vec; +} diff --git a/src/split.hpp b/src/split.hpp new file mode 100644 index 0000000..fc0813b --- /dev/null +++ b/src/split.hpp @@ -0,0 +1,7 @@ +#pragma once +#include +#include + +typedef std::vector string_vector; + +string_vector split(std::string str, const std::string& sep, bool ignore_empty=false); diff --git a/subject.pdf b/subject.pdf new file mode 100644 index 0000000..ed0a1c1 Binary files /dev/null and b/subject.pdf differ