/ libi2pd / Reseed.cpp
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  }