#ifndef CLIENT_H
#define CLIENT_H
#ifdef _WIN32
#define _WIN32_WINNT 0x0501
#endif
#include
#include
#include
#include
#include
#include "auxiliar.h"
class User;
class PingPong;
class Client : private boost::noncopyable,
public boost::enable_shared_from_this
{
public:
Client(boost::asio::io_service& service);
~Client();
static unsigned int clientCount;
void start();
void send(const std::string& msg);
void close();
void closeConnection(const std::string& reason);
std::string getHostName();
std::string getIP();
boost::asio::ip::tcp::socket& socket() { return m_socket; }
boost::asio::io_service::strand& _strand() { return strand; }
protected:
void receiveFromSocket(const boost::system::error_code& e, std::size_t bytes);
void disconnectClient();
private:
User* m_user;
bool sentMotd;
void firstTime();
void handleRequest(StringVec __msg);
void sendLine(const char *fmt, ...);
bool sentUser;
bool sentCommon;
bool closed;
boost::asio::ip::tcp::socket m_socket;
boost::asio::io_service::strand strand;
boost::array m_buffers;
uint32_t eventId, pongEvent;
void handle_write(const boost::system::error_code& e, size_t bytes);
void closeWithError(int error);
void receive();
PingPong* pingPong;
};
#endif
#include "client.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef _WIN32
#include
#include
#endif
#include "user.h"
#include "channels.h"
#include "auxiliar.h"
#include "defines.h"
#include "configreader.h"
#include "dispatcher.h"
#include "mainframe.h"
#include "scripting.h"
extern Config* g_config;
extern Channels* g_channels;
extern MainFrame* g_mainFrame;
extern Script* g_script;
extern Dispatcher* g_dispatcher;
unsigned int Client::clientCount;
/*!
* @brief client class
* \todo shutdown socket on each error.
*/
/*!
* @brief Ping? Pong! class
*/
class PingPong
{
public:
PingPong(Client* c);
~PingPong() {}
void sendPing();
int32_t getLastPingTime() { return lastPong; }
void onReceivePong(const boost::system::error_code& e, std::size_t bytes_transferred);
bool gotReply() { return reply; }
private:
Client* m_client;
int32_t lastPong;
std::string lastPing;
bool reply;
boost::array m_buffers;
protected:
void closeConnection();
};
PingPong::PingPong(Client* c)
: m_client(c)
{
reply = false;
lastPing = "";
lastPong = 0;
}
void PingPong::sendPing()
{
reply = false;
std::stringstream ss;
ss << "PING :";
for (int32_t i = 1; i < 10; ++i) {
int32_t p = rand() % i + 1;
ss << p;
lastPing += static_cast(p);
}
ss << "\r\n";
boost::asio::write(m_client->socket(), boost::asio::buffer(ss.str(), ss.str().length()));
lastPong++;
m_client->socket().async_receive(boost::asio::buffer(m_buffers),
m_client->_strand().wrap(
boost::bind(&PingPong::onReceivePong, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
void PingPong::onReceivePong(const boost::system::error_code& e, std::size_t bytes_transferred)
{
if (e) {
m_client->socket().async_receive(boost::asio::buffer(m_buffers),
m_client->_strand().wrap(
boost::bind(&PingPong::onReceivePong, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
return;
}
std::string str(m_buffers.data());
size_t __wat = str.rfind("\r\n");
if (__wat == std::string::npos) {
__wat = str.rfind("\n");
if (__wat == std::string::npos) {
m_client->socket().async_read_some(boost::asio::buffer(m_buffers),
m_client->_strand().wrap(
boost::bind(&PingPong::onReceivePong, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
return;
}
}
str.erase(__wat);
if (str.substr(0, 4) == "PONG") {
std::string pong = str.substr(5, str.length()-5);
if (pong == lastPing && getLastPingTime() != 120) {
lastPong = 0xF * 8;
reply = true;
g_dispatcher->addEvent(new Task(lastPong, boost::bind(
&PingPong::sendPing, this)));
}
} else {
g_dispatcher->addEvent(new Task(40, boost::bind(
&PingPong::closeConnection, this)));
}
}
void PingPong::closeConnection()
{
std::stringstream p;
p << "ERROR: Closing link (Ping timeout)";
boost::asio::write(m_client->socket(), boost::asio::buffer(p.str(), p.str().length()));
boost::system::error_code ignored_ec;
m_client->socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
m_client->socket().close();
}
Client::Client(boost::asio::io_service& service)
: m_socket(service), strand(service)
{
pingPong = new PingPong(this);
eventId = pongEvent = 0;
clientCount++;
sentUser = sentCommon = sentMotd = false;
m_user = NULL;
closed = false;
}
Client::~Client()
{
if (clientCount > 0)
clientCount--;
}
void Client::sendLine(const char *fmt, ...)
{
va_list arg;
va_start(arg, fmt);
char buffer[512];
vsprintf(buffer, fmt, arg);
va_end(arg);
send(std::string(buffer));
}
void Client::firstTime()
{
std::stringstream ss;
ss << MOTD_COMMON << "Looking up your hostname\r\n";
send(ss.str());
ss.str("");
ss << MOTD_COMMON << "Checking Ident\r\n";
send(ss.str());
ss.str("");
if (!getHostName().empty())
ss << MOTD_COMMON << "Found your hostname: " << getHostName() << ".";
else
ss << MOTD_COMMON << "Couldn't look up your hostname... Using your IP address instead";
ss << "\r\n";
send(ss.str());
ss.str("");
switch (ident(getIP())) {
case SOCKET_NOT_INITED:
{
std::clog << "[Info - Client::useIdent] - Unable to initalize socket." << std::endl;
break;
}
case SOCKET_NOT_CONNECTED:
{
std::clog << "[Info - Client::useIdent] - Unable to connect socket." << std::endl;
break;
}
case SOCKET_UNABLE_TO_WRITE:
{
std::clog << "[Info - Client::useIdent] - Unable to write data on the socket." << std::endl;
break;
}
case SOCKET_UNABLE_TO_READ:
{
std::clog << "[Info - Client::useIdent] - Unable to read data on the socket." << std::endl;
break;
}
case CONNECTION_SUCCESS:
default:
{
ss << MOTD_COMMON << "Got ident response";
ss << "\r\n";
send(ss.str());
return;
}
}
ss.str("");
ss << MOTD_COMMON << "No ident response";
ss << "\r\n";
send(ss.str());
}
void Client::start()
{
g_script->addClient(this);
std::clog << "New Client." << std::endl;
firstTime();
g_mainFrame->addClient(this);
receive();
}
void
Client::receive()
{
m_socket.async_receive(boost::asio::buffer(m_buffers),
strand.wrap(
boost::bind(&Client::receiveFromSocket, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
std::string Client::getIP()
{
return m_socket.remote_endpoint().address().to_string();
}
std::string Client::getHostName()
{
hostent* remoteHost = gethostbyaddr(getIP().c_str(), 4, AF_INET);
if (remoteHost)
return std::string(remoteHost->h_name);
return std::string();
}
void Client::send(const std::string& msg)
{
boost::asio::async_write(m_socket, boost::asio::buffer(msg),
boost::bind(&Client::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void
Client::handle_write(const boost::system::error_code& e, size_t bytes)
{
if (e)
closeWithError(0xFF);
}
void Client::closeWithError(int error)
{
switch (error) {
case 10054:
{
closeConnection("Read error: Connection reset by peer");
break;
}
case 10053:
{
closeConnection("Software caused connection abort");
break;
}
default:
{
closeConnection("Input/Output error");
break;
}
}
}
void Client::receiveFromSocket(const boost::system::error_code& e, std::size_t bytes)
{
if (e) {
closeWithError(10054);
return;
}
std::string str(m_buffers.data());
if (str.empty())
return;
size_t __wat = str.rfind("\r\n");
if (__wat == std::string::npos) {
__wat = str.rfind("\n");
if (__wat == std::string::npos) {
receive();
return;
}
}
str.erase(__wat);
/*size_t start = 0, end;
std::string sep = (str.rfind("\r\n") != std::string::npos ? "\r\n" : "\n");
while ((end = str.find(sep, start)) != std::string::npos) {
str.erase(end);
start = end + sep.size();
}*/
std::clog << "Received message from client: " << getIP() << " :" << str << std::endl;
StringVec splited = splitString(str, " ");
std::clog << "splited[0] = " << splited[0] << "." << std::endl;
if (splited[0] == "USER") {
if (splited.size() < 4) {
receive();
return;
}
std::string localUser = splited[4];
size_t sep = localUser.find(":");
if (sep == std::string::npos) {
receive();
return;
}
localUser.erase(sep);
if (!m_user) {
if (localUser.length() > 15)
m_user = new User(localUser.substr(0, 15), this);
else
m_user = new User(localUser, this);
} else {
if (localUser.length() > 15)
m_user->setName(localUser.substr(0, 15));
else
m_user->setName(localUser);
}
if (!sentUser)
sentUser = true;
} else if (splited[0] == "NICK") {
if (splited.size() < 2) {
receive();
return;
}
bool canUse = true;
for (uint32_t i = 0; i < sizeof(forbiddenNames) / sizeof(const char*); ++i) {
if (toLower(splited[1]) == toLower(std::string(&forbiddenNames[i]))) {
canUse = false;
sendLine(":%s 433 * %s :Nickname is already in use.\r\n", g_config->getString(Config::C_HOST).c_str(),
splited[1].c_str());
break;
}
}
if (!canUse) {
receive();
return;
}
if (!m_user) {
if (splited.size() > 15)
m_user = new User(splited[1].substr(0, 15), this);
else
m_user = new User(splited[1], this);
}
if (splited.size() > 15)
m_user->setNick(splited[1].substr(0, 15));
else
m_user->setNick(splited[1]);
if (!sentCommon) {
m_user->sendMotd(":%s 001 %s :Welcome to %s IRC network, %s\r\n", m_user->getServer().c_str(),
m_user->getName().c_str(), g_config->getString(0x07).c_str(), m_user->getNick().c_str());
m_user->sendMotd(":%s 002 %s :Your host is %s running version %s\r\n", m_user->getServer().c_str(),
m_user->getName().c_str(), m_user->getHost().c_str(), __DISTURBTION__);
m_user->sendMotd(":%s 003 %s :This server was created %s %s\r\n", m_user->getServer().c_str(),
m_user->getName().c_str(), __TIME__, __DATE__);
m_user->sendMotd(":%s 252 %s %d :operator(s) online\r\n", m_user->getServer().c_str(),
m_user->getName().c_str(), m_user->operCount);
m_user->sendMotd(":%s NOTICE %s :Highest connection count: %d (%d clients)\r\n", m_user->getServer().c_str(),
m_user->getName().c_str(), m_user->userCount, Client::clientCount);
m_user->sendMotd(":%s 221 %s +i\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str());
m_user->setMode('i');
m_user->sendMotd(":%s!%s@%s MODE %s +i\r\n", m_user->getServer().c_str(),
m_user->getIdent().c_str(), m_user->getHost().c_str(),
m_user->getNick().c_str());
sentCommon = true;
}
if (!m_user->getName().empty() && !m_user->getNick().empty())
g_mainFrame->addUser(m_user);
} else {
if (!sentUser || !m_user && !closed) {
eventId = g_dispatcher->addEvent(new Task(120, boost::bind(&Client::closeConnection, this, "Registration timeout")));
receive();
closed = true;
} else {
if (pongEvent == 0)
pongEvent = g_dispatcher->addEvent(new Task(0, boost::bind(&PingPong::sendPing, pingPong)));
if (pingPong->gotReply()) {
g_dispatcher->stopEvent(eventId);
handleRequest(splited);
}
}
}
}
void Client::disconnectClient()
{
g_script->removeClient(this);
std::clog << "Client disconnected: " << getIP() << std::endl;
clientCount--;
if (m_user)
m_user->clearChannels();
if (m_user) {
delete m_user;
m_user = NULL;
}
}
void Client::closeConnection(const std::string& reason)
{
std::clog << "Closing connection: " << getIP() << " reason: " << reason << "...";
g_script->removeClient(this);
if (m_user) {
m_user->quit(reason);
delete m_user;
m_user = NULL;
}
boost::system::error_code ignored_ec;
m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
m_socket.close();
std::clog << "[OK]" << std::endl;
}
void Client::handleRequest(StringVec __msg)
{
StringVec splited = __msg;
/* change mode */
if (splited[0] == "MODE") {
if (splited.size() < 2) //requires 2 params
return;
if (splited[1] != m_user->getNick()) { //channel mode
if (splited[1].substr(0, 1) == "#") {
Channel* chan = m_user->getChannelByName(splited[1]);
if (!chan) {
m_user->sendLine(":%s 442 %s %s :You're not on that channel\r\n", m_user->getServer().c_str(),
m_user->getName().c_str(), splited[1].c_str());
return;
} else {
if (chan->getUserMode(m_user, UM_OP)) {
for (_UsersMap::iterator it = chan->getUsers()->begin(); it != chan->getUsers()->end(); ++it)
it->second->sendLine(":%s!%s@%s MODE %s %s\r\n", m_user->getNick().c_str(),
m_user->getIdent().c_str(), m_user->getHost().c_str(), splited[1].c_str(),
splited[2].c_str());
} else {
m_user->sendLine(":%s 482 %s %s :You're not channel operator\r\n", m_user->getServer().c_str(),
m_user->getName().c_str(), splited[1].c_str());
return;
}
}
}
} else { //user mode
if (m_user->hasMode(*const_cast(splited[2].c_str()) - 65))
return;
m_user->setMode(*const_cast(splited[2].c_str()));
m_user->sendLine(":%s!%s@%s MODE %s %s\r\n", m_user->getNick().c_str(),
m_user->getIdent().c_str(), m_user->getHost().c_str(), m_user->getNick().c_str(),
splited[2].c_str());
}
/* join a channel */
} else if (splited[0] == "JOIN") {
if (splited.size() < 1)
return;
Channel* p = NULL;
if ((p = m_user->getChannelByName(splited[1])))
return;
if (!(p = g_channels->getChannel(splited[1]))) { //create new
Channel* chan = new Channel(splited[1]);
m_user->sendLine(":%s!%s@%s JOIN %s\r\n", m_user->getName().c_str(),
m_user->getIdent().c_str(), m_user->getHost().c_str(), splited[1].c_str());
m_user->sendLine(":%s 353 %s = %s @%s\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str(), chan->getName().c_str(), m_user->getNick().c_str());
m_user->sendLine(":%s 366 %s %s :End of /NAMES list.\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str(), chan->getName().c_str());
g_channels->addChannel(chan);
m_user->joinChannel(splited[1]);
} else { //already exists
Ban *b = p->getBan(m_user);
if (!B) { //b& or not
if (!p->addUser(m_user))
return;
std::string users = "";
_UsersMap *__u = p->getUsers();
for (_UsersMap::const_iterator it = __u->begin(); it != __u->end(); ++it) {
if (!it->second)
continue;
if (p->getUserMode(it->second, UM_OP))
users += "@" + it->second->getNick() + " ";
else if (p->getUserMode(it->second, UM_VOICE))
users += "+" + it->second->getNick() + " ";
else
users += it->second->getNick() + " ";
}
m_user->sendLine(":%s!%s@%s JOIN %s\r\n", m_user->getName().c_str(),
m_user->getIdent().c_str(), m_user->getHost().c_str(), splited[1].c_str());
m_user->sendLine(":%s 353 %s = %s %s\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str(), p->getName().c_str(), users.c_str());
m_user->sendLine(":%s 366 %s %s :End of /NAMES list.\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str(), p->getName().c_str());
m_user->joinChannel(splited[1]);
} else {
m_user->sendLine(":%s 474 %s %s :Cannot join channel, you are banned (+B)\r\n",
m_user->getServer().c_str(), m_user->getNick().c_str(), splited[1].c_str());
return;
}
}
/* part a channel */
} else if (splited[0] == "PART") {
//TODO
/* become an operator */
} else if (splited[0] == "OPER") {
if (splited.size() > 1) {
if (!m_user->checkOperPassword(splited[1], splited[2])) {
m_user->sendLine(":%s 491 %s :No Operator block for your host\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str());
return;
} else {
m_user->sendLine(":%s 381 %s :You are now IRC Operator\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str());
}
}
/* who is someone */
} else if (splited[0] == "WHOIS") {
//TODO
/* private message channel/someone */
} else if (splited[0] == "PRIVMSG") {
if (splited[1].substr(0, 1) == "#") {
//private message a channel
//lets do some checks on b&
//or +m
Channel* p = g_channels->getChannel(splited[1]);
if (!p) {
m_user->sendLine(":%s 403 %s %s :No such channel.\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str(), splited[1].c_str());
return;
} else if ((p = m_user->getChannelByName(splited[1]))) {
if (p->getMode(CM_MODERATED)) {
if (p->getUserMode(m_user, UM_NONE)) {
m_user->sendLine(":%s 404 %s %s :Cannot send to channel\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str(), splited[1].c_str());
return;
} else {
std::string msg = "";
for (int32_t i = 2; i < static_cast(splited.size()); ++i)
msg += splited[i] + " ";
_UsersMap *__u = p->getUsers();
for (_UsersMap::const_iterator it = __u->begin(); it != __u->end(); ++it) {
if (!it->second)
continue;
it->second->sendPrivateMessage(m_user->getNick(), m_user->getIdent(),
m_user->getHost(), splited[1], msg);
}
}
} else {
std::string msg = "";
for (int32_t i = 2; i < static_cast(splited.size()); ++i)
msg += splited[i] + " ";
_UsersMap *__u = p->getUsers();
for (_UsersMap::const_iterator it = __u->begin(); it != __u->end(); ++it) {
if (!it->second)
continue;
it->second->sendPrivateMessage(m_user->getNick(), m_user->getIdent(),
m_user->getHost(), splited[1], msg);
}
}
}
} else {
// private message someone
// maybe hes using modes +R
User* user = g_mainFrame->getUserByName(splited[1]);
if (!user) {
m_user->sendLine(":%s 401 %s %s :No such nick\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str(), splited[1].c_str());
return;
} else {
if (splited.size() < 2) {
m_user->sendLine(":%s 412 %s :No text to send\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str());
return;
}
if (!user->hasMode('R')) {
std::string msg = "";
for (int i = 2; i < static_cast(splited.size()); ++i)
msg += splited[i] + " ";
user->sendPrivateMessage(m_user->getNick(), m_user->getIdent(),
m_user->getHost(), user->getNick(), msg);
}
}
}
/* restart server */
} else if (splited[0] == "RESTART") {
if (m_user->getType() != UT_IRC_OP) {
m_user->sendLine(":%s 481 %s :Premission Denied: Insufficient privileges\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str());
return;
}
std::string exec_name = g_config->getString(Config::C_EXECUTABLE_NAME);
std::string cmd;
#ifdef _WIN32
cmd = exec_name + ".exe";
#else
cmd = "./" + exec_name;
#endif
std::system(cmd.c_str());
std::exit(1);
/* quit */
} else if (splited[0] == "QUIT") {
if (splited.size() > 0)
closeConnection(splited[1]);
else
closeConnection("");
/* user host */
} else if (splited[0] == "USERHOST") {
if (splited.size() < 1)
return;
User* target = g_mainFrame->getUserByName(splited[1]);
if (!target)
return;
m_user->sendLine(":%s 302 %s :%s=+%s@%s\r\n", m_user->getServer().c_str(),
m_user->getNick().c_str(), target->getName().c_str(),
target->getIdent().c_str(), target->getHost().c_str());
/* reload config */
} else if (splited[0] == "RELOAD") {
if (m_user->getType() != UT_IRC_OP)
return;
g_config->reload();
g_script->reload();
/*if (g_config->reload() && g_script->reload())
m_user->sendPrivateMessage(Me[0].nick, Me[0].ident, Me[0].host, Me[0].nick,
"Reloaded everything.");
else
m_user->sendPrivateMessage(Me[0].nick, Me[0].ident, Me[0].host, Me[0].nick,
"Unable to reload anything.");*/
}
}
void Client::close()
{
g_script->removeClient(this);
clientCount--;
delete m_user;
m_user = NULL;
boost::system::error_code ignored_ec;
m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
m_socket.close();
}