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 "SearchManager.h"
22 #include "UploadManager.h"
23 #include "format.h"
24 #include "ClientManager.h"
25 #include "ShareManager.h"
26 #include "SearchResult.h"
27 #include "LogManager.h"
28 #include "QueueManager.h"
29 #include "QueueItem.h"
30 #include "StringTokenizer.h"
31 #include "FinishedManager.h"
32 
33 namespace dcpp {
34 
35 const char* SearchManager::types[TYPE_LAST] = {
36         N_("Any"),
37         N_("Audio"),
38         N_("Compressed"),
39         N_("Document"),
40         N_("Executable"),
41         N_("Picture"),
42         N_("Video"),
43         N_("Directory"),
44         N_("TTH"),
45         N_("CD Image")
46 };
getTypeStr(int type)47 const char* SearchManager::getTypeStr(int type) {
48     return _(types[type]);
49 }
50 
SearchManager()51 SearchManager::SearchManager() :
52     port(0),
53     stop(false)
54 {
55     queue.start();
56 }
57 
~SearchManager()58 SearchManager::~SearchManager() {
59     if(socket.get()) {
60         stop = true;
61         socket->disconnect();
62 #ifdef _WIN32
63         join();
64 #endif
65     }
66 }
67 
normalizeWhitespace(const string & aString)68 string SearchManager::normalizeWhitespace(const string& aString){
69     string::size_type found = 0;
70     string normalized = aString;
71     while((found = normalized.find_first_of("\t\n\r", found)) != string::npos) {
72         normalized[found] = ' ';
73         found++;
74     }
75     return normalized;
76 }
77 
search(const string & aName,int64_t aSize,TypeModes aTypeMode,SizeModes aSizeMode,const string & aToken,void * aOwner)78 void SearchManager::search(const string& aName, int64_t aSize, TypeModes aTypeMode /* = TYPE_ANY */, SizeModes aSizeMode /* = SIZE_ATLEAST */, const string& aToken /* = Util::emptyString */, void* aOwner /* = NULL */) {
79     ClientManager::getInstance()->search(aSizeMode, aSize, aTypeMode, normalizeWhitespace(aName), aToken, aOwner);
80 }
81 
search(StringList & who,const string & aName,int64_t aSize,TypeModes aTypeMode,SizeModes aSizeMode,const string & aToken,const StringList & aExtList,void * aOwner)82 uint64_t SearchManager::search(StringList& who, const string& aName, int64_t aSize /* = 0 */, TypeModes aTypeMode /* = TYPE_ANY */, SizeModes aSizeMode /* = SIZE_ATLEAST */, const string& aToken /* = Util::emptyString */, const StringList& aExtList, void* aOwner /* = NULL */) {
83     return ClientManager::getInstance()->search(who, aSizeMode, aSize, aTypeMode, normalizeWhitespace(aName), aToken, aExtList, aOwner);
84 }
85 
listen()86 void SearchManager::listen() {
87 
88     disconnect();
89 
90     try {
91         socket.reset(new Socket);
92         socket->create(Socket::TYPE_UDP);
93         socket->setBlocking(true);
94         socket->setSocketOpt(SO_REUSEADDR, 1);
95         port = socket->bind(static_cast<uint16_t>(SETTING(UDP_PORT)), SETTING(BIND_IFACE)? socket->getIfaceI4(SETTING(BIND_IFACE_NAME)).c_str() : SETTING(BIND_ADDRESS));
96         start();
97     } catch(...) {
98         socket.reset();
99         throw;
100     }
101 }
102 
disconnect()103 void SearchManager::disconnect() noexcept {
104     if(socket.get()) {
105         stop = true;
106         queue.shutdown();
107         socket->disconnect();
108         port = 0;
109 
110         join();
111 
112         socket.reset();
113 
114         stop = false;
115     }
116 }
117 
118 #define BUFSIZE 8192
run()119 int SearchManager::run() {
120     setThreadName("SearchManager");
121     boost::scoped_array<uint8_t> buf(new uint8_t[BUFSIZE]);
122     int len;
123     sockaddr_in remoteAddr = { 0 };
124 
125     while(!stop) {
126         try {
127             if (!socket.get()) {
128                 continue;
129             }
130             if(socket->wait(400, Socket::WAIT_READ) != Socket::WAIT_READ) {
131                 continue;
132             }
133             if ((len = socket->read(&buf[0], BUFSIZE, remoteAddr)) > 0) {
134                 onData(&buf[0], len, inet_ntoa(remoteAddr.sin_addr));
135                 continue;
136             }
137         } catch(const SocketException& e) {
138             dcdebug("SearchManager::run Error: %s\n", e.getError().c_str());
139         }
140 
141         bool failed = false;
142         while(!stop) {
143             try {
144                 socket->disconnect();
145                 socket->create(Socket::TYPE_UDP);
146                 socket->setBlocking(true);
147                 socket->bind(port, SETTING(BIND_ADDRESS));
148                 if(failed) {
149                     LogManager::getInstance()->message(_("Search enabled again"));
150                     failed = false;
151                 }
152                 break;
153             } catch(const SocketException& e) {
154                 dcdebug("SearchManager::run Stopped listening: %s\n", e.getError().c_str());
155 
156                 if(!failed) {
157                     LogManager::getInstance()->message(str(F_("Search disabled: %1%") % e.getError()));
158                     failed = true;
159                 }
160 
161                 // Spin for 60 seconds
162                 for(int i = 0; i < 60 && !stop; ++i) {
163                     Thread::sleep(1000);
164                 }
165             }
166         }
167     }
168     return 0;
169 }
170 
run()171 int SearchManager::UdpQueue::run() {
172     setThreadName("UdpQueue");
173     string x = Util::emptyString;
174     string remoteIp = Util::emptyString;
175     stop = false;
176 ;
177     while(true) {
178         if (resultList.empty())
179             s.wait();
180 
181         if(stop)
182             break;
183 
184         {
185             Lock l(csudp);
186             if(resultList.empty()) continue;
187 
188             x = resultList.front().first;
189             remoteIp = resultList.front().second;
190             resultList.pop_front();
191         }
192 
193     if(x.compare(0, 4, "$SR ") == 0) {
194         string::size_type i, j;
195         // Directories: $SR <nick><0x20><directory><0x20><free slots>/<total slots><0x05><Hubname><0x20>(<Hubip:port>)
196         // Files:       $SR <nick><0x20><filename><0x05><filesize><0x20><free slots>/<total slots><0x05><Hubname><0x20>(<Hubip:port>)
197         i = 4;
198         if( (j = x.find(' ', i)) == string::npos) {
199             continue;
200         }
201         string nick = x.substr(i, j-i);
202         i = j + 1;
203 
204         // A file has 2 0x05, a directory only one
205         size_t cnt = count(x.begin() + j, x.end(), 0x05);
206 
207         SearchResult::Types type = SearchResult::TYPE_FILE;
208         string file;
209         int64_t size = 0;
210 
211         if(cnt == 1) {
212             // We have a directory...find the first space beyond the first 0x05 from the back
213             // (dirs might contain spaces as well...clever protocol, eh?)
214             type = SearchResult::TYPE_DIRECTORY;
215             // Get past the hubname that might contain spaces
216             if((j = x.rfind(0x05)) == string::npos) {
217                 continue;
218             }
219             // Find the end of the directory info
220             if((j = x.rfind(' ', j-1)) == string::npos) {
221                 continue;
222             }
223             if(j < i + 1) {
224                 continue;
225             }
226             file = x.substr(i, j-i) + '\\';
227         } else if(cnt == 2) {
228             if( (j = x.find((char)5, i)) == string::npos) {
229                 continue;
230             }
231             file = x.substr(i, j-i);
232             i = j + 1;
233             if( (j = x.find(' ', i)) == string::npos) {
234                 continue;
235             }
236             size = Util::toInt64(x.substr(i, j-i));
237         }
238         i = j + 1;
239 
240         if( (j = x.find('/', i)) == string::npos) {
241             continue;
242         }
243         uint8_t freeSlots = (uint8_t)Util::toInt(x.substr(i, j-i));
244         i = j + 1;
245         if( (j = x.find((char)5, i)) == string::npos) {
246             continue;
247         }
248         uint8_t slots = (uint8_t)Util::toInt(x.substr(i, j-i));
249         i = j + 1;
250         if( (j = x.rfind(" (")) == string::npos) {
251             continue;
252         }
253         string hubName = x.substr(i, j-i);
254         i = j + 2;
255         if( (j = x.rfind(')')) == string::npos) {
256             continue;
257         }
258 
259         string hubIpPort = x.substr(i, j-i);
260         string url = ClientManager::getInstance()->findHub(hubIpPort);
261 
262         string encoding = ClientManager::getInstance()->findHubEncoding(url);
263         nick = Text::toUtf8(nick, encoding);
264         file = Text::toUtf8(file, encoding);
265         hubName = Text::toUtf8(hubName, encoding);
266 
267         UserPtr user = ClientManager::getInstance()->findUser(nick, url);
268         if(!user) {
269             // Could happen if hub has multiple URLs / IPs
270             user = ClientManager::getInstance()->findLegacyUser(nick);
271             if(!user)
272                 continue;
273         }
274 
275         ClientManager::getInstance()->setIPUser(user, remoteIp);
276 
277         string tth;
278         if(hubName.compare(0, 4, "TTH:") == 0) {
279             tth = hubName.substr(4);
280             StringList names = ClientManager::getInstance()->getHubNames(user->getCID(), Util::emptyString);
281             hubName = names.empty() ? _("Offline") : Util::toString(names);
282         }
283 
284         if(tth.empty() && type == SearchResult::TYPE_FILE) {
285             continue;
286         }
287 
288         SearchResultPtr sr(new SearchResult(user, type, slots, freeSlots, size,
289                         file, hubName, url, remoteIp, TTHValue(tth), Util::emptyString));
290         SearchManager::getInstance()->fire(SearchManagerListener::SR(), sr);
291 
292     } else if(x.compare(1, 4, "RES ") == 0 && x[x.length() - 1] == 0x0a) {
293         AdcCommand c(x.substr(0, x.length()-1));
294         if(c.getParameters().empty())
295             continue;
296         string cid = c.getParam(0);
297         if(cid.size() != 39)
298             continue;
299 
300         UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
301         if(!user)
302             continue;
303 
304         // This should be handled by AdcCommand really...
305         c.getParameters().erase(c.getParameters().begin());
306 
307         SearchManager::getInstance()->onRES(c, user, remoteIp);
308 
309     } if(x.compare(1, 4, "PSR ") == 0 && x[x.length() - 1] == 0x0a) {
310             AdcCommand c(x.substr(0, x.length()-1));
311             if(c.getParameters().empty())
312                     continue;
313             string cid = c.getParam(0);
314             if(cid.size() != 39)
315                     continue;
316 
317             UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
318             // when user == NULL then it is probably NMDC user, check it later
319 
320             c.getParameters().erase(c.getParameters().begin());
321 
322             SearchManager::getInstance()->onPSR(c, user, remoteIp);
323 
324     } /*else if(x.compare(1, 4, "SCH ") == 0 && x[x.length() - 1] == 0x0a) {
325         try {
326             respond(AdcCommand(x.substr(0, x.length()-1)));
327         } catch(ParseException& ) {
328         }
329     }*/ // Needs further DoS investigation
330 
331 
332                 Thread::sleep(10);
333         }
334         return 0;
335 }
336 
onData(const uint8_t * buf,size_t aLen,const string & remoteIp)337 void SearchManager::onData(const uint8_t* buf, size_t aLen, const string& remoteIp) {
338     string x((char*)buf, aLen);
339     queue.addResult(x, remoteIp);
340 }
341 
onRES(const AdcCommand & cmd,const UserPtr & from,const string & remoteIp)342 void SearchManager::onRES(const AdcCommand& cmd, const UserPtr& from, const string& remoteIp) {
343     int freeSlots = -1;
344     int64_t size = -1;
345     string file;
346     string tth;
347     string token;
348 
349     for(StringIterC i = cmd.getParameters().begin(); i != cmd.getParameters().end(); ++i) {
350         const string& str = *i;
351         if(str.compare(0, 2, "FN") == 0) {
352             file = Util::toNmdcFile(str.substr(2));
353         } else if(str.compare(0, 2, "SL") == 0) {
354             freeSlots = Util::toInt(str.substr(2));
355         } else if(str.compare(0, 2, "SI") == 0) {
356             size = Util::toInt64(str.substr(2));
357         } else if(str.compare(0, 2, "TR") == 0) {
358             tth = str.substr(2);
359         } else if(str.compare(0, 2, "TO") == 0) {
360             token = str.substr(2);
361         }
362     }
363 
364     if(!file.empty() && freeSlots != -1 && size != -1) {
365 
366         /// @todo get the hub this was sent from, to be passed as a hint? (eg by using the token?)
367         StringList names = ClientManager::getInstance()->getHubNames(from->getCID(), Util::emptyString);
368         string hubName = names.empty() ? _("Offline") : Util::toString(names);
369         StringList hubs = ClientManager::getInstance()->getHubs(from->getCID(), Util::emptyString);
370         string hub = hubs.empty() ? _("Offline") : Util::toString(hubs);
371 
372         SearchResult::Types type = (file[file.length() - 1] == '\\' ? SearchResult::TYPE_DIRECTORY : SearchResult::TYPE_FILE);
373         if(type == SearchResult::TYPE_FILE && tth.empty())
374                 return;
375 
376         uint8_t slots = ClientManager::getInstance()->getSlots(from->getCID());
377         SearchResultPtr sr(new SearchResult(from, type, slots, (uint8_t)freeSlots, size,
378                 file, hubName, hub, remoteIp, TTHValue(tth), token));
379         fire(SearchManagerListener::SR(), sr);
380     }
381 }
382 
onPSR(const AdcCommand & cmd,UserPtr from,const string & remoteIp)383 void SearchManager::onPSR(const AdcCommand& cmd, UserPtr from, const string& remoteIp) {
384 
385     uint16_t udpPort = 0;
386     uint32_t partialCount = 0;
387     string tth;
388     string hubIpPort;
389     string nick;
390     PartsInfo partialInfo;
391 
392     for(StringIterC i = cmd.getParameters().begin(); i != cmd.getParameters().end(); ++i) {
393         const string& str = *i;
394         if(str.compare(0, 2, "U4") == 0) {
395             udpPort = static_cast<uint16_t>(Util::toInt(str.substr(2)));
396         } else if(str.compare(0, 2, "NI") == 0) {
397             nick = str.substr(2);
398         } else if(str.compare(0, 2, "HI") == 0) {
399             hubIpPort = str.substr(2);
400         } else if(str.compare(0, 2, "TR") == 0) {
401             tth = str.substr(2);
402         } else if(str.compare(0, 2, "PC") == 0) {
403             partialCount = Util::toUInt32(str.substr(2))*2;
404         } else if(str.compare(0, 2, "PI") == 0) {
405             StringTokenizer<string> tok(str.substr(2), ',');
406             for(StringIter i = tok.getTokens().begin(); i != tok.getTokens().end(); ++i) {
407                 partialInfo.push_back((uint16_t)Util::toInt(*i));
408             }
409         }
410     }
411 
412     string url = ClientManager::getInstance()->findHub(hubIpPort);
413     if(!from || from == ClientManager::getInstance()->getMe()) {
414         // for NMDC support
415 
416         if(nick.empty() || hubIpPort.empty()) {
417             return;
418         }
419 
420         from = ClientManager::getInstance()->findUser(nick, url);
421         if(!from) {
422             // Could happen if hub has multiple URLs / IPs
423             from = ClientManager::getInstance()->findLegacyUser(nick);
424             if(!from) {
425                 dcdebug("Search result from unknown user");
426                 return;
427             }
428         }
429     }
430 
431     ClientManager::getInstance()->setIPUser(from, remoteIp, udpPort);
432 
433     if(partialInfo.size() != partialCount) {
434         // what to do now ? just ignore partial search result :-/
435         return;
436     }
437 
438     PartsInfo outPartialInfo;
439     QueueItem::PartialSource ps(from->isNMDC() ? ClientManager::getInstance()->getClient(url)->getMyIdentity().getNick() : Util::emptyString, hubIpPort, remoteIp, udpPort);
440     ps.setPartialInfo(partialInfo);
441 
442     QueueManager::getInstance()->handlePartialResult(from, url, TTHValue(tth), ps, outPartialInfo);
443 
444     if((udpPort > 0) && !outPartialInfo.empty()) {
445         try {
446             AdcCommand cmd = SearchManager::getInstance()->toPSR(false, ps.getMyNick(), hubIpPort, tth, outPartialInfo);
447             ClientManager::getInstance()->send(cmd, from->getCID());
448         } catch(...) {
449             dcdebug("Partial search caught error\n");
450         }
451     }
452 
453 }
454 
respond(const AdcCommand & adc,const CID & from,bool isUdpActive,const string & hubIpPort)455 void SearchManager::respond(const AdcCommand& adc, const CID& from,  bool isUdpActive, const string& hubIpPort) {
456     // Filter own searches
457     if(from == ClientManager::getInstance()->getMe()->getCID())
458         return;
459 
460     UserPtr p = ClientManager::getInstance()->findUser(from);
461     if(!p)
462         return;
463 
464     SearchResultList results;
465     ShareManager::getInstance()->search(results, adc.getParameters(), isUdpActive ? 10 : 5);
466 
467     string token;
468 
469     adc.getParam("TO", 0, token);
470 
471     // TODO: don't send replies to passive users
472     if(results.empty()) {
473         string tth;
474         if(!adc.getParam("TR", 0, tth))
475             return;
476 
477         PartsInfo partialInfo;
478         if(!QueueManager::getInstance()->handlePartialSearch(TTHValue(tth), partialInfo)) {
479             // if not found, try to find in finished list
480             if(!FinishedManager::getInstance()->handlePartialRequest(TTHValue(tth), partialInfo)) {
481                 return;
482             }
483         }
484 
485         AdcCommand cmd = toPSR(true, Util::emptyString, hubIpPort, tth, partialInfo);
486         ClientManager::getInstance()->send(cmd, from);
487         return;
488     }
489 
490     for(SearchResultList::const_iterator i = results.begin(); i != results.end(); ++i) {
491         AdcCommand cmd = (*i)->toRES(AdcCommand::TYPE_UDP);
492         if(!token.empty())
493             cmd.addParam("TO", token);
494         ClientManager::getInstance()->send(cmd, from);
495     }
496 }
497 
getPartsString(const PartsInfo & partsInfo) const498 string SearchManager::getPartsString(const PartsInfo& partsInfo) const {
499     string ret;
500 
501     for(PartsInfo::const_iterator i = partsInfo.begin(); i < partsInfo.end(); i+=2){
502         ret += Util::toString(*i) + "," + Util::toString(*(i+1)) + ",";
503     }
504 
505     return ret.substr(0, ret.size()-1);
506 }
507 
toPSR(bool wantResponse,const string & myNick,const string & hubIpPort,const string & tth,const vector<uint16_t> & partialInfo) const508 AdcCommand SearchManager::toPSR(bool wantResponse, const string& myNick, const string& hubIpPort, const string& tth, const vector<uint16_t>& partialInfo) const {
509     AdcCommand cmd(AdcCommand::CMD_PSR, AdcCommand::TYPE_UDP);
510 
511     if(!myNick.empty())
512         cmd.addParam("NI", Text::utf8ToAcp(myNick));
513 
514     cmd.addParam("HI", hubIpPort);
515     cmd.addParam("U4", Util::toString(wantResponse && ClientManager::getInstance()->isActive(hubIpPort) ? SearchManager::getInstance()->getPort() : 0));
516     cmd.addParam("TR", tth);
517     cmd.addParam("PC", Util::toString(partialInfo.size() / 2));
518     cmd.addParam("PI", getPartsString(partialInfo));
519 
520     return cmd;
521 }
522 
523 } // namespace dcpp
524