mirror of https://github.com/PurpleI2P/i2pd.git
I2P: End-to-End encrypted and anonymous Internet
https://i2pd.website/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
787 lines
23 KiB
787 lines
23 KiB
/* |
|
* Copyright (c) 2013-2024, The PurpleI2P Project |
|
* |
|
* This file is part of Purple i2pd project and licensed under BSD3 |
|
* |
|
* See full license text in LICENSE file at top of project tree |
|
*/ |
|
|
|
#include <string.h> |
|
#include <fstream> |
|
#include <sstream> |
|
#include <boost/asio.hpp> |
|
#include <boost/asio/ssl.hpp> |
|
#include <boost/algorithm/string.hpp> |
|
#include <openssl/ssl.h> |
|
#include <openssl/err.h> |
|
#include <zlib.h> |
|
|
|
#include "Crypto.h" |
|
#include "I2PEndian.h" |
|
#include "Reseed.h" |
|
#include "FS.h" |
|
#include "Log.h" |
|
#include "Identity.h" |
|
#include "NetDb.hpp" |
|
#include "HTTP.h" |
|
#include "util.h" |
|
#include "Config.h" |
|
#include "Socks5.h" |
|
|
|
namespace i2p |
|
{ |
|
namespace data |
|
{ |
|
|
|
Reseeder::Reseeder() |
|
{ |
|
} |
|
|
|
Reseeder::~Reseeder() |
|
{ |
|
} |
|
|
|
/** |
|
@brief tries to bootstrap into I2P network (from local files and servers, with respect of options) |
|
*/ |
|
void Reseeder::Bootstrap () |
|
{ |
|
std::string su3FileName; i2p::config::GetOption("reseed.file", su3FileName); |
|
std::string zipFileName; i2p::config::GetOption("reseed.zipfile", zipFileName); |
|
|
|
if (su3FileName.length() > 0) // bootstrap from SU3 file or URL |
|
{ |
|
int num; |
|
if (su3FileName.length() > 8 && su3FileName.substr(0, 8) == "https://") |
|
{ |
|
num = ReseedFromSU3Url (su3FileName); // from https URL |
|
} |
|
else |
|
{ |
|
num = ProcessSU3File (su3FileName.c_str ()); |
|
} |
|
if (num == 0) |
|
LogPrint (eLogWarning, "Reseed: Failed to reseed from ", su3FileName); |
|
} |
|
else if (zipFileName.length() > 0) // bootstrap from ZIP file |
|
{ |
|
int num = ProcessZIPFile (zipFileName.c_str ()); |
|
if (num == 0) |
|
LogPrint (eLogWarning, "Reseed: Failed to reseed from ", zipFileName); |
|
} |
|
else // bootstrap from reseed servers |
|
{ |
|
int num = ReseedFromServers (); |
|
if (num == 0) |
|
LogPrint (eLogWarning, "Reseed: Failed to reseed from servers"); |
|
} |
|
} |
|
|
|
/** |
|
* @brief bootstrap from random server, retry 10 times |
|
* @return number of entries added to netDb |
|
*/ |
|
int Reseeder::ReseedFromServers () |
|
{ |
|
bool ipv6; i2p::config::GetOption("ipv6", ipv6); |
|
bool ipv4; i2p::config::GetOption("ipv4", ipv4); |
|
bool yggdrasil; i2p::config::GetOption("meshnets.yggdrasil", yggdrasil); |
|
|
|
std::vector<std::string> httpsReseedHostList; |
|
if (ipv4 || ipv6) |
|
{ |
|
std::string reseedURLs; i2p::config::GetOption("reseed.urls", reseedURLs); |
|
if (!reseedURLs.empty ()) |
|
boost::split(httpsReseedHostList, reseedURLs, boost::is_any_of(","), boost::token_compress_on); |
|
} |
|
|
|
std::vector<std::string> yggReseedHostList; |
|
if (yggdrasil && !i2p::util::net::GetYggdrasilAddress ().is_unspecified ()) |
|
{ |
|
LogPrint (eLogInfo, "Reseed: Yggdrasil is supported"); |
|
std::string yggReseedURLs; i2p::config::GetOption("reseed.yggurls", yggReseedURLs); |
|
if (!yggReseedURLs.empty ()) |
|
boost::split(yggReseedHostList, yggReseedURLs, boost::is_any_of(","), boost::token_compress_on); |
|
} |
|
|
|
if (httpsReseedHostList.empty () && yggReseedHostList.empty()) |
|
{ |
|
LogPrint (eLogWarning, "Reseed: No reseed servers specified"); |
|
return 0; |
|
} |
|
|
|
int reseedRetries = 0; |
|
while (reseedRetries < 10) |
|
{ |
|
auto ind = rand () % (httpsReseedHostList.size () + yggReseedHostList.size ()); |
|
bool isHttps = ind < httpsReseedHostList.size (); |
|
std::string reseedUrl = isHttps ? httpsReseedHostList[ind] : |
|
yggReseedHostList[ind - httpsReseedHostList.size ()]; |
|
reseedUrl += "i2pseeds.su3"; |
|
auto num = ReseedFromSU3Url (reseedUrl, isHttps); |
|
if (num > 0) return num; // success |
|
reseedRetries++; |
|
} |
|
LogPrint (eLogWarning, "Reseed: Failed to reseed from servers after 10 attempts"); |
|
return 0; |
|
} |
|
|
|
/** |
|
* @brief bootstrap from HTTPS URL with SU3 file |
|
* @param url |
|
* @return number of entries added to netDb |
|
*/ |
|
int Reseeder::ReseedFromSU3Url (const std::string& url, bool isHttps) |
|
{ |
|
LogPrint (eLogInfo, "Reseed: Downloading SU3 from ", url); |
|
std::string su3 = isHttps ? HttpsRequest (url) : YggdrasilRequest (url); |
|
if (su3.length () > 0) |
|
{ |
|
std::stringstream s(su3); |
|
return ProcessSU3Stream (s); |
|
} |
|
else |
|
{ |
|
LogPrint (eLogWarning, "Reseed: SU3 download failed"); |
|
return 0; |
|
} |
|
} |
|
|
|
int Reseeder::ProcessSU3File (const char * filename) |
|
{ |
|
std::ifstream s(filename, std::ifstream::binary); |
|
if (s.is_open ()) |
|
return ProcessSU3Stream (s); |
|
else |
|
{ |
|
LogPrint (eLogCritical, "Reseed: Can't open file ", filename); |
|
return 0; |
|
} |
|
} |
|
|
|
int Reseeder::ProcessZIPFile (const char * filename) |
|
{ |
|
std::ifstream s(filename, std::ifstream::binary); |
|
if (s.is_open ()) |
|
{ |
|
s.seekg (0, std::ios::end); |
|
auto len = s.tellg (); |
|
s.seekg (0, std::ios::beg); |
|
return ProcessZIPStream (s, len); |
|
} |
|
else |
|
{ |
|
LogPrint (eLogCritical, "Reseed: Can't open file ", filename); |
|
return 0; |
|
} |
|
} |
|
|
|
const char SU3_MAGIC_NUMBER[]="I2Psu3"; |
|
int Reseeder::ProcessSU3Stream (std::istream& s) |
|
{ |
|
char magicNumber[7]; |
|
s.read (magicNumber, 7); // magic number and zero byte 6 |
|
if (strcmp (magicNumber, SU3_MAGIC_NUMBER)) |
|
{ |
|
LogPrint (eLogError, "Reseed: Unexpected SU3 magic number"); |
|
return 0; |
|
} |
|
s.seekg (1, std::ios::cur); // su3 file format version |
|
SigningKeyType signatureType; |
|
s.read ((char *)&signatureType, 2); // signature type |
|
signatureType = be16toh (signatureType); |
|
uint16_t signatureLength; |
|
s.read ((char *)&signatureLength, 2); // signature length |
|
signatureLength = be16toh (signatureLength); |
|
s.seekg (1, std::ios::cur); // unused |
|
uint8_t versionLength; |
|
s.read ((char *)&versionLength, 1); // version length |
|
s.seekg (1, std::ios::cur); // unused |
|
uint8_t signerIDLength; |
|
s.read ((char *)&signerIDLength, 1); // signer ID length |
|
uint64_t contentLength; |
|
s.read ((char *)&contentLength, 8); // content length |
|
contentLength = be64toh (contentLength); |
|
s.seekg (1, std::ios::cur); // unused |
|
uint8_t fileType; |
|
s.read ((char *)&fileType, 1); // file type |
|
if (fileType != 0x00) // zip file |
|
{ |
|
LogPrint (eLogError, "Reseed: Can't handle file type ", (int)fileType); |
|
return 0; |
|
} |
|
s.seekg (1, std::ios::cur); // unused |
|
uint8_t contentType; |
|
s.read ((char *)&contentType, 1); // content type |
|
if (contentType != 0x03) // reseed data |
|
{ |
|
LogPrint (eLogError, "Reseed: Unexpected content type ", (int)contentType); |
|
return 0; |
|
} |
|
s.seekg (12, std::ios::cur); // unused |
|
|
|
s.seekg (versionLength, std::ios::cur); // skip version |
|
char signerID[256]; |
|
s.read (signerID, signerIDLength); // signerID |
|
signerID[signerIDLength] = 0; |
|
|
|
bool verify; i2p::config::GetOption("reseed.verify", verify); |
|
if (verify) |
|
{ |
|
//try to verify signature |
|
auto it = m_SigningKeys.find (signerID); |
|
if (it != m_SigningKeys.end ()) |
|
{ |
|
// TODO: implement all signature types |
|
if (signatureType == SIGNING_KEY_TYPE_RSA_SHA512_4096) |
|
{ |
|
size_t pos = s.tellg (); |
|
size_t tbsLen = pos + contentLength; |
|
uint8_t * tbs = new uint8_t[tbsLen]; |
|
s.seekg (0, std::ios::beg); |
|
s.read ((char *)tbs, tbsLen); |
|
uint8_t * signature = new uint8_t[signatureLength]; |
|
s.read ((char *)signature, signatureLength); |
|
// RSA-raw |
|
{ |
|
// calculate digest |
|
uint8_t digest[64]; |
|
SHA512 (tbs, tbsLen, digest); |
|
// encrypt signature |
|
BN_CTX * bnctx = BN_CTX_new (); |
|
BIGNUM * s = BN_new (), * n = BN_new (); |
|
BN_bin2bn (signature, signatureLength, s); |
|
BN_bin2bn (it->second, 512, n); // RSA 4096 assumed |
|
BN_mod_exp (s, s, i2p::crypto::GetRSAE (), n, bnctx); // s = s^e mod n |
|
uint8_t * enSigBuf = new uint8_t[signatureLength]; |
|
i2p::crypto::bn2buf (s, enSigBuf, signatureLength); |
|
// digest is right aligned |
|
// we can't use RSA_verify due wrong padding in SU3 |
|
if (memcmp (enSigBuf + (signatureLength - 64), digest, 64)) |
|
LogPrint (eLogWarning, "Reseed: SU3 signature verification failed"); |
|
else |
|
verify = false; // verified |
|
delete[] enSigBuf; |
|
BN_free (s); BN_free (n); |
|
BN_CTX_free (bnctx); |
|
} |
|
|
|
delete[] signature; |
|
delete[] tbs; |
|
s.seekg (pos, std::ios::beg); |
|
} |
|
else |
|
LogPrint (eLogWarning, "Reseed: Signature type ", signatureType, " is not supported"); |
|
} |
|
else |
|
LogPrint (eLogWarning, "Reseed: Certificate for ", signerID, " not loaded"); |
|
} |
|
|
|
if (verify) // not verified |
|
{ |
|
LogPrint (eLogCritical, "Reseed: SU3 verification failed"); |
|
return 0; |
|
} |
|
|
|
// handle content |
|
return ProcessZIPStream (s, contentLength); |
|
} |
|
|
|
const uint32_t ZIP_HEADER_SIGNATURE = 0x04034B50; |
|
const uint32_t ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014B50; |
|
const uint16_t ZIP_BIT_FLAG_DATA_DESCRIPTOR = 0x0008; |
|
int Reseeder::ProcessZIPStream (std::istream& s, uint64_t contentLength) |
|
{ |
|
int numFiles = 0; |
|
size_t contentPos = s.tellg (); |
|
while (!s.eof ()) |
|
{ |
|
uint32_t signature; |
|
s.read ((char *)&signature, 4); |
|
signature = le32toh (signature); |
|
if (signature == ZIP_HEADER_SIGNATURE) |
|
{ |
|
// next local file |
|
s.seekg (2, std::ios::cur); // version |
|
uint16_t bitFlag; |
|
s.read ((char *)&bitFlag, 2); |
|
bitFlag = le16toh (bitFlag); |
|
uint16_t compressionMethod; |
|
s.read ((char *)&compressionMethod, 2); |
|
compressionMethod = le16toh (compressionMethod); |
|
s.seekg (4, std::ios::cur); // skip fields we don't care about |
|
uint32_t compressedSize, uncompressedSize; |
|
uint32_t crc_32; |
|
s.read ((char *)&crc_32, 4); |
|
crc_32 = le32toh (crc_32); |
|
s.read ((char *)&compressedSize, 4); |
|
compressedSize = le32toh (compressedSize); |
|
s.read ((char *)&uncompressedSize, 4); |
|
uncompressedSize = le32toh (uncompressedSize); |
|
uint16_t fileNameLength, extraFieldLength; |
|
s.read ((char *)&fileNameLength, 2); |
|
fileNameLength = le16toh (fileNameLength); |
|
if ( fileNameLength >= 255 ) { |
|
// too big |
|
LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength); |
|
return numFiles; |
|
} |
|
s.read ((char *)&extraFieldLength, 2); |
|
extraFieldLength = le16toh (extraFieldLength); |
|
char localFileName[255]; |
|
s.read (localFileName, fileNameLength); |
|
localFileName[fileNameLength] = 0; |
|
s.seekg (extraFieldLength, std::ios::cur); |
|
// take care about data descriptor if presented |
|
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR) |
|
{ |
|
size_t pos = s.tellg (); |
|
if (!FindZipDataDescriptor (s)) |
|
{ |
|
LogPrint (eLogError, "Reseed: SU3 archive data descriptor not found"); |
|
return numFiles; |
|
} |
|
s.read ((char *)&crc_32, 4); |
|
crc_32 = le32toh (crc_32); |
|
s.read ((char *)&compressedSize, 4); |
|
compressedSize = le32toh (compressedSize) + 4; // ??? we must consider signature as part of compressed data |
|
s.read ((char *)&uncompressedSize, 4); |
|
uncompressedSize = le32toh (uncompressedSize); |
|
|
|
// now we know compressed and uncompressed size |
|
s.seekg (pos, std::ios::beg); // back to compressed data |
|
} |
|
|
|
LogPrint (eLogDebug, "Reseed: Processing file ", localFileName, " ", compressedSize, " bytes"); |
|
if (!compressedSize) |
|
{ |
|
LogPrint (eLogWarning, "Reseed: Unexpected size 0. Skipped"); |
|
continue; |
|
} |
|
|
|
uint8_t * compressed = new uint8_t[compressedSize]; |
|
s.read ((char *)compressed, compressedSize); |
|
if (compressionMethod) // we assume Deflate |
|
{ |
|
z_stream inflator; |
|
memset (&inflator, 0, sizeof (inflator)); |
|
inflateInit2 (&inflator, -MAX_WBITS); // no zlib header |
|
uint8_t * uncompressed = new uint8_t[uncompressedSize]; |
|
inflator.next_in = compressed; |
|
inflator.avail_in = compressedSize; |
|
inflator.next_out = uncompressed; |
|
inflator.avail_out = uncompressedSize; |
|
int err; |
|
if ((err = inflate (&inflator, Z_SYNC_FLUSH)) >= 0) |
|
{ |
|
uncompressedSize -= inflator.avail_out; |
|
if (crc32 (0, uncompressed, uncompressedSize) == crc_32) |
|
{ |
|
i2p::data::netdb.AddRouterInfo (uncompressed, uncompressedSize); |
|
numFiles++; |
|
} |
|
else |
|
LogPrint (eLogError, "Reseed: CRC32 verification failed"); |
|
} |
|
else |
|
LogPrint (eLogError, "Reseed: SU3 decompression error ", err); |
|
delete[] uncompressed; |
|
inflateEnd (&inflator); |
|
} |
|
else // no compression |
|
{ |
|
i2p::data::netdb.AddRouterInfo (compressed, compressedSize); |
|
numFiles++; |
|
} |
|
delete[] compressed; |
|
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR) |
|
s.seekg (12, std::ios::cur); // skip data descriptor section if presented (12 = 16 - 4) |
|
} |
|
else |
|
{ |
|
if (signature != ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE) |
|
LogPrint (eLogWarning, "Reseed: Missing zip central directory header"); |
|
break; // no more files |
|
} |
|
size_t end = s.tellg (); |
|
if (end - contentPos >= contentLength) |
|
break; // we are beyond contentLength |
|
} |
|
if (numFiles) // check if routers are not outdated |
|
{ |
|
auto ts = i2p::util::GetMillisecondsSinceEpoch (); |
|
int numOutdated = 0; |
|
i2p::data::netdb.VisitRouterInfos ( |
|
[&numOutdated, ts](std::shared_ptr<const RouterInfo> r) |
|
{ |
|
if (r && ts > r->GetTimestamp () + 10*i2p::data::NETDB_MAX_EXPIRATION_TIMEOUT*1000LL) // 270 hours |
|
{ |
|
LogPrint (eLogError, "Reseed: Router ", r->GetIdentHash().ToBase64 (), " is outdated by ", (ts - r->GetTimestamp ())/1000LL/3600LL, " hours"); |
|
numOutdated++; |
|
} |
|
}); |
|
if (numOutdated > numFiles/2) // more than half |
|
{ |
|
LogPrint (eLogError, "Reseed: Mammoth's shit\n" |
|
" *_____*\n" |
|
" *_*****_*\n" |
|
" *_(O)_(O)_*\n" |
|
" **____V____**\n" |
|
" **_________**\n" |
|
" **_________**\n" |
|
" *_________*\n" |
|
" ***___***"); |
|
i2p::data::netdb.ClearRouterInfos (); |
|
numFiles = 0; |
|
} |
|
} |
|
return numFiles; |
|
} |
|
|
|
const uint8_t ZIP_DATA_DESCRIPTOR_SIGNATURE[] = { 0x50, 0x4B, 0x07, 0x08 }; |
|
bool Reseeder::FindZipDataDescriptor (std::istream& s) |
|
{ |
|
size_t nextInd = 0; |
|
while (!s.eof ()) |
|
{ |
|
uint8_t nextByte; |
|
s.read ((char *)&nextByte, 1); |
|
if (nextByte == ZIP_DATA_DESCRIPTOR_SIGNATURE[nextInd]) |
|
{ |
|
nextInd++; |
|
if (nextInd >= sizeof (ZIP_DATA_DESCRIPTOR_SIGNATURE)) |
|
return true; |
|
} |
|
else |
|
nextInd = 0; |
|
} |
|
return false; |
|
} |
|
|
|
void Reseeder::LoadCertificate (const std::string& filename) |
|
{ |
|
SSL_CTX * ctx = SSL_CTX_new (TLS_method ()); |
|
int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM); |
|
if (ret) |
|
{ |
|
SSL * ssl = SSL_new (ctx); |
|
X509 * cert = SSL_get_certificate (ssl); |
|
// verify |
|
if (cert) |
|
{ |
|
// extract issuer name |
|
char name[100]; |
|
X509_NAME_oneline (X509_get_issuer_name(cert), name, 100); |
|
char * cn = strstr (name, "CN="); |
|
if (cn) |
|
{ |
|
cn += 3; |
|
char * terminator = strchr (cn, '/'); |
|
if (terminator) terminator[0] = 0; |
|
} |
|
// extract RSA key (we need n only, e = 65537) |
|
const RSA * key = EVP_PKEY_get0_RSA (X509_get_pubkey (cert)); |
|
const BIGNUM * n, * e, * d; |
|
RSA_get0_key(key, &n, &e, &d); |
|
PublicKey value; |
|
i2p::crypto::bn2buf (n, value, 512); |
|
if (cn) |
|
m_SigningKeys[cn] = value; |
|
else |
|
LogPrint (eLogError, "Reseed: Can't find CN field in ", filename); |
|
} |
|
SSL_free (ssl); |
|
} |
|
else |
|
LogPrint (eLogCritical, "Reseed: Can't open certificate file ", filename); |
|
SSL_CTX_free (ctx); |
|
} |
|
|
|
void Reseeder::LoadCertificates () |
|
{ |
|
std::string certDir = i2p::fs::GetCertsDir() + i2p::fs::dirSep + "reseed"; |
|
|
|
std::vector<std::string> files; |
|
int numCertificates = 0; |
|
|
|
if (!i2p::fs::ReadDir(certDir, files)) { |
|
LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir); |
|
return; |
|
} |
|
|
|
for (const std::string & file : files) { |
|
if (file.compare(file.size() - 4, 4, ".crt") != 0) { |
|
LogPrint(eLogWarning, "Reseed: Ignoring file ", file); |
|
continue; |
|
} |
|
LoadCertificate (file); |
|
numCertificates++; |
|
} |
|
LogPrint (eLogInfo, "Reseed: ", numCertificates, " certificates loaded"); |
|
} |
|
|
|
std::string Reseeder::HttpsRequest (const std::string& address) |
|
{ |
|
i2p::http::URL proxyUrl; |
|
std::string proxy; i2p::config::GetOption("reseed.proxy", proxy); |
|
// check for proxy url |
|
if(proxy.size()) { |
|
// parse |
|
if(proxyUrl.parse(proxy)) { |
|
if (proxyUrl.schema == "http" && !proxyUrl.port) { |
|
proxyUrl.port = 80; |
|
} else if (proxyUrl.schema == "socks" && !proxyUrl.port) { |
|
proxyUrl.port = 1080; |
|
} |
|
// check for valid proxy url schema |
|
if (proxyUrl.schema != "http" && proxyUrl.schema != "socks") { |
|
LogPrint(eLogCritical, "Reseed: Bad proxy url: ", proxy); |
|
return ""; |
|
} |
|
} else { |
|
LogPrint(eLogCritical, "Reseed: Bad proxy url: ", proxy); |
|
return ""; |
|
} |
|
} |
|
i2p::http::URL url; |
|
if (!url.parse(address)) { |
|
LogPrint(eLogCritical, "Reseed: Failed to parse url: ", address); |
|
return ""; |
|
} |
|
url.schema = "https"; |
|
if (!url.port) |
|
url.port = 443; |
|
|
|
boost::asio::io_context service; |
|
boost::system::error_code ecode; |
|
|
|
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); |
|
ctx.set_verify_mode(boost::asio::ssl::context::verify_none); |
|
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> s(service, ctx); |
|
|
|
if(proxyUrl.schema.size()) |
|
{ |
|
// proxy connection |
|
auto it = boost::asio::ip::tcp::resolver(service).resolve (proxyUrl.host, std::to_string(proxyUrl.port), ecode); |
|
if(!ecode) |
|
{ |
|
s.lowest_layer().connect(*it.begin (), ecode); |
|
if(!ecode) |
|
{ |
|
auto & sock = s.next_layer(); |
|
if(proxyUrl.schema == "http") |
|
{ |
|
i2p::http::HTTPReq proxyReq; |
|
i2p::http::HTTPRes proxyRes; |
|
proxyReq.method = "CONNECT"; |
|
proxyReq.version = "HTTP/1.1"; |
|
proxyReq.uri = url.host + ":" + std::to_string(url.port); |
|
auto auth = i2p::http::CreateBasicAuthorizationString (proxyUrl.user, proxyUrl.pass); |
|
if (!auth.empty ()) |
|
proxyReq.AddHeader("Proxy-Authorization", auth); |
|
|
|
boost::asio::streambuf writebuf, readbuf; |
|
std::ostream out(&writebuf); |
|
out << proxyReq.to_string(); |
|
|
|
boost::asio::write(sock, writebuf.data(), boost::asio::transfer_all(), ecode); |
|
if (ecode) |
|
{ |
|
sock.close(); |
|
LogPrint(eLogError, "Reseed: HTTP CONNECT write error: ", ecode.message()); |
|
return ""; |
|
} |
|
boost::asio::read_until(sock, readbuf, "\r\n\r\n", ecode); |
|
if (ecode) |
|
{ |
|
sock.close(); |
|
LogPrint(eLogError, "Reseed: HTTP CONNECT read error: ", ecode.message()); |
|
return ""; |
|
} |
|
if(proxyRes.parse(std::string {boost::asio::buffers_begin(readbuf.data ()), boost::asio::buffers_begin(readbuf.data ()) + readbuf.size ()}) <= 0) |
|
{ |
|
sock.close(); |
|
LogPrint(eLogError, "Reseed: HTTP CONNECT malformed reply"); |
|
return ""; |
|
} |
|
if(proxyRes.code != 200) |
|
{ |
|
sock.close(); |
|
LogPrint(eLogError, "Reseed: HTTP CONNECT got bad status: ", proxyRes.code); |
|
return ""; |
|
} |
|
} |
|
else |
|
{ |
|
// assume socks if not http, is checked before this for other types |
|
// TODO: support username/password auth etc |
|
bool success = false; |
|
i2p::transport::Socks5Handshake (sock, std::make_pair(url.host, url.port), |
|
[&success](const boost::system::error_code& ec) |
|
{ |
|
if (!ec) |
|
success = true; |
|
else |
|
LogPrint (eLogError, "Reseed: SOCKS handshake failed: ", ec.message()); |
|
}); |
|
service.run (); // execute all async operations |
|
if (!success) |
|
{ |
|
sock.close(); |
|
return ""; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// direct connection |
|
auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode); |
|
if (!ecode) |
|
{ |
|
bool connected = false; |
|
for (const auto& it: endpoints) |
|
{ |
|
boost::asio::ip::tcp::endpoint ep = it; |
|
bool supported = false; |
|
if (!ep.address ().is_unspecified ()) |
|
{ |
|
if (ep.address ().is_v4 ()) |
|
supported = i2p::context.SupportsV4 (); |
|
else if (ep.address ().is_v6 ()) |
|
supported = i2p::util::net::IsYggdrasilAddress (ep.address ()) ? |
|
i2p::context.SupportsMesh () : i2p::context.SupportsV6 (); |
|
} |
|
if (supported) |
|
{ |
|
s.lowest_layer().connect (ep, ecode); |
|
if (!ecode) |
|
{ |
|
LogPrint (eLogDebug, "Reseed: Resolved to ", ep.address ()); |
|
connected = true; |
|
break; |
|
} |
|
} |
|
} |
|
if (!connected) |
|
{ |
|
LogPrint(eLogError, "Reseed: Failed to connect to ", url.host); |
|
return ""; |
|
} |
|
} |
|
} |
|
if (!ecode) |
|
{ |
|
SSL_set_tlsext_host_name(s.native_handle(), url.host.c_str ()); |
|
s.handshake (boost::asio::ssl::stream_base::client, ecode); |
|
if (!ecode) |
|
{ |
|
LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port); |
|
return ReseedRequest (s, url.to_string()); |
|
} |
|
else |
|
LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ()); |
|
} |
|
else |
|
LogPrint (eLogError, "Reseed: Couldn't connect to ", url.host, ": ", ecode.message ()); |
|
return ""; |
|
} |
|
|
|
template<typename Stream> |
|
std::string Reseeder::ReseedRequest (Stream& s, const std::string& uri) |
|
{ |
|
boost::system::error_code ecode; |
|
i2p::http::HTTPReq req; |
|
req.uri = uri; |
|
req.AddHeader("User-Agent", "Wget/1.11.4"); |
|
req.AddHeader("Connection", "close"); |
|
s.write_some (boost::asio::buffer (req.to_string())); |
|
// read response |
|
std::stringstream rs; |
|
char recv_buf[1024]; size_t l = 0; |
|
do { |
|
l = s.read_some (boost::asio::buffer (recv_buf, sizeof(recv_buf)), ecode); |
|
if (l) rs.write (recv_buf, l); |
|
} while (!ecode && l); |
|
// process response |
|
std::string data = rs.str(); |
|
i2p::http::HTTPRes res; |
|
int len = res.parse(data); |
|
if (len <= 0) { |
|
LogPrint(eLogWarning, "Reseed: Incomplete/broken response from ", uri); |
|
return ""; |
|
} |
|
if (res.code != 200) { |
|
LogPrint(eLogError, "Reseed: Failed to reseed from ", uri, ", http code ", res.code); |
|
return ""; |
|
} |
|
data.erase(0, len); /* drop http headers from response */ |
|
LogPrint(eLogDebug, "Reseed: Got ", data.length(), " bytes of data from ", uri); |
|
if (res.is_chunked()) { |
|
std::stringstream in(data), out; |
|
if (!i2p::http::MergeChunkedResponse(in, out)) { |
|
LogPrint(eLogWarning, "Reseed: Failed to merge chunked response from ", uri); |
|
return ""; |
|
} |
|
LogPrint(eLogDebug, "Reseed: Got ", data.length(), "(", out.tellg(), ") bytes of data from ", uri); |
|
data = out.str(); |
|
} |
|
return data; |
|
} |
|
|
|
std::string Reseeder::YggdrasilRequest (const std::string& address) |
|
{ |
|
i2p::http::URL url; |
|
if (!url.parse(address)) |
|
{ |
|
LogPrint(eLogError, "Reseed: Failed to parse url: ", address); |
|
return ""; |
|
} |
|
url.schema = "http"; |
|
if (!url.port) url.port = 80; |
|
|
|
boost::system::error_code ecode; |
|
boost::asio::io_context service; |
|
boost::asio::ip::tcp::socket s(service, boost::asio::ip::tcp::v6()); |
|
|
|
auto endpoints = boost::asio::ip::tcp::resolver(service).resolve (url.host, std::to_string(url.port), ecode); |
|
if (!ecode) |
|
{ |
|
bool connected = false; |
|
for (const auto& it: endpoints) |
|
{ |
|
boost::asio::ip::tcp::endpoint ep = it; |
|
if ( |
|
i2p::util::net::IsYggdrasilAddress (ep.address ()) && |
|
i2p::context.SupportsMesh () |
|
) |
|
{ |
|
LogPrint (eLogDebug, "Reseed: Yggdrasil: Resolved to ", ep.address ()); |
|
s.connect (ep, ecode); |
|
if (!ecode) |
|
{ |
|
connected = true; |
|
break; |
|
} |
|
} |
|
} |
|
if (!connected) |
|
{ |
|
LogPrint(eLogError, "Reseed: Yggdrasil: Failed to connect to ", url.host); |
|
return ""; |
|
} |
|
} |
|
|
|
if (!ecode) |
|
{ |
|
LogPrint (eLogDebug, "Reseed: Yggdrasil: Connected to ", url.host, ":", url.port); |
|
return ReseedRequest (s, url.to_string()); |
|
} |
|
else |
|
LogPrint (eLogError, "Reseed: Yggdrasil: Couldn't connect to ", url.host, ": ", ecode.message ()); |
|
|
|
return ""; |
|
} |
|
} |
|
}
|
|
|