/* * 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 #include #include #include 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(&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(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(&e); if(!c) { return; } auto i = find_if(logins.begin(), logins.end(), CompareFirst (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); } }