/*
* Copyright (C) 2001-2024 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 3 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, see <https://www.gnu.org/licenses/>.
*/
#include"stdinc.h"
#include"AdcHub.h"
#include<boost/format.hpp>
#include<boost/scoped_array.hpp>
#include"AdcCommand.h"
#include"ChatMessage.h"
#include"ClientManager.h"
#include"ConnectionManager.h"
#include"ConnectivityManager.h"
#include"CryptoManager.h"
#include"format.h"
#include"LogManager.h"
#include"ShareManager.h"
#include"StringTokenizer.h"
#include"ThrottleManager.h"
#include"UploadManager.h"
#include"PluginManager.h"
#include"UserCommand.h"
#include"Util.h"
#include"version.h"
#include<cmath>
namespacedcpp{
usingstd::make_pair;
conststringAdcHub::CLIENT_PROTOCOL("ADC/1.0");
conststringAdcHub::SECURE_CLIENT_PROTOCOL("ADCS/0.10");
conststringAdcHub::ADCS_FEATURE("ADC0");
conststringAdcHub::TCP4_FEATURE("TCP4");
conststringAdcHub::TCP6_FEATURE("TCP6");
conststringAdcHub::UDP4_FEATURE("UDP4");
conststringAdcHub::UDP6_FEATURE("UDP6");
conststringAdcHub::NAT0_FEATURE("NAT0");
conststringAdcHub::SEGA_FEATURE("SEGA");
conststringAdcHub::CCPM_FEATURE("CCPM");
conststringAdcHub::SUDP_FEATURE("SUDP");
conststringAdcHub::BASE_SUPPORT("ADBASE");
conststringAdcHub::BAS0_SUPPORT("ADBAS0");
conststringAdcHub::TIGR_SUPPORT("ADTIGR");
conststringAdcHub::UCM0_SUPPORT("ADUCM0");
conststringAdcHub::BLO0_SUPPORT("ADBLO0");
conststringAdcHub::ZLIF_SUPPORT("ADZLIF");
constvector<StringList>AdcHub::searchExts;
AdcHub::AdcHub(conststring&aHubURL,boolsecure):
Client(aHubURL,'\n',secure),oldPassword(false),udp(Socket::TYPE_UDP),sid(0){
TimerManager::getInstance()->addListener(this);
}
AdcHub::~AdcHub(){
TimerManager::getInstance()->removeListener(this);
clearUsers();
}
OnlineUser&AdcHub::getUser(constuint32_taSID,constCID&aCID){
OnlineUser*ou=findUser(aSID);
if(ou){
return*ou;
}
UserPtrp=ClientManager::getInstance()->getUser(aCID);
{
Lockl(cs);
ou=users.emplace(aSID,newOnlineUser(p,*this,aSID)).first->second;
}
if(aSID!=AdcCommand::HUB_SID)
ClientManager::getInstance()->putOnline(ou);
return*ou;
}
OnlineUser*AdcHub::findUser(constuint32_taSID)const{
Lockl(cs);
autoi=users.find(aSID);
returni==users.end()?NULL:i->second;
}
OnlineUser*AdcHub::findUser(constCID&aCID)const{
Lockl(cs);
for(auto&i:users){
if(i.second->getUser()->getCID()==aCID){
returni.second;
}
}
return0;
}
voidAdcHub::putUser(constuint32_taSID,booldisconnect){
OnlineUser*ou=nullptr;
{
Lockl(cs);
autoi=users.find(aSID);
if(i==users.end())
return;
ou=i->second;
users.erase(i);
}
if(aSID!=AdcCommand::HUB_SID)
ClientManager::getInstance()->putOffline(ou,disconnect);
fire(ClientListener::UserRemoved(),this,*ou);
deleteou;
}
voidAdcHub::clearUsers(){
decltype(users)tmp;
{
Lockl(cs);
users.swap(tmp);
}
for(auto&i:tmp){
if(i.first!=AdcCommand::HUB_SID)
ClientManager::getInstance()->putOffline(i.second);
deletei.second;
}
}
voidAdcHub::handle(AdcCommand::INF,AdcCommand&c)noexcept{
if(c.getParameters().empty())
return;
stringcid;
OnlineUser*u=0;
if(c.getParam("ID",0,cid)){
u=findUser(CID(cid));
if(u){
if(u->getIdentity().getSID()!=c.getFrom()){
// Same CID but different SID not allowed - buggy hub?
stringnick;
if(!c.getParam("NI",0,nick)){
nick="[nick unknown]";
}
fire(ClientListener::StatusMessage(),this,str(F_("%1% (%2%) has same CID {%3%} as %4% (%5%), ignoring")
%u->getIdentity().getNick()%u->getIdentity().getSIDString()%cid%nick%AdcCommand::fromSID(c.getFrom())),
ClientListener::FLAG_IS_SPAM);
return;
}
}else{
u=&getUser(c.getFrom(),CID(cid));
}
}elseif(c.getFrom()==AdcCommand::HUB_SID){
u=&getUser(c.getFrom(),CID());
}else{
u=findUser(c.getFrom());
}
if(!u){
dcdebug("AdcHub::INF Unknown user / no ID\n");
return;
}
for(auto&i:c.getParameters()){
if(i.length()<2)
continue;
u->getIdentity().set(i.c_str(),i.substr(2));
}
if(u->getIdentity().supports(ADCS_FEATURE)){
u->getUser()->setFlag(User::TLS);
}
if(u->getUser()==getMyIdentity().getUser()){
state=STATE_NORMAL;
setAutoReconnect(true);
setMyIdentity(u->getIdentity());
updateCounts(false);
}
if(u->getIdentity().isHub()){
setHubIdentity(u->getIdentity());
fire(ClientListener::HubUpdated(),this);
}else{
updated(*u);
}
}
voidAdcHub::handle(AdcCommand::SUP,AdcCommand&c)noexcept{
if(state!=STATE_PROTOCOL)/** @todo SUP changes */
return;
boolbaseOk=false;
booltigrOk=false;
for(auto&i:c.getParameters()){
if(i==BAS0_SUPPORT){
baseOk=true;
tigrOk=true;
}elseif(i==BASE_SUPPORT){
baseOk=true;
}elseif(i==TIGR_SUPPORT){
tigrOk=true;
}
}
if(!baseOk){
fire(ClientListener::StatusMessage(),this,_("Failed to negotiate base protocol"));
disconnect(false);
return;
}elseif(!tigrOk){
oldPassword=true;
// Some hubs fake BASE support without TIGR support =/
fire(ClientListener::StatusMessage(),this,_("Hub probably uses an old version of ADC, please encourage the owner to upgrade"));
}
}
voidAdcHub::handle(AdcCommand::SID,AdcCommand&c)noexcept{
if(state!=STATE_PROTOCOL){
dcdebug("Invalid state for SID\n");
return;
}
if(c.getParameters().empty())
return;
sid=AdcCommand::toSID(c.getParam(0));
state=STATE_IDENTIFY;
infoImpl();
}
voidAdcHub::handle(AdcCommand::MSG,AdcCommand&c)noexcept{
if(c.getParameters().empty())
return;
autofrom=findUser(c.getFrom());
if(!from||from->getIdentity().noChat())
return;
decltype(from)to=nullptr,replyTo=nullptr;
stringtemp;
stringchatMessage=c.getParam(0);
if(c.getParam("PM",1,temp)){// add PM<group-cid> as well
to=findUser(c.getTo());
if(!to)
return;
replyTo=findUser(AdcCommand::toSID(temp));
if(!replyTo||PluginManager::getInstance()->runHook(HOOK_CHAT_PM_IN,replyTo,chatMessage))
return;
}elseif(PluginManager::getInstance()->runHook(HOOK_CHAT_IN,this,chatMessage))
return;
fire(ClientListener::Message(),this,ChatMessage(chatMessage,from,to,replyTo,c.hasFlag("ME",1),
c.getParam("TS",1,temp)?Util::toInt64(temp):0));
}
voidAdcHub::handle(AdcCommand::GPA,AdcCommand&c)noexcept{
if(c.getParameters().empty())
return;
salt=c.getParam(0);
state=STATE_VERIFY;
fire(ClientListener::GetPassword(),this);
}
voidAdcHub::handle(AdcCommand::QUI,AdcCommand&c)noexcept{
uint32_ts=AdcCommand::toSID(c.getParam(0));
OnlineUser*victim=findUser(s);
if(victim){
stringtmp;
if(c.getParam("MS",1,tmp)){
OnlineUser*source=0;
stringtmp2;
if(c.getParam("ID",1,tmp2)){
source=findUser(AdcCommand::toSID(tmp2));
}
if(source){
tmp=str(F_("%1% was kicked by %2%: %3%")%victim->getIdentity().getNick()%
source->getIdentity().getNick()%tmp);
}else{
tmp=str(F_("%1% was kicked: %2%")%victim->getIdentity().getNick()%tmp);
}
fire(ClientListener::StatusMessage(),this,tmp,ClientListener::FLAG_IS_SPAM);
}
putUser(s,c.getParam("DI",1,tmp));
}
if(s==sid){
// this QUI is directed to us
stringtmp;
if(c.getParam("TL",1,tmp)){
if(tmp=="-1"){
setAutoReconnect(false);
}else{
setAutoReconnect(true);
setReconnDelay(Util::toUInt32(tmp));
}
}
if(!victim&&c.getParam("MS",1,tmp)){
fire(ClientListener::StatusMessage(),this,tmp,ClientListener::FLAG_NORMAL);
}
if(c.getParam("RD",1,tmp)){
fire(ClientListener::Redirect(),this,tmp);
}
}
}
voidAdcHub::handle(AdcCommand::CTM,AdcCommand&c)noexcept{
if(c.getParameters().size()<3)
return;
OnlineUser*u=findUser(c.getFrom());
if(!u||u->getUser()==ClientManager::getInstance()->getMe())
return;
conststring&protocol=c.getParam(0);
conststring&port=c.getParam(1);
conststring&token=c.getParam(2);
boolsecure=false;
if(protocol==CLIENT_PROTOCOL&&!SETTING(REQUIRE_TLS)){
// Nothing special
}elseif(protocol==SECURE_CLIENT_PROTOCOL&&CryptoManager::getInstance()->TLSOk()){
secure=true;
}else{
unknownProtocol(c.getFrom(),protocol,token);
return;
}
if(!u->getIdentity().isTcpActive()){
send(AdcCommand(AdcCommand::SEV_FATAL,AdcCommand::ERROR_PROTOCOL_GENERIC,"IP unknown",AdcCommand::TYPE_DIRECT).setTo(c.getFrom()));
return;
}
ConnectionManager::getInstance()->adcConnect(*u,port,token,secure);
}
voidAdcHub::handle(AdcCommand::RCM,AdcCommand&c)noexcept{
if(c.getParameters().size()<2){
return;
}
OnlineUser*u=findUser(c.getFrom());
if(!u||u->getUser()==ClientManager::getInstance()->getMe())
return;
conststring&protocol=c.getParam(0);
conststring&token=c.getParam(1);
boolsecure;
if(protocol==CLIENT_PROTOCOL&&!SETTING(REQUIRE_TLS)){
secure=false;
}elseif(protocol==SECURE_CLIENT_PROTOCOL&&CryptoManager::getInstance()->TLSOk()){
secure=true;
}else{
unknownProtocol(c.getFrom(),protocol,token);
return;
}
if(ClientManager::getInstance()->isActive()){
connect(*u,token,CONNECTION_TYPE_LAST,secure);
return;
}
if(!u->getIdentity().supports(NAT0_FEATURE))
return;
// Attempt to traverse NATs and/or firewalls with TCP.
// If they respond with their own, symmetric, RNT command, both
// clients call ConnectionManager::adcConnect.
send(AdcCommand(AdcCommand::CMD_NAT,u->getIdentity().getSID(),AdcCommand::TYPE_DIRECT).
addParam(protocol).addParam(std::to_string(sock->getLocalPort())).addParam(token));
}
voidAdcHub::handle(AdcCommand::CMD,AdcCommand&c)noexcept{
if(c.getParameters().size()<1)
return;
conststring&name=c.getParam(0);
boolrem=c.hasFlag("RM",1);
if(rem){
fire(ClientListener::HubUserCommand(),this,(int)UserCommand::TYPE_REMOVE,0,name,Util::emptyString);
return;
}
boolsep=c.hasFlag("SP",1);
stringsctx;
if(!c.getParam("CT",1,sctx))
return;
intctx=Util::toInt(sctx);
if(ctx<=0)
return;
if(sep){
fire(ClientListener::HubUserCommand(),this,(int)UserCommand::TYPE_SEPARATOR,ctx,name,Util::emptyString);
return;
}
boolonce=c.hasFlag("CO",1);
stringtxt;
if(!c.getParam("TT",1,txt))
return;
fire(ClientListener::HubUserCommand(),this,(int)(once?UserCommand::TYPE_RAW_ONCE:UserCommand::TYPE_RAW),ctx,name,txt);
}
voidAdcHub::sendUDP(constAdcCommand&cmd)noexcept{
stringcommand;
stringip;
stringport;
{
Lockl(cs);
autoi=users.find(cmd.getTo());
if(i==users.end()){
dcdebug("AdcHub::sendUDP: invalid user\n");
return;
}
OnlineUser&ou=*i->second;
if(!ou.getIdentity().isUdpActive()){
return;
}
ip=CONNSTATE(INCOMING_CONNECTIONS6)?ou.getIdentity().getIp():ou.getIdentity().getIp4();
port=ou.getIdentity().getUdpPort();
command=cmd.toString(ou.getUser()->getCID());
}
try{
udp.writeTo(ip,port,command);
}catch(constSocketException&e){
dcdebug("AdcHub::sendUDP: write failed: %s\n",e.getError().c_str());
udp.close();
}
}
voidAdcHub::handle(AdcCommand::STA,AdcCommand&c)noexcept{
if(c.getParameters().size()<2)
return;
OnlineUser*u=c.getFrom()==AdcCommand::HUB_SID?&getUser(c.getFrom(),CID()):findUser(c.getFrom());
if(!u)
return;
//int severity = Util::toInt(c.getParam(0).substr(0, 1));
if(c.getParam(0).size()!=3){
return;
}
switch(Util::toInt(c.getParam(0).substr(1))){
caseAdcCommand::ERROR_BAD_PASSWORD:
{
if(c.getType()==AdcCommand::TYPE_INFO){
setPassword(Util::emptyString);
}
break;
}
caseAdcCommand::ERROR_COMMAND_ACCESS:
{
if(c.getType()==AdcCommand::TYPE_INFO){
stringtmp;
if(c.getParam("FC",1,tmp)&&tmp.size()==4)
forbiddenCommands.insert(AdcCommand::toFourCC(tmp.c_str()));
}
break;
}
caseAdcCommand::ERROR_PROTOCOL_UNSUPPORTED:
{
stringtmp;
if(c.getParam("PR",1,tmp)){
if(tmp==CLIENT_PROTOCOL){
u->getUser()->setFlag(User::NO_ADC_1_0_PROTOCOL);
}elseif(tmp==SECURE_CLIENT_PROTOCOL){
u->getUser()->setFlag(User::NO_ADCS_0_10_PROTOCOL);
u->getUser()->unsetFlag(User::TLS);
}
// Try again...
ConnectionManager::getInstance()->force(u->getUser());
}
return;
}
}
fire(ClientListener::Message(),this,ChatMessage(c.getParam(1),u));
}
voidAdcHub::handle(AdcCommand::SCH,AdcCommand&c)noexcept{
OnlineUser*ou=findUser(c.getFrom());
if(!ou){
dcdebug("Invalid user in AdcHub::onSCH\n");
return;
}
fire(ClientListener::AdcSearch(),this,c,*ou);
}
voidAdcHub::handle(AdcCommand::RES,AdcCommand&c)noexcept{
OnlineUser*ou=findUser(c.getFrom());
if(!ou){
dcdebug("Invalid user in AdcHub::onRES\n");
return;
}
SearchManager::getInstance()->onRES(c,ou->getUser());
}
voidAdcHub::handle(AdcCommand::GET,AdcCommand&c)noexcept{
if(c.getParameters().size()<5){
if(!c.getParameters().empty()){
if(c.getParam(0)=="blom"){
send(AdcCommand(AdcCommand::SEV_FATAL,AdcCommand::ERROR_PROTOCOL_GENERIC,
"Too few parameters for blom",AdcCommand::TYPE_HUB));
}else{
send(AdcCommand(AdcCommand::SEV_FATAL,AdcCommand::ERROR_TRANSFER_GENERIC,
"Unknown transfer type",AdcCommand::TYPE_HUB));
}
}else{
send(AdcCommand(AdcCommand::SEV_FATAL,AdcCommand::ERROR_PROTOCOL_GENERIC,
"Too few parameters for GET",AdcCommand::TYPE_HUB));
}
return;
}
conststring&type=c.getParam(0);
stringsk,sh;
if(type=="blom"&&c.getParam("BK",4,sk)&&c.getParam("BH",4,sh)){
ByteVectorv;
size_tm=Util::toUInt32(c.getParam(3))*8;
size_tk=Util::toUInt32(sk);
size_th=Util::toUInt32(sh);
if(k>8||k<1){
send(AdcCommand(AdcCommand::SEV_FATAL,AdcCommand::ERROR_TRANSFER_GENERIC,
"Unsupported k",AdcCommand::TYPE_HUB));
return;
}
if(h>64||h<1){
send(AdcCommand(AdcCommand::SEV_FATAL,AdcCommand::ERROR_TRANSFER_GENERIC,
"Unsupported h",AdcCommand::TYPE_HUB));
return;
}
size_tn=ShareManager::getInstance()->getSharedFiles();
// Ideal size for m is n * k / ln(2), but we allow some slack
// When h >= 32, m can't go above 2^h anyway since it's stored in a size_t.
if(m>(5*Util::roundUp((int64_t)(n*k/log(2.)),(int64_t)64))||(h<32&&m>static_cast<size_t>(1U<<h))){
send(AdcCommand(AdcCommand::SEV_FATAL,AdcCommand::ERROR_TRANSFER_GENERIC,
"Unsupported m",AdcCommand::TYPE_HUB));
return;
}
if(m>0){
ShareManager::getInstance()->getBloom(v,k,m,h);
}
AdcCommandcmd(AdcCommand::CMD_SND,AdcCommand::TYPE_HUB);
cmd.addParam(c.getParam(0));
cmd.addParam(c.getParam(1));
cmd.addParam(c.getParam(2));
cmd.addParam(c.getParam(3));
cmd.addParam(c.getParam(4));
send(cmd);
if(m>0){
send((char*)&v[0],v.size());
}
}
}
voidAdcHub::handle(AdcCommand::NAT,AdcCommand&c)noexcept{
if(c.getParameters().size()<3)
return;
OnlineUser*u=findUser(c.getFrom());
if(!u||u->getUser()==ClientManager::getInstance()->getMe())
return;
conststring&protocol=c.getParam(0);
conststring&port=c.getParam(1);
conststring&token=c.getParam(2);
// bool secure = secureAvail(c.getFrom(), protocol, token);
boolsecure=false;
if(protocol==CLIENT_PROTOCOL){
// Nothing special
}elseif(protocol==SECURE_CLIENT_PROTOCOL&&CryptoManager::getInstance()->TLSOk()){
secure=true;
}else{
unknownProtocol(c.getFrom(),protocol,token);
return;
}
// Trigger connection attempt sequence locally ...
autolocalPort=std::to_string(sock->getLocalPort());
dcdebug("triggering connecting attempt in NAT: remote port = %s, local port = %d\n",port.c_str(),sock->getLocalPort());
ConnectionManager::getInstance()->adcConnect(*u,port,localPort,BufferedSocket::NAT_CLIENT,token,secure);
// ... and signal other client to do likewise.
send(AdcCommand(AdcCommand::CMD_RNT,u->getIdentity().getSID(),AdcCommand::TYPE_DIRECT).addParam(protocol).
addParam(localPort).addParam(token));
}
voidAdcHub::handle(AdcCommand::RNT,AdcCommand&c)noexcept{
if(c.getParameters().size()<3)
return;
// Sent request for NAT traversal cooperation, which
// was acknowledged (with requisite local port information).
OnlineUser*u=findUser(c.getFrom());
if(!u||u->getUser()==ClientManager::getInstance()->getMe())
return;
conststring&protocol=c.getParam(0);
conststring&port=c.getParam(1);
conststring&token=c.getParam(2);
boolsecure=false;
if(protocol==CLIENT_PROTOCOL){
// Nothing special
}elseif(protocol==SECURE_CLIENT_PROTOCOL&&CryptoManager::getInstance()->TLSOk()){
secure=true;
}else{
unknownProtocol(c.getFrom(),protocol,token);
return;
}
// Trigger connection attempt sequence locally
dcdebug("triggering connecting attempt in RNT: remote port = %s, local port = %d\n",port.c_str(),sock->getLocalPort());
ConnectionManager::getInstance()->adcConnect(*u,port,std::to_string(sock->getLocalPort()),BufferedSocket::NAT_SERVER,token,secure);
}
voidAdcHub::handle(AdcCommand::ZON,AdcCommand&c)noexcept{
if(c.getType()==AdcCommand::TYPE_INFO){
try{
sock->setMode(BufferedSocket::MODE_ZPIPE);
}catch(constException&e){
dcdebug("AdcHub::handleZON failed with error: %s\n",e.getError().c_str());
}
}
}
voidAdcHub::handle(AdcCommand::ZOF,AdcCommand&c)noexcept{
if(c.getType()==AdcCommand::TYPE_INFO){
try{
sock->setMode(BufferedSocket::MODE_LINE);
}catch(constException&e){
dcdebug("AdcHub::handleZOF failed with error: %s\n",e.getError().c_str());
}
}
}
voidAdcHub::connect(constOnlineUser&user,conststring&token,ConnectionTypetype){
connect(user,token,type,CryptoManager::getInstance()->TLSOk()&&user.getUser()->isSet(User::TLS));
}
voidAdcHub::connect(constOnlineUser&user,conststring&token,ConnectionTypetype,boolsecure){
if(state!=STATE_NORMAL)
return;
conststring*proto;
if(secure){
if(user.getUser()->isSet(User::NO_ADCS_0_10_PROTOCOL)){
/// @todo log
return;
}
proto=&SECURE_CLIENT_PROTOCOL;
}else{
if(user.getUser()->isSet(User::NO_ADC_1_0_PROTOCOL)||SETTING(REQUIRE_TLS)){
/// @todo log, consider removing from queue
return;
}
proto=&CLIENT_PROTOCOL;
}
ConnectionManager::getInstance()->addToken(token,user,type);
if(ClientManager::getInstance()->isActive()){
conststring&port=secure?ConnectionManager::getInstance()->getSecurePort():ConnectionManager::getInstance()->getPort();
if(port.empty()){
// Oops?
LogManager::getInstance()->message(str(F_("Not listening for connections - please restart %1%")%APPNAME));
return;
}
send(AdcCommand(AdcCommand::CMD_CTM,user.getIdentity().getSID(),AdcCommand::TYPE_DIRECT).addParam(*proto).addParam(port).addParam(token));
}else{
send(AdcCommand(AdcCommand::CMD_RCM,user.getIdentity().getSID(),AdcCommand::TYPE_DIRECT).addParam(*proto).addParam(token));
}
}
voidAdcHub::hubMessage(conststring&aMessage,boolthirdPerson){
if(state!=STATE_NORMAL)
return;
if(PluginManager::getInstance()->runHook(HOOK_CHAT_OUT,this,aMessage))
return;
AdcCommandc(AdcCommand::CMD_MSG,AdcCommand::TYPE_BROADCAST);
c.addParam(aMessage);
if(thirdPerson)
c.addParam("ME","1");
send(c);
}
voidAdcHub::privateMessage(constOnlineUser&user,conststring&aMessage,boolthirdPerson){
if(state!=STATE_NORMAL)
return;
AdcCommandc(AdcCommand::CMD_MSG,user.getIdentity().getSID(),AdcCommand::TYPE_ECHO);
c.addParam(aMessage);
if(thirdPerson)
c.addParam("ME","1");
c.addParam("PM",getMySID());
send(c);
}
voidAdcHub::sendUserCmd(constUserCommand&command,constParamMap¶ms){
if(state!=STATE_NORMAL)
return;
stringcmd=Util::formatParams(command.getCommand(),params,escape);
if(command.isChat()){
if(command.getTo().empty()){
hubMessage(cmd);
}else{
conststring&to=command.getTo();
Lockl(cs);
for(auto&i:users){
if(i.second->getIdentity().getNick()==to){
privateMessage(*i.second,cmd);
return;
}
}
}
}else{
send(cmd);
}
}
constvector<StringList>&AdcHub::getSearchExts(){
if(!searchExts.empty())
returnsearchExts;
// the list is always immutable except for this function where it is initially being filled.
const_cast<vector<StringList>&>(searchExts)={
// these extensions *must* be sorted alphabetically!
{"ape","flac","m4a","mid","mp3","mpc","ogg","ra","wav","wma"},
{"7z","ace","arj","bz2","gz","lha","lzh","rar","tar","z","zip"},
{"doc","docx","htm","html","nfo","odf","odp","ods","odt","pdf","ppt","pptx","rtf","txt","xls","xlsx","xml","xps"},
{"app","bat","cmd","com","dll","exe","jar","msi","ps1","vbs","wsf"},
{"bmp","cdr","eps","gif","ico","img","jpeg","jpg","png","ps","psd","sfw","tga","tif","webp"},
{"3gp","asf","asx","avi","divx","flv","mkv","mov","mp4","mpeg","mpg","ogm","pxp","qt","rm","rmvb","swf","vob","webm","wmv"}
};
returnsearchExts;
}
StringListAdcHub::parseSearchExts(intflag){
StringListret;
constauto&searchExts=getSearchExts();
for(autoi=searchExts.cbegin(),ibegin=i,iend=searchExts.cend();i!=iend;++i){
if(flag&(1<<(i-ibegin))){
ret.insert(ret.begin(),i->begin(),i->end());
}
}
returnret;
}
voidAdcHub::search(intaSizeMode,int64_taSize,intaFileType,conststring&aString,conststring&aToken,constStringList&aExtList,conststring&aKey){
if(state!=STATE_NORMAL)
return;
AdcCommandc(AdcCommand::CMD_SCH,AdcCommand::TYPE_BROADCAST);
/* token format: [per-hub unique id] "/" [per-search actual token]
this allows easily knowing which hub a search was sent on when parsing a search result,
whithout having to bother maintaining a list of sent tokens. */
c.addParam("TO",std::to_string(getUniqueId())+"/"+aToken);
if(aFileType==SearchManager::TYPE_TTH){
c.addParam("TR",aString);
}else{
if(aSizeMode==SearchManager::SIZE_ATLEAST){
c.addParam("GE",Util::toString(aSize));
}elseif(aSizeMode==SearchManager::SIZE_ATMOST){
c.addParam("LE",Util::toString(aSize));
}
StringTokenizer<string>st(aString,' ');
for(auto&i:st.getTokens()){
c.addParam("AN",i);
}
if(aFileType==SearchManager::TYPE_DIRECTORY){
c.addParam("TY","2");
}
if(aExtList.size()>2){
StringListexts=aExtList;
sort(exts.begin(),exts.end());
uint8_tgr=0;
StringListrx;
constauto&searchExts=getSearchExts();
for(autoi=searchExts.cbegin(),ibegin=i,iend=searchExts.cend();i!=iend;++i){
constStringList&def=*i;
// gather the exts not present in any of the lists
StringListtemp(def.size()+exts.size());
temp=StringList(temp.begin(),set_symmetric_difference(def.begin(),def.end(),
exts.begin(),exts.end(),temp.begin()));
// figure out whether the remaining exts have to be added or removed from the set
StringListrx_;
boolok=true;
for(autodiff=temp.begin();diff!=temp.end();){
if(find(def.cbegin(),def.cend(),*diff)==def.cend()){
++diff;// will be added further below as an "EX"
}else{
if(rx_.size()==2){
ok=false;
break;
}
rx_.push_back(*diff);
diff=temp.erase(diff);
}
}
if(!ok)// too many "RX"s necessary - disregard this group
continue;
// let's include this group!
gr+=1<<(i-ibegin);
exts=temp;// the exts to still add (that were not defined in the group)
rx.insert(rx.begin(),rx_.begin(),rx_.end());
if(exts.size()<=2)
break;
// keep looping to see if there are more exts that can be grouped
}
if(gr){
// some extensions can be grouped; let's send a command with grouped exts.
AdcCommandc_gr(AdcCommand::CMD_SCH,AdcCommand::TYPE_FEATURE);
c_gr.setFeatures('+'+SEGA_FEATURE);
constauto¶ms=c.getParameters();
for(auto&i:params)
c_gr.addParam(i);
for(auto&i:exts)
c_gr.addParam("EX",i);
c_gr.addParam("GR",Util::toString(gr));
for(auto&i:rx)
c_gr.addParam("RX",i);
sendSearch(c_gr);
// make sure users with the feature don't receive the search twice.
c.setType(AdcCommand::TYPE_FEATURE);
c.setFeatures('-'+SEGA_FEATURE);
}
}
for(auto&i:aExtList)
c.addParam("EX",i);
}
// Verify that it is an active ADCS hub
if(SETTING(ENABLE_SUDP)&&!aKey.empty()&&ClientManager::getInstance()->isActive()&&isSecure()){
c.addParam("KY",aKey);
}
sendSearch(c);
}
voidAdcHub::sendSearch(AdcCommand&c){
if(ClientManager::getInstance()->isActive()){
send(c);
}else{
c.setType(AdcCommand::TYPE_FEATURE);
stringfeatures=c.getFeatures();
c.setFeatures(features+'+'+TCP4_FEATURE+'-'+NAT0_FEATURE);
send(c);
c.setFeatures(features+'+'+NAT0_FEATURE);
send(c);
}
}
voidAdcHub::password(conststring&pwd){
if(state!=STATE_VERIFY)
return;
if(!salt.empty()){
size_tsaltBytes=salt.size()*5/8;
boost::scoped_array<uint8_t>buf(newuint8_t[saltBytes]);
Encoder::fromBase32(salt.c_str(),&buf[0],saltBytes);
TigerHashth;
if(oldPassword){
CIDcid=getMyIdentity().getUser()->getCID();
th.update(cid.data(),CID::SIZE);
}
th.update(pwd.data(),pwd.length());
th.update(&buf[0],saltBytes);
send(AdcCommand(AdcCommand::CMD_PAS,AdcCommand::TYPE_HUB).addParam(Encoder::toBase32(th.finalize(),TigerHash::BYTES)));
salt.clear();
}
}
staticvoidaddParam(StringMap&lastInfoMap,AdcCommand&c,conststring&var,conststring&value){
autoi=lastInfoMap.find(var);
if(i!=lastInfoMap.end()){
if(i->second!=value){
if(value.empty()){
lastInfoMap.erase(i);
}else{
i->second=value;
}
c.addParam(var,value);
}
}elseif(!value.empty()){
lastInfoMap.emplace(var,value);
c.addParam(var,value);
}
}
voidAdcHub::appendConnectivity(StringMap&lastInfoMap,AdcCommand&c,boolv4,boolv6){
if(v4){
if(CONNSETTING(NO_IP_OVERRIDE)&&!getUserIp4().empty()){
addParam(lastInfoMap,c,"I4",Socket::resolve(getUserIp4(),AF_INET));
}else{
addParam(lastInfoMap,c,"I4","0.0.0.0");
}
if(isActiveV4()){
addParam(lastInfoMap,c,"U4",SearchManager::getInstance()->getPort());
}else{
addParam(lastInfoMap,c,"U4","");
}
}else{
addParam(lastInfoMap,c,"I4","");
addParam(lastInfoMap,c,"U4","");
}
if(v6){
if(CONNSETTING(NO_IP_OVERRIDE6)&&!getUserIp6().empty()){
addParam(lastInfoMap,c,"I6",Socket::resolve(getUserIp6(),AF_INET6));
}else{
addParam(lastInfoMap,c,"I6","::");
}
if(isActiveV6()){
addParam(lastInfoMap,c,"U6",SearchManager::getInstance()->getPort());
}else{
addParam(lastInfoMap,c,"U6","");
}
}else{
addParam(lastInfoMap,c,"I6","");
addParam(lastInfoMap,c,"U6","");
}
}
voidAdcHub::infoImpl(){
if(state!=STATE_IDENTIFY&&state!=STATE_NORMAL)
return;
reloadSettings(false);
AdcCommandc(AdcCommand::CMD_INF,AdcCommand::TYPE_BROADCAST);
if(state==STATE_NORMAL){
updateCounts(false);
}
addParam(lastInfoMap,c,"ID",ClientManager::getInstance()->getMyCID().toBase32());
addParam(lastInfoMap,c,"PD",ClientManager::getInstance()->getMyPID().toBase32());
addParam(lastInfoMap,c,"NI",get(Nick));
addParam(lastInfoMap,c,"DE",get(Description));
addParam(lastInfoMap,c,"SL",Util::toString(SETTING(SLOTS)));
addParam(lastInfoMap,c,"FS",Util::toString(UploadManager::getInstance()->getFreeSlots()));
addParam(lastInfoMap,c,"SS",ShareManager::getInstance()->getShareSizeString());
addParam(lastInfoMap,c,"SF",std::to_string(ShareManager::getInstance()->getSharedFiles()));
addParam(lastInfoMap,c,"EM",get(Email));
addParam(lastInfoMap,c,"HN",std::to_string(counts[COUNT_NORMAL]));
addParam(lastInfoMap,c,"HR",std::to_string(counts[COUNT_REGISTERED]));
addParam(lastInfoMap,c,"HO",std::to_string(counts[COUNT_OP]));
addParam(lastInfoMap,c,"AP","++");
addParam(lastInfoMap,c,"VE",VERSIONSTRING);
addParam(lastInfoMap,c,"AW",Util::getAway()?"1":Util::emptyString);
intlimit=ThrottleManager::getInstance()->getDownLimit();
if(limit>0){
addParam(lastInfoMap,c,"DS",Util::toString(limit*1024));
}else{
addParam(lastInfoMap,c,"DS",Util::emptyString);
}
limit=ThrottleManager::getInstance()->getUpLimit();
if(limit>0){
addParam(lastInfoMap,c,"US",Util::toString(limit*1024));
}else{
addParam(lastInfoMap,c,"US",std::to_string((long)(Util::toDouble(SETTING(UPLOAD_SPEED))*1024*1024/8)));
}
addParam(lastInfoMap,c,"LC",Util::getIETFLang());
stringsu(SEGA_FEATURE);
if(CryptoManager::getInstance()->TLSOk()){
su+=","+ADCS_FEATURE;
if(SETTING(ENABLE_CCPM)){
su+=","+CCPM_FEATURE;
}
auto&kp=CryptoManager::getInstance()->getKeyprint();
addParam(lastInfoMap,c,"KP",CryptoManager::getInstance()->keyprintToString(kp));
}
if(SETTING(ENABLE_SUDP)&&isSecure()){
su+=","+SUDP_FEATURE;
}
booladdV4=!sock->isV6Valid()||(get(HubSettings::Connection)!=SettingsManager::INCOMING_DISABLED);
booladdV6=sock->isV6Valid()||(get(HubSettings::Connection6)!=SettingsManager::INCOMING_DISABLED);
if(addV4&&isActiveV4()){
su+=","+TCP4_FEATURE;
su+=","+UDP4_FEATURE;
}
if(addV6&&isActiveV6()){
su+=","+TCP6_FEATURE;
su+=","+UDP6_FEATURE;
}
if((addV6&&!isActiveV6()&&get(HubSettings::Connection6)!=SettingsManager::INCOMING_DISABLED)||
(addV4&&!isActiveV4()&&get(HubSettings::Connection)!=SettingsManager::INCOMING_DISABLED)){
su+=","+NAT0_FEATURE;
}
addParam(lastInfoMap,c,"SU",su);
appendConnectivity(lastInfoMap,c,addV4,addV6);
if(!c.getParameters().empty()){
send(c);
}
}
int64_tAdcHub::getAvailable()const{
Lockl(cs);
int64_tx=0;
for(auto&i:users){
x+=i.second->getIdentity().getBytesShared();
}
returnx;
}
voidAdcHub::checkNick(string&nick){
for(size_ti=0,n=nick.size();i<n;++i){
if(static_cast<uint8_t>(nick[i])<=32){
nick[i]='_';
}
}
}
voidAdcHub::send(constAdcCommand&cmd){
if(forbiddenCommands.find(AdcCommand::toFourCC(cmd.getFourCC().c_str()))==forbiddenCommands.end()){
if(cmd.getType()==AdcCommand::TYPE_UDP)
sendUDP(cmd);
send(cmd.toString(sid));
}
}
voidAdcHub::unknownProtocol(uint32_ttarget,conststring&protocol,conststring&token){
AdcCommandcmd(AdcCommand::SEV_FATAL,AdcCommand::ERROR_PROTOCOL_UNSUPPORTED,"Protocol unknown",AdcCommand::TYPE_DIRECT);
cmd.setTo(target);
cmd.addParam("PR",protocol);
cmd.addParam("TO",token);
send(cmd);
}
voidAdcHub::on(Connectedc)noexcept{
Client::on(c);
if(state!=STATE_PROTOCOL){
return;
}
lastInfoMap.clear();
sid=0;
forbiddenCommands.clear();
AdcCommandcmd(AdcCommand::CMD_SUP,AdcCommand::TYPE_HUB);
cmd.addParam(BAS0_SUPPORT).addParam(BASE_SUPPORT).addParam(TIGR_SUPPORT);
if(SETTING(HUB_USER_COMMANDS)){
cmd.addParam(UCM0_SUPPORT);
}
if(SETTING(SEND_BLOOM)){
cmd.addParam(BLO0_SUPPORT);
}
cmd.addParam(ZLIF_SUPPORT);
send(cmd);
}
voidAdcHub::on(Linel,conststring&aLine)noexcept{
Client::on(l,aLine);
if(!Text::validateUtf8(aLine)){
// @todo report to user?
return;
}
if(PluginManager::getInstance()->runHook(HOOK_NETWORK_HUB_IN,this,aLine))
return;
dispatch(aLine);
}
voidAdcHub::on(Failedf,conststring&aLine)noexcept{
clearUsers();
Client::on(f,aLine);
}
voidAdcHub::on(Seconds,uint64_taTick)noexcept{
Client::on(s,aTick);
if(state==STATE_NORMAL&&(aTick>(getLastActivity()+120*1000))){
send("\n",1);
}
}
OnlineUserListAdcHub::getUsers()const{
Lockl(cs);
OnlineUserListret;
ret.reserve(users.size());
for(auto&i:users)
ret.push_back(i.second);
returnret;
}
}// namespace dcpp