Reseed.cpp
1 /* 2 * Copyright (c) 2013-2026, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 */ 8 9 #include <string.h> 10 #include <fstream> 11 #include <sstream> 12 #include <boost/asio.hpp> 13 #include <boost/asio/ssl.hpp> 14 #include <boost/algorithm/string.hpp> 15 #include <openssl/ssl.h> 16 #include <openssl/err.h> 17 #if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 18 #include <openssl/core_names.h> 19 #endif 20 #include <zlib.h> 21 22 #include "Crypto.h" 23 #include "I2PEndian.h" 24 #include "Reseed.h" 25 #include "FS.h" 26 #include "Log.h" 27 #include "Identity.h" 28 #include "NetDb.hpp" 29 #include "HTTP.h" 30 #include "util.h" 31 #include "Config.h" 32 #include "Socks5.h" 33 34 namespace i2p 35 { 36 namespace data 37 { 38 39 Reseeder::Reseeder() 40 { 41 } 42 43 Reseeder::~Reseeder() 44 { 45 } 46 47 /** 48 @brief tries to bootstrap into I2P network (from local files and servers, with respect of options) 49 */ 50 void Reseeder::Bootstrap () 51 { 52 std::string su3FileName; i2p::config::GetOption("reseed.file", su3FileName); 53 std::string zipFileName; i2p::config::GetOption("reseed.zipfile", zipFileName); 54 55 if (su3FileName.length() > 0) // bootstrap from SU3 file or URL 56 { 57 int num; 58 if (su3FileName.length() > 8 && su3FileName.substr(0, 8) == "https://") 59 { 60 num = ReseedFromSU3Url (su3FileName); // from https URL 61 } 62 else 63 { 64 num = ProcessSU3File (su3FileName.c_str ()); 65 } 66 if (num == 0) 67 LogPrint (eLogWarning, "Reseed: Failed to reseed from ", su3FileName); 68 } 69 else if (zipFileName.length() > 0) // bootstrap from ZIP file 70 { 71 int num = ProcessZIPFile (zipFileName.c_str ()); 72 if (num == 0) 73 LogPrint (eLogWarning, "Reseed: Failed to reseed from ", zipFileName); 74 } 75 else // bootstrap from reseed servers 76 { 77 int num = ReseedFromServers (); 78 if (num == 0) 79 LogPrint (eLogWarning, "Reseed: Failed to reseed from servers"); 80 } 81 } 82 83 /** 84 * @brief bootstrap from random server, retry 10 times 85 * @return number of entries added to netDb 86 */ 87 int Reseeder::ReseedFromServers () 88 { 89 bool ipv6; i2p::config::GetOption("ipv6", ipv6); 90 bool ipv4; i2p::config::GetOption("ipv4", ipv4); 91 bool yggdrasil; i2p::config::GetOption("meshnets.yggdrasil", yggdrasil); 92 93 std::vector<std::string> httpsReseedHostList; 94 if (ipv4 || ipv6) 95 { 96 std::string reseedURLs; i2p::config::GetOption("reseed.urls", reseedURLs); 97 if (!reseedURLs.empty ()) 98 boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); 99 } 100 101 std::vector<std::string> yggReseedHostList; 102 if (yggdrasil && !i2p::util::net::GetYggdrasilAddress ().is_unspecified ()) 103 { 104 LogPrint (eLogInfo, "Reseed: Yggdrasil is supported"); 105 std::string yggReseedURLs; i2p::config::GetOption("reseed.yggurls", yggReseedURLs); 106 if (!yggReseedURLs.empty ()) 107 boost::split(yggReseedHostList, yggReseedURLs, boost::is_any_of(","), boost::token_compress_on); 108 } 109 110 if (httpsReseedHostList.empty () && yggReseedHostList.empty()) 111 { 112 LogPrint (eLogWarning, "Reseed: No reseed servers specified"); 113 return 0; 114 } 115 116 int reseedRetries = 0; 117 while (reseedRetries < 10) 118 { 119 auto ind = rand () % (httpsReseedHostList.size () + yggReseedHostList.size ()); 120 bool isHttps = ind < httpsReseedHostList.size (); 121 std::string reseedUrl = isHttps ? httpsReseedHostList[ind] : 122 yggReseedHostList[ind - httpsReseedHostList.size ()]; 123 reseedUrl += "i2pseeds.su3"; 124 auto num = ReseedFromSU3Url (reseedUrl, isHttps); 125 if (num > 0) return num; // success 126 reseedRetries++; 127 } 128 LogPrint (eLogWarning, "Reseed: Failed to reseed from servers after 10 attempts"); 129 return 0; 130 } 131 132 /** 133 * @brief bootstrap from HTTPS URL with SU3 file 134 * @param url 135 * @return number of entries added to netDb 136 */ 137 int Reseeder::ReseedFromSU3Url (const std::string& url, bool isHttps) 138 { 139 LogPrint (eLogInfo, "Reseed: Downloading SU3 from ", url); 140 std::string su3 = isHttps ? HttpsRequest (url) : YggdrasilRequest (url); 141 if (su3.length () > 0) 142 { 143 std::stringstream s(su3); 144 return ProcessSU3Stream (s); 145 } 146 else 147 { 148 LogPrint (eLogWarning, "Reseed: SU3 download failed"); 149 return 0; 150 } 151 } 152 153 int Reseeder::ProcessSU3File (const char * filename) 154 { 155 std::ifstream s(filename, std::ifstream::binary); 156 if (s.is_open ()) 157 return ProcessSU3Stream (s); 158 else 159 { 160 LogPrint (eLogCritical, "Reseed: Can't open file ", filename); 161 return 0; 162 } 163 } 164 165 int Reseeder::ProcessZIPFile (const char * filename) 166 { 167 std::ifstream s(filename, std::ifstream::binary); 168 if (s.is_open ()) 169 { 170 s.seekg (0, std::ios::end); 171 auto len = s.tellg (); 172 s.seekg (0, std::ios::beg); 173 return ProcessZIPStream (s, len); 174 } 175 else 176 { 177 LogPrint (eLogCritical, "Reseed: Can't open file ", filename); 178 return 0; 179 } 180 } 181 182 const char SU3_MAGIC_NUMBER[]="I2Psu3"; 183 int Reseeder::ProcessSU3Stream (std::istream& s) 184 { 185 char magicNumber[7]; 186 s.read (magicNumber, 7); // magic number and zero byte 6 187 if (strcmp (magicNumber, SU3_MAGIC_NUMBER)) 188 { 189 LogPrint (eLogError, "Reseed: Unexpected SU3 magic number"); 190 return 0; 191 } 192 s.seekg (1, std::ios::cur); // su3 file format version 193 SigningKeyType signatureType; 194 s.read ((char *)&signatureType, 2); // signature type 195 signatureType = be16toh (signatureType); 196 uint16_t signatureLength; 197 s.read ((char *)&signatureLength, 2); // signature length 198 signatureLength = be16toh (signatureLength); 199 s.seekg (1, std::ios::cur); // unused 200 uint8_t versionLength; 201 s.read ((char *)&versionLength, 1); // version length 202 s.seekg (1, std::ios::cur); // unused 203 uint8_t signerIDLength; 204 s.read ((char *)&signerIDLength, 1); // signer ID length 205 uint64_t contentLength; 206 s.read ((char *)&contentLength, 8); // content length 207 contentLength = be64toh (contentLength); 208 s.seekg (1, std::ios::cur); // unused 209 uint8_t fileType; 210 s.read ((char *)&fileType, 1); // file type 211 if (fileType != 0x00) // zip file 212 { 213 LogPrint (eLogError, "Reseed: Can't handle file type ", (int)fileType); 214 return 0; 215 } 216 s.seekg (1, std::ios::cur); // unused 217 uint8_t contentType; 218 s.read ((char *)&contentType, 1); // content type 219 if (contentType != 0x03) // reseed data 220 { 221 LogPrint (eLogError, "Reseed: Unexpected content type ", (int)contentType); 222 return 0; 223 } 224 s.seekg (12, std::ios::cur); // unused 225 226 s.seekg (versionLength, std::ios::cur); // skip version 227 char signerID[256]; 228 s.read (signerID, signerIDLength); // signerID 229 signerID[signerIDLength] = 0; 230 231 bool verify; i2p::config::GetOption("reseed.verify", verify); 232 if (verify) 233 { 234 //try to verify signature 235 auto it = m_SigningKeys.find (signerID); 236 if (it != m_SigningKeys.end ()) 237 { 238 // TODO: implement all signature types 239 if (signatureType == SIGNING_KEY_TYPE_RSA_SHA512_4096) 240 { 241 size_t pos = s.tellg (); 242 size_t tbsLen = pos + contentLength; 243 uint8_t * tbs = new uint8_t[tbsLen]; 244 s.seekg (0, std::ios::beg); 245 s.read ((char *)tbs, tbsLen); 246 uint8_t * signature = new uint8_t[signatureLength]; 247 s.read ((char *)signature, signatureLength); 248 // RSA-raw 249 { 250 // calculate digest 251 uint8_t digest[64]; 252 SHA512 (tbs, tbsLen, digest); 253 // encrypt signature 254 BN_CTX * bnctx = BN_CTX_new (); 255 BIGNUM * s = BN_new (), * n = BN_new (); 256 BN_bin2bn (signature, signatureLength, s); 257 BN_bin2bn (it->second, 512, n); // RSA 4096 assumed 258 BN_mod_exp (s, s, i2p::crypto::GetRSAE (), n, bnctx); // s = s^e mod n 259 uint8_t * enSigBuf = new uint8_t[signatureLength]; 260 i2p::crypto::bn2buf (s, enSigBuf, signatureLength); 261 // digest is right aligned 262 // we can't use RSA_verify due wrong padding in SU3 263 if (memcmp (enSigBuf + (signatureLength - 64), digest, 64)) 264 LogPrint (eLogWarning, "Reseed: SU3 signature verification failed"); 265 else 266 verify = false; // verified 267 delete[] enSigBuf; 268 BN_free (s); BN_free (n); 269 BN_CTX_free (bnctx); 270 } 271 272 delete[] signature; 273 delete[] tbs; 274 s.seekg (pos, std::ios::beg); 275 } 276 else 277 LogPrint (eLogWarning, "Reseed: Signature type ", signatureType, " is not supported"); 278 } 279 else 280 LogPrint (eLogWarning, "Reseed: Certificate for ", signerID, " not loaded"); 281 } 282 283 if (verify) // not verified 284 { 285 LogPrint (eLogCritical, "Reseed: SU3 verification failed"); 286 return 0; 287 } 288 289 // handle content 290 return ProcessZIPStream (s, contentLength); 291 } 292 293 const uint32_t ZIP_HEADER_SIGNATURE = 0x04034B50; 294 const uint32_t ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014B50; 295 const uint16_t ZIP_BIT_FLAG_DATA_DESCRIPTOR = 0x0008; 296 int Reseeder::ProcessZIPStream (std::istream& s, uint64_t contentLength) 297 { 298 int numFiles = 0; 299 size_t contentPos = s.tellg (); 300 while (!s.eof ()) 301 { 302 uint32_t signature; 303 s.read ((char *)&signature, 4); 304 signature = le32toh (signature); 305 if (signature == ZIP_HEADER_SIGNATURE) 306 { 307 // next local file 308 s.seekg (2, std::ios::cur); // version 309 uint16_t bitFlag; 310 s.read ((char *)&bitFlag, 2); 311 bitFlag = le16toh (bitFlag); 312 uint16_t compressionMethod; 313 s.read ((char *)&compressionMethod, 2); 314 compressionMethod = le16toh (compressionMethod); 315 s.seekg (4, std::ios::cur); // skip fields we don't care about 316 uint32_t compressedSize, uncompressedSize; 317 uint32_t crc_32; 318 s.read ((char *)&crc_32, 4); 319 crc_32 = le32toh (crc_32); 320 s.read ((char *)&compressedSize, 4); 321 compressedSize = le32toh (compressedSize); 322 s.read ((char *)&uncompressedSize, 4); 323 uncompressedSize = le32toh (uncompressedSize); 324 uint16_t fileNameLength, extraFieldLength; 325 s.read ((char *)&fileNameLength, 2); 326 fileNameLength = le16toh (fileNameLength); 327 if ( fileNameLength >= 255 ) { 328 // too big 329 LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength); 330 return numFiles; 331 } 332 s.read ((char *)&extraFieldLength, 2); 333 extraFieldLength = le16toh (extraFieldLength); 334 char localFileName[255]; 335 s.read (localFileName, fileNameLength); 336 localFileName[fileNameLength] = 0; 337 s.seekg (extraFieldLength, std::ios::cur); 338 // take care about data descriptor if presented 339 if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR) 340 { 341 size_t pos = s.tellg (); 342 if (!FindZipDataDescriptor (s)) 343 { 344 LogPrint (eLogError, "Reseed: SU3 archive data descriptor not found"); 345 return numFiles; 346 } 347 s.read ((char *)&crc_32, 4); 348 crc_32 = le32toh (crc_32); 349 s.read ((char *)&compressedSize, 4); 350 compressedSize = le32toh (compressedSize) + 4; // ??? we must consider signature as part of compressed data 351 s.read ((char *)&uncompressedSize, 4); 352 uncompressedSize = le32toh (uncompressedSize); 353 354 // now we know compressed and uncompressed size 355 s.seekg (pos, std::ios::beg); // back to compressed data 356 } 357 358 LogPrint (eLogDebug, "Reseed: Processing file ", localFileName, " ", compressedSize, " bytes"); 359 if (!compressedSize) 360 { 361 LogPrint (eLogWarning, "Reseed: Unexpected size 0. Skipped"); 362 continue; 363 } 364 365 uint8_t * compressed = new uint8_t[compressedSize]; 366 s.read ((char *)compressed, compressedSize); 367 if (compressionMethod) // we assume Deflate 368 { 369 z_stream inflator; 370 memset (&inflator, 0, sizeof (inflator)); 371 inflateInit2 (&inflator, -MAX_WBITS); // no zlib header 372 uint8_t * uncompressed = new uint8_t[uncompressedSize]; 373 inflator.next_in = compressed; 374 inflator.avail_in = compressedSize; 375 inflator.next_out = uncompressed; 376 inflator.avail_out = uncompressedSize; 377 int err; 378 if ((err = inflate (&inflator, Z_SYNC_FLUSH)) >= 0) 379 { 380 uncompressedSize -= inflator.avail_out; 381 if (crc32 (0, uncompressed, uncompressedSize) == crc_32) 382 { 383 i2p::data::netdb.AddRouterInfo (uncompressed, uncompressedSize); 384 numFiles++; 385 } 386 else 387 LogPrint (eLogError, "Reseed: CRC32 verification failed"); 388 } 389 else 390 LogPrint (eLogError, "Reseed: SU3 decompression error ", err); 391 delete[] uncompressed; 392 inflateEnd (&inflator); 393 } 394 else // no compression 395 { 396 i2p::data::netdb.AddRouterInfo (compressed, compressedSize); 397 numFiles++; 398 } 399 delete[] compressed; 400 if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR) 401 s.seekg (12, std::ios::cur); // skip data descriptor section if presented (12 = 16 - 4) 402 } 403 else 404 { 405 if (signature != ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE) 406 LogPrint (eLogWarning, "Reseed: Missing zip central directory header"); 407 break; // no more files 408 } 409 size_t end = s.tellg (); 410 if (end - contentPos >= contentLength) 411 break; // we are beyond contentLength 412 } 413 if (numFiles) // check if routers are not outdated 414 { 415 auto ts = i2p::util::GetMillisecondsSinceEpoch (); 416 int numOutdated = 0; 417 i2p::data::netdb.VisitRouterInfos ( 418 [&numOutdated, ts](std::shared_ptr<const RouterInfo> r) 419 { 420 if (r && ts > r->GetTimestamp () + 10*i2p::data::NETDB_MAX_EXPIRATION_TIMEOUT*1000LL) // 270 hours 421 { 422 LogPrint (eLogError, "Reseed: Router ", r->GetIdentHash().ToBase64 (), " is outdated by ", (ts - r->GetTimestamp ())/1000LL/3600LL, " hours"); 423 numOutdated++; 424 } 425 }); 426 if (numOutdated > numFiles/2) // more than half 427 { 428 LogPrint (eLogError, "Reseed: Mammoth's shit\n" 429 " *_____*\n" 430 " *_*****_*\n" 431 " *_(O)_(O)_*\n" 432 " **____V____**\n" 433 " **_________**\n" 434 " **_________**\n" 435 " *_________*\n" 436 " ***___***"); 437 i2p::data::netdb.ClearRouterInfos (); 438 numFiles = 0; 439 } 440 } 441 return numFiles; 442 } 443 444 const uint8_t ZIP_DATA_DESCRIPTOR_SIGNATURE[] = { 0x50, 0x4B, 0x07, 0x08 }; 445 bool Reseeder::FindZipDataDescriptor (std::istream& s) 446 { 447 size_t nextInd = 0; 448 while (!s.eof ()) 449 { 450 uint8_t nextByte; 451 s.read ((char *)&nextByte, 1); 452 if (nextByte == ZIP_DATA_DESCRIPTOR_SIGNATURE[nextInd]) 453 { 454 nextInd++; 455 if (nextInd >= sizeof (ZIP_DATA_DESCRIPTOR_SIGNATURE)) 456 return true; 457 } 458 else 459 nextInd = 0; 460 } 461 return false; 462 } 463 464 void Reseeder::LoadCertificate (const std::string& filename) 465 { 466 SSL_CTX * ctx = SSL_CTX_new (TLS_method ()); 467 int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM); 468 if (ret) 469 { 470 SSL * ssl = SSL_new (ctx); 471 X509 * cert = SSL_get_certificate (ssl); 472 // verify 473 if (cert) 474 { 475 // extract issuer name 476 char name[100]; 477 X509_NAME_oneline (X509_get_issuer_name(cert), name, 100); 478 char * cn = strstr (name, "CN="); 479 if (cn) 480 { 481 cn += 3; 482 char * terminator = strchr (cn, '/'); 483 if (terminator) terminator[0] = 0; 484 } 485 // extract RSA key (we need n only, e = 65537) 486 EVP_PKEY * pubKey = X509_get_pubkey (cert); 487 const BIGNUM * n = nullptr; 488 #if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 489 BIGNUM * n1 = BN_new (); 490 if (EVP_PKEY_get_bn_param (pubKey, OSSL_PKEY_PARAM_RSA_N, &n1) > 0) 491 n = n1; 492 #else 493 const RSA * key = EVP_PKEY_get0_RSA (pubKey); 494 const BIGNUM * e, * d; 495 RSA_get0_key(key, &n, &e, &d); 496 #endif 497 if (n) 498 { 499 PublicKey value; 500 i2p::crypto::bn2buf (n, value, 512); 501 if (cn) 502 m_SigningKeys[cn] = value; 503 else 504 LogPrint (eLogError, "Reseed: Can't find CN field in ", filename); 505 } 506 else 507 LogPrint (eLogError, "Reseed: Can't extract RSA key from ", filename); 508 #if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 509 BN_free (n1); 510 #endif 511 } 512 SSL_free (ssl); 513 } 514 else 515 LogPrint (eLogCritical, "Reseed: Can't open certificate file ", filename); 516 SSL_CTX_free (ctx); 517 } 518 519 void Reseeder::LoadCertificates () 520 { 521 std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "reseed"; 522 523 std::vector<std::string> files; 524 int numCertificates = 0; 525 526 if (!i2p::fs::ReadDir(certDir, files)) { 527 LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir); 528 return; 529 } 530 531 for (const std::string & file : files) { 532 if (file.compare(file.size() - 4, 4, ".crt") != 0) { 533 LogPrint(eLogWarning, "Reseed: Ignoring file ", file); 534 continue; 535 } 536 LoadCertificate (file); 537 numCertificates++; 538 } 539 LogPrint (eLogInfo, "Reseed: ", numCertificates, " certificates loaded"); 540 } 541 542 std::string Reseeder::HttpsRequest (const std::string& address) 543 { 544 i2p::http::URL proxyUrl; 545 std::string proxy; i2p::config::GetOption("reseed.proxy", proxy); 546 // check for proxy url 547 if(proxy.size()) { 548 // parse 549 if(proxyUrl.parse(proxy)) { 550 if (proxyUrl.schema == "http" && !proxyUrl.port) { 551 proxyUrl.port = 80; 552 } else if (proxyUrl.schema == "socks" && !proxyUrl.port) { 553 proxyUrl.port = 1080; 554 } 555 // check for valid proxy url schema 556 if (proxyUrl.schema != "http" && proxyUrl.schema != "socks") { 557 LogPrint(eLogCritical, "Reseed: Bad proxy url: ", proxy); 558 return ""; 559 } 560 } else { 561 LogPrint(eLogCritical, "Reseed: Bad proxy url: ", proxy); 562 return ""; 563 } 564 } 565 i2p::http::URL url; 566 if (!url.parse(address)) { 567 LogPrint(eLogCritical, "Reseed: Failed to parse url: ", address); 568 return ""; 569 } 570 url.schema = "https"; 571 if (!url.port) 572 url.port = 443; 573 574 boost::asio::io_context service; 575 boost::system::error_code ecode; 576 577 boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); 578 ctx.set_verify_mode(boost::asio::ssl::context::verify_none); 579 boost::asio::ssl::stream<boost::asio::ip::tcp::socket> s(service, ctx); 580 581 if(proxyUrl.schema.size()) 582 { 583 // proxy connection 584 auto it = boost::asio::ip::tcp::resolver(service).resolve (proxyUrl.host, std::to_string(proxyUrl.port), ecode); 585 if(!ecode) 586 { 587 s.lowest_layer().connect(*it.begin (), ecode); 588 if(!ecode) 589 { 590 auto & sock = s.next_layer(); 591 if(proxyUrl.schema == "http") 592 { 593 i2p::http::HTTPReq proxyReq; 594 i2p::http::HTTPRes proxyRes; 595 proxyReq.method = "CONNECT"; 596 proxyReq.version = "HTTP/1.1"; 597 proxyReq.uri = url.host + ":" + std::to_string(url.port); 598 auto auth = i2p::http::CreateBasicAuthorizationString (proxyUrl.user, proxyUrl.pass); 599 if (!auth.empty ()) 600 proxyReq.AddHeader("Proxy-Authorization", auth); 601 602 boost::asio::streambuf writebuf, readbuf; 603 std::ostream out(&writebuf); 604 out << proxyReq.to_string(); 605 606 boost::asio::write(sock, writebuf.data(), boost::asio::transfer_all(), ecode); 607 if (ecode) 608 { 609 sock.close(); 610 LogPrint(eLogError, "Reseed: HTTP CONNECT write error: ", ecode.message()); 611 return ""; 612 } 613 boost::asio::read_until(sock, readbuf, "\r\n\r\n", ecode); 614 if (ecode) 615 { 616 sock.close(); 617 LogPrint(eLogError, "Reseed: HTTP CONNECT read error: ", ecode.message()); 618 return ""; 619 } 620 if(proxyRes.parse(std::string {boost::asio::buffers_begin(readbuf.data ()), boost::asio::buffers_begin(readbuf.data ()) + readbuf.size ()}) <= 0) 621 { 622 sock.close(); 623 LogPrint(eLogError, "Reseed: HTTP CONNECT malformed reply"); 624 return ""; 625 } 626 if(proxyRes.code != 200) 627 { 628 sock.close(); 629 LogPrint(eLogError, "Reseed: HTTP CONNECT got bad status: ", proxyRes.code); 630 return ""; 631 } 632 } 633 else 634 { 635 // assume socks if not http, is checked before this for other types 636 // TODO: support username/password auth etc 637 bool success = false; 638 i2p::transport::Socks5Handshake (sock, std::make_pair(url.host, url.port), 639 [&success](const boost::system::error_code& ec) 640 { 641 if (!ec) 642 success = true; 643 else 644 LogPrint (eLogError, "Reseed: SOCKS handshake failed: ", ec.message()); 645 }); 646 service.run (); // execute all async operations 647 if (!success) 648 { 649 sock.close(); 650 return ""; 651 } 652 } 653 } 654 } 655 } 656 else 657 { 658 // direct connection 659 auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode); 660 if (!ecode) 661 { 662 bool connected = false; 663 for (const auto& it: endpoints) 664 { 665 boost::asio::ip::tcp::endpoint ep = it; 666 bool supported = false; 667 if (!ep.address ().is_unspecified ()) 668 { 669 if (ep.address ().is_v4 ()) 670 supported = i2p::context.SupportsV4 (); 671 else if (ep.address ().is_v6 ()) 672 supported = i2p::util::net::IsYggdrasilAddress (ep.address ()) ? 673 i2p::context.SupportsMesh () : i2p::context.SupportsV6 (); 674 } 675 if (supported) 676 { 677 s.lowest_layer().connect (ep, ecode); 678 if (!ecode) 679 { 680 LogPrint (eLogDebug, "Reseed: Resolved to ", ep.address ()); 681 connected = true; 682 break; 683 } 684 } 685 } 686 if (!connected) 687 { 688 LogPrint(eLogError, "Reseed: Failed to connect to ", url.host); 689 return ""; 690 } 691 } 692 } 693 if (!ecode) 694 { 695 SSL_set_tlsext_host_name(s.native_handle(), url.host.c_str ()); 696 s.handshake (boost::asio::ssl::stream_base::client, ecode); 697 if (!ecode) 698 { 699 LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port); 700 return ReseedRequest (s, url.to_string()); 701 } 702 else 703 LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ()); 704 } 705 else 706 LogPrint (eLogError, "Reseed: Couldn't connect to ", url.host, ": ", ecode.message ()); 707 return ""; 708 } 709 710 template<typename Stream> 711 std::string Reseeder::ReseedRequest (Stream& s, const std::string& uri) 712 { 713 bool follow; i2p::config::GetOption("reseed.followredirect", follow); 714 boost::system::error_code ecode; 715 i2p::http::HTTPReq req; 716 i2p::http::HTTPRes res; 717 718 req.uri = uri; 719 req.AddHeader("User-Agent", "Wget/1.11.4"); 720 req.AddHeader("Connection", "close"); 721 s.write_some (boost::asio::buffer (req.to_string())); 722 723 // read response 724 std::stringstream rs; 725 char recv_buf[1024]; size_t l = 0; 726 do { 727 l = s.read_some (boost::asio::buffer (recv_buf, sizeof(recv_buf)), ecode); 728 if (l) rs.write (recv_buf, l); 729 } while (!ecode && l); 730 731 // process response 732 std::string data = rs.str(); 733 int len = res.parse(data); 734 if (len <= 0) { 735 LogPrint(eLogWarning, "Reseed: Incomplete/broken response from ", uri); 736 return ""; 737 } 738 739 if ((res.code == 301 || res.code == 302 || res.code == 307) && follow) { 740 LogPrint(eLogDebug, "Reseed: Recieved redirect from ", uri); 741 742 std::string location = res.get_header("Location"); 743 if (location.length() == 0) { 744 LogPrint(eLogWarning, "Reseed: Broken redirect from ", uri); 745 return ""; 746 } 747 bool isHttps = (location.length() > 8 && location.substr(0, 8) == "https://"); 748 return (isHttps ? HttpsRequest (location) : YggdrasilRequest (location)); 749 } 750 751 if (res.code != 200) { 752 LogPrint(eLogError, "Reseed: Failed to reseed from ", uri, ", http code ", res.code); 753 return ""; 754 } 755 756 data.erase(0, len); /* drop http headers from response */ 757 LogPrint(eLogDebug, "Reseed: Got ", data.length(), " bytes of data from ", uri); 758 if (res.is_chunked()) { 759 std::stringstream in(data), out; 760 if (!i2p::http::MergeChunkedResponse(in, out)) { 761 LogPrint(eLogWarning, "Reseed: Failed to merge chunked response from ", uri); 762 return ""; 763 } 764 LogPrint(eLogDebug, "Reseed: Got ", data.length(), "(", out.tellg(), ") bytes of data from ", uri); 765 data = out.str(); 766 } 767 return data; 768 } 769 770 std::string Reseeder::YggdrasilRequest (const std::string& address) 771 { 772 i2p::http::URL url; 773 if (!url.parse(address)) 774 { 775 LogPrint(eLogError, "Reseed: Failed to parse url: ", address); 776 return ""; 777 } 778 url.schema = "http"; 779 if (!url.port) url.port = 80; 780 781 boost::system::error_code ecode; 782 boost::asio::io_context service; 783 boost::asio::ip::tcp::socket s(service, boost::asio::ip::tcp::v6()); 784 785 auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode); 786 if (!ecode) 787 { 788 bool connected = false; 789 for (const auto& it: endpoints) 790 { 791 boost::asio::ip::tcp::endpoint ep = it; 792 if ( 793 i2p::util::net::IsYggdrasilAddress (ep.address ()) && 794 i2p::context.SupportsMesh () 795 ) 796 { 797 LogPrint (eLogDebug, "Reseed: Yggdrasil: Resolved to ", ep.address ()); 798 s.connect (ep, ecode); 799 if (!ecode) 800 { 801 connected = true; 802 break; 803 } 804 } 805 } 806 if (!connected) 807 { 808 LogPrint(eLogError, "Reseed: Yggdrasil: Failed to connect to ", url.host); 809 return ""; 810 } 811 } 812 813 if (!ecode) 814 { 815 LogPrint (eLogDebug, "Reseed: Yggdrasil: Connected to ", url.host, ":", url.port); 816 return ReseedRequest (s, url.to_string()); 817 } 818 else 819 LogPrint (eLogError, "Reseed: Yggdrasil: Couldn't connect to ", url.host, ": ", ecode.message ()); 820 821 return ""; 822 } 823 } 824 }