www.pudn.com > DCPlusPlus-src.zip > NmdcHub.cpp
/*
* Copyright (C) 2001-2004 Jacek Sieka, j_s at telia 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 "stdinc.h"
#include "DCPlusPlus.h"
#include "NmdcHub.h"
#include "ResourceManager.h"
#include "ClientManager.h"
#include "SearchManager.h"
#include "ShareManager.h"
#include "CryptoManager.h"
#include "ConnectionManager.h"
#include "Socket.h"
#include "UserCommand.h"
#include "StringTokenizer.h"
NmdcHub::NmdcHub(const string& aHubURL) : Client(aHubURL, '|'), supportFlags(0),
adapter(this), state(STATE_CONNECT),
lastActivity(GET_TICK()),
reconnect(true), lastUpdate(0)
{
TimerManager::getInstance()->addListener(this);
};
NmdcHub::~NmdcHub() throw() {
TimerManager::getInstance()->removeListener(this);
Speaker::removeListeners();
Lock l(cs);
clearUsers();
};
void NmdcHub::connect() {
setRegistered(false);
reconnect = true;
supportFlags = 0;
lastMyInfo.clear();
if(socket->isConnected()) {
disconnect();
}
state = STATE_LOCK;
if(getPort() == 0) {
setPort(411);
}
socket->connect(getAddress(), getPort());
}
void NmdcHub::connect(const User* aUser) {
checkstate();
dcdebug("NmdcHub::connectToMe %s\n", aUser->getNick().c_str());
if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_ACTIVE) {
send("$ConnectToMe " + toNmdc(aUser->getNick()) + " " + getLocalIp() + ":" + Util::toString(SETTING(IN_PORT)) + "|");
} else {
send("$RevConnectToMe " + toNmdc(getNick()) + " " + toNmdc(aUser->getNick()) + "|");
}
}
void NmdcHub::redirect(const User* aUser, const string& aServer, const string& aMsg) {
checkstate();
dcdebug("NmdcHub::opForceMove\n");
send("$OpForceMove $Who:" + toNmdc(aUser->getNick()) + "$Where:" + aServer + "$Msg:" + toNmdc(aMsg) + "|");
}
int64_t NmdcHub::getAvailable() const {
Lock l(cs);
int64_t x = 0;
for(User::NickMap::const_iterator i = users.begin(); i != users.end(); ++i) {
x+=i->second->getBytesShared();
}
return x;
}
void NmdcHub::refreshUserList(bool unknownOnly /* = false */) {
Lock l(cs);
if(unknownOnly) {
for(User::NickIter i = users.begin(); i != users.end(); ++i) {
if(i->second->getConnection().empty()) {
getInfo(i->second);
}
}
} else {
clearUsers();
getNickList();
}
}
void NmdcHub::clearUsers() {
for(User::NickIter i = users.begin(); i != users.end(); ++i) {
ClientManager::getInstance()->putUserOffline(i->second);
}
users.clear();
}
void NmdcHub::onLine(const string& aLine) throw() {
lastActivity = GET_TICK();
if(aLine.length() == 0)
return;
if(aLine[0] != '$') {
// Check if we're being banned...
if(state != STATE_CONNECTED) {
if(Util::findSubString(aLine, "banned") != string::npos) {
reconnect = false;
}
}
Speaker::fire(NmdcHubListener::Message(), this, Util::validateMessage(fromNmdc(aLine), true));
return;
}
string cmd;
string param;
string::size_type x;
if( (x = aLine.find(' ')) == string::npos) {
cmd = aLine;
} else {
cmd = aLine.substr(0, x);
param = aLine.substr(x+1);
}
if(cmd == "$Search") {
if(state != STATE_CONNECTED) {
return;
}
string::size_type i = 0;
string::size_type j = param.find(' ', i);
if(j == string::npos || i == j)
return;
string seeker = fromNmdc(param.substr(i, j-i));
// Filter own searches
if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_ACTIVE) {
if(seeker == (getLocalIp() + ":" + Util::toString(SETTING(IN_PORT)))) {
return;
}
} else {
// Hub:seeker
if(Util::stricmp(seeker.c_str() + 4, getNick().c_str()) == 0) {
return;
}
}
i = j + 1;
{
Lock l(cs);
u_int32_t tick = GET_TICK();
seekers.push_back(make_pair(seeker, tick));
// First, check if it's a flooder
FloodIter fi;
for(fi = flooders.begin(); fi != flooders.end(); ++fi) {
if(fi->first == seeker) {
return;
}
}
int count = 0;
for(fi = seekers.begin(); fi != seekers.end(); ++fi) {
if(fi->first == seeker)
count++;
if(count > 7) {
if(seeker.compare(0, 4, "Hub:") == 0)
Speaker::fire(NmdcHubListener::SearchFlood(), this, seeker.substr(4));
else
Speaker::fire(NmdcHubListener::SearchFlood(), this, seeker + STRING(NICK_UNKNOWN));
flooders.push_back(make_pair(seeker, tick));
return;
}
}
}
int a;
if(param[i] == 'F') {
a = SearchManager::SIZE_DONTCARE;
} else if(param[i+2] == 'F') {
a = SearchManager::SIZE_ATLEAST;
} else {
a = SearchManager::SIZE_ATMOST;
}
i += 4;
j = param.find('?', i);
if(j == string::npos || i == j)
return;
string size = param.substr(i, j-i);
i = j + 1;
j = param.find('?', i);
if(j == string::npos || i == j)
return;
int type = Util::toInt(param.substr(i, j-i)) - 1;
i = j + 1;
param = param.substr(i);
if(param.size() > 0) {
Speaker::fire(NmdcHubListener::Search(), this, seeker, a, Util::toInt64(size), type, fromNmdc(param));
if(seeker.compare(0, 4, "Hub:") == 0) {
User::Ptr u;
{
Lock l(cs);
User::NickIter ni = users.find(seeker.substr(4));
if(ni != users.end() && !ni->second->isSet(User::PASSIVE)) {
u = ni->second;
u->setFlag(User::PASSIVE);
}
}
if(u) {
updated(u);
}
}
}
} else if(cmd == "$MyINFO") {
string::size_type i, j;
i = 5;
j = param.find(' ', i);
if( (j == string::npos) || (j == i) )
return;
string nick = fromNmdc(param.substr(i, j-i));
i = j + 1;
User::Ptr u;
dcassert(nick.size() > 0);
{
Lock l(cs);
User::NickIter ni = users.find(nick);
if(ni == users.end()) {
u = users[nick] = ClientManager::getInstance()->getUser(nick, this);
} else {
u = ni->second;
}
}
j = param.find('$', i);
if(j == string::npos)
return;
string tmpDesc = Util::validateMessage(fromNmdc(param.substr(i, j-i)), true);
// Look for a tag...
if(tmpDesc.size() > 0 && tmpDesc[tmpDesc.size()-1] == '>') {
x = tmpDesc.rfind('<');
if(x != string::npos) {
// Hm, we have something...
u->setTag(tmpDesc.substr(x));
tmpDesc.erase(x);
} else {
u->setTag(Util::emptyString);
}
} else {
u->setTag(Util::emptyString);
}
u->setDescription(tmpDesc);
i = j + 3;
j = param.find('$', i);
if(j == string::npos)
return;
u->setConnection(fromNmdc(param.substr(i, j-i-1)));
i = j + 1;
j = param.find('$', i);
if(j == string::npos)
return;
u->setEmail(Util::validateMessage(fromNmdc(param.substr(i, j-i)), true));
i = j + 1;
j = param.find('$', i);
if(j == string::npos)
return;
u->setBytesShared(param.substr(i, j-i));
Speaker::fire(NmdcHubListener::MyInfo(), this, u);
} else if(cmd == "$Quit") {
if(!param.empty()) {
User::Ptr u;
{
Lock l(cs);
User::NickIter i = users.find(fromNmdc(param));
if(i == users.end()) {
dcdebug("C::onLine Quitting user %s not found\n", param.c_str());
return;
}
u = i->second;
users.erase(i);
}
Speaker::fire(NmdcHubListener::Quit(), this, u);
ClientManager::getInstance()->putUserOffline(u, true);
}
} else if(cmd == "$ConnectToMe") {
if(state != STATE_CONNECTED) {
return;
}
string::size_type i = param.find(' ');
string::size_type j;
if( (i == string::npos) || ((i + 1) >= param.size()) ) {
return;
}
i++;
j = param.find(':', i);
if(j == string::npos) {
return;
}
string server = fromNmdc(param.substr(i, j-i));
if(j+1 >= param.size()) {
return;
}
string port = param.substr(j+1);
ConnectionManager::getInstance()->connect(server, (short)Util::toInt(port), getNick());
Speaker::fire(NmdcHubListener::ConnectToMe(), this, server, (short)Util::toInt(port));
} else if(cmd == "$RevConnectToMe") {
if(state != STATE_CONNECTED) {
return;
}
User::Ptr u;
bool up = false;
{
Lock l(cs);
string::size_type j = param.find(' ');
if(j == string::npos) {
return;
}
User::NickIter i = users.find(fromNmdc(param.substr(0, j)));
if(i == users.end()) {
return;
}
u = i->second;
if(!u->isSet(User::PASSIVE)) {
u->setFlag(User::PASSIVE);
up = true;
}
}
if(u) {
if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_ACTIVE) {
connectToMe(u);
Speaker::fire(NmdcHubListener::RevConnectToMe(), this, u);
} else {
// Notify the user that we're passive too...
if(up)
revConnectToMe(u);
}
if(up)
updated(u);
}
} else if(cmd == "$SR") {
SearchManager::getInstance()->onSearchResult(aLine);
} else if(cmd == "$HubName") {
name = fromNmdc(param);
Speaker::fire(NmdcHubListener::HubName(), this);
} else if(cmd == "$Supports") {
StringTokenizer st(param, ' ');
StringList& sl = st.getTokens();
for(StringIter i = sl.begin(); i != sl.end(); ++i) {
if(*i == "UserCommand") {
supportFlags |= SUPPORTS_USERCOMMAND;
} else if(*i == "NoGetINFO") {
supportFlags |= SUPPORTS_NOGETINFO;
} else if(*i == "UserIP2") {
supportFlags |= SUPPORTS_USERIP2;
}
}
Speaker::fire(NmdcHubListener::Supports(), this, sl);
} else if(cmd == "$UserCommand") {
string::size_type i = 0;
string::size_type j = param.find(' ');
if(j == string::npos)
return;
int type = Util::toInt(param.substr(0, j));
i = j+1;
if(type == UserCommand::TYPE_SEPARATOR || type == UserCommand::TYPE_CLEAR) {
int ctx = Util::toInt(param.substr(i));
Speaker::fire(NmdcHubListener::UserCommand(), this, type, ctx, Util::emptyString, Util::emptyString);
} else if(type == UserCommand::TYPE_RAW || type == UserCommand::TYPE_RAW_ONCE) {
j = param.find(' ', i);
if(j == string::npos)
return;
int ctx = Util::toInt(param.substr(i));
i = j+1;
j = param.find('$');
if(j == string::npos)
return;
string name = fromNmdc(param.substr(i, j-i));
i = j+1;
string command = fromNmdc(param.substr(i, param.length() - i));
Speaker::fire(NmdcHubListener::UserCommand(), this, type, ctx, Util::validateMessage(name, true, false), Util::validateMessage(command, true, false));
}
} else if(cmd == "$Lock") {
if(state != STATE_LOCK) {
return;
}
state = STATE_HELLO;
if(!param.empty()) {
string::size_type j = param.find(" Pk=");
string lock, pk;
if( j != string::npos ) {
lock = param.substr(0, j);
pk = param.substr(j + 4);
} else {
// Workaround for faulty linux hubs...
j = param.find(" ");
if(j != string::npos)
lock = param.substr(0, j);
else
lock = param;
}
if(CryptoManager::getInstance()->isExtended(lock)) {
StringList feat;
feat.push_back("UserCommand");
feat.push_back("NoGetINFO");
feat.push_back("NoHello");
feat.push_back("UserIP2");
feat.push_back("TTHSearch");
if(BOOLSETTING(COMPRESS_TRANSFERS))
feat.push_back("GetZBlock");
supports(feat);
}
key(CryptoManager::getInstance()->makeKey(lock));
validateNick(getNick());
Speaker::fire(NmdcHubListener::CLock(), this, lock, pk);
}
} else if(cmd == "$Hello") {
if(!param.empty()) {
string nick = fromNmdc(param);
User::Ptr u = ClientManager::getInstance()->getUser(nick, this);
{
Lock l(cs);
users[nick] = u;
}
if(getNick() == nick) {
setMe(u);
u->setFlag(User::DCPLUSPLUS);
if(SETTING(CONNECTION_TYPE) != SettingsManager::CONNECTION_ACTIVE)
u->setFlag(User::PASSIVE);
else
u->unsetFlag(User::PASSIVE);
}
if(state == STATE_HELLO) {
state = STATE_CONNECTED;
updateCounts(false);
version();
getNickList();
myInfo();
}
Speaker::fire(NmdcHubListener::Hello(), this, u);
}
} else if(cmd == "$ForceMove") {
disconnect();
Speaker::fire(NmdcHubListener::Redirect(), this, param);
} else if(cmd == "$HubIsFull") {
Speaker::fire(NmdcHubListener::HubFull(), this);
} else if(cmd == "$ValidateDenide") { // Mind the spelling...
disconnect();
Speaker::fire(NmdcHubListener::ValidateDenied(), this);
} else if(cmd == "$UserIP") {
if(!param.empty()) {
User::List v;
StringTokenizer t(fromNmdc(param), "$$");
StringList& l = t.getTokens();
for(StringIter it = l.begin(); it != l.end(); ++it) {
string::size_type j = 0;
if((j = it->find(' ')) == string::npos)
continue;
if((j+1) == it->length())
continue;
v.push_back(ClientManager::getInstance()->getUser(it->substr(0, j), this));
v.back()->setIp(it->substr(j+1));
}
Speaker::fire(NmdcHubListener::UserIp(), this, v);
}
} else if(cmd == "$NickList") {
if(!param.empty()) {
User::List v;
StringTokenizer t(fromNmdc(param), "$$");
StringList& sl = t.getTokens();
for(StringIter it = sl.begin(); it != sl.end(); ++it) {
v.push_back(ClientManager::getInstance()->getUser(*it, this));
}
{
Lock l(cs);
for(User::Iter it2 = v.begin(); it2 != v.end(); ++it2) {
users[(*it2)->getNick()] = *it2;
}
}
if(!(getSupportFlags() & SUPPORTS_NOGETINFO)) {
string tmp;
// Let's assume 10 characters per nick...
tmp.reserve(v.size() * (11 + 10 + getNick().length()));
for(User::List::const_iterator i = v.begin(); i != v.end(); ++i) {
tmp += "$GetINFO ";
tmp += (*i)->getNick();
tmp += ' ';
tmp += getNick();
tmp += '|';
}
if(!tmp.empty()) {
send(tmp);
}
}
Speaker::fire(NmdcHubListener::NickList(), this, v);
}
} else if(cmd == "$OpList") {
if(!param.empty()) {
User::List v;
StringTokenizer t(fromNmdc(param), "$$");
StringList& sl = t.getTokens();
for(StringIter it = sl.begin(); it != sl.end(); ++it) {
v.push_back(ClientManager::getInstance()->getUser(*it, this));
v.back()->setFlag(User::OP);
}
{
Lock l(cs);
for(User::Iter it2 = v.begin(); it2 != v.end(); ++it2) {
users[(*it2)->getNick()] = *it2;
}
}
Speaker::fire(NmdcHubListener::OpList(), this, v);
updateCounts(false);
// Special...to avoid op's complaining that their count is not correctly
// updated when they log in (they'll be counted as registered first...)
myInfo();
}
} else if(cmd == "$To:") {
string::size_type i = param.find("From:");
if(i != string::npos) {
i+=6;
string::size_type j = param.find("$");
if(j != string::npos) {
string from = fromNmdc(param.substr(i, j - 1 - i));
if(from.size() > 0 && param.size() > (j + 1)) {
Speaker::fire(NmdcHubListener::PrivateMessage(), this, ClientManager::getInstance()->getUser(from, this, false), Util::validateMessage(fromNmdc(param.substr(j + 1)), true));
}
}
}
} else if(cmd == "$GetPass") {
setRegistered(true);
Speaker::fire(NmdcHubListener::GetPassword(), this);
} else if(cmd == "$BadPass") {
Speaker::fire(NmdcHubListener::BadPassword(), this);
} else if(cmd == "$LogedIn") {
Speaker::fire(NmdcHubListener::LoggedIn(), this);
} else {
dcassert(cmd[0] == '$');
dcdebug("NmdcHub::onLine Unknown command %s\n", aLine.c_str());
}
}
string NmdcHub::checkNick(const string& aNick) {
string tmp = aNick;
string::size_type i = 0;
while( (i = tmp.find_first_of("|$ ", i)) != string::npos) {
tmp[i++]='_';
}
return tmp;
}
void NmdcHub::myInfo() {
checkstate();
dcdebug("MyInfo %s...\n", getNick().c_str());
lastCounts = counts;
string tmp1 = ";**\x1fU9";
string tmp2 = "+L9";
string tmp3 = "+G9";
string tmp4 = "+R9";
string tmp5 = "+N9";
string::size_type i;
for(i = 0; i < 6; i++) {
tmp1[i]++;
}
for(i = 0; i < 3; i++) {
tmp2[i]++; tmp3[i]++; tmp4[i]++; tmp5[i]++;
}
char modeChar = '?';
if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_ACTIVE)
modeChar = 'A';
else if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_PASSIVE)
modeChar = 'P';
else if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_SOCKS5)
modeChar = '5';
string uMin = (SETTING(MIN_UPLOAD_SPEED) == 0) ? Util::emptyString : tmp5 + Util::toString(SETTING(MIN_UPLOAD_SPEED));
string minf =
"$MyINFO $ALL " + toNmdc(checkNick(getNick())) + " " + toNmdc(Util::validateMessage(getDescription(), false)) +
tmp1 + VERSIONSTRING + tmp2 + modeChar + tmp3 + getCounts() + tmp4 + Util::toString(SETTING(SLOTS)) + uMin +
">$ $" + SETTING(CONNECTION) + "\x01$" + toNmdc(Util::validateMessage(SETTING(EMAIL), false)) + '$' +
ShareManager::getInstance()->getShareSizeString() + "$|";
if(minf != lastMyInfo) {
send(minf);
lastMyInfo = minf;
}
}
void NmdcHub::disconnect() throw() {
state = STATE_CONNECT;
socket->disconnect();
{
Lock l(cs);
clearUsers();
}
}
void NmdcHub::search(int aSizeType, int64_t aSize, int aFileType, const string& aString){
checkstate();
char* buf;
char c1 = (aSizeType == SearchManager::SIZE_DONTCARE) ? 'F' : 'T';
char c2 = (aSizeType == SearchManager::SIZE_ATLEAST) ? 'F' : 'T';
string tmp = toNmdc(aString);
string::size_type i;
while((i = tmp.find(' ')) != string::npos) {
tmp[i] = '$';
}
int chars = 0;
if(SETTING(CONNECTION_TYPE) == SettingsManager::CONNECTION_ACTIVE) {
string x = getLocalIp();
buf = new char[x.length() + aString.length() + 64];
chars = sprintf(buf, "$Search %s:%d %c?%c?%s?%d?%s|", x.c_str(), SETTING(IN_PORT), c1, c2, Util::toString(aSize).c_str(), aFileType+1, tmp.c_str());
} else {
buf = new char[getNick().length() + aString.length() + 64];
chars = sprintf(buf, "$Search Hub:%s %c?%c?%s?%d?%s|", getNick().c_str(), c1, c2, Util::toString(aSize).c_str(), aFileType+1, tmp.c_str());
}
send(buf, chars);
delete[] buf;
}
void NmdcHub::kick(const User::Ptr& aUser, const string& aMsg) {
checkstate();
dcdebug("NmdcHub::kick\n");
static const char str[] =
"$To: %s From: %s $<%s> You are being kicked because: %s|<%s> %s is kicking %s because: %s|";
string msg2 = toNmdc(Util::validateMessage(aMsg, false));
char* tmp = new char[sizeof(str) + 2*aUser->getNick().length() + 2*msg2.length() + 4*getNick().length()];
const char* u = aUser->getNick().c_str();
const char* n = getNick().c_str();
const char* m = msg2.c_str();
sprintf(tmp, str, u, n, n, m, n, n, u, m);
send(tmp);
delete[] tmp;
// Short, short break to allow the message to reach the NmdcHub...
Thread::sleep(200);
send("$Kick " + toNmdc(aUser->getNick()) + "|");
}
void NmdcHub::kick(const User* aUser, const string& aMsg) {
checkstate();
dcdebug("NmdcHub::kick\n");
static const char str[] =
"$To: %s From: %s $<%s> You are being kicked because: %s|<%s> %s is kicking %s because: %s|";
string msg2 = toNmdc(Util::validateMessage(aMsg, false));
char* tmp = new char[sizeof(str) + 2*aUser->getNick().length() + 2*msg2.length() + 4*getNick().length()];
const char* u = aUser->getNick().c_str();
const char* n = getNick().c_str();
const char* m = msg2.c_str();
sprintf(tmp, str, u, n, n, m, n, n, u, m);
send(tmp);
delete[] tmp;
// Short, short break to allow the message to reach the NmdcHub...
Thread::sleep(100);
send("$Kick " + toNmdc(aUser->getNick()) + "|");
}
// TimerManagerListener
void NmdcHub::on(TimerManagerListener::Second, u_int32_t aTick) throw() {
if(socket && (lastActivity + (120+Util::rand(0, 60)) * 1000) < aTick) {
// Nothing's happened for ~120 seconds, check if we're connected, if not, try to connect...
lastActivity = aTick;
// Try to send something for the fun of it...
if(isConnected()) {
dcdebug("Testing writing...\n");
socket->write("|", 1);
} else {
// Try to reconnect...
if(reconnect && !getAddress().empty())
connect();
}
}
{
Lock l(cs);
while(!seekers.empty() && seekers.front().second + (5 * 1000) < aTick) {
seekers.pop_front();
}
while(!flooders.empty() && flooders.front().second + (120 * 1000) < aTick) {
flooders.pop_front();
}
}
}
// BufferedSocketListener
void NmdcHub::on(BufferedSocketListener::Failed, const string& aLine) throw() {
{
Lock l(cs);
clearUsers();
}
if(state == STATE_CONNECTED)
state = STATE_CONNECT;
Speaker::fire(NmdcHubListener::Failed(), this, aLine);
}
/**
* @file
* $Id: NmdcHub.cpp,v 1.13 2004/09/26 18:54:08 arnetheduck Exp $
*/