mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2025-04-30 12:47:48 +02:00
728 lines
33 KiB
C++
728 lines
33 KiB
C++
/*
|
|
* Copyright (c) 2013-2022, 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"
|
|
|
|
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(eLogError, "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(eLogError, "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(eLogError, "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(eLogError, "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(eLogError, "Reseed: Bad proxy url: ", proxy);
|
|
return "";
|
|
}
|
|
} else {
|
|
LogPrint(eLogError, "Reseed: Bad proxy url: ", proxy);
|
|
return "";
|
|
}
|
|
}
|
|
i2p::http::URL url;
|
|
if (!url.parse(address)) {
|
|
LogPrint(eLogError, "Reseed: Failed to parse url: ", address);
|
|
return "";
|
|
}
|
|
url.schema = "https";
|
|
if (!url.port)
|
|
url.port = 443;
|
|
|
|
boost::asio::io_service 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(
|
|
boost::asio::ip::tcp::resolver::query(proxyUrl.host, std::to_string(proxyUrl.port)), ecode);
|
|
if (!ecode) {
|
|
s.lowest_layer().connect(*it, 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(boost::asio::buffer_cast<const char *>(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
|
|
uint8_t hs_writebuf[3] = {0x05, 0x01, 0x00};
|
|
uint8_t hs_readbuf[2];
|
|
boost::asio::write(sock, boost::asio::buffer(hs_writebuf, 3), boost::asio::transfer_all(),
|
|
ecode);
|
|
if (ecode) {
|
|
sock.close();
|
|
LogPrint(eLogError, "Reseed: SOCKS handshake write failed: ", ecode.message());
|
|
return "";
|
|
}
|
|
boost::asio::read(sock, boost::asio::buffer(hs_readbuf, 2), ecode);
|
|
if (ecode) {
|
|
sock.close();
|
|
LogPrint(eLogError, "Reseed: SOCKS handshake read failed: ", ecode.message());
|
|
return "";
|
|
}
|
|
size_t sz = 0;
|
|
uint8_t buf[256];
|
|
|
|
buf[0] = 0x05;
|
|
buf[1] = 0x01;
|
|
buf[2] = 0x00;
|
|
buf[3] = 0x03;
|
|
sz += 4;
|
|
size_t hostsz = url.host.size();
|
|
if (1 + 2 + hostsz + sz > sizeof(buf)) {
|
|
sock.close();
|
|
LogPrint(eLogError, "Reseed: SOCKS handshake failed, hostname too big: ", url.host);
|
|
return "";
|
|
}
|
|
buf[4] = (uint8_t) hostsz;
|
|
memcpy(buf + 5, url.host.c_str(), hostsz);
|
|
sz += hostsz + 1;
|
|
htobe16buf(buf + sz, url.port);
|
|
sz += 2;
|
|
boost::asio::write(sock, boost::asio::buffer(buf, sz), boost::asio::transfer_all(), ecode);
|
|
if (ecode) {
|
|
sock.close();
|
|
LogPrint(eLogError, "Reseed: SOCKS handshake failed writing: ", ecode.message());
|
|
return "";
|
|
}
|
|
boost::asio::read(sock, boost::asio::buffer(buf, 10), ecode);
|
|
if (ecode) {
|
|
sock.close();
|
|
LogPrint(eLogError, "Reseed: SOCKS handshake failed reading: ", ecode.message());
|
|
return "";
|
|
}
|
|
if (buf[1] != 0x00) {
|
|
sock.close();
|
|
LogPrint(eLogError, "Reseed: SOCKS handshake bad reply code: ", std::to_string(buf[1]));
|
|
return "";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// direct connection
|
|
auto it = boost::asio::ip::tcp::resolver(service).resolve(
|
|
boost::asio::ip::tcp::resolver::query(url.host, std::to_string(url.port)), ecode);
|
|
if (!ecode) {
|
|
bool connected = false;
|
|
boost::asio::ip::tcp::resolver::iterator end;
|
|
while (it != end) {
|
|
boost::asio::ip::tcp::endpoint ep = *it;
|
|
if ((ep.address().is_v4() && i2p::context.SupportsV4()) ||
|
|
(ep.address().is_v6() && i2p::context.SupportsV6())) {
|
|
s.lowest_layer().connect(ep, ecode);
|
|
if (!ecode) {
|
|
connected = true;
|
|
break;
|
|
}
|
|
}
|
|
it++;
|
|
}
|
|
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_service service;
|
|
boost::asio::ip::tcp::socket s(service, boost::asio::ip::tcp::v6());
|
|
|
|
if (url.host.length() < 2) return ""; // assume []
|
|
auto host = url.host.substr(1, url.host.length() - 2);
|
|
LogPrint(eLogDebug, "Reseed: Connecting to Yggdrasil ", url.host, ":", url.port);
|
|
s.connect(boost::asio::ip::tcp::endpoint(boost::asio::ip::address_v6::from_string(host), url.port), ecode);
|
|
if (!ecode) {
|
|
LogPrint(eLogDebug, "Reseed: Connected to Yggdrasil ", url.host, ":", url.port);
|
|
return ReseedRequest(s, url.to_string());
|
|
} else
|
|
LogPrint(eLogError, "Reseed: Couldn't connect to Yggdrasil ", url.host, ": ", ecode.message());
|
|
|
|
return "";
|
|
}
|
|
}
|
|
}
|