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