Merge pull request #391 from PurpleI2P/openssl

new fs
pull/394/head
orignal 8 years ago
commit dbdc7279c4

@ -5,11 +5,11 @@
#include <fstream>
#include <chrono>
#include <condition_variable>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include "Base.h"
#include "util.h"
#include "Identity.h"
#include "FS.h"
#include "Log.h"
#include "NetDb.h"
#include "ClientContext.h"
@ -19,143 +19,107 @@ namespace i2p
{
namespace client
{
// TODO: this is actually proxy class
class AddressBookFilesystemStorage: public AddressBookStorage
{
public:
private:
i2p::fs::HashedStorage storage;
std::string indexPath;
AddressBookFilesystemStorage ();
public:
AddressBookFilesystemStorage (): storage("addressbook", "b", "", "b32") {};
std::shared_ptr<const i2p::data::IdentityEx> GetAddress (const i2p::data::IdentHash& ident) const;
void AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address);
void RemoveAddress (const i2p::data::IdentHash& ident);
bool Init ();
int Load (std::map<std::string, i2p::data::IdentHash>& addresses);
int Save (const std::map<std::string, i2p::data::IdentHash>& addresses);
private:
boost::filesystem::path GetPath () const
{
return i2p::util::filesystem::GetDefaultDataDir() / "addressbook";
}
boost::filesystem::path GetAddressPath (const i2p::data::IdentHash& ident) const
{
auto b32 = ident.ToBase32();
return GetPath () / (std::string ("b") + b32[0]) / (b32 + ".b32");
}
};
AddressBookFilesystemStorage::AddressBookFilesystemStorage ()
bool AddressBookFilesystemStorage::Init()
{
auto path = GetPath ();
if (!boost::filesystem::exists (path))
{
// Create directory is necessary
if (!boost::filesystem::create_directory (path))
LogPrint (eLogError, "Addressbook: failed to create addressbook directory");
}
storage.SetPlace(i2p::fs::GetDataDir());
indexPath = storage.GetRoot() + i2p::fs::dirSep + "addresses.csv";
return storage.Init(i2p::data::GetBase32SubstitutionTable(), 32);
}
std::shared_ptr<const i2p::data::IdentityEx> AddressBookFilesystemStorage::GetAddress (const i2p::data::IdentHash& ident) const
{
auto filename = GetAddressPath (ident);
if (!boost::filesystem::exists (filename))
{
boost::filesystem::create_directory (filename.parent_path ());
// try to find in main folder
auto filename1 = GetPath () / (ident.ToBase32 () + ".b32");
if (!boost::filesystem::exists (filename1))
{
boost::system::error_code ec;
boost::filesystem::rename (filename1, filename, ec);
if (ec)
LogPrint (eLogError, "Addresbook: couldn't move file ", ec.message ());
}
else
return nullptr; // address doesn't exist
}
std::ifstream f(filename.string (), std::ifstream::binary);
if (f.is_open ())
{
f.seekg (0,std::ios::end);
size_t len = f.tellg ();
if (len < i2p::data::DEFAULT_IDENTITY_SIZE)
{
LogPrint (eLogError, "Addresbook: File ", filename, " is too short. ", len);
return nullptr;
}
f.seekg(0, std::ios::beg);
uint8_t * buf = new uint8_t[len];
f.read((char *)buf, len);
auto address = std::make_shared<i2p::data::IdentityEx>(buf, len);
delete[] buf;
return address;
std::string filename = storage.Path(ident.ToBase32());
std::ifstream f(filename, std::ifstream::binary);
if (!f.is_open ()) {
LogPrint(eLogDebug, "Addressbook: Requested, but not found: ", filename);
return nullptr;
}
else
f.seekg (0,std::ios::end);
size_t len = f.tellg ();
if (len < i2p::data::DEFAULT_IDENTITY_SIZE) {
LogPrint (eLogError, "Addresbook: File ", filename, " is too short: ", len);
return nullptr;
}
f.seekg(0, std::ios::beg);
uint8_t * buf = new uint8_t[len];
f.read((char *)buf, len);
auto address = std::make_shared<i2p::data::IdentityEx>(buf, len);
delete[] buf;
return address;
}
void AddressBookFilesystemStorage::AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address)
{
auto filename = GetAddressPath (address->GetIdentHash ());
std::ofstream f (filename.string (), std::ofstream::binary | std::ofstream::out);
if (!f.is_open ())
{
// create subdirectory
if (boost::filesystem::create_directory (filename.parent_path ()))
f.open (filename.string (), std::ofstream::binary | std::ofstream::out); // and try to open again
}
if (f.is_open ())
{
size_t len = address->GetFullLen ();
uint8_t * buf = new uint8_t[len];
address->ToBuffer (buf, len);
f.write ((char *)buf, len);
delete[] buf;
std::string path = storage.Path( address->GetIdentHash().ToBase32() );
std::ofstream f (path, std::ofstream::binary | std::ofstream::out);
if (!f.is_open ()) {
LogPrint (eLogError, "Addresbook: can't open file ", path);
return;
}
else
LogPrint (eLogError, "Addresbook: can't open file ", filename);
size_t len = address->GetFullLen ();
uint8_t * buf = new uint8_t[len];
address->ToBuffer (buf, len);
f.write ((char *)buf, len);
delete[] buf;
}
void AddressBookFilesystemStorage::RemoveAddress (const i2p::data::IdentHash& ident)
{
auto filename = GetAddressPath (ident);
if (boost::filesystem::exists (filename))
boost::filesystem::remove (filename);
storage.Remove( ident.ToBase32() );
}
int AddressBookFilesystemStorage::Load (std::map<std::string, i2p::data::IdentHash>& addresses)
{
int num = 0;
auto filename = GetPath () / "addresses.csv";
std::ifstream f (filename.string (), std::ifstream::in); // in text mode
if (f.is_open ())
{
addresses.clear ();
while (!f.eof ())
std::string s;
std::ifstream f (indexPath, std::ifstream::in); // in text mode
if (f.is_open ()) {
LogPrint(eLogInfo, "Addressbook: using index file ", indexPath);
} else {
LogPrint(eLogWarning, "Addressbook: Can't open ", indexPath);
return 0;
}
addresses.clear ();
while (!f.eof ()) {
getline(f, s);
if (!s.length())
continue; // skip empty line
std::size_t pos = s.find(',');
if (pos != std::string::npos)
{
std::string s;
getline(f, s);
if (!s.length())
continue; // skip empty line
std::string name = s.substr(0, pos++);
std::string addr = s.substr(pos);
size_t pos = s.find(',');
if (pos != std::string::npos)
{
std::string name = s.substr(0, pos++);
std::string addr = s.substr(pos);
i2p::data::IdentHash ident;
ident.FromBase32 (addr);
addresses[name] = ident;
num++;
}
}
LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded");
i2p::data::IdentHash ident;
ident.FromBase32 (addr);
addresses[name] = ident;
num++;
}
}
else
LogPrint (eLogWarning, "Addressbook: ", filename, " not found");
LogPrint (eLogInfo, "Addressbook: ", num, " addresses loaded from storage");
return num;
}
@ -167,24 +131,23 @@ namespace client
}
int num = 0;
auto filename = GetPath () / "addresses.csv";
std::ofstream f (filename.string (), std::ofstream::out); // in text mode
if (f.is_open ())
{
for (auto it: addresses)
{
f << it.first << "," << it.second.ToBase32 () << std::endl;
num++;
}
LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved");
std::ofstream f (indexPath, std::ofstream::out); // in text mode
if (!f.is_open ()) {
LogPrint (eLogWarning, "Addressbook: Can't open ", indexPath);
return 0;
}
for (auto it: addresses) {
f << it.first << "," << it.second.ToBase32 () << std::endl;
num++;
}
else
LogPrint (eLogError, "Addresbook: can't open file ", filename);
LogPrint (eLogInfo, "Addressbook: ", num, " addresses saved");
return num;
}
//---------------------------------------------------------------------
AddressBook::AddressBook (): m_Storage (nullptr), m_IsLoaded (false), m_IsDownloading (false),
AddressBook::AddressBook (): m_Storage(new AddressBookFilesystemStorage), m_IsLoaded (false), m_IsDownloading (false),
m_DefaultSubscription (nullptr), m_SubscriptionsUpdateTimer (nullptr)
{
}
@ -196,6 +159,7 @@ namespace client
void AddressBook::Start ()
{
m_Storage->Init();
LoadHosts (); /* try storage, then hosts.txt, then download */
StartSubscriptions ();
}
@ -235,11 +199,6 @@ namespace client
m_Subscriptions.clear ();
}
AddressBookStorage * AddressBook::CreateStorage ()
{
return new AddressBookFilesystemStorage ();
}
bool AddressBook::GetIdentHash (const std::string& address, i2p::data::IdentHash& ident)
{
auto pos = address.find(".b32.i2p");
@ -283,8 +242,6 @@ namespace client
{
auto ident = std::make_shared<i2p::data::IdentityEx>();
ident->FromBase64 (base64);
if (!m_Storage)
m_Storage = CreateStorage ();
m_Storage->AddAddress (ident);
m_Addresses[address] = ident->GetIdentHash ();
LogPrint (eLogInfo, "Addressbook: added ", address," -> ", ToAddress(ident->GetIdentHash ()));
@ -292,15 +249,11 @@ namespace client
void AddressBook::InsertAddress (std::shared_ptr<const i2p::data::IdentityEx> address)
{
if (!m_Storage)
m_Storage = CreateStorage ();
m_Storage->AddAddress (address);
}
std::shared_ptr<const i2p::data::IdentityEx> AddressBook::GetAddress (const std::string& address)
{
if (!m_Storage)
m_Storage = CreateStorage ();
i2p::data::IdentHash ident;
if (!GetIdentHash (address, ident)) return nullptr;
return m_Storage->GetAddress (ident);
@ -308,16 +261,14 @@ namespace client
void AddressBook::LoadHosts ()
{
if (!m_Storage)
m_Storage = CreateStorage ();
if (m_Storage->Load (m_Addresses) > 0)
{
m_IsLoaded = true;
return;
}
// try hosts.txt first
std::ifstream f (i2p::util::filesystem::GetFullPath ("hosts.txt").c_str (), std::ifstream::in); // in text mode
// then try hosts.txt
std::ifstream f (i2p::fs::DataDirPath("hosts.txt"), std::ifstream::in); // in text mode
if (f.is_open ())
{
LoadHostsFromStream (f);
@ -367,7 +318,7 @@ namespace client
{
if (!m_Subscriptions.size ())
{
std::ifstream f (i2p::util::filesystem::GetFullPath ("subscriptions.txt").c_str (), std::ifstream::in); // in text mode
std::ifstream f (i2p::fs::DataDirPath ("subscriptions.txt"), std::ifstream::in); // in text mode
if (f.is_open ())
{
std::string s;
@ -431,7 +382,10 @@ namespace client
if (ecode != boost::asio::error::operation_aborted)
{
auto dest = i2p::client::context.GetSharedLocalDestination ();
if (!dest) return;
if (!dest) {
LogPrint(eLogWarning, "Addressbook: missing local destination, skip subscription update");
return;
}
if (!m_IsDownloading && dest->IsReady ())
{
if (!m_IsLoaded)
@ -495,7 +449,7 @@ namespace client
});
if (newDataReceived.wait_for (l, std::chrono::seconds (SUBSCRIPTION_REQUEST_TIMEOUT)) == std::cv_status::timeout)
{
LogPrint (eLogError, "Subscription LeaseSet request timeout expired");
LogPrint (eLogError, "Addressbook: Subscription LeaseSet request timeout expired");
i2p::client::context.GetSharedLocalDestination ()->CancelDestinationRequest (ident);
}
}
@ -602,7 +556,7 @@ namespace client
LogPrint (eLogError, "Addressbook: Can't resolve ", u.host_);
if (!success)
LogPrint (eLogError, "Addressbook: download failed");
LogPrint (eLogError, "Addressbook: download hosts.txt from ", m_Link, " failed");
m_Book.DownloadComplete (success);
}

@ -10,7 +10,6 @@
#include <memory>
#include <boost/asio.hpp>
#include "Base.h"
#include "util.h"
#include "Identity.h"
#include "Log.h"
@ -36,6 +35,7 @@ namespace client
virtual void AddAddress (std::shared_ptr<const i2p::data::IdentityEx> address) = 0;
virtual void RemoveAddress (const i2p::data::IdentHash& ident) = 0;
virtual bool Init () = 0;
virtual int Load (std::map<std::string, i2p::data::IdentHash>& addresses) = 0;
virtual int Save (const std::map<std::string, i2p::data::IdentHash>& addresses) = 0;
};
@ -65,7 +65,6 @@ namespace client
void StartSubscriptions ();
void StopSubscriptions ();
AddressBookStorage * CreateStorage ();
void LoadHosts ();
void LoadSubscriptions ();

@ -6,6 +6,18 @@ namespace i2p
{
namespace data
{
static const char T32[32] = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'k', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 't', 't', 'u', 'v', 'w', 'x',
'y', 'z', '2', '3', '4', '5', '6', '7',
};
const char * GetBase32SubstitutionTable ()
{
return T32;
}
static void iT64Build(void);
/*
@ -16,7 +28,7 @@ namespace data
* Direct Substitution Table
*/
static char T64[64] = {
static const char T64[64] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',

@ -13,6 +13,7 @@ namespace data
{
size_t ByteStreamToBase64 (const uint8_t * InBuffer, size_t InCount, char * OutBuffer, size_t len);
size_t Base64ToByteStream (const char * InBuffer, size_t InCount, uint8_t * OutBuffer, size_t len );
const char * GetBase32SubstitutionTable ();
const char * GetBase64SubstitutionTable ();
size_t Base32ToByteStream (const char * inBuf, size_t len, uint8_t * outBuf, size_t outLen);

@ -3,7 +3,7 @@
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include "Config.h"
#include "util.h"
#include "FS.h"
#include "Log.h"
#include "Identity.h"
#include "ClientContext.h"
@ -148,10 +148,10 @@ namespace client
m_SharedLocalDestination = nullptr;
}
void ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType)
void ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType)
{
std::string fullPath = i2p::util::filesystem::GetFullPath (filename);
std::ifstream s(fullPath.c_str (), std::ifstream::binary);
std::string fullPath = i2p::fs::DataDirPath (filename);
std::ifstream s(fullPath, std::ifstream::binary);
if (s.is_open ())
{
s.seekg (0, std::ios::end);
@ -252,14 +252,17 @@ namespace client
void ClientContext::ReadTunnels ()
{
boost::property_tree::ptree pt;
std::string pathTunnelsConfigFile = i2p::util::filesystem::GetTunnelsConfigFile().string();
try
std::string tunConf; i2p::config::GetOption("tunconf", tunConf);
if (tunConf == "")
tunConf = i2p::fs::DataDirPath ("tunnels.cfg");
LogPrint(eLogDebug, "FS: tunnels config file: ", tunConf);
try
{
boost::property_tree::read_ini (pathTunnelsConfigFile, pt);
}
catch (std::exception& ex)
boost::property_tree::read_ini (tunConf, pt);
}
catch (std::exception& ex)
{
LogPrint (eLogWarning, "Clients: Can't read ", pathTunnelsConfigFile, ": ", ex.what ());
LogPrint (eLogWarning, "Clients: Can't read ", tunConf, ": ", ex.what ());
return;
}
@ -347,7 +350,7 @@ namespace client
numServerTunnels++;
}
else
LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", pathTunnelsConfigFile);
LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf);
}
catch (std::exception& ex)

@ -5,6 +5,7 @@
#include "Config.h"
#include "Log.h"
#include "FS.h"
#include "Base.h"
#include "version.h"
#include "Transports.h"
@ -14,7 +15,6 @@
#include "Tunnel.h"
#include "NetDb.h"
#include "Garlic.h"
#include "util.h"
#include "Streaming.h"
#include "Destination.h"
#include "HTTPServer.h"
@ -63,9 +63,18 @@ namespace i2p
i2p::config::Init();
i2p::config::ParseCmdline(argc, argv);
std::string config = i2p::util::filesystem::GetConfigFile().string();
std::string tunconf = i2p::util::filesystem::GetTunnelsConfigFile().string();
std::string datadir = i2p::util::filesystem::GetDataDir().string();
std::string config; i2p::config::GetOption("conf", config);
std::string datadir; i2p::config::GetOption("datadir", datadir);
i2p::fs::DetectDataDir(datadir, IsService());
i2p::fs::Init();
datadir = i2p::fs::GetDataDir();
if (config == "")
{
config = i2p::fs::DataDirPath("i2p.conf");
// use i2p.cong only if exists
if (!i2p::fs::Exists (config)) config = ""; /* reset */
}
i2p::config::ParseConfig(config);
i2p::config::Finalize();
@ -79,7 +88,6 @@ namespace i2p
LogPrint(eLogInfo, "i2pd v", VERSION, " starting");
LogPrint(eLogDebug, "FS: main config file: ", config);
LogPrint(eLogDebug, "FS: tunnels config: ", tunconf);
LogPrint(eLogDebug, "FS: data directory: ", datadir);
uint16_t port; i2p::config::GetOption("port", port);
@ -149,18 +157,9 @@ namespace i2p
if (isDaemon && (logs == "" || logs == "stdout"))
logs = "file";
if (logs == "file")
{
if (logs == "file") {
if (logfile == "")
{
// use autodetect of logfile
logfile = IsService () ? "/var/log" : i2p::util::filesystem::GetDataDir().string();
#ifndef _WIN32
logfile.append("/i2pd.log");
#else
logfile.append("\\i2pd.log");
#endif
}
logfile = i2p::fs::DataDirPath("i2pd.log");
StartLog (logfile);
} else {
// use stdout

@ -9,8 +9,8 @@
#include <sys/stat.h>
#include "Config.h"
#include "FS.h"
#include "Log.h"
#include "util.h"
void handle_signal(int sig)
{
@ -55,7 +55,7 @@ namespace i2p
LogPrint(eLogError, "Daemon: could not create process group.");
return false;
}
std::string d(i2p::util::filesystem::GetDataDir().string ()); // make a copy
std::string d = i2p::fs::GetDataDir();
if (chdir(d.c_str()) != 0)
{
LogPrint(eLogError, "Daemon: could not chdir: ", strerror(errno));
@ -75,8 +75,7 @@ namespace i2p
// this code is c-styled and a bit ugly, but we need fd for locking pidfile
std::string pidfile; i2p::config::GetOption("pidfile", pidfile);
if (pidfile == "") {
pidfile = IsService () ? "/var/run" : i2p::util::filesystem::GetDataDir().string();
pidfile.append("/i2pd.pid");
pidfile = i2p::fs::DataDirPath("i2pd.pid");
}
if (pidfile != "") {
pidFH = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600);
@ -115,7 +114,7 @@ namespace i2p
bool DaemonLinux::stop()
{
unlink(pidfile.c_str());
i2p::fs::Remove(pidfile);
return Daemon_Singleton::stop();
}

@ -2,8 +2,9 @@
#include <cassert>
#include <boost/lexical_cast.hpp>
#include <openssl/rand.h>
#include "Log.h"
#include "util.h"
#include "FS.h"
#include "Crypto.h"
#include "Timestamp.h"
#include "NetDb.h"
@ -459,7 +460,8 @@ namespace client
{
auto s = shared_from_this ();
RequestLeaseSet (GetIdentHash (),
[s](std::shared_ptr<i2p::data::LeaseSet> leaseSet)
// "this" added due to bug in gcc 4.7-4.8
[s,this](std::shared_ptr<i2p::data::LeaseSet> leaseSet)
{
if (leaseSet)
{
@ -751,30 +753,26 @@ namespace client
void ClientDestination::PersistTemporaryKeys ()
{
auto path = i2p::util::filesystem::GetDefaultDataDir() / "destinations";
auto filename = path / (GetIdentHash ().ToBase32 () + ".dat");
std::ifstream f(filename.string (), std::ifstream::binary);
if (f)
{
f.read ((char *)m_EncryptionPublicKey, 256);
std::string ident = GetIdentHash().ToBase32();
std::string path = i2p::fs::DataDirPath("destinations", (ident + ".dat"));
std::ifstream f(path, std::ifstream::binary);
if (f) {
f.read ((char *)m_EncryptionPublicKey, 256);
f.read ((char *)m_EncryptionPrivateKey, 256);
return;
}
if (!f)
{
LogPrint (eLogInfo, "Creating new temporary keys for address ", GetIdentHash ().ToBase32 ());
i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey);
if (!boost::filesystem::exists (path))
{
if (!boost::filesystem::create_directory (path))
LogPrint (eLogError, "Failed to create destinations directory");
}
std::ofstream f1 (filename.string (), std::ofstream::binary | std::ofstream::out);
if (f1)
{
f1.write ((char *)m_EncryptionPublicKey, 256);
f1.write ((char *)m_EncryptionPrivateKey, 256);
}
}
LogPrint (eLogInfo, "Destination: Creating new temporary keys for address ", ident, ".b32.i2p");
i2p::crypto::GenerateElGamalKeyPair(m_EncryptionPrivateKey, m_EncryptionPublicKey);
std::ofstream f1 (path, std::ofstream::binary | std::ofstream::out);
if (f1) {
f1.write ((char *)m_EncryptionPublicKey, 256);
f1.write ((char *)m_EncryptionPrivateKey, 256);
return;
}
LogPrint(eLogError, "Destinations: Can't save keys to ", path);
}
}
}

158
FS.cpp

@ -0,0 +1,158 @@
/*
* Copyright (c) 2013-2016, 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 <algorithm>
#include <boost/filesystem.hpp>
#ifdef WIN32
#include <shlobj.h>
#endif
#include "Base.h"
#include "FS.h"
#include "Log.h"
namespace i2p {
namespace fs {
std::string appName = "i2pd";
std::string dataDir = "";
#ifdef _WIN32
std::string dirSep = "\\";
#else
std::string dirSep = "/";
#endif
const std::string & GetAppName () {
return appName;
}
void SetAppName (const std::string& name) {
appName = name;
}
const std::string & GetDataDir () {
return dataDir;
}
void DetectDataDir(const std::string & cmdline_param, bool isService) {
if (cmdline_param != "") {
dataDir = cmdline_param;
return;
}
#if defined(WIN32) || defined(_WIN32)
char localAppData[MAX_PATH];
SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData);
dataDir = std::string(localAppData) + "\\" + appName;
return;
#elif defined(MAC_OSX)
char *home = getenv("HOME");
dataDir = (home != NULL && strlen(home) > 0) ? home : "";
dataDir += "/Library/Application Support/" + appName;
return;
#else /* other unix */
char *home = getenv("HOME");
if (isService) {
dataDir = "/var/lib/" + appName;
} else if (home != NULL && strlen(home) > 0) {
dataDir = std::string(home) + "/." + appName;
} else {
dataDir = "/tmp/" + appName;
}
return;
#endif
}
bool Init() {
if (boost::filesystem::exists(dataDir))
boost::filesystem::create_directory(dataDir);
std::string destinations = DataDirPath("destinations");
if (boost::filesystem::exists(destinations))
boost::filesystem::create_directory(destinations);
return true;
}
bool ReadDir(const std::string & path, std::vector<std::string> & files) {
if (!boost::filesystem::exists(path))
return false;
boost::filesystem::directory_iterator it(path);
boost::filesystem::directory_iterator end;
for ( ; it != end; it++) {
if (!boost::filesystem::is_regular_file(it->status()))
continue;
files.push_back(it->path().string());
}
return true;
}
bool Exists(const std::string & path) {
return boost::filesystem::exists(path);
}
bool Remove(const std::string & path) {
if (!boost::filesystem::exists(path))
return false;
return boost::filesystem::remove(path);
}
void HashedStorage::SetPlace(const std::string &path) {
root = path + i2p::fs::dirSep + name;
}
bool HashedStorage::Init(const char * chars, size_t count) {
if (!boost::filesystem::exists(root)) {
boost::filesystem::create_directory(root);
}
for (size_t i = 0; i < count; i++) {
auto p = root + i2p::fs::dirSep + prefix1 + chars[i];
if (boost::filesystem::exists(p))
continue;
if (boost::filesystem::create_directory(p))
continue; /* ^ throws exception on failure */
return false;
}
return true;
}
std::string HashedStorage::Path(const std::string & ident) const {
std::string safe_ident = ident;
std::replace(safe_ident.begin(), safe_ident.end(), '/', '-');
std::replace(safe_ident.begin(), safe_ident.end(), '\\', '-');
std::stringstream t("");
t << this->root << i2p::fs::dirSep;
t << prefix1 << safe_ident[0] << i2p::fs::dirSep;
t << prefix2 << safe_ident << "." << suffix;
return t.str();
}
void HashedStorage::Remove(const std::string & ident) {
std::string path = Path(ident);
if (!boost::filesystem::exists(path))
return;
boost::filesystem::remove(path);
}
void HashedStorage::Traverse(std::vector<std::string> & files) {
boost::filesystem::path p(root);
boost::filesystem::recursive_directory_iterator it(p);
boost::filesystem::recursive_directory_iterator end;
for ( ; it != end; it++) {
if (!boost::filesystem::is_regular_file( it->status() ))
continue;
const std::string & t = it->path().string();
files.push_back(t);
}
}
} // fs
} // i2p

142
FS.h

@ -0,0 +1,142 @@
/*
* Copyright (c) 2013-2016, 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
*/
#ifndef FS_H__
#define FS_H__
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
namespace i2p {
namespace fs {
extern std::string dirSep;
/**
* @brief Class to work with NetDb & Router profiles
*
* Usage:
*
* const char alphabet[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
* auto h = HashedStorage("name", "y", "z-", ".txt");
* h.SetPlace("/tmp/hs-test");
* h.GetName() -> gives "name"
* h.GetRoot() -> gives "/tmp/hs-test/name"
* h.Init(alphabet, 8); <- creates needed dirs, 8 is size of alphabet
* h.Path("abcd"); <- returns /tmp/hs-test/name/ya/z-abcd.txt
* h.Remove("abcd"); <- removes /tmp/hs-test/name/ya/z-abcd.txt, if it exists
* std::vector<std::string> files;
* h.Traverse(files); <- finds all files in storage and saves in given vector
*/
class HashedStorage {
protected:
std::string root; /**< path to storage with it's name included */
std::string name; /**< name of the storage */
std::string prefix1; /**< hashed directory prefix */
std::string prefix2; /**< prefix of file in storage */
std::string suffix; /**< suffix of file in storage (extension) */
public:
HashedStorage(const char *n, const char *p1, const char *p2, const char *s):
name(n), prefix1(p1), prefix2(p2), suffix(s) {};
/** create subdirs in storage */
bool Init(const char* chars, size_t cnt);
const std::string & GetRoot() const { return this->root; }
const std::string & GetName() const { return this->name; }
/** set directory where to place storage directory */
void SetPlace(const std::string & path);
/** path to file with given ident */
std::string Path(const std::string & ident) const;
/** remove file by ident */
void Remove(const std::string & ident);
/** find all files in storage and store list in provided vector */
void Traverse(std::vector<std::string> & files);
};
/** @brief Returns current application name, default 'i2pd' */
const std::string & GetAppName ();
/** @brief Set applicaton name, affects autodetection of datadir */
void SetAppName (const std::string& name);
/** @brief Returns datadir path */
const std::string & GetDataDir();
/**
* @brief Set datadir either from cmdline option or using autodetection
* @param cmdline_param Value of cmdline parameter --datadir=<something>
* @param isService Value of cmdline parameter --service
*
* Examples of autodetected paths:
*
* Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd\
* Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd\
* Mac: /Library/Application Support/i2pd/ or ~/Library/Application Support/i2pd/
* Unix: /var/lib/i2pd/ (system=1) >> ~/.i2pd/ or /tmp/i2pd/
*/
void DetectDataDir(const std::string & cmdline_datadir, bool isService = false);
/**
* @brief Create subdirectories inside datadir
*/
bool Init();
/**
* @brief Get list of files in directory
* @param path Path to directory
* @param files Vector to store found files
* @return true on success and false if directory not exists
*/
bool ReadDir(const std::string & path, std::vector<std::string> & files);
/**
* @brief Remove file with given path
* @param path Absolute path to file
* @return true on success, false if file not exists, throws exception on error
*/
bool Remove(const std::string & path);
/**
* @brief Check existence of file
* @param path Absolute path to file
* @return true if file exists, false otherwise
*/
bool Exists(const std::string & path);
template<typename T>
void _ExpandPath(std::stringstream & path, T c) {
path << i2p::fs::dirSep << c;
}
template<typename T, typename ... Other>
void _ExpandPath(std::stringstream & path, T c, Other ... other) {
_ExpandPath(path, c);
_ExpandPath(path, other ...);
}
/**
* @brief Get path relative to datadir
*
* Examples (with datadir = "/tmp/i2pd"):
*
* i2p::fs::Path("test") -> '/tmp/i2pd/test'
* i2p::fs::Path("test", "file.txt") -> '/tmp/i2pd/test/file.txt'
*/
template<typename ... Other>
std::string DataDirPath(Other ... components) {
std::stringstream s("");
s << i2p::fs::GetDataDir();
_ExpandPath(s, components ...);
return s.str();
}
} // fs
} // i2p
#endif // /* FS_H__ */

@ -1,7 +1,7 @@
#include <string.h>
#include "util.h"
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include "FS.h"
#include "Log.h"
#include "Crypto.h"
#include "Family.h"
@ -89,21 +89,24 @@ namespace data
void Families::LoadCertificates ()
{
boost::filesystem::path familyDir = i2p::util::filesystem::GetCertificatesDir() / "family";
if (!boost::filesystem::exists (familyDir)) return;
std::string certDir = i2p::fs::DataDirPath("certificates", "family");
std::vector<std::string> files;
int numCertificates = 0;
boost::filesystem::directory_iterator end; // empty
for (boost::filesystem::directory_iterator it (familyDir); it != end; ++it)
{
if (boost::filesystem::is_regular_file (it->status()) && it->path ().extension () == ".crt")
{
LoadCertificate (it->path ().string ());
numCertificates++;
}
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, "Family: ignoring file ", file);
continue;
}
LoadCertificate (file);
numCertificates++;
}
if (numCertificates > 0)
LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded");
LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded");
}
bool Families::VerifyFamily (const std::string& family, const IdentHash& ident,
@ -124,10 +127,10 @@ namespace data
std::string CreateFamilySignature (const std::string& family, const IdentHash& ident)
{
auto filename = i2p::fs::DataDirPath("family", (family + ".key"));
std::string sig;
auto filename = i2p::util::filesystem::GetDefaultDataDir() / "family" / (family + ".key");
SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ());
int ret = SSL_CTX_use_PrivateKey_file (ctx, filename.string ().c_str (), SSL_FILETYPE_PEM);
int ret = SSL_CTX_use_PrivateKey_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
if (ret)
{
SSL * ssl = SSL_new (ctx);
@ -163,7 +166,7 @@ namespace data
SSL_free (ssl);
}
else
LogPrint (eLogError, "Family: Can't open keys file ", filename.string ());
LogPrint (eLogError, "Family: Can't open keys file: ", filename);
SSL_CTX_free (ctx);
return sig;
}

@ -4,6 +4,7 @@
#include <boost/lexical_cast.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "Base.h"
#include "FS.h"
#include "Log.h"
#include "Tunnel.h"
#include "TransitTunnel.h"
@ -420,7 +421,7 @@ namespace util
s << " (" << i2p::transport::transports.GetInBandwidth () <<" Bps)<br>\r\n";
s << "<b>Sent:</b> " << i2p::transport::transports.GetTotalSentBytes ()/1000 << "K";
s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)<br>\r\n";
s << "<b>Data path:</b> " << i2p::util::filesystem::GetDataDir().string() << "<br>\r\n<br>\r\n";
s << "<b>Data path:</b> " << i2p::fs::GetDataDir() << "<br>\r\n<br>\r\n";
s << "<b>Our external address:</b>" << "<br>\r\n" ;
for (auto& address : i2p::context.GetRouterInfo().GetAddresses())
{

@ -13,6 +13,7 @@
#include <boost/property_tree/json_parser.hpp>
#endif
#include "FS.h"
#include "Log.h"
#include "Config.h"
#include "NetDb.h"
@ -39,15 +40,12 @@ namespace client
// certificate / keys
std::string i2pcp_crt; i2p::config::GetOption("i2pcontrol.cert", i2pcp_crt);
std::string i2pcp_key; i2p::config::GetOption("i2pcontrol.key", i2pcp_key);
auto path = GetPath ();
// TODO: move this to i2p::fs::expand
if (i2pcp_crt.at(0) != '/')
i2pcp_crt.insert(0, (path / "/").string());
i2pcp_crt = i2p::fs::DataDirPath(i2pcp_crt);
if (i2pcp_key.at(0) != '/')
i2pcp_key.insert(0, (path / "/").string());
if (!boost::filesystem::exists (i2pcp_crt) ||
!boost::filesystem::exists (i2pcp_key))
{
i2pcp_key = i2p::fs::DataDirPath(i2pcp_key);
if (!i2p::fs::Exists (i2pcp_crt) || !i2p::fs::Exists (i2pcp_key)) {
LogPrint (eLogInfo, "I2PControl: creating new certificate for control connection");
CreateCertificate (i2pcp_crt.c_str(), i2pcp_key.c_str());
} else {

@ -12,8 +12,6 @@
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/filesystem.hpp>
#include "util.h"
namespace i2p
{
@ -52,7 +50,6 @@ namespace client
void HandleResponseSent (const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<ssl_socket> socket, std::shared_ptr<I2PControlBuffer> buf);
boost::filesystem::path GetPath () const { return i2p::util::filesystem::GetDefaultDataDir(); };
void CreateCertificate (const char *crt_path, const char *key_path);
private:

@ -1,10 +1,10 @@
#include <string.h>
#include "I2PEndian.h"
#include <fstream>
#include <vector>
#include <boost/asio.hpp>
#include <openssl/rand.h>
#include <zlib.h>
#include "I2PEndian.h"
#include "Base.h"
#include "Log.h"
#include "Timestamp.h"
@ -22,10 +22,9 @@ namespace i2p
{
namespace data
{
const char NetDb::m_NetDbPath[] = "netDb";
NetDb netdb;
NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr)
NetDb::NetDb (): m_IsRunning (false), m_Thread (nullptr), m_Reseeder (nullptr), m_Storage("netDb", "r", "routerInfo-", "dat")
{
}
@ -37,6 +36,9 @@ namespace data
void NetDb::Start ()
{
m_Storage.SetPlace(i2p::fs::GetDataDir());
m_Storage.Init(i2p::data::GetBase64SubstitutionTable(), 64);
InitProfilesStorage ();
m_Families.LoadCertificates ();
Load ();
if (m_RouterInfos.size () < 25) // reseed if # of router less than 50
@ -168,6 +170,7 @@ namespace data
{
r->Update (buf, len);
LogPrint (eLogInfo, "NetDb: RouterInfo updated: ", ident.ToBase64());
// TODO: check if floodfill has been changed
}
else
{
@ -185,7 +188,7 @@ namespace data
std::unique_lock<std::mutex> l(m_RouterInfosMutex);
m_RouterInfos[r->GetIdentHash ()] = r;
}
if (r->IsFloodfill ())
if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable
{
std::unique_lock<std::mutex> l(m_FloodfillsMutex);
m_Floodfills.push_back (r);
@ -273,30 +276,6 @@ namespace data
return it->second->SetUnreachable (unreachable);
}
// TODO: Move to reseed and/or scheduled tasks. (In java version, scheduler fix this as well as sort RIs.)
bool NetDb::CreateNetDb(boost::filesystem::path directory)
{
LogPrint (eLogInfo, "NetDb: storage directory doesn't exist, trying to create it.");
if (!boost::filesystem::create_directory (directory))
{
LogPrint (eLogError, "NetDb: failed to create directory ", directory);
return false;
}
// list of chars might appear in base64 string
const char * chars = GetBase64SubstitutionTable (); // 64 bytes
for (int i = 0; i < 64; i++)
{
auto p = directory / (std::string ("r") + chars[i]);
if (!boost::filesystem::exists (p) && !boost::filesystem::create_directory (p))
{
LogPrint (eLogError, "NetDb: failed to create directory ", p);
return false;
}
}
return true;
}
void NetDb::Reseed ()
{
if (!m_Reseeder)
@ -311,154 +290,141 @@ namespace data
LogPrint (eLogWarning, "NetDb: failed to reseed after 10 attempts");
}
void NetDb::Load ()
bool NetDb::LoadRouterInfo (const std::string & path)
{
boost::filesystem::path p(i2p::util::filesystem::GetDataDir() / m_NetDbPath);
if (!boost::filesystem::exists (p))
auto r = std::make_shared<RouterInfo>(path);
if (r->GetRouterIdentity () && !r->IsUnreachable () &&
(!r->UsesIntroducer () || m_LastLoad < r->GetTimestamp () + 3600*1000LL)) // 1 hour
{
r->DeleteBuffer ();
r->ClearProperties (); // properties are not used for regular routers
m_RouterInfos[r->GetIdentHash ()] = r;
if (r->IsFloodfill () && r->IsReachable ()) // floodfill must be reachable
m_Floodfills.push_back (r);
}
else
{
// seems netDb doesn't exist yet
if (!CreateNetDb(p)) return;
LogPrint(eLogWarning, "NetDb: RI from ", path, " is invalid. Delete");
i2p::fs::Remove(path);
}
return true;
}
void NetDb::Load ()
{
// make sure we cleanup netDb from previous attempts
m_RouterInfos.clear ();
m_Floodfills.clear ();
// load routers now
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
int numRouters = 0;
boost::filesystem::directory_iterator end;
for (boost::filesystem::directory_iterator it (p); it != end; ++it)
{
if (boost::filesystem::is_directory (it->status()))
{
for (boost::filesystem::directory_iterator it1 (it->path ()); it1 != end; ++it1)
{
#if BOOST_VERSION > 10500
const std::string& fullPath = it1->path().string();
#else
const std::string& fullPath = it1->path();
#endif
auto r = std::make_shared<RouterInfo>(fullPath);
if (r->GetRouterIdentity () && !r->IsUnreachable () &&
(!r->UsesIntroducer () || ts < r->GetTimestamp () + 3600*1000LL)) // 1 hour
{
r->DeleteBuffer ();
r->ClearProperties (); // properties are not used for regular routers
m_RouterInfos[r->GetIdentHash ()] = r;
if (r->IsFloodfill ())
m_Floodfills.push_back (r);
numRouters++;
}
else
{
if (boost::filesystem::exists (fullPath))
boost::filesystem::remove (fullPath);
}
}
}
}
LogPrint (eLogInfo, "NetDb: ", numRouters, " routers loaded (", m_Floodfills.size (), " floodfils)");
m_LastLoad = i2p::util::GetSecondsSinceEpoch();
std::vector<std::string> files;
m_Storage.Traverse(files);
for (auto path : files)
LoadRouterInfo(path);
LogPrint (eLogInfo, "NetDb: ", m_RouterInfos.size(), " routers loaded (", m_Floodfills.size (), " floodfils)");
}
void NetDb::SaveUpdated ()
{
auto GetFilePath = [](const boost::filesystem::path& directory, const RouterInfo * routerInfo)
{
std::string s(routerInfo->GetIdentHashBase64());
return directory / (std::string("r") + s[0]) / ("routerInfo-" + s + ".dat");
};
boost::filesystem::path fullDirectory (i2p::util::filesystem::GetDataDir() / m_NetDbPath);
int count = 0, deletedCount = 0;
int updatedCount = 0, deletedCount = 0;
auto total = m_RouterInfos.size ();
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch ();
uint64_t ts = i2p::util::GetMillisecondsSinceEpoch();
for (auto it: m_RouterInfos)
{
if (it.second->IsUpdated ())
std::string ident = it.second->GetIdentHashBase64();
std::string path = m_Storage.Path(ident);
if (it.second->IsUpdated ())
{
std::string f = GetFilePath(fullDirectory, it.second.get()).string();
it.second->SaveToFile (f);
it.second->SaveToFile (path);
it.second->SetUpdated (false);
it.second->SetUnreachable (false);
it.second->DeleteBuffer ();
count++;
updatedCount++;
continue;
}
else
// find & mark unreachable routers
if (it.second->UsesIntroducer () && ts > it.second->GetTimestamp () + 3600*1000LL)
{
// RouterInfo expires after 1 hour if uses introducer
if (it.second->UsesIntroducer () && ts > it.second->GetTimestamp () + 3600*1000LL) // 1 hour
it.second->SetUnreachable (true);
else if (total > 75 && ts > (i2p::context.GetStartupTime () + 600)*1000LL) // routers don't expire if less than 25 or uptime is less than 10 minutes
it.second->SetUnreachable (true);
}
else if (total > 75 && ts > (i2p::context.GetStartupTime () + 600)*1000LL)
{
// routers don't expire if less than 25 or uptime is less than 10 minutes
if (i2p::context.IsFloodfill ())
{
if (i2p::context.IsFloodfill ())
{
if (ts > it.second->GetTimestamp () + 3600*1000LL) // 1 hours
{
it.second->SetUnreachable (true);
total--;
}
if (ts > it.second->GetTimestamp () + 3600*1000LL)
{ // 1 hour
it.second->SetUnreachable (true);
total--;
}
else if (total > 2500)
{
if (ts > it.second->GetTimestamp () + 12*3600*1000LL) // 12 hours
{
it.second->SetUnreachable (true);
total--;
}
}
else if (total > 2500)
{
if (ts > it.second->GetTimestamp () + 12*3600*1000LL) // 12 hours
{
it.second->SetUnreachable (true);
total--;
}
else if (total > 300)
{
if (ts > it.second->GetTimestamp () + 30*3600*1000LL) // 30 hours
{
it.second->SetUnreachable (true);
total--;
}
}
else if (total > 120)
{
if (ts > it.second->GetTimestamp () + 72*3600*1000LL) // 72 hours
{
it.second->SetUnreachable (true);
total--;
}
}
}
if (it.second->IsUnreachable ())
{
total--;
// delete RI file
if (boost::filesystem::exists (GetFilePath (fullDirectory, it.second.get ())))
{
boost::filesystem::remove (GetFilePath (fullDirectory, it.second.get ()));
deletedCount++;
}
else if (total > 300)
{
if (ts > it.second->GetTimestamp () + 30*3600*1000LL) // 30 hours
{
it.second->SetUnreachable (true);
total--;
}
// delete from floodfills list
if (it.second->IsFloodfill ())
{
std::unique_lock<std::mutex> l(m_FloodfillsMutex);
m_Floodfills.remove (it.second);
}
else if (total > 120)
{
if (ts > it.second->GetTimestamp () + 72*3600*1000LL)
{
// 72 hours
it.second->SetUnreachable (true);
total--;
}
}
}
}
if (count > 0)
LogPrint (eLogInfo, "NetDb: ", count, " new/updated routers saved");
}
if (it.second->IsUnreachable ())
{
total--;
// delete RI file
m_Storage.Remove(ident);
deletedCount++;
}
} // m_RouterInfos iteration
if (updatedCount > 0)
LogPrint (eLogInfo, "NetDb: saved ", updatedCount, " new/updated routers");
if (deletedCount > 0)
{
LogPrint (eLogDebug, "NetDb: ", deletedCount, " routers deleted");
LogPrint (eLogInfo, "NetDb: deleting ", deletedCount, " unreachable routers");
// clean up RouterInfos table
std::unique_lock<std::mutex> l(m_RouterInfosMutex);
for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();)
{
if (it->second->IsUnreachable ())
{
it->second->SaveProfile ();
it = m_RouterInfos.erase (it);
}
else
std::unique_lock<std::mutex> l(m_RouterInfosMutex);
for (auto it = m_RouterInfos.begin (); it != m_RouterInfos.end ();)
{
if (it->second->IsUnreachable ())
{
it->second->SaveProfile ();
it = m_RouterInfos.erase (it);
continue;
}
it++;
}
}
}
// clean up expired floodfiils
{
std::unique_lock<std::mutex> l(m_FloodfillsMutex);
for (auto it = m_Floodfills.begin (); it != m_Floodfills.end ();)
if ((*it)->IsUnreachable ())
it = m_Floodfills.erase (it);
else
it++;
}
}
}

@ -8,8 +8,8 @@
#include <string>
#include <thread>
#include <mutex>
#include <boost/filesystem.hpp>
#include "Base.h"
#include "FS.h"
#include "Queue.h"
#include "I2NPProtocol.h"
#include "RouterInfo.h"
@ -24,7 +24,6 @@ namespace i2p
{
namespace data
{
class NetDb
{
public:
@ -71,8 +70,8 @@ namespace data
private:
bool CreateNetDb(boost::filesystem::path directory);
void Load ();
bool LoadRouterInfo (const std::string & path);
void SaveUpdated ();
void Run (); // exploratory thread
void Explore (int numDestinations);
@ -92,17 +91,17 @@ namespace data
std::list<std::shared_ptr<RouterInfo> > m_Floodfills;
bool m_IsRunning;
uint64_t m_LastLoad;
std::thread * m_Thread;
i2p::util::Queue<std::shared_ptr<const I2NPMessage> > m_Queue; // of I2NPDatabaseStoreMsg
GzipInflator m_Inflator;
Reseeder * m_Reseeder;
Families m_Families;
i2p::fs::HashedStorage m_Storage;
friend class NetDbRequests;
NetDbRequests m_Requests;
static const char m_NetDbPath[];
};
extern NetDb netdb;

@ -1,8 +1,8 @@
#include <boost/filesystem.hpp>
#include <sys/stat.h>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include "Base.h"
#include "util.h"
#include "FS.h"
#include "Log.h"
#include "Profiling.h"
@ -10,6 +10,8 @@ namespace i2p
{
namespace data
{
i2p::fs::HashedStorage m_ProfilesStorage("peerProfiles", "p", "profile-", "txt");
RouterProfile::RouterProfile (const IdentHash& identHash):
m_IdentHash (identHash), m_LastUpdateTime (boost::posix_time::second_clock::local_time()),
m_NumTunnelsAgreed (0), m_NumTunnelsDeclined (0), m_NumTunnelsNonReplied (0),
@ -41,101 +43,69 @@ namespace data
boost::property_tree::ptree pt;
pt.put (PEER_PROFILE_LAST_UPDATE_TIME, boost::posix_time::to_simple_string (m_LastUpdateTime));
pt.put_child (PEER_PROFILE_SECTION_PARTICIPATION, participation);
pt.put_child (PEER_PROFILE_SECTION_USAGE, usage);
pt.put_child (PEER_PROFILE_SECTION_USAGE, usage);
// save to file
auto path = i2p::util::filesystem::GetDefaultDataDir() / PEER_PROFILES_DIRECTORY;
if (!boost::filesystem::exists (path))
{
// Create directory is necessary
if (!boost::filesystem::create_directory (path))
{
LogPrint (eLogError, "Failed to create directory ", path);
return;
}
const char * chars = GetBase64SubstitutionTable (); // 64 bytes
for (int i = 0; i < 64; i++)
{
auto path1 = path / (std::string ("p") + chars[i]);
if (!boost::filesystem::exists (path1) && !boost::filesystem::create_directory (path1))
{
LogPrint (eLogError, "Failed to create directory ", path1);
return;
}
}
}
std::string base64 = m_IdentHash.ToBase64 ();
path = path / (std::string ("p") + base64[0]);
auto filename = path / (std::string (PEER_PROFILE_PREFIX) + base64 + ".txt");
try
{
boost::property_tree::write_ini (filename.string (), pt);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Can't write ", filename, ": ", ex.what ());
std::string ident = m_IdentHash.ToBase64 ();
std::string path = m_ProfilesStorage.Path(ident);
try {
boost::property_tree::write_ini (path, pt);
} catch (std::exception& ex) {
/* boost exception verbose enough */
LogPrint (eLogError, "Profiling: ", ex.what ());
}
}
}
void RouterProfile::Load ()
{
std::string base64 = m_IdentHash.ToBase64 ();
auto path = i2p::util::filesystem::GetDefaultDataDir() / PEER_PROFILES_DIRECTORY;
path /= std::string ("p") + base64[0];
auto filename = path / (std::string (PEER_PROFILE_PREFIX) + base64 + ".txt");
if (boost::filesystem::exists (filename))
{
boost::property_tree::ptree pt;
try
{
boost::property_tree::read_ini (filename.string (), pt);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Can't read ", filename, ": ", ex.what ());
return;
}
try
{
auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, "");
if (t.length () > 0)
m_LastUpdateTime = boost::posix_time::time_from_string (t);
if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT)
{
try
{
// read participations
auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION);
m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0);
m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0);
m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0);
}
catch (boost::property_tree::ptree_bad_path& ex)
{
LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_PARTICIPATION);
}
try
{
// read usage
auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE);
m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0);
m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0);
}
catch (boost::property_tree::ptree_bad_path& ex)
{
LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_USAGE);
}
std::string ident = m_IdentHash.ToBase64 ();
std::string path = m_ProfilesStorage.Path(ident);
boost::property_tree::ptree pt;
if (!i2p::fs::Exists(path)) {
LogPrint(eLogWarning, "Profiling: no profile yet for ", ident);
return;
}
try {
boost::property_tree::read_ini (path, pt);
} catch (std::exception& ex) {
/* boost exception verbose enough */
LogPrint (eLogError, "Profiling: ", ex.what ());
return;
}
try {
auto t = pt.get (PEER_PROFILE_LAST_UPDATE_TIME, "");
if (t.length () > 0)
m_LastUpdateTime = boost::posix_time::time_from_string (t);
if ((GetTime () - m_LastUpdateTime).hours () < PEER_PROFILE_EXPIRATION_TIMEOUT) {
try {
// read participations
auto participations = pt.get_child (PEER_PROFILE_SECTION_PARTICIPATION);
m_NumTunnelsAgreed = participations.get (PEER_PROFILE_PARTICIPATION_AGREED, 0);
m_NumTunnelsDeclined = participations.get (PEER_PROFILE_PARTICIPATION_DECLINED, 0);
m_NumTunnelsNonReplied = participations.get (PEER_PROFILE_PARTICIPATION_NON_REPLIED, 0);
} catch (boost::property_tree::ptree_bad_path& ex) {
LogPrint (eLogWarning, "Profiling: Missing section ", PEER_PROFILE_SECTION_PARTICIPATION, " in profile for ", ident);
}
else
*this = RouterProfile (m_IdentHash);
}
catch (std::exception& ex)
{
LogPrint (eLogError, "Can't read profile ", base64, " :", ex.what ());
}
}
}
try {
// read usage
auto usage = pt.get_child (PEER_PROFILE_SECTION_USAGE);
m_NumTimesTaken = usage.get (PEER_PROFILE_USAGE_TAKEN, 0);
m_NumTimesRejected = usage.get (PEER_PROFILE_USAGE_REJECTED, 0);
} catch (boost::property_tree::ptree_bad_path& ex) {
LogPrint (eLogWarning, "Missing section ", PEER_PROFILE_SECTION_USAGE, " in profile for ", ident);
}
} else {
*this = RouterProfile (m_IdentHash);
}
} catch (std::exception& ex) {
LogPrint (eLogError, "Profiling: Can't read profile ", ident, " :", ex.what ());
}
}
void RouterProfile::TunnelBuildResponse (uint8_t ret)
{
UpdateTime ();
@ -184,31 +154,29 @@ namespace data
return profile;
}
void InitProfilesStorage ()
{
m_ProfilesStorage.SetPlace(i2p::fs::GetDataDir());
m_ProfilesStorage.Init(i2p::data::GetBase64SubstitutionTable(), 64);
}
void DeleteObsoleteProfiles ()
{
int num = 0;
auto ts = boost::posix_time::second_clock::local_time();
boost::filesystem::path p (i2p::util::filesystem::GetDataDir()/PEER_PROFILES_DIRECTORY);
if (boost::filesystem::exists (p))
{
boost::filesystem::directory_iterator end;
for (boost::filesystem::directory_iterator it (p); it != end; ++it)
{
if (boost::filesystem::is_directory (it->status()))
{
for (boost::filesystem::directory_iterator it1 (it->path ()); it1 != end; ++it1)
{
auto lastModified = boost::posix_time::from_time_t (boost::filesystem::last_write_time (it1->path ()));
if ((ts - lastModified).hours () >= PEER_PROFILE_EXPIRATION_TIMEOUT)
{
boost::filesystem::remove (it1->path ());
num++;
}
}
}
}
struct stat st;
std::time_t now = std::time(nullptr);
std::vector<std::string> files;
m_ProfilesStorage.Traverse(files);
for (auto path: files) {
if (stat(path.c_str(), &st) != 0) {
LogPrint(eLogWarning, "Profiling: Can't stat(): ", path);
continue;
}
if (((now - st.st_mtime) / 3600) >= PEER_PROFILE_EXPIRATION_TIMEOUT) {
LogPrint(eLogDebug, "Profiling: removing expired peer profile: ", path);
i2p::fs::Remove(path);
}
}
LogPrint (eLogInfo, num, " obsolete profiles deleted");
}
}
}
}

@ -9,8 +9,6 @@ namespace i2p
{
namespace data
{
const char PEER_PROFILES_DIRECTORY[] = "peerProfiles";
const char PEER_PROFILE_PREFIX[] = "profile-";
// sections
const char PEER_PROFILE_SECTION_PARTICIPATION[] = "participation";
const char PEER_PROFILE_SECTION_USAGE[] = "usage";
@ -62,6 +60,7 @@ namespace data
};
std::shared_ptr<RouterProfile> GetRouterProfile (const IdentHash& identHash);
void InitProfilesStorage ();
void DeleteObsoleteProfiles ();
}
}

@ -2,15 +2,16 @@
#include <fstream>
#include <sstream>
#include <boost/regex.hpp>
#include <boost/filesystem.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <openssl/bn.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <zlib.h>
#include "I2PEndian.h"
#include "Reseed.h"
#include "FS.h"
#include "Log.h"
#include "Identity.h"
#include "Crypto.h"
@ -347,23 +348,22 @@ namespace data
void Reseeder::LoadCertificates ()
{
boost::filesystem::path reseedDir = i2p::util::filesystem::GetCertificatesDir() / "reseed";
if (!boost::filesystem::exists (reseedDir))
{
LogPrint (eLogWarning, "Reseed: certificates not loaded, ", reseedDir, " doesn't exist");
std::string certDir = i2p::fs::DataDirPath("certificates", "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;
}
int numCertificates = 0;
boost::filesystem::directory_iterator end; // empty
for (boost::filesystem::directory_iterator it (reseedDir); it != end; ++it)
{
if (boost::filesystem::is_regular_file (it->status()) && it->path ().extension () == ".crt")
{
LoadCertificate (it->path ().string ());
numCertificates++;
}
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");
}

@ -5,6 +5,7 @@
#include "Timestamp.h"
#include "I2NPProtocol.h"
#include "NetDb.h"
#include "FS.h"
#include "util.h"
#include "version.h"
#include "Log.h"
@ -65,7 +66,7 @@ namespace i2p
void RouterContext::UpdateRouterInfo ()
{
m_RouterInfo.CreateBuffer (m_Keys);
m_RouterInfo.SaveToFile (i2p::util::filesystem::GetFullPath (ROUTER_INFO));
m_RouterInfo.SaveToFile (i2p::fs::DataDirPath (ROUTER_INFO));
m_LastUpdateTime = i2p::util::GetSecondsSinceEpoch ();
}
@ -310,7 +311,7 @@ namespace i2p
bool RouterContext::Load ()
{
std::ifstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ifstream::binary | std::ifstream::in);
std::ifstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ifstream::in | std::ifstream::binary);
if (!fk.is_open ()) return false;
fk.seekg (0, std::ios::end);
size_t len = fk.tellg();
@ -330,7 +331,7 @@ namespace i2p
delete[] buf;
}
i2p::data::RouterInfo routerInfo(i2p::util::filesystem::GetFullPath (ROUTER_INFO)); // TODO
i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO)); // TODO
m_RouterInfo.SetRouterIdentity (GetIdentity ());
m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ());
m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION);
@ -349,7 +350,7 @@ namespace i2p
void RouterContext::SaveKeys ()
{
// save in the same format as .dat files
std::ofstream fk (i2p::util::filesystem::GetFullPath (ROUTER_KEYS).c_str (), std::ofstream::binary | std::ofstream::out);
std::ofstream fk (i2p::fs::DataDirPath (ROUTER_KEYS), std::ofstream::binary | std::ofstream::out);
size_t len = m_Keys.GetFullLen ();
uint8_t * buf = new uint8_t[len];
m_Keys.ToBuffer (buf, len);

@ -629,11 +629,6 @@ namespace data
m_Properties.erase (key);
}
bool RouterInfo::IsFloodfill () const
{
return m_Caps & Caps::eFloodfill;
}
bool RouterInfo::IsNTCP (bool v4only) const
{
if (v4only)

@ -128,7 +128,8 @@ namespace data
void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only
void DeleteProperty (const std::string& key); // called from RouterContext only
void ClearProperties () { m_Properties.clear (); };
bool IsFloodfill () const;
bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; };
bool IsReachable () const { return m_Caps & Caps::eReachable; };
bool IsNTCP (bool v4only = true) const;
bool IsSSU (bool v4only = true) const;
bool IsV6 () const;

@ -157,7 +157,7 @@ namespace transport
ProcessData (buf + headerSize, len - headerSize);
break;
case PAYLOAD_TYPE_SESSION_REQUEST:
ProcessSessionRequest (buf + headerSize, len - headerSize, senderEndpoint);
ProcessSessionRequest (buf, len, senderEndpoint); // buf with header
break;
case PAYLOAD_TYPE_SESSION_CREATED:
ProcessSessionCreated (buf, len); // buf with header
@ -196,11 +196,29 @@ namespace transport
void SSUSession::ProcessSessionRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& senderEndpoint)
{
LogPrint (eLogDebug, "SSU message: session request");
bool sendRelayTag = true;
auto headerSize = sizeof (SSUHeader);
if (((SSUHeader *)buf)->IsExtendedOptions ())
{
uint8_t extendedOptionsLen = buf[headerSize];
headerSize++;
if (extendedOptionsLen >= 3) // options are presented
{
uint16_t flags = bufbe16toh (buf + headerSize);
sendRelayTag = flags & EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG;
}
headerSize += extendedOptionsLen;
}
if (headerSize >= len)
{
LogPrint (eLogError, "Session reaquest header size ", headerSize, " exceeds packet length ", len);
return;
}
m_RemoteEndpoint = senderEndpoint;
if (!m_DHKeysPair)
m_DHKeysPair = transports.GetNextDHKeysPair ();
CreateAESandMacKey (buf);
SendSessionCreated (buf);
CreateAESandMacKey (buf + headerSize);
SendSessionCreated (buf + headerSize, sendRelayTag);
}
void SSUSession::ProcessSessionCreated (uint8_t * buf, size_t len)
@ -357,7 +375,7 @@ namespace transport
m_Server.Send (buf, 96, m_RemoteEndpoint);
}
void SSUSession::SendSessionCreated (const uint8_t * x)
void SSUSession::SendSessionCreated (const uint8_t * x, bool sendRelayTag)
{
auto address = IsV6 () ? i2p::context.GetRouterInfo ().GetSSUV6Address () :
i2p::context.GetRouterInfo ().GetSSUAddress (true); //v4 only
@ -401,7 +419,7 @@ namespace transport
s.Insert (address->host.to_v6 ().to_bytes ().data (), 16); // our IP V6
s.Insert<uint16_t> (htobe16 (address->port)); // our port
uint32_t relayTag = 0;
if (i2p::context.GetRouterInfo ().IsIntroducer () && !IsV6 ())
if (sendRelayTag && i2p::context.GetRouterInfo ().IsIntroducer () && !IsV6 ())
{
RAND_bytes((uint8_t *)&relayTag, 4);
if (!relayTag) relayTag = 1;

@ -39,6 +39,9 @@ namespace transport
const uint8_t PAYLOAD_TYPE_PEER_TEST = 7;
const uint8_t PAYLOAD_TYPE_SESSION_DESTROYED = 8;
// extended options
const uint16_t EXTENDED_OPTIONS_FLAG_REQUEST_RELAY_TAG = 0x0001;
enum SessionState
{
eSessionStateUnknown,
@ -101,7 +104,7 @@ namespace transport
void SendSessionRequest ();
void SendRelayRequest (const i2p::data::RouterInfo::Introducer& introducer, uint32_t nonce);
void ProcessSessionCreated (uint8_t * buf, size_t len);
void SendSessionCreated (const uint8_t * x);
void SendSessionCreated (const uint8_t * x, bool sendRelayTag = true);
void ProcessSessionConfirmed (const uint8_t * buf, size_t len);
void SendSessionConfirmed (const uint8_t * y, const uint8_t * ourAddress, size_t ourAddressLen);
void ProcessRelayRequest (const uint8_t * buf, size_t len, const boost::asio::ip::udp::endpoint& from);

@ -9,7 +9,7 @@
#include "Identity.h"
#include "Destination.h"
#include "Crypto.h"
#include "util.h"
#include "FS.h"
#include "api.h"
namespace i2p
@ -18,10 +18,16 @@ namespace api
{
void InitI2P (int argc, char* argv[], const char * appName)
{
i2p::util::filesystem::SetAppName (appName);
i2p::config::Init ();
i2p::config::ParseCmdline (argc, argv);
i2p::config::Finalize ();
std::string datadir; i2p::config::GetOption("datadir", datadir);
i2p::fs::SetAppName (appName);
i2p::fs::DetectDataDir(datadir, false);
i2p::fs::Init();
i2p::crypto::InitCrypto ();
i2p::context.Init ();
}
@ -36,7 +42,7 @@ namespace api
if (logStream)
StartLog (logStream);
else
StartLog (i2p::util::filesystem::GetFullPath (i2p::util::filesystem::GetAppName () + ".log"));
StartLog (i2p::fs::DataDirPath (i2p::fs::GetAppName () + ".log"));
LogPrint(eLogInfo, "API: starting NetDB");
i2p::data::netdb.Start();
LogPrint(eLogInfo, "API: starting Transports");

@ -23,6 +23,7 @@ set (LIBI2PD_SRC
"${CMAKE_SOURCE_DIR}/I2NPProtocol.cpp"
"${CMAKE_SOURCE_DIR}/Identity.cpp"
"${CMAKE_SOURCE_DIR}/LeaseSet.cpp"
"${CMAKE_SOURCE_DIR}/FS.cpp"
"${CMAKE_SOURCE_DIR}/Log.cpp"
"${CMAKE_SOURCE_DIR}/NTCPSession.cpp"
"${CMAKE_SOURCE_DIR}/NetDbRequests.cpp"

@ -26,4 +26,5 @@ Contents:
build_notes_unix
build_notes_windows
configuration
family

@ -4,8 +4,8 @@ LIB_SRC = \
Reseed.cpp RouterContext.cpp RouterInfo.cpp Signature.cpp SSU.cpp \
SSUSession.cpp SSUData.cpp Streaming.cpp Identity.cpp TransitTunnel.cpp \
Transports.cpp Tunnel.cpp TunnelEndpoint.cpp TunnelPool.cpp TunnelGateway.cpp \
Destination.cpp Base.cpp I2PEndian.cpp Config.cpp Family.cpp util.cpp \
api.cpp
Destination.cpp Base.cpp I2PEndian.cpp FS.cpp Config.cpp Family.cpp \
util.cpp api.cpp
LIB_CLIENT_SRC = \
AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \

@ -6,13 +6,7 @@
#include <fstream>
#include <set>
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/program_options/detail/config_file.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/algorithm/string.hpp>
#include "Config.h"
#include "util.h"
#include "Log.h"
@ -67,131 +61,6 @@ namespace i2p
{
namespace util
{
namespace filesystem
{
std::string appName ("i2pd");
void SetAppName (const std::string& name)
{
appName = name;
}
std::string GetAppName ()
{
return appName;
}
const boost::filesystem::path &GetDataDir()
{
static boost::filesystem::path path;
// TODO: datadir parameter is useless because GetDataDir is called before OptionParser
// and mapArgs is not initialized yet
/*
std::string datadir; i2p::config::GetOption("datadir", datadir);
if (datadir != "")
path = boost::filesystem::system_complete(datadir);
else */
path = GetDefaultDataDir();
if (!boost::filesystem::exists( path ))
{
// Create data directory
if (!boost::filesystem::create_directory( path ))
{
LogPrint(eLogError, "FS: Failed to create data directory!");
path = "";
return path;
}
}
if (!boost::filesystem::is_directory(path))
path = GetDefaultDataDir();
return path;
}
std::string GetFullPath (const std::string& filename)
{
std::string fullPath = GetDataDir ().string ();
#ifndef _WIN32
fullPath.append ("/");
#else
fullPath.append ("\\");
#endif
fullPath.append (filename);
return fullPath;
}
boost::filesystem::path GetConfigFile()
{
std::string config; i2p::config::GetOption("conf", config);
if (config != "") {
/* config file set with cmdline */
boost::filesystem::path path(config);
return path;
}
/* else - try autodetect */
boost::filesystem::path path("i2p.conf");
path = GetDataDir() / path;
if (!boost::filesystem::exists(path))
path = ""; /* reset */
return path;
}
boost::filesystem::path GetTunnelsConfigFile()
{
std::string tunconf; i2p::config::GetOption("tunconf", tunconf);
if (tunconf != "") {
/* config file set with cmdline */
boost::filesystem::path path(tunconf);
return path;
}
/* else - try autodetect */
boost::filesystem::path path("tunnels.cfg");
path = GetDataDir() / path;
if (!boost::filesystem::exists(path))
path = ""; /* reset */
return path;
}
boost::filesystem::path GetDefaultDataDir()
{
// Windows < Vista: C:\Documents and Settings\Username\Application Data\i2pd
// Windows >= Vista: C:\Users\Username\AppData\Roaming\i2pd
// Mac: ~/Library/Application Support/i2pd
// Unix: ~/.i2pd or /var/lib/i2pd is system=1
#ifdef WIN32
// Windows
char localAppData[MAX_PATH];
SHGetFolderPath(NULL, CSIDL_APPDATA, 0, NULL, localAppData);
return boost::filesystem::path(std::string(localAppData) + "\\" + appName);
#else /* UNIX */
bool service; i2p::config::GetOption("service", service);
if (service) // use system folder
return boost::filesystem::path(std::string ("/var/lib/") + appName);
boost::filesystem::path pathRet;
char* pszHome = getenv("HOME");
if (pszHome == NULL || strlen(pszHome) == 0)
pathRet = boost::filesystem::path("/");
else
pathRet = boost::filesystem::path(pszHome);
#ifdef MAC_OSX
// Mac
pathRet /= "Library/Application Support";
boost::filesystem::create_directory(pathRet);
return pathRet / appName;
#else /* Other Unix */
// Unix
return pathRet / (std::string (".") + appName);
#endif
#endif /* UNIX */
}
boost::filesystem::path GetCertificatesDir()
{
return GetDataDir () / "certificates";
}
}
namespace http
{
std::string GetHttpContent (std::istream& response)
@ -212,7 +81,7 @@ namespace http
if (colon != std::string::npos)
{
std::string field = header.substr (0, colon);
boost::to_lower (field);
std::transform(field.begin(), field.end(), field.begin(), ::tolower);
if (field == i2p::util::http::TRANSFER_ENCODING)
isChunked = (header.find ("chunked", colon + 1) != std::string::npos);
}

@ -5,28 +5,11 @@
#include <string>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#define PAIRTYPE(t1, t2) std::pair<t1, t2>
namespace i2p
{
namespace util
{
namespace filesystem
{
void SetAppName (const std::string& name);
std::string GetAppName ();
const boost::filesystem::path &GetDataDir();
std::string GetFullPath (const std::string& filename);
boost::filesystem::path GetDefaultDataDir();
boost::filesystem::path GetConfigFile();
boost::filesystem::path GetTunnelsConfigFile();
boost::filesystem::path GetCertificatesDir();
}
namespace http
{
// in (lower case)

Loading…
Cancel
Save