648 lines
17 KiB
C++
648 lines
17 KiB
C++
/*
|
|
* Copyright (C) 2006-2016 Jacek Sieka, arnetheduck on gmail point com
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include "adchpp.h"
|
|
|
|
#include "ClientManager.h"
|
|
|
|
#include "File.h"
|
|
#include "Client.h"
|
|
#include "LogManager.h"
|
|
#include "SocketManager.h"
|
|
#include "TigerHash.h"
|
|
#include "Encoder.h"
|
|
#include "version.h"
|
|
|
|
#include <boost/asio/ip/address.hpp>
|
|
#include <boost/asio/ip/address_v4.hpp>
|
|
#include <boost/asio/ip/address_v6.hpp>
|
|
#include <boost/locale.hpp>
|
|
|
|
namespace adchpp {
|
|
|
|
using namespace std;
|
|
|
|
const string ClientManager::className = "ClientManager";
|
|
|
|
ClientManager::ClientManager(Core &core) throw() :
|
|
core(core),
|
|
hub(*this),
|
|
maxCommandSize(16 * 1024),
|
|
logTimeout(30 * 1000)
|
|
{
|
|
hub.addSupports(AdcCommand::toFourCC("BASE"));
|
|
hub.addSupports(AdcCommand::toFourCC("TIGR"));
|
|
}
|
|
|
|
Bot* ClientManager::createBot(const Bot::SendHandler& handler) {
|
|
Bot* ret = new Bot(*this, makeSID(), handler);
|
|
return ret;
|
|
}
|
|
|
|
void ClientManager::regBot(Bot& bot) {
|
|
enterIdentify(bot, false);
|
|
enterNormal(bot, false, true);
|
|
cids.insert(make_pair(bot.getCID(), &bot));
|
|
nicks.insert(make_pair(bot.getField("NI"), &bot));
|
|
}
|
|
|
|
void ClientManager::send(const AdcCommand& cmd) throw() {
|
|
if(cmd.getPriority() == AdcCommand::PRIORITY_IGNORE) {
|
|
return;
|
|
}
|
|
|
|
bool all = false;
|
|
switch(cmd.getType()) {
|
|
case AdcCommand::TYPE_BROADCAST:
|
|
all = true; // Fallthrough
|
|
case AdcCommand::TYPE_FEATURE: {
|
|
for(EntityIter i = entities.begin(); i != entities.end(); ++i) {
|
|
if(all || !i->second->isFiltered(cmd.getFeatures())) {
|
|
maybeSend(*i->second, cmd);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case AdcCommand::TYPE_DIRECT: // Fallthrough
|
|
case AdcCommand::TYPE_ECHO: {
|
|
Entity* e = getEntity(cmd.getTo());
|
|
if(e) {
|
|
maybeSend(*e, cmd);
|
|
|
|
if(cmd.getType() == AdcCommand::TYPE_ECHO) {
|
|
e = getEntity(cmd.getFrom());
|
|
if(e) {
|
|
maybeSend(*e, cmd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ClientManager::maybeSend(Entity& c, const AdcCommand& cmd) {
|
|
bool ok = true;
|
|
signalSend_(c, cmd, ok);
|
|
if(ok) {
|
|
c.send(cmd);
|
|
}
|
|
}
|
|
|
|
void ClientManager::sendToAll(const BufferPtr& buf) throw() {
|
|
for(EntityIter i = entities.begin(); i != entities.end(); ++i) {
|
|
i->second->send(buf);
|
|
}
|
|
}
|
|
|
|
size_t ClientManager::getQueuedBytes() throw() {
|
|
size_t total = 0;
|
|
|
|
for(EntityIter i = entities.begin(); i != entities.end(); ++i) {
|
|
total += i->second->getQueuedBytes();
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
void ClientManager::sendTo(const BufferPtr& buffer, uint32_t to) {
|
|
EntityIter i = entities.find(to);
|
|
if(i != entities.end()) {
|
|
i->second->send(buffer);
|
|
}
|
|
}
|
|
|
|
void ClientManager::handleIncoming(const ManagedSocketPtr& socket) throw() {
|
|
Client::create(*this, socket, makeSID());
|
|
}
|
|
|
|
uint32_t ClientManager::makeSID() {
|
|
while(true) {
|
|
union {
|
|
uint32_t sid;
|
|
char chars[4];
|
|
} sid;
|
|
sid.chars[0] = Encoder::base32Alphabet[Util::rand(sizeof(Encoder::base32Alphabet))];
|
|
sid.chars[1] = Encoder::base32Alphabet[Util::rand(sizeof(Encoder::base32Alphabet))];
|
|
sid.chars[2] = Encoder::base32Alphabet[Util::rand(sizeof(Encoder::base32Alphabet))];
|
|
sid.chars[3] = Encoder::base32Alphabet[Util::rand(sizeof(Encoder::base32Alphabet))];
|
|
if(sid.sid != 0 && entities.find(sid.sid) == entities.end()) {
|
|
return sid.sid;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClientManager::onConnected(Client& c) throw() {
|
|
dcdebug("%s connected\n", AdcCommand::fromSID(c.getSID()).c_str());
|
|
// First let's check if any clients have passed the login timeout...
|
|
auto timeout = time::now() - time::millisec(getLogTimeout());
|
|
|
|
while(!logins.empty() && (timeout > logins.front().second)) {
|
|
Client* cc = logins.front().first;
|
|
|
|
dcdebug("ClientManager: Login timeout in state %d\n", cc->getState());
|
|
cc->disconnect(Util::REASON_LOGIN_TIMEOUT);
|
|
logins.pop_front();
|
|
}
|
|
|
|
logins.push_back(make_pair(&c, time::now()));
|
|
|
|
signalConnected_(c);
|
|
}
|
|
|
|
void ClientManager::onReady(Client& c) throw() {
|
|
dcdebug("%s ready\n", AdcCommand::fromSID(c.getSID()).c_str());
|
|
signalReady_(c);
|
|
}
|
|
|
|
void ClientManager::onReceive(Entity& c, AdcCommand& cmd) throw() {
|
|
if(c.isSet(Entity::FLAG_GHOST)) {
|
|
return;
|
|
}
|
|
|
|
if(!(cmd.getType() == AdcCommand::TYPE_BROADCAST || cmd.getType() == AdcCommand::TYPE_DIRECT || cmd.getType()
|
|
== AdcCommand::TYPE_ECHO || cmd.getType() == AdcCommand::TYPE_FEATURE || cmd.getType() == AdcCommand::TYPE_HUB))
|
|
{
|
|
disconnect(c, Util::REASON_INVALID_COMMAND_TYPE, "Invalid command type");
|
|
return;
|
|
}
|
|
|
|
bool ok = true;
|
|
signalReceive_(c, cmd, ok);
|
|
|
|
if(ok) {
|
|
if(!dispatch(c, cmd)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
send(cmd);
|
|
}
|
|
|
|
void ClientManager::onBadLine(Client& c, const string& aLine) throw() {
|
|
if(c.isSet(Entity::FLAG_GHOST)) {
|
|
return;
|
|
}
|
|
|
|
signalBadLine_(c, aLine);
|
|
}
|
|
|
|
void ClientManager::badState(Entity& c, const AdcCommand& cmd) throw() {
|
|
disconnect(c, Util::REASON_BAD_STATE, "Invalid state for command", AdcCommand::ERROR_BAD_STATE, "FC" + cmd.getFourCC());
|
|
}
|
|
|
|
bool ClientManager::handleDefault(Entity& c, AdcCommand& cmd) throw() {
|
|
if(c.getState() != Entity::STATE_NORMAL) {
|
|
badState(c, cmd);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ClientManager::handle(AdcCommand::SUP, Entity& c, AdcCommand& cmd) throw() {
|
|
if(!verifySUP(c, cmd)) {
|
|
return false;
|
|
}
|
|
|
|
if(c.getState() == Entity::STATE_PROTOCOL) {
|
|
enterIdentify(c, true);
|
|
} else if(c.getState() != Entity::STATE_NORMAL) {
|
|
badState(c, cmd);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ClientManager::verifySUP(Entity& c, AdcCommand& cmd) throw() {
|
|
c.updateSupports(cmd);
|
|
|
|
if(!c.hasSupport(AdcCommand::toFourCC("BASE"))) {
|
|
disconnect(c, Util::REASON_NO_BASE_SUPPORT, "This hub requires BASE support");
|
|
return false;
|
|
}
|
|
|
|
if(!c.hasSupport(AdcCommand::toFourCC("TIGR"))) {
|
|
disconnect(c, Util::REASON_NO_TIGR_SUPPORT, "This hub requires TIGR support");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ClientManager::verifyINF(Entity& c, AdcCommand& cmd) throw() {
|
|
Client* cc = dynamic_cast<Client*>(&c);
|
|
|
|
if(cc) {
|
|
if(!verifyIp(*cc, cmd))
|
|
return false;
|
|
}
|
|
|
|
if(!verifyCID(c, cmd))
|
|
return false;
|
|
|
|
if(!verifyNick(c, cmd))
|
|
return false;
|
|
|
|
if(cmd.getParam("DE", 0, strtmp)) {
|
|
if(!Util::validateCharset(strtmp, 32)) {
|
|
disconnect(c, Util::REASON_INVALID_DESCRIPTION, "Invalid character in description");
|
|
return false;
|
|
}
|
|
}
|
|
c.updateFields(cmd);
|
|
return true;
|
|
}
|
|
|
|
bool ClientManager::verifyPassword(Entity& c, const string& password, const ByteVector& salt,
|
|
const string& suppliedHash, TigerHash&& tiger) {
|
|
tiger.update(&password[0], password.size());
|
|
tiger.update(&salt[0], salt.size());
|
|
uint8_t tmp[TigerHash::BYTES];
|
|
Encoder::fromBase32(suppliedHash.c_str(), tmp, TigerHash::BYTES);
|
|
if(memcmp(tiger.finalize(), tmp, TigerHash::BYTES) == 0) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ClientManager::verifyPassword(Entity& c, const string& password, const ByteVector& salt,
|
|
const string& suppliedHash) {
|
|
return verifyPassword(c, password, salt, suppliedHash, TigerHash());
|
|
}
|
|
|
|
bool ClientManager::verifyHashedPassword(Entity& c, const ByteVector& hashedPassword, int64_t hashedPasswordLen,
|
|
const ByteVector& salt, const string& suppliedHash) {
|
|
// hashedPassword must be in little-endian order; this code itself is endian-independent.
|
|
uint64_t initial_res[TigerHash::BYTES/8];
|
|
for (auto i = 0; i < 3; ++i) {
|
|
initial_res[i] = 0;
|
|
for (auto j = 0; j < 8; ++j)
|
|
initial_res[i] = initial_res[i] * 256 + hashedPassword[8*i+(7-j)];
|
|
}
|
|
return verifyPassword(c, "", salt, suppliedHash, TigerHash(hashedPasswordLen, initial_res));
|
|
}
|
|
|
|
bool ClientManager::verifyOverflow(Entity& c) {
|
|
size_t overflowing = 0;
|
|
for(EntityIter i = entities.begin(), iend = entities.end(); i != iend; ++i) {
|
|
if(!i->second->getOverflow().is_not_a_date_time()) {
|
|
overflowing++;
|
|
}
|
|
}
|
|
|
|
if(overflowing > 3 && overflowing > (entities.size() / 4)) {
|
|
disconnect(c, Util::REASON_NO_BANDWIDTH, "Not enough bandwidth available, please try again later", AdcCommand::ERROR_HUB_FULL);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ClientManager::handle(AdcCommand::INF, Entity& c, AdcCommand& cmd) throw() {
|
|
if(c.getState() != Entity::STATE_IDENTIFY && c.getState() != Entity::STATE_NORMAL) {
|
|
badState(c, cmd);
|
|
return false;
|
|
}
|
|
|
|
if(!verifyINF(c, cmd))
|
|
return false;
|
|
|
|
if(c.getState() == Entity::STATE_IDENTIFY) {
|
|
if(!verifyOverflow(c)) {
|
|
return false;
|
|
}
|
|
|
|
enterNormal(c, true, true);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ClientManager::verifyIp(Client& c, AdcCommand& cmd) throw() {
|
|
if(c.isSet(Entity::FLAG_OK_IP))
|
|
return true;
|
|
|
|
using namespace boost::asio::ip;
|
|
|
|
auto remote = address::from_string(c.getIp());
|
|
std::string ip;
|
|
|
|
if(remote.is_v4() || (remote.is_v6() && remote.to_v6().is_v4_mapped())) {
|
|
auto v4 = remote.is_v4() ? remote.to_v4() : remote.to_v6().to_v4();
|
|
|
|
if(cmd.getParam("I4", 0, ip)) {
|
|
dcdebug("%s verifying IP %s\n", AdcCommand::fromSID(c.getSID()).c_str(), ip.c_str());
|
|
if(ip.empty() || address_v4::from_string(ip) == address_v4::any()) {
|
|
cmd.delParam("I4", 0);
|
|
} else if(address_v4::from_string(ip) != v4 && !Util::isPrivateIp(c.getIp())) {
|
|
disconnect(c, Util::REASON_INVALID_IP, "Your IP is " + c.getIp() +
|
|
", reconfigure your client settings", AdcCommand::ERROR_BAD_IP, "IP" + c.getIp());
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(!c.hasField("I4")) {
|
|
c.setField("I4", v4.to_string());
|
|
}
|
|
|
|
if(c.getState() != Entity::STATE_NORMAL) {
|
|
cmd.addParam("I4", v4.to_string());
|
|
}
|
|
|
|
cmd.delParam("I6", 0); // We can't check this so we remove it instead...fix?
|
|
} else if(remote.is_v6()) {
|
|
if(cmd.getParam("I6", 0, ip)) {
|
|
dcdebug("%s verifying IPv6 %s\n", AdcCommand::fromSID(c.getSID()).c_str(), ip.c_str());
|
|
if(ip.empty() || address_v6::from_string(ip) == address_v6::any()) {
|
|
cmd.delParam("I6", 0);
|
|
} else if(address_v6::from_string(ip) != remote.to_v6() && !Util::isPrivateIp(c.getIp())) {
|
|
disconnect(c, Util::REASON_INVALID_IP, "Your IP is " + c.getIp() +
|
|
", reconfigure your client settings", AdcCommand::ERROR_BAD_IP, "IP" + c.getIp());
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if(!c.hasField("I6")) {
|
|
c.setField("I6", c.getIp());
|
|
}
|
|
|
|
if(c.getState() != Entity::STATE_NORMAL) {
|
|
cmd.addParam("I6", c.getIp());
|
|
}
|
|
|
|
cmd.delParam("I4", 0); // We can't check this so we remove it instead...fix?
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ClientManager::verifyCID(Entity& c, AdcCommand& cmd) throw() {
|
|
if(cmd.getParam("ID", 0, strtmp)) {
|
|
dcdebug("%s verifying CID %s\n", AdcCommand::fromSID(c.getSID()).c_str(), strtmp.c_str());
|
|
if(c.getState() != Entity::STATE_IDENTIFY) {
|
|
disconnect(c, Util::REASON_CID_CHANGE, "CID changes not allowed");
|
|
return false;
|
|
}
|
|
|
|
if(strtmp.size() != CID::BASE32_SIZE) {
|
|
disconnect(c, Util::REASON_PID_CID_LENGTH, "Invalid CID length");
|
|
return false;
|
|
}
|
|
|
|
CID cid(strtmp);
|
|
|
|
strtmp.clear();
|
|
|
|
if(!cmd.getParam("PD", 0, strtmp)) {
|
|
disconnect(c, Util::REASON_PID_MISSING, "PID missing", AdcCommand::ERROR_INF_MISSING, "FLPD");
|
|
return false;
|
|
}
|
|
|
|
if(strtmp.size() != CID::BASE32_SIZE) {
|
|
disconnect(c, Util::REASON_PID_CID_LENGTH, "Invalid PID length");
|
|
return false;
|
|
}
|
|
|
|
CID pid(strtmp);
|
|
|
|
TigerHash th;
|
|
th.update(pid.data(), CID::SIZE);
|
|
if(!(CID(th.finalize()) == cid)) {
|
|
disconnect(c, Util::REASON_PID_CID_MISMATCH, "PID does not correspond to CID", AdcCommand::ERROR_INVALID_PID);
|
|
return false;
|
|
}
|
|
|
|
auto other = cids.find(cid);
|
|
if(other != cids.end()) {
|
|
// disconnect the ghost
|
|
disconnect(*other->second, Util::REASON_CID_TAKEN, "CID taken", AdcCommand::ERROR_CID_TAKEN);
|
|
removeEntity(*other->second, Util::REASON_CID_TAKEN, Util::emptyString);
|
|
}
|
|
|
|
c.setCID(cid);
|
|
|
|
cids.insert(make_pair(c.getCID(), &c));
|
|
cmd.delParam("PD", 0);
|
|
}
|
|
|
|
if(cmd.getParam("PD", 0, strtmp)) {
|
|
disconnect(c, Util::REASON_PID_WITHOUT_CID, "CID required when sending PID");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
bool validateNickF(wchar_t c) { /// @todo lambda
|
|
|
|
// the following are explicitly allowed (isprint sometimes differs)
|
|
if(c >= L'\u2100' && c <= L'\u214F' /* letter-like symbols */) {
|
|
return false;
|
|
}
|
|
|
|
// the following are explicitly disallowed (isprint sometimes differs)
|
|
if(c == L'\u00AD' /* soft hyphen */) {
|
|
return true;
|
|
}
|
|
|
|
static std::locale loc = boost::locale::generator().generate("");
|
|
return !std::isprint(c, loc);
|
|
}
|
|
|
|
bool validateNick(const string& nick) {
|
|
if(!Util::validateCharset(nick, 33)) { // chars < 33 forbidden (including the space char)
|
|
return false;
|
|
}
|
|
|
|
// avoid impersonators
|
|
std::wstring nickW = boost::locale::conv::utf_to_utf<wchar_t>(nick);
|
|
if(std::find_if(nickW.begin(), nickW.end(), validateNickF) != nickW.end()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool ClientManager::verifyNick(Entity& c, const AdcCommand& cmd) throw() {
|
|
if(cmd.getParam("NI", 0, strtmp)) {
|
|
dcdebug("%s verifying nick %s\n", AdcCommand::fromSID(c.getSID()).c_str(), strtmp.c_str());
|
|
|
|
if(!validateNick(strtmp)) {
|
|
disconnect(c, Util::REASON_NICK_INVALID, "Invalid character in nick", AdcCommand::ERROR_NICK_INVALID);
|
|
return false;
|
|
}
|
|
|
|
const string& oldNick = c.getField("NI");
|
|
if(!oldNick.empty())
|
|
nicks.erase(oldNick);
|
|
|
|
if(nicks.find(strtmp) != nicks.end()) {
|
|
disconnect(c, Util::REASON_NICK_TAKEN, "Nick taken, please pick another one", AdcCommand::ERROR_NICK_TAKEN);
|
|
return false;
|
|
}
|
|
|
|
nicks.insert(make_pair(strtmp, &c));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ClientManager::setState(Entity& c, Entity::State newState) throw() {
|
|
Entity::State oldState = c.getState();
|
|
c.setState(newState);
|
|
signalState_(c, oldState);
|
|
}
|
|
|
|
void ClientManager::disconnect(Entity& c, Util::Reason reason, const std::string& info, AdcCommand::Error error, const std::string& staParam) {
|
|
// send a fatal STA
|
|
AdcCommand sta(AdcCommand::SEV_FATAL, error, info);
|
|
if(!staParam.empty())
|
|
sta.addParam(staParam);
|
|
c.send(sta);
|
|
|
|
// send a QUI
|
|
c.send(AdcCommand(AdcCommand::CMD_QUI).addParam(AdcCommand::fromSID(c.getSID()))
|
|
.addParam("DI", "1").addParam("MS", info).addParam("TL", "-1"));
|
|
|
|
c.disconnect(reason);
|
|
}
|
|
|
|
void ClientManager::enterIdentify(Entity& c, bool sendData) throw() {
|
|
dcassert(c.getState() == Entity::STATE_PROTOCOL);
|
|
dcdebug("%s entering IDENTIFY\n", AdcCommand::fromSID(c.getSID()).c_str());
|
|
if(sendData) {
|
|
c.send(hub.getSUP());
|
|
c.send(AdcCommand(AdcCommand::CMD_SID).addParam(AdcCommand::fromSID(c.getSID())));
|
|
c.send(hub.getINF());
|
|
}
|
|
|
|
setState(c, Entity::STATE_IDENTIFY);
|
|
}
|
|
|
|
ByteVector ClientManager::enterVerify(Entity& c, bool sendData) throw() {
|
|
dcassert(c.getState() == Entity::STATE_IDENTIFY);
|
|
dcdebug("%s entering VERIFY\n", AdcCommand::fromSID(c.getSID()).c_str());
|
|
|
|
ByteVector challenge;
|
|
challenge.reserve(32);
|
|
for(int i = 0; i < 32 / 4; ++i) {
|
|
uint32_t r = Util::rand();
|
|
challenge.insert(challenge.end(), (uint8_t*) &r, 4 + (uint8_t*) &r);
|
|
}
|
|
|
|
if(sendData) {
|
|
c.send(AdcCommand(AdcCommand::CMD_GPA).addParam(Encoder::toBase32(&challenge[0], challenge.size())));
|
|
}
|
|
|
|
setState(c, Entity::STATE_VERIFY);
|
|
return challenge;
|
|
}
|
|
|
|
bool ClientManager::enterNormal(Entity& c, bool sendData, bool sendOwnInf) throw() {
|
|
dcassert(c.getState() == Entity::STATE_IDENTIFY || c.getState() == Entity::STATE_VERIFY);
|
|
dcdebug("%s entering NORMAL\n", AdcCommand::fromSID(c.getSID()).c_str());
|
|
|
|
if(sendData) {
|
|
for(EntityIter i = entities.begin(); i != entities.end(); ++i) {
|
|
c.send(i->second->getINF());
|
|
}
|
|
}
|
|
|
|
if(sendOwnInf) {
|
|
sendToAll(c.getINF());
|
|
if(sendData) {
|
|
c.send(c.getINF());
|
|
}
|
|
}
|
|
|
|
removeLogins(c);
|
|
|
|
entities.insert(make_pair(c.getSID(), &c));
|
|
|
|
setState(c, Entity::STATE_NORMAL);
|
|
return true;
|
|
}
|
|
|
|
void ClientManager::removeLogins(Entity& e) throw() {
|
|
Client* c = dynamic_cast<Client*>(&e);
|
|
if(!c) {
|
|
return;
|
|
}
|
|
|
|
auto i = find_if(logins.begin(), logins.end(), CompareFirst<Client*, time::ptime> (c));
|
|
if(i != logins.end()) {
|
|
logins.erase(i);
|
|
}
|
|
}
|
|
|
|
void ClientManager::removeEntity(Entity& c, Util::Reason reason, const std::string &info) throw() {
|
|
if(c.isSet(Entity::FLAG_GHOST))
|
|
return;
|
|
|
|
dcdebug("Removing %s - %s\n", AdcCommand::fromSID(c.getSID()).c_str(), c.getCID().toBase32().c_str());
|
|
c.setFlag(Entity::FLAG_GHOST);
|
|
|
|
signalDisconnected_(c, reason, info);
|
|
|
|
if(c.getState() == Entity::STATE_NORMAL) {
|
|
entities.erase(c.getSID());
|
|
sendToAll(AdcCommand(AdcCommand::CMD_QUI).addParam(AdcCommand::fromSID(c.getSID())).addParam("DI", "1").getBuffer());
|
|
} else {
|
|
removeLogins(c);
|
|
}
|
|
|
|
nicks.erase(c.getField("NI"));
|
|
cids.erase(c.getCID());
|
|
}
|
|
|
|
Entity* ClientManager::getEntity(uint32_t aSid) throw() {
|
|
switch(aSid) {
|
|
case AdcCommand::INVALID_SID: return nullptr;
|
|
case AdcCommand::HUB_SID: return &hub;
|
|
default:
|
|
{
|
|
EntityIter i = entities.find(aSid);
|
|
return (i == entities.end()) ? nullptr : i->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t ClientManager::getSID(const string& aNick) const throw() {
|
|
NickMap::const_iterator i = nicks.find(aNick);
|
|
return (i == nicks.end()) ? AdcCommand::INVALID_SID : i->second->getSID();
|
|
}
|
|
|
|
uint32_t ClientManager::getSID(const CID& cid) const throw() {
|
|
CIDMap::const_iterator i = cids.find(cid);
|
|
return (i == cids.end()) ? AdcCommand::INVALID_SID : i->second->getSID();
|
|
}
|
|
|
|
void ClientManager::onFailed(Client& c, Util::Reason reason, const std::string &info) throw() {
|
|
removeEntity(c, reason, info);
|
|
}
|
|
|
|
}
|