1 /*
2 * Copyright (C) 2001-2012 Jacek Sieka, arnetheduck on gmail point com
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include "stdinc.h"
20
21 #include "NmdcHub.h"
22
23 #include "ChatMessage.h"
24 #include "ClientManager.h"
25 #include "SearchManager.h"
26 #include "ShareManager.h"
27 #include "CryptoManager.h"
28 #include "ConnectionManager.h"
29 #include "ThrottleManager.h"
30 #include "version.h"
31 #include "UploadManager.h"
32 #include "Socket.h"
33 #include "UserCommand.h"
34 #include "StringTokenizer.h"
35
36 namespace dcpp {
37
NmdcHub(const string & aHubURL,bool secure)38 NmdcHub::NmdcHub(const string& aHubURL, bool secure) :
39 Client(aHubURL, '|', secure),
40 supportFlags(0),
41 lastUpdate(0)
42 {
43 }
44
~NmdcHub()45 NmdcHub::~NmdcHub() {
46 clearUsers();
47 }
48
49
50 #define checkstate() if(state != STATE_NORMAL) return
51
connect(const OnlineUser & aUser,const string &)52 void NmdcHub::connect(const OnlineUser& aUser, const string&) {
53 checkstate();
54 dcdebug("NmdcHub::connect %s\n", aUser.getIdentity().getNick().c_str());
55 if(isActive()) {
56 connectToMe(aUser);
57 } else {
58 revConnectToMe(aUser);
59 }
60 }
61
getAvailable() const62 int64_t NmdcHub::getAvailable() const {
63 Lock l(cs);
64 int64_t x = 0;
65 for(auto i = users.begin(); i != users.end(); ++i) {
66 x+=i->second->getIdentity().getBytesShared();
67 }
68 return x;
69 }
70
getUser(const string & aNick)71 OnlineUser& NmdcHub::getUser(const string& aNick) {
72 OnlineUser* u = NULL;
73 {
74 Lock l(cs);
75
76 NickIter i = users.find(aNick);
77 if(i != users.end())
78 return *i->second;
79 }
80
81 UserPtr p;
82 if(aNick == getCurrentNick()) {
83 p = ClientManager::getInstance()->getMe();
84 } else {
85 p = ClientManager::getInstance()->getUser(aNick, getHubUrl());
86 }
87
88 {
89 Lock l(cs);
90 u = users.insert(make_pair(aNick, new OnlineUser(p, *this, 0))).first->second;
91 u->getIdentity().setNick(aNick);
92 if(u->getUser() == getMyIdentity().getUser()) {
93 setMyIdentity(u->getIdentity());
94 }
95 }
96
97 ClientManager::getInstance()->putOnline(u);
98 return *u;
99 }
100
supports(const StringList & feat)101 void NmdcHub::supports(const StringList& feat) {
102 const string x = Util::toString(" ",feat);
103 send("$Supports " + x + '|');
104 }
105
findUser(const string & aNick)106 OnlineUser* NmdcHub::findUser(const string& aNick) {
107 Lock l(cs);
108 NickIter i = users.find(aNick);
109 return i == users.end() ? NULL : i->second;
110 }
111
putUser(const string & aNick)112 void NmdcHub::putUser(const string& aNick) {
113 OnlineUser* ou = NULL;
114 {
115 Lock l(cs);
116 NickIter i = users.find(aNick);
117 if(i == users.end())
118 return;
119 ou = i->second;
120 users.erase(i);
121 }
122 ClientManager::getInstance()->putOffline(ou);
123 delete ou;
124 }
125
clearUsers()126 void NmdcHub::clearUsers() {
127 NickMap u2;
128
129 {
130 Lock l(cs);
131 u2.swap(users);
132 }
133
134 for(auto i = u2.begin(); i != u2.end(); ++i) {
135 ClientManager::getInstance()->putOffline(i->second);
136 delete i->second;
137 }
138 }
139
updateFromTag(Identity & id,const string & tag)140 void NmdcHub::updateFromTag(Identity& id, const string& tag) {
141 StringTokenizer<string> tok(tag, ',');
142 string::size_type j;
143 id.set("US", Util::emptyString);
144 for(auto i = tok.getTokens().begin(); i != tok.getTokens().end(); ++i) {
145 if(i->length() < 2)
146 continue;
147
148 if(i->compare(0, 2, "H:") == 0) {
149 StringTokenizer<string> t(i->substr(2), '/');
150 if(t.getTokens().size() != 3)
151 continue;
152 id.set("HN", t.getTokens()[0]);
153 id.set("HR", t.getTokens()[1]);
154 id.set("HO", t.getTokens()[2]);
155 } else if(i->compare(0, 2, "S:") == 0) {
156 id.set("SL", i->substr(2));
157 } else if((j = i->find("V:")) != string::npos) {
158 i->erase(i->begin(), i->begin() + j + 2);
159 id.set("VE", *i);
160 } else if(i->compare(0, 2, "M:") == 0) {
161 if(i->size() == 3) {
162 if((*i)[2] == 'A')
163 id.getUser()->unsetFlag(User::PASSIVE);
164 else
165 id.getUser()->setFlag(User::PASSIVE);
166 }
167 } else if((j = i->find("L:")) != string::npos) {
168 i->erase(i->begin() + j, i->begin() + j + 2);
169 id.set("US", Util::toString(Util::toInt(*i) * 1024));
170 }
171 }
172 /// @todo Think about this
173 id.set("TA", '<' + tag + '>');
174 }
175
onLine(const string & aLine)176 void NmdcHub::onLine(const string& aLine) noexcept {
177 if(aLine.length() == 0)
178 return;
179
180 if(aLine[0] != '$') {
181 // Check if we're being banned...
182 if(state != STATE_NORMAL) {
183 if(Util::findSubString(aLine, "banned") != string::npos) {
184 setAutoReconnect(false);
185 }
186 }
187 string line = toUtf8(aLine);
188 if(line[0] != '<') {
189 fire(ClientListener::StatusMessage(), this, unescape(line));
190 return;
191 }
192 string::size_type i = line.find('>', 2);
193 if(i == string::npos) {
194 fire(ClientListener::StatusMessage(), this, unescape(line));
195 return;
196 }
197 string nick = line.substr(1, i-1);
198 string message;
199 if((line.length()-1) > i) {
200 message = line.substr(i+2);
201 } else {
202 fire(ClientListener::StatusMessage(), this, unescape(line));
203 return;
204 }
205
206 if((line.find("Hub-Security") != string::npos) && (line.find("was kicked by") != string::npos)) {
207 fire(ClientListener::StatusMessage(), this, unescape(line), ClientListener::FLAG_IS_SPAM);
208 return;
209 } else if((line.find("is kicking") != string::npos) && (line.find("because:") != string::npos)) {
210 fire(ClientListener::StatusMessage(), this, unescape(line), ClientListener::FLAG_IS_SPAM);
211 return;
212 }
213
214 ChatMessage chatMessage = { unescape(message), findUser(nick) };
215
216 if(!chatMessage.from) {
217 OnlineUser& o = getUser(nick);
218 // Assume that messages from unknown users come from the hub
219 o.getIdentity().setHub(true);
220 o.getIdentity().setHidden(true);
221 fire(ClientListener::UserUpdated(), this, o);
222
223 chatMessage.from = &o;
224 }
225
226 fire(ClientListener::Message(), this, chatMessage);
227 return;
228 }
229
230 string cmd;
231 string param;
232 string::size_type x;
233
234 if( (x = aLine.find(' ')) == string::npos) {
235 cmd = aLine;
236 } else {
237 cmd = aLine.substr(0, x);
238 param = toUtf8(aLine.substr(x+1));
239 }
240
241 if(cmd == "$Search") {
242 if(state != STATE_NORMAL) {
243 return;
244 }
245 string::size_type i = 0;
246 string::size_type j = param.find(' ', i);
247 if(j == string::npos || i == j)
248 return;
249
250 string seeker = param.substr(i, j-i);
251 auto pos_slashes = seeker.find("://");
252 if (pos_slashes != string::npos) {
253 //seeker = (pos_slashes + 3 < seeker.size()) ? seeker.substr(pos_slashes+3) : Util::emptyString;
254 //fire(ClientListener::SearchFlood(), this, str(F_("NLO Try generate DDOS on %1%, do nothing") % seeker));
255 return;
256 }
257 bool passive = (seeker.compare(0, 4, "Hub:") == 0);
258 //printf("$Search->%s\n", seeker.c_str()); fflush(stdout);
259 // Filter own searches
260 if(isActive()) {
261 if(seeker == (getLocalIp() + ":" + Util::toString(SearchManager::getInstance()->getPort()))) {
262 return;
263 }
264 } else {
265 // Hub:seeker
266 if(seeker.size() > 4 &&
267 Util::stricmp(seeker.c_str() + 4, getMyNick().c_str()) == 0) {
268 return;
269 }
270 }
271
272 i = j + 1;
273
274 uint64_t tick = GET_TICK();
275 clearFlooders(tick);
276
277 seekers.push_back(make_pair(seeker, tick));
278
279 // First, check if it's a flooder
280 for(auto fi = flooders.begin(); fi != flooders.end(); ++fi) {
281 if(fi->first == seeker) {
282 return;
283 }
284 }
285
286 int count = 0;
287 for(auto fi = seekers.begin(); fi != seekers.end(); ++fi) {
288 if(fi->first == seeker)
289 count++;
290
291 if(count > 7) {
292 if(seeker.compare(0, 4, "Hub:") == 0)
293 fire(ClientListener::SearchFlood(), this, seeker.substr(4));
294 else
295 fire(ClientListener::SearchFlood(), this, str(F_("%1% (Nick unknown)") % seeker));
296
297 flooders.push_back(make_pair(seeker, tick));
298 return;
299 }
300 }
301
302 int a;
303 if(param[i] == 'F') {
304 a = SearchManager::SIZE_DONTCARE;
305 } else if(param[i+2] == 'F') {
306 a = SearchManager::SIZE_ATLEAST;
307 } else {
308 a = SearchManager::SIZE_ATMOST;
309 }
310 i += 4;
311 j = param.find('?', i);
312 if(j == string::npos || i == j)
313 return;
314 string size = param.substr(i, j-i);
315 i = j + 1;
316 j = param.find('?', i);
317 if(j == string::npos || i == j)
318 return;
319 int type = Util::toInt(param.substr(i, j-i)) - 1;
320 i = j + 1;
321 string terms = unescape(param.substr(i));
322
323 if(!terms.empty()) {
324 if(seeker.compare(0, 4, "Hub:") == 0) {
325 OnlineUser* u = findUser(seeker.substr(4));
326
327 if(u == NULL) {
328 return;
329 }
330
331 if(!u->getUser()->isSet(User::PASSIVE)) {
332 u->getUser()->setFlag(User::PASSIVE);
333 updated(*u);
334 }
335 }
336
337 fire(ClientListener::NmdcSearch(), this, seeker, a, Util::toInt64(size), type, terms);
338 }
339 } else if(cmd == "$MyINFO") {
340 string::size_type i, j;
341 i = 5;
342 j = param.find(' ', i);
343 if( (j == string::npos) || (j == i) )
344 return;
345 string nick = param.substr(i, j-i);
346
347 if(nick.empty())
348 return;
349
350 i = j + 1;
351
352 OnlineUser& u = getUser(nick);
353
354 // If he is already considered to be the hub (thus hidden), probably should appear in the UserList
355 if(u.getIdentity().isHidden()) {
356 u.getIdentity().setHidden(false);
357 u.getIdentity().setHub(false);
358 }
359
360 j = param.find('$', i);
361 if(j == string::npos)
362 return;
363
364 string tmpDesc = unescape(param.substr(i, j-i));
365 // Look for a tag...
366 if(tmpDesc.size() > 0 && tmpDesc[tmpDesc.size()-1] == '>') {
367 x = tmpDesc.rfind('<');
368 if(x != string::npos) {
369 // Hm, we have something...disassemble it...
370 updateFromTag(u.getIdentity(), tmpDesc.substr(x + 1, tmpDesc.length() - x - 2));
371 tmpDesc.erase(x);
372 }
373 }
374 u.getIdentity().setDescription(tmpDesc);
375
376 i = j + 3;
377 j = param.find('$', i);
378 if(j == string::npos)
379 return;
380
381 string connection = param.substr(i, j-i-1);
382 if(connection.empty()) {
383 // No connection = bot...
384 u.getUser()->setFlag(User::BOT);
385 u.getIdentity().setHub(false);
386 } else {
387 u.getUser()->unsetFlag(User::BOT);
388 u.getIdentity().setBot(false);
389 }
390
391 u.getIdentity().setHub(false);
392
393 u.getIdentity().setConnection(connection);
394 u.getIdentity().setStatus(Util::toString(param[j-1]));
395
396 if(u.getIdentity().getStatus() & Identity::TLS) {
397 u.getUser()->setFlag(User::TLS);
398 } else {
399 u.getUser()->unsetFlag(User::TLS);
400 }
401
402 if(u.getIdentity().getStatus() & Identity::NAT) {
403 u.getUser()->setFlag(User::NAT_TRAVERSAL);
404 } else {
405 u.getUser()->unsetFlag(User::NAT_TRAVERSAL);
406 }
407 i = j + 1;
408 j = param.find('$', i);
409
410 if(j == string::npos)
411 return;
412
413 u.getIdentity().setEmail(unescape(param.substr(i, j-i)));
414
415 i = j + 1;
416 j = param.find('$', i);
417 if(j == string::npos)
418 return;
419 u.getIdentity().setBytesShared(param.substr(i, j-i));
420
421 if(u.getUser() == getMyIdentity().getUser()) {
422 setMyIdentity(u.getIdentity());
423 }
424
425 fire(ClientListener::UserUpdated(), this, u);
426 } else if(cmd == "$Quit") {
427 if(!param.empty()) {
428 const string& nick = param;
429 OnlineUser* u = findUser(nick);
430 if(!u)
431 return;
432
433 fire(ClientListener::UserRemoved(), this, *u);
434
435 putUser(nick);
436 }
437 } else if(cmd == "$ConnectToMe") {
438 if(state != STATE_NORMAL) {
439 return;
440 }
441 string::size_type i = param.find(' ');
442 string::size_type j;
443 if( (i == string::npos) || ((i + 1) >= param.size()) ) {
444 return;
445 }
446 i++;
447 j = param.find(':', i);
448 if(j == string::npos) {
449 return;
450 }
451 string server = param.substr(i, j-i);
452 if(j+1 >= param.size()) {
453 return;
454 }
455 string senderNick;
456 string port;
457
458 i = param.find(' ', j+1);
459 if(i == string::npos) {
460 port = param.substr(j+1);
461 } else {
462 senderNick = param.substr(i+1);
463 port = param.substr(j+1, i-j-1);
464 }
465
466 bool secure = false;
467 if(port[port.size() - 1] == 'S') {
468 port.erase(port.size() - 1);
469 if(CryptoManager::getInstance()->TLSOk()) {
470 secure = true;
471 }
472 }
473
474 if(BOOLSETTING(ALLOW_NATT)) {
475 if(port[port.size() - 1] == 'N') {
476 if(senderNick.empty())
477 return;
478
479 port.erase(port.size() - 1);
480
481 // Trigger connection attempt sequence locally ...
482 ConnectionManager::getInstance()->nmdcConnect(server, static_cast<uint16_t>(Util::toInt(port)), sock->getLocalPort(),
483 BufferedSocket::NAT_CLIENT, getMyNick(), getHubUrl(), getEncoding(), secure);
484
485 // ... and signal other client to do likewise.
486 send("$ConnectToMe " + senderNick + " " + getLocalIp() + ":" + Util::toString(sock->getLocalPort()) + (secure ? "RS" : "R") + "|");
487 return;
488 } else if(port[port.size() - 1] == 'R') {
489 port.erase(port.size() - 1);
490
491 // Trigger connection attempt sequence locally
492 ConnectionManager::getInstance()->nmdcConnect(server, static_cast<uint16_t>(Util::toInt(port)), sock->getLocalPort(),
493 BufferedSocket::NAT_SERVER, getMyNick(), getHubUrl(), getEncoding(), secure);
494 return;
495 }
496 }
497
498 if(port.empty())
499 return;
500 // For simplicity, we make the assumption that users on a hub have the same character encoding
501 ConnectionManager::getInstance()->nmdcConnect(server, static_cast<uint16_t>(Util::toInt(port)), getMyNick(), getHubUrl(), getEncoding(), secure);
502 } else if(cmd == "$RevConnectToMe") {
503 if(state != STATE_NORMAL) {
504 return;
505 }
506
507 string::size_type j = param.find(' ');
508 if(j == string::npos) {
509 return;
510 }
511
512 OnlineUser* u = findUser(param.substr(0, j));
513 if(u == NULL)
514 return;
515
516 if(isActive()) {
517 connectToMe(*u);
518 } else if(BOOLSETTING(ALLOW_NATT) && (u->getIdentity().getStatus() & Identity::NAT)) {
519 bool secure = CryptoManager::getInstance()->TLSOk() && u->getUser()->isSet(User::TLS);
520 // NMDC v2.205 supports "$ConnectToMe sender_nick remote_nick ip:port", but many NMDC hubsofts block it
521 // sender_nick at the end should work at least in most used hubsofts
522 send("$ConnectToMe " + fromUtf8(u->getIdentity().getNick()) + " " + getLocalIp() + ":" + Util::toString(sock->getLocalPort()) + (secure ? "NS " : "N ") + fromUtf8(getMyNick()) + "|");
523 } else {
524 if(!u->getUser()->isSet(User::PASSIVE)) {
525 u->getUser()->setFlag(User::PASSIVE);
526 // Notify the user that we're passive too...
527 revConnectToMe(*u);
528 updated(*u);
529
530 return;
531 }
532 }
533 } else if(cmd == "$SR") {
534 SearchManager::getInstance()->onSearchResult(aLine);
535 } else if(cmd == "$HubName") {
536 // If " - " found, the first part goes to hub name, rest to description
537 // If no " - " found, first word goes to hub name, rest to description
538
539 string::size_type i = param.find(" - ");
540 if(i == string::npos) {
541 i = param.find(' ');
542 if(i == string::npos) {
543 getHubIdentity().setNick(unescape(param));
544 getHubIdentity().setDescription(Util::emptyString);
545 } else {
546 getHubIdentity().setNick(unescape(param.substr(0, i)));
547 getHubIdentity().setDescription(unescape(param.substr(i+1)));
548 }
549 } else {
550 getHubIdentity().setNick(unescape(param.substr(0, i)));
551 getHubIdentity().setDescription(unescape(param.substr(i+3)));
552 }
553 fire(ClientListener::HubUpdated(), this);
554 } else if(cmd == "$Supports") {
555 StringTokenizer<string> st(param, ' ');
556 StringList& sl = st.getTokens();
557 for(auto i = sl.begin(); i != sl.end(); ++i) {
558 if(*i == "UserCommand") {
559 supportFlags |= SUPPORTS_USERCOMMAND;
560 } else if(*i == "NoGetINFO") {
561 supportFlags |= SUPPORTS_NOGETINFO;
562 } else if(*i == "UserIP2") {
563 supportFlags |= SUPPORTS_USERIP2;
564 }
565 }
566 } else if(cmd == "$UserCommand") {
567 string::size_type i = 0;
568 string::size_type j = param.find(' ');
569 if(j == string::npos)
570 return;
571
572 int type = Util::toInt(param.substr(0, j));
573 i = j+1;
574 if(type == UserCommand::TYPE_SEPARATOR || type == UserCommand::TYPE_CLEAR) {
575 int ctx = Util::toInt(param.substr(i));
576 fire(ClientListener::HubUserCommand(), this, type, ctx, Util::emptyString, Util::emptyString);
577 } else if(type == UserCommand::TYPE_RAW || type == UserCommand::TYPE_RAW_ONCE) {
578 j = param.find(' ', i);
579 if(j == string::npos)
580 return;
581 int ctx = Util::toInt(param.substr(i));
582 i = j+1;
583 j = param.find('$');
584 if(j == string::npos)
585 return;
586 string name = unescape(param.substr(i, j-i));
587 // NMDC uses '\' as a separator but both ADC and our internal representation use '/'
588 Util::replace("/", "//", name);
589 Util::replace("\\", "/", name);
590 i = j+1;
591 string command = unescape(param.substr(i, param.length() - i));
592 fire(ClientListener::HubUserCommand(), this, type, ctx, name, command);
593 }
594 } else if(cmd == "$Lock") {
595 if(state != STATE_PROTOCOL) {
596 return;
597 }
598 state = STATE_IDENTIFY;
599
600 // Param must not be toUtf8'd...
601 param = aLine.substr(6);
602
603 if(!param.empty()) {
604 string::size_type j = param.find(" Pk=");
605 string lock, pk;
606 if( j != string::npos ) {
607 lock = param.substr(0, j);
608 pk = param.substr(j + 4);
609 } else {
610 // Workaround for faulty linux hubs...
611 j = param.find(" ");
612 if(j != string::npos)
613 lock = param.substr(0, j);
614 else
615 lock = param;
616 }
617
618 if(CryptoManager::getInstance()->isExtended(lock)) {
619 StringList feat;
620 feat.push_back("UserCommand");
621 feat.push_back("NoGetINFO");
622 feat.push_back("NoHello");
623 feat.push_back("UserIP2");
624 feat.push_back("TTHSearch");
625 feat.push_back("ZPipe0");
626
627 if(CryptoManager::getInstance()->TLSOk())
628 feat.push_back("TLS");
629
630 #ifdef WITH_DHT
631 if(BOOLSETTING(USE_DHT))
632 feat.push_back("DHT0");
633 #endif
634
635 supports(feat);
636 }
637
638 key(CryptoManager::getInstance()->makeKey(lock));
639 OnlineUser& ou = getUser(getCurrentNick());
640 validateNick(ou.getIdentity().getNick());
641 }
642 } else if(cmd == "$Hello") {
643 if(!param.empty()) {
644 OnlineUser& u = getUser(param);
645
646 if(u.getUser() == getMyIdentity().getUser()) {
647 if(isActive())
648 u.getUser()->unsetFlag(User::PASSIVE);
649 else
650 u.getUser()->setFlag(User::PASSIVE);
651 }
652
653 if(state == STATE_IDENTIFY && u.getUser() == getMyIdentity().getUser()) {
654 state = STATE_NORMAL;
655 updateCounts(false);
656
657 version();
658 getNickList();
659 myInfo(true);
660 }
661
662 fire(ClientListener::UserUpdated(), this, u);
663 }
664 } else if(cmd == "$ForceMove") {
665 disconnect(false);
666 fire(ClientListener::Redirect(), this, param);
667 } else if(cmd == "$HubIsFull") {
668 fire(ClientListener::HubFull(), this);
669 }else if(cmd == "$HubTopic") {
670 //dcdebug("Nmdc topic:%s",aLine.c_str());
671 string line;
672 string str2= _("Hub topic:");
673 line=toUtf8(aLine);
674 line.replace(0,9,str2);
675 fire(ClientListener::StatusMessage(), this, unescape(line), ClientListener::FLAG_NORMAL);
676 } else if(cmd == "$ValidateDenide") { // Mind the spelling...
677 disconnect(false);
678 fire(ClientListener::NickTaken(), this);
679 } else if(cmd == "$UserIP") {
680 if(!param.empty()) {
681 OnlineUserList v;
682 StringTokenizer<string> t(param, "$$");
683 StringList& l = t.getTokens();
684 for(auto it = l.begin(); it != l.end(); ++it) {
685 string::size_type j = 0;
686 if((j = it->find(' ')) == string::npos)
687 continue;
688 if((j+1) == it->length())
689 continue;
690
691 OnlineUser* u = findUser(it->substr(0, j));
692
693 if(!u)
694 continue;
695
696 u->getIdentity().setIp(it->substr(j+1));
697 if(u->getUser() == getMyIdentity().getUser()) {
698 setMyIdentity(u->getIdentity());
699 }
700 v.push_back(u);
701 }
702
703 fire(ClientListener::UsersUpdated(), this, v);
704 }
705 } else if(cmd == "$NickList") {
706 if(!param.empty()) {
707 OnlineUserList v;
708 StringTokenizer<string> t(param, "$$");
709 StringList& sl = t.getTokens();
710
711 for(auto it = sl.begin(); it != sl.end(); ++it) {
712 if(it->empty())
713 continue;
714
715 v.push_back(&getUser(*it));
716 }
717
718 if(!(supportFlags & SUPPORTS_NOGETINFO)) {
719 string tmp;
720 // Let's assume 10 characters per nick...
721 tmp.reserve(v.size() * (11 + 10 + getMyNick().length()));
722 string n = ' ' + fromUtf8(getMyNick()) + '|';
723 for(auto i = v.begin(); i != v.end(); ++i) {
724 tmp += "$GetINFO ";
725 tmp += fromUtf8((*i)->getIdentity().getNick());
726 tmp += n;
727 }
728 if(!tmp.empty()) {
729 send(tmp);
730 }
731 }
732
733 fire(ClientListener::UsersUpdated(), this, v);
734 }
735 } else if(cmd == "$OpList") {
736 if(!param.empty()) {
737 OnlineUserList v;
738 StringTokenizer<string> t(param, "$$");
739 StringList& sl = t.getTokens();
740 for(auto it = sl.begin(); it != sl.end(); ++it) {
741 if(it->empty())
742 continue;
743 OnlineUser& ou = getUser(*it);
744 ou.getIdentity().setOp(true);
745 if(ou.getUser() == getMyIdentity().getUser()) {
746 setMyIdentity(ou.getIdentity());
747 }
748 v.push_back(&ou);
749 }
750
751 fire(ClientListener::UsersUpdated(), this, v);
752 updateCounts(false);
753
754 // Special...to avoid op's complaining that their count is not correctly
755 // updated when they log in (they'll be counted as registered first...)
756 myInfo(false);
757 }
758 } else if(cmd == "$To:") {
759 string::size_type i = param.find("From:");
760 if(i == string::npos)
761 return;
762
763 i+=6;
764 string::size_type j = param.find('$', i);
765 if(j == string::npos)
766 return;
767
768 string rtNick = param.substr(i, j - 1 - i);
769 if(rtNick.empty())
770 return;
771 i = j + 1;
772
773 if(param.size() < i + 3 || param[i] != '<')
774 return;
775
776 j = param.find('>', i);
777 if(j == string::npos)
778 return;
779
780 string fromNick = param.substr(i+1, j-i-1);
781 if(fromNick.empty() || param.size() < j + 2)
782 return;
783
784 ChatMessage message = { unescape(param.substr(j + 2)), findUser(fromNick), &getUser(getMyNick()), findUser(rtNick) };
785
786 if(!message.replyTo || !message.from) {
787 if(!message.replyTo) {
788 // Assume it's from the hub
789 OnlineUser& replyTo = getUser(rtNick);
790 replyTo.getIdentity().setHub(true);
791 replyTo.getIdentity().setHidden(true);
792 fire(ClientListener::UserUpdated(), this, replyTo);
793 }
794 if(!message.from) {
795 // Assume it's from the hub
796 OnlineUser& from = getUser(fromNick);
797 from.getIdentity().setHub(true);
798 from.getIdentity().setHidden(true);
799 fire(ClientListener::UserUpdated(), this, from);
800 }
801
802 // Update pointers just in case they've been invalidated
803 message.replyTo = findUser(rtNick);
804 message.from = findUser(fromNick);
805 }
806
807 fire(ClientListener::Message(), this, message);
808 } else if(cmd == "$GetPass") {
809 OnlineUser& ou = getUser(getMyNick());
810 ou.getIdentity().set("RG", "1");
811 setMyIdentity(ou.getIdentity());
812 fire(ClientListener::GetPassword(), this);
813 } else if(cmd == "$BadPass") {
814 setPassword(Util::emptyString);
815 } else if(cmd == "$ZOn") {
816 try {
817 sock->setMode(BufferedSocket::MODE_ZPIPE);
818 } catch (const Exception& e) {
819 dcdebug("NmdcHub::onLine %s failed with error: %s\n", cmd.c_str(), e.getError().c_str());
820 }
821 } else {
822 dcassert(cmd[0] == '$');
823 dcdebug("NmdcHub::onLine Unknown command %s\n", aLine.c_str());
824 }
825 }
826
checkNick(const string & aNick)827 string NmdcHub::checkNick(const string& aNick) {
828 string tmp = aNick;
829 for(size_t i = 0; i < aNick.size(); ++i) {
830 if(static_cast<uint8_t>(tmp[i]) <= 32 || tmp[i] == '|' || tmp[i] == '$' || tmp[i] == '<' || tmp[i] == '>') {
831 tmp[i] = '_';
832 }
833 }
834 return tmp;
835 }
836
connectToMe(const OnlineUser & aUser)837 void NmdcHub::connectToMe(const OnlineUser& aUser) {
838 checkstate();
839 dcdebug("NmdcHub::connectToMe %s\n", aUser.getIdentity().getNick().c_str());
840 string nick = fromUtf8(aUser.getIdentity().getNick());
841 ConnectionManager::getInstance()->nmdcExpect(nick, getMyNick(), getHubUrl());
842 bool secure = CryptoManager::getInstance()->TLSOk() && aUser.getUser()->isSet(User::TLS);
843 uint16_t port = secure ? ConnectionManager::getInstance()->getSecurePort() : ConnectionManager::getInstance()->getPort();
844 send("$ConnectToMe " + nick + " " + getLocalIp() + ":" + Util::toString(port) + (secure ? "S" : "") + "|");
845 }
846
revConnectToMe(const OnlineUser & aUser)847 void NmdcHub::revConnectToMe(const OnlineUser& aUser) {
848 checkstate();
849 dcdebug("NmdcHub::revConnectToMe %s\n", aUser.getIdentity().getNick().c_str());
850 send("$RevConnectToMe " + fromUtf8(getMyNick()) + " " + fromUtf8(aUser.getIdentity().getNick()) + "|");
851 }
852
hubMessage(const string & aMessage,bool thirdPerson)853 void NmdcHub::hubMessage(const string& aMessage, bool thirdPerson) {
854 checkstate();
855 send(fromUtf8( "<" + getMyNick() + "> " + escape(thirdPerson ? "/me " + aMessage : aMessage) + "|" ) );
856 }
857
myInfo(bool alwaysSend)858 void NmdcHub::myInfo(bool alwaysSend) {
859 checkstate();
860
861 reloadSettings(false);
862
863 char StatusMode = Identity::NORMAL;
864
865 char modeChar = '?';
866 if(SETTING(OUTGOING_CONNECTIONS) == SettingsManager::OUTGOING_SOCKS5)
867 modeChar = '5';
868 else if(isActive())
869 modeChar = 'A';
870 else
871 modeChar = 'P';
872 string uploadSpeed;
873 int upLimit = ThrottleManager::getInstance()->getUpLimit();
874 if (upLimit > 0 && BOOLSETTING(THROTTLE_ENABLE)) {
875 uploadSpeed = Util::toString(upLimit) + " KiB/s";
876 } else {
877 uploadSpeed = SETTING(UPLOAD_SPEED);
878 }
879 if(Util::getAway()) {
880 StatusMode |= Identity::AWAY;
881 }
882 if(BOOLSETTING(ALLOW_NATT) && !isActive()) {
883 StatusMode |= Identity::NAT;
884 }
885 if (CryptoManager::getInstance()->TLSOk()) {
886 StatusMode |= Identity::TLS;
887 }
888
889 bool gslotf = BOOLSETTING(SHOW_FREE_SLOTS_DESC);
890 string gslot = "["+Util::toString(UploadManager::getInstance()->getFreeSlots())+"]";
891 string uMin = (SETTING(MIN_UPLOAD_SPEED) == 0) ? Util::emptyString : ",O:" + Util::toString(SETTING(MIN_UPLOAD_SPEED));
892 string myInfoA =
893 "$MyINFO $ALL " + fromUtf8(getMyNick()) + " " +
894 fromUtf8(escape((gslotf ? gslot :"")+getCurrentDescription())) + " <"+ getClientId().c_str() + ",M:" + modeChar + ",H:" + getCounts();
895 string myInfoB = ",S:" + Util::toString(SETTING(SLOTS));
896 string myInfoC = uMin +
897 ">$ $" + uploadSpeed + StatusMode + "$" + fromUtf8(escape(SETTING(EMAIL))) + '$';
898 string myInfoD = ShareManager::getInstance()->getShareSizeString() + "$|";
899 // we always send A and C; however, B (slots) and D (share size) can frequently change so we delay them if needed
900 //printf("%s\n", (myInfoA + myInfoB + myInfoC + myInfoD).c_str());
901 if(lastMyInfoA != myInfoA || lastMyInfoC != myInfoC ||
902 alwaysSend || ((lastMyInfoB != myInfoB || lastMyInfoD != myInfoD) && lastUpdate + 15*60*1000 < GET_TICK())) {
903 dcdebug("MyInfo %s...\n", getMyNick().c_str());
904 send(myInfoA + myInfoB + myInfoC + myInfoD);
905 lastMyInfoA = myInfoA;
906 lastMyInfoB = myInfoB;
907 lastMyInfoC = myInfoC;
908 lastMyInfoD = myInfoD;
909 lastUpdate = GET_TICK();
910 }
911 }
912
search(int aSizeType,int64_t aSize,int aFileType,const string & aString,const string &,const StringList &)913 void NmdcHub::search(int aSizeType, int64_t aSize, int aFileType, const string& aString, const string&, const StringList&) {
914 checkstate();
915 char c1 = (aSizeType == SearchManager::SIZE_DONTCARE) ? 'F' : 'T';
916 char c2 = (aSizeType == SearchManager::SIZE_ATLEAST) ? 'F' : 'T';
917 string tmp = ((aFileType == SearchManager::TYPE_TTH) ? "TTH:" + aString : fromUtf8(escape(aString)));
918 string::size_type i;
919 while((i = tmp.find(' ')) != string::npos) {
920 tmp[i] = '$';
921 }
922 string tmp2;
923 if(isActive() && !BOOLSETTING(SEARCH_PASSIVE)) {
924 tmp2 = getLocalIp() + ':' + Util::toString(SearchManager::getInstance()->getPort());
925 } else {
926 tmp2 = "Hub:" + fromUtf8(getMyNick());
927 }
928 send("$Search " + tmp2 + ' ' + c1 + '?' + c2 + '?' + Util::toString(aSize) + '?' + Util::toString(aFileType+1) + '?' + tmp + '|');
929 }
930
validateMessage(string tmp,bool reverse)931 string NmdcHub::validateMessage(string tmp, bool reverse) {
932 string::size_type i = 0;
933
934 if(reverse) {
935 while( (i = tmp.find("$", i)) != string::npos) {
936 tmp.replace(i, 5, "$");
937 i++;
938 }
939 i = 0;
940 while( (i = tmp.find("|", i)) != string::npos) {
941 tmp.replace(i, 6, "|");
942 i++;
943 }
944 i = 0;
945 while( (i = tmp.find("&", i)) != string::npos) {
946 tmp.replace(i, 5, "&");
947 i++;
948 }
949 } else {
950 i = 0;
951 while( (i = tmp.find("&", i)) != string::npos) {
952 tmp.replace(i, 1, "&");
953 i += 4;
954 }
955 i = 0;
956 while( (i = tmp.find("$", i)) != string::npos) {
957 tmp.replace(i, 1, "&");
958 i += 4;
959 }
960 i = 0;
961 while( (i = tmp.find("|", i)) != string::npos) {
962 tmp.replace(i, 1, "&");
963 i += 4;
964 }
965 i = 0;
966 while( (i = tmp.find('$', i)) != string::npos) {
967 tmp.replace(i, 1, "$");
968 i += 4;
969 }
970 i = 0;
971 while( (i = tmp.find('|', i)) != string::npos) {
972 tmp.replace(i, 1, "|");
973 i += 5;
974 }
975 }
976 return tmp;
977 }
978
privateMessage(const string & nick,const string & message)979 void NmdcHub::privateMessage(const string& nick, const string& message) {
980 send("$To: " + fromUtf8(nick) + " From: " + fromUtf8(getMyNick()) + " $" + fromUtf8(escape("<" + getMyNick() + "> " + message)) + "|");
981 }
982
privateMessage(const OnlineUser & aUser,const string & aMessage,bool)983 void NmdcHub::privateMessage(const OnlineUser& aUser, const string& aMessage, bool /*thirdPerson*/) {
984 checkstate();
985
986 privateMessage(aUser.getIdentity().getNick(), aMessage);
987 // Emulate a returning message...
988 Lock l(cs);
989 OnlineUser* ou = findUser(getMyNick());
990 if(ou) {
991 ChatMessage message = { aMessage, ou, &aUser, ou };
992 fire(ClientListener::Message(), this, message);
993 }
994 }
995
sendUserCmd(const UserCommand & command,const StringMap & params)996 void NmdcHub::sendUserCmd(const UserCommand& command, const StringMap& params) {
997 checkstate();
998 string cmd = Util::formatParams(command.getCommand(), params, false);
999 if(command.isChat()) {
1000 if(command.getTo().empty()) {
1001 hubMessage(cmd);
1002 } else {
1003 privateMessage(command.getTo(), cmd);
1004 }
1005 } else {
1006 send(fromUtf8(cmd));
1007 }
1008 }
1009
clearFlooders(uint64_t aTick)1010 void NmdcHub::clearFlooders(uint64_t aTick) {
1011 while(!seekers.empty() && seekers.front().second + (5 * 1000) < aTick) {
1012 seekers.pop_front();
1013 }
1014
1015 while(!flooders.empty() && flooders.front().second + (120 * 1000) < aTick) {
1016 flooders.pop_front();
1017 }
1018 }
1019
on(Connected)1020 void NmdcHub::on(Connected) noexcept {
1021 Client::on(Connected());
1022 if(state != STATE_PROTOCOL) {
1023 return;
1024 }
1025 supportFlags = 0;
1026 lastMyInfoA.clear();
1027 lastMyInfoB.clear();
1028 lastMyInfoC.clear();
1029 lastMyInfoD.clear();
1030 lastUpdate = 0;
1031 }
1032
on(Line,const string & aLine)1033 void NmdcHub::on(Line, const string& aLine) noexcept {
1034 #ifdef LUA_SCRIPT
1035 if (onClientMessage(this, validateMessage(aLine, true)))
1036 return;
1037 #endif
1038 if (BOOLSETTING(NMDC_DEBUG))
1039 fire(ClientListener::StatusMessage(), this, "<NMDC>" + aLine + "</NMDC>");
1040 Client::on(Line(), aLine);
1041 onLine(aLine);
1042 }
1043
on(Failed,const string & aLine)1044 void NmdcHub::on(Failed, const string& aLine) noexcept {
1045 clearUsers();
1046 Client::on(Failed(), aLine);
1047 }
1048
on(Second,uint64_t aTick)1049 void NmdcHub::on(Second, uint64_t aTick) noexcept {
1050 Client::on(Second(), aTick);
1051
1052 if(state == STATE_NORMAL && (aTick > (getLastActivity() + 120*1000)) ) {
1053 send("|", 1);
1054 }
1055 }
1056
1057 #ifdef LUA_SCRIPT
onClientMessage(NmdcHub * aClient,const string & aLine)1058 bool NmdcHubScriptInstance::onClientMessage(NmdcHub* aClient, const string& aLine) {
1059 Lock l(cs);
1060 MakeCall("nmdch", "DataArrival", 1, aClient, aLine);
1061 return GetLuaBool();
1062 }
1063 #endif
1064 } // namespace dcpp
1065