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("&#36;", i)) != string::npos) {
936             tmp.replace(i, 5, "$");
937             i++;
938         }
939         i = 0;
940         while( (i = tmp.find("&#124;", i)) != string::npos) {
941             tmp.replace(i, 6, "|");
942             i++;
943         }
944         i = 0;
945         while( (i = tmp.find("&amp;", i)) != string::npos) {
946             tmp.replace(i, 5, "&");
947             i++;
948         }
949     } else {
950         i = 0;
951         while( (i = tmp.find("&amp;", i)) != string::npos) {
952             tmp.replace(i, 1, "&amp;");
953             i += 4;
954         }
955         i = 0;
956         while( (i = tmp.find("&#36;", i)) != string::npos) {
957             tmp.replace(i, 1, "&amp;");
958             i += 4;
959         }
960         i = 0;
961         while( (i = tmp.find("&#124;", i)) != string::npos) {
962             tmp.replace(i, 1, "&amp;");
963             i += 4;
964         }
965         i = 0;
966         while( (i = tmp.find('$', i)) != string::npos) {
967             tmp.replace(i, 1, "&#36;");
968             i += 4;
969         }
970         i = 0;
971         while( (i = tmp.find('|', i)) != string::npos) {
972             tmp.replace(i, 1, "&#124;");
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