Implement #243, separate core/client (PCH support dropped for now)

This commit is contained in:
EinMByte 2015-08-21 22:22:34 +02:00
parent bdaf2c16aa
commit 8ac9520dfd
153 changed files with 360 additions and 20020 deletions

645
client/BOB.cpp Normal file
View file

@ -0,0 +1,645 @@
#include <string.h>
#include <boost/lexical_cast.hpp>
#include "util/Log.h"
#include "ClientContext.h"
#include "BOB.h"
namespace i2p
{
namespace client
{
BOBI2PInboundTunnel::BOBI2PInboundTunnel (int port, std::shared_ptr<ClientDestination> localDestination):
BOBI2PTunnel (localDestination),
m_Acceptor (localDestination->GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v4(), port))
{
}
BOBI2PInboundTunnel::~BOBI2PInboundTunnel ()
{
Stop ();
}
void BOBI2PInboundTunnel::Start ()
{
m_Acceptor.listen ();
Accept ();
}
void BOBI2PInboundTunnel::Stop ()
{
m_Acceptor.close();
ClearHandlers ();
}
void BOBI2PInboundTunnel::Accept ()
{
auto receiver = std::make_shared<AddressReceiver> ();
receiver->socket = std::make_shared<boost::asio::ip::tcp::socket> (GetService ());
m_Acceptor.async_accept (*receiver->socket, std::bind (&BOBI2PInboundTunnel::HandleAccept, this,
std::placeholders::_1, receiver));
}
void BOBI2PInboundTunnel::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<AddressReceiver> receiver)
{
if (!ecode)
{
Accept ();
ReceiveAddress (receiver);
}
}
void BOBI2PInboundTunnel::ReceiveAddress (std::shared_ptr<AddressReceiver> receiver)
{
receiver->socket->async_read_some (boost::asio::buffer(
receiver->buffer + receiver->bufferOffset,
BOB_COMMAND_BUFFER_SIZE - receiver->bufferOffset),
std::bind(&BOBI2PInboundTunnel::HandleReceivedAddress, this,
std::placeholders::_1, std::placeholders::_2, receiver));
}
void BOBI2PInboundTunnel::HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<AddressReceiver> receiver)
{
if (ecode)
LogPrint ("BOB inbound tunnel read error: ", ecode.message ());
else
{
receiver->bufferOffset += bytes_transferred;
receiver->buffer[receiver->bufferOffset] = 0;
char * eol = strchr (receiver->buffer, '\n');
if (eol)
{
*eol = 0;
receiver->data = (uint8_t *)eol + 1;
receiver->dataLen = receiver->bufferOffset - (eol - receiver->buffer + 1);
i2p::data::IdentHash ident;
if (!context.GetAddressBook ().GetIdentHash (receiver->buffer, ident))
{
LogPrint (eLogError, "BOB address ", receiver->buffer, " not found");
return;
}
auto leaseSet = GetLocalDestination ()->FindLeaseSet (ident);
if (leaseSet)
CreateConnection (receiver, leaseSet);
else
GetLocalDestination ()->RequestDestination (ident,
std::bind (&BOBI2PInboundTunnel::HandleDestinationRequestComplete,
this, std::placeholders::_1, receiver));
}
else
{
if (receiver->bufferOffset < BOB_COMMAND_BUFFER_SIZE)
ReceiveAddress (receiver);
else
LogPrint ("BOB missing inbound address ");
}
}
}
void BOBI2PInboundTunnel::HandleDestinationRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<AddressReceiver> receiver)
{
if (leaseSet)
CreateConnection (receiver, leaseSet);
else
LogPrint ("LeaseSet for BOB inbound destination not found");
}
void BOBI2PInboundTunnel::CreateConnection (std::shared_ptr<AddressReceiver> receiver, std::shared_ptr<const i2p::data::LeaseSet> leaseSet)
{
LogPrint ("New BOB inbound connection");
auto connection = std::make_shared<I2PTunnelConnection>(this, receiver->socket, leaseSet);
AddHandler (connection);
connection->I2PConnect (receiver->data, receiver->dataLen);
}
BOBI2POutboundTunnel::BOBI2POutboundTunnel (const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, bool quiet): BOBI2PTunnel (localDestination),
m_Endpoint (boost::asio::ip::address::from_string (address), port), m_IsQuiet (quiet)
{
}
void BOBI2POutboundTunnel::Start ()
{
Accept ();
}
void BOBI2POutboundTunnel::Stop ()
{
ClearHandlers ();
}
void BOBI2POutboundTunnel::Accept ()
{
auto localDestination = GetLocalDestination ();
if (localDestination)
localDestination->AcceptStreams (std::bind (&BOBI2POutboundTunnel::HandleAccept, this, std::placeholders::_1));
else
LogPrint ("Local destination not set for server tunnel");
}
void BOBI2POutboundTunnel::HandleAccept (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
auto conn = std::make_shared<I2PTunnelConnection> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), m_Endpoint, m_IsQuiet);
AddHandler (conn);
conn->Connect ();
}
}
BOBDestination::BOBDestination (std::shared_ptr<ClientDestination> localDestination):
m_LocalDestination (localDestination),
m_OutboundTunnel (nullptr), m_InboundTunnel (nullptr)
{
}
BOBDestination::~BOBDestination ()
{
delete m_OutboundTunnel;
delete m_InboundTunnel;
i2p::client::context.DeleteLocalDestination (m_LocalDestination);
}
void BOBDestination::Start ()
{
if (m_OutboundTunnel) m_OutboundTunnel->Start ();
if (m_InboundTunnel) m_InboundTunnel->Start ();
}
void BOBDestination::Stop ()
{
StopTunnels ();
m_LocalDestination->Stop ();
}
void BOBDestination::StopTunnels ()
{
if (m_OutboundTunnel)
{
m_OutboundTunnel->Stop ();
delete m_OutboundTunnel;
m_OutboundTunnel = nullptr;
}
if (m_InboundTunnel)
{
m_InboundTunnel->Stop ();
delete m_InboundTunnel;
m_InboundTunnel = nullptr;
}
}
void BOBDestination::CreateInboundTunnel (int port)
{
if (!m_InboundTunnel)
m_InboundTunnel = new BOBI2PInboundTunnel (port, m_LocalDestination);
}
void BOBDestination::CreateOutboundTunnel (const std::string& address, int port, bool quiet)
{
if (!m_OutboundTunnel)
m_OutboundTunnel = new BOBI2POutboundTunnel (address, port, m_LocalDestination, quiet);
}
BOBCommandSession::BOBCommandSession (BOBCommandChannel& owner):
m_Owner (owner), m_Socket (m_Owner.GetService ()), m_ReceiveBufferOffset (0),
m_IsOpen (true), m_IsQuiet (false), m_InPort (0), m_OutPort (0),
m_CurrentDestination (nullptr)
{
}
BOBCommandSession::~BOBCommandSession ()
{
}
void BOBCommandSession::Terminate ()
{
m_Socket.close ();
m_IsOpen = false;
}
void BOBCommandSession::Receive ()
{
m_Socket.async_read_some (boost::asio::buffer(m_ReceiveBuffer + m_ReceiveBufferOffset, BOB_COMMAND_BUFFER_SIZE - m_ReceiveBufferOffset),
std::bind(&BOBCommandSession::HandleReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void BOBCommandSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint ("BOB command channel read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
size_t size = m_ReceiveBufferOffset + bytes_transferred;
m_ReceiveBuffer[size] = 0;
char * eol = strchr (m_ReceiveBuffer, '\n');
if (eol)
{
*eol = 0;
char * operand = strchr (m_ReceiveBuffer, ' ');
if (operand)
{
*operand = 0;
operand++;
}
else
operand = eol;
// process command
auto& handlers = m_Owner.GetCommandHandlers ();
auto it = handlers.find (m_ReceiveBuffer);
if (it != handlers.end ())
(this->*(it->second))(operand, eol - operand);
else
{
LogPrint (eLogError, "BOB unknown command ", m_ReceiveBuffer);
SendReplyError ("unknown command");
}
m_ReceiveBufferOffset = size - (eol - m_ReceiveBuffer) - 1;
memmove (m_ReceiveBuffer, eol + 1, m_ReceiveBufferOffset);
}
else
{
if (size < BOB_COMMAND_BUFFER_SIZE)
m_ReceiveBufferOffset = size;
else
{
LogPrint (eLogError, "Malformed input of the BOB command channel");
Terminate ();
}
}
}
}
void BOBCommandSession::Send (size_t len)
{
boost::asio::async_write (m_Socket, boost::asio::buffer (m_SendBuffer, len),
boost::asio::transfer_all (),
std::bind(&BOBCommandSession::HandleSent, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void BOBCommandSession::HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint ("BOB command channel send error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
if (m_IsOpen)
Receive ();
else
Terminate ();
}
}
void BOBCommandSession::SendReplyOK (const char * msg)
{
#ifdef _MSC_VER
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg);
#else
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_OK, msg);
#endif
Send (len);
}
void BOBCommandSession::SendReplyError (const char * msg)
{
#ifdef _MSC_VER
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg);
#else
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_REPLY_ERROR, msg);
#endif
Send (len);
}
void BOBCommandSession::SendVersion ()
{
size_t len = strlen (BOB_VERSION);
memcpy (m_SendBuffer, BOB_VERSION, len);
Send (len);
}
void BOBCommandSession::SendData (const char * nickname)
{
#ifdef _MSC_VER
size_t len = sprintf_s (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname);
#else
size_t len = snprintf (m_SendBuffer, BOB_COMMAND_BUFFER_SIZE, BOB_DATA, nickname);
#endif
Send (len);
}
void BOBCommandSession::ZapCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: zap");
Terminate ();
}
void BOBCommandSession::QuitCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: quit");
m_IsOpen = false;
SendReplyOK ("Bye!");
}
void BOBCommandSession::StartCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: start ", m_Nickname);
if (!m_CurrentDestination)
{
m_CurrentDestination = new BOBDestination (i2p::client::context.CreateNewLocalDestination (m_Keys, true, &m_Options));
m_Owner.AddDestination (m_Nickname, m_CurrentDestination);
}
if (m_InPort)
m_CurrentDestination->CreateInboundTunnel (m_InPort);
if (m_OutPort && !m_Address.empty ())
m_CurrentDestination->CreateOutboundTunnel (m_Address, m_OutPort, m_IsQuiet);
m_CurrentDestination->Start ();
SendReplyOK ("tunnel starting");
}
void BOBCommandSession::StopCommandHandler (const char * operand, size_t len)
{
auto dest = m_Owner.FindDestination (m_Nickname);
if (dest)
{
dest->StopTunnels ();
SendReplyOK ("tunnel stopping");
}
else
SendReplyError ("tunnel not found");
}
void BOBCommandSession::SetNickCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: setnick ", operand);
m_Nickname = operand;
std::string msg ("Nickname set to ");
msg += operand;
SendReplyOK (msg.c_str ());
}
void BOBCommandSession::GetNickCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: getnick ", operand);
m_CurrentDestination = m_Owner.FindDestination (operand);
if (m_CurrentDestination)
{
m_Keys = m_CurrentDestination->GetKeys ();
m_Nickname = operand;
std::string msg ("Nickname set to ");
msg += operand;
SendReplyOK (msg.c_str ());
}
else
SendReplyError ("tunnel not found");
}
void BOBCommandSession::NewkeysCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: newkeys");
m_Keys = i2p::data::PrivateKeys::CreateRandomKeys ();
SendReplyOK (m_Keys.GetPublic ().ToBase64 ().c_str ());
}
void BOBCommandSession::SetkeysCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: setkeys ", operand);
m_Keys.FromBase64 (operand);
SendReplyOK (m_Keys.GetPublic ().ToBase64 ().c_str ());
}
void BOBCommandSession::GetkeysCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: getkeys");
SendReplyOK (m_Keys.ToBase64 ().c_str ());
}
void BOBCommandSession::GetdestCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: getdest");
SendReplyOK (m_Keys.GetPublic ().ToBase64 ().c_str ());
}
void BOBCommandSession::OuthostCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: outhost ", operand);
m_Address = operand;
SendReplyOK ("outhost set");
}
void BOBCommandSession::OutportCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: outport ", operand);
m_OutPort = boost::lexical_cast<int>(operand);
SendReplyOK ("outbound port set");
}
void BOBCommandSession::InhostCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: inhost ", operand);
m_Address = operand;
SendReplyOK ("inhost set");
}
void BOBCommandSession::InportCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: inport ", operand);
m_InPort = boost::lexical_cast<int>(operand);
SendReplyOK ("inbound port set");
}
void BOBCommandSession::QuietCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: quiet");
m_IsQuiet = true;
SendReplyOK ("quiet");
}
void BOBCommandSession::LookupCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: lookup ", operand);
i2p::data::IdentHash ident;
if (!context.GetAddressBook ().GetIdentHash (operand, ident) || !m_CurrentDestination)
{
SendReplyError ("Address Not found");
return;
}
auto localDestination = m_CurrentDestination->GetLocalDestination ();
auto leaseSet = localDestination->FindLeaseSet (ident);
if (leaseSet)
SendReplyOK (leaseSet->GetIdentity ().ToBase64 ().c_str ());
else
{
auto s = shared_from_this ();
localDestination->RequestDestination (ident,
[s](std::shared_ptr<i2p::data::LeaseSet> ls)
{
if (ls)
s->SendReplyOK (ls->GetIdentity ().ToBase64 ().c_str ());
else
s->SendReplyError ("LeaseSet Not found");
}
);
}
}
void BOBCommandSession::ClearCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: clear");
m_Owner.DeleteDestination (m_Nickname);
SendReplyOK ("cleared");
}
void BOBCommandSession::ListCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: list");
auto& destinations = m_Owner.GetDestinations ();
for (auto it: destinations)
SendData (it.first.c_str ());
SendReplyOK ("Listing done");
}
void BOBCommandSession::OptionCommandHandler (const char * operand, size_t len)
{
LogPrint (eLogDebug, "BOB: option ", operand);
const char * value = strchr (operand, '=');
if (value)
{
*(const_cast<char *>(value)) = 0;
m_Options[operand] = value + 1;
*(const_cast<char *>(value)) = '=';
SendReplyOK ("option");
}
else
SendReplyError ("malformed");
}
BOBCommandChannel::BOBCommandChannel(const std::string& address, int port)
: m_IsRunning (false), m_Thread (nullptr),
m_Acceptor(m_Service, boost::asio::ip::tcp::endpoint(
boost::asio::ip::address::from_string(address), port)
)
{
// command -> handler
m_CommandHandlers[BOB_COMMAND_ZAP] = &BOBCommandSession::ZapCommandHandler;
m_CommandHandlers[BOB_COMMAND_QUIT] = &BOBCommandSession::QuitCommandHandler;
m_CommandHandlers[BOB_COMMAND_START] = &BOBCommandSession::StartCommandHandler;
m_CommandHandlers[BOB_COMMAND_STOP] = &BOBCommandSession::StopCommandHandler;
m_CommandHandlers[BOB_COMMAND_SETNICK] = &BOBCommandSession::SetNickCommandHandler;
m_CommandHandlers[BOB_COMMAND_GETNICK] = &BOBCommandSession::GetNickCommandHandler;
m_CommandHandlers[BOB_COMMAND_NEWKEYS] = &BOBCommandSession::NewkeysCommandHandler;
m_CommandHandlers[BOB_COMMAND_GETKEYS] = &BOBCommandSession::GetkeysCommandHandler;
m_CommandHandlers[BOB_COMMAND_SETKEYS] = &BOBCommandSession::SetkeysCommandHandler;
m_CommandHandlers[BOB_COMMAND_GETDEST] = &BOBCommandSession::GetdestCommandHandler;
m_CommandHandlers[BOB_COMMAND_OUTHOST] = &BOBCommandSession::OuthostCommandHandler;
m_CommandHandlers[BOB_COMMAND_OUTPORT] = &BOBCommandSession::OutportCommandHandler;
m_CommandHandlers[BOB_COMMAND_INHOST] = &BOBCommandSession::InhostCommandHandler;
m_CommandHandlers[BOB_COMMAND_INPORT] = &BOBCommandSession::InportCommandHandler;
m_CommandHandlers[BOB_COMMAND_QUIET] = &BOBCommandSession::QuietCommandHandler;
m_CommandHandlers[BOB_COMMAND_LOOKUP] = &BOBCommandSession::LookupCommandHandler;
m_CommandHandlers[BOB_COMMAND_CLEAR] = &BOBCommandSession::ClearCommandHandler;
m_CommandHandlers[BOB_COMMAND_LIST] = &BOBCommandSession::ListCommandHandler;
m_CommandHandlers[BOB_COMMAND_OPTION] = &BOBCommandSession::OptionCommandHandler;
}
BOBCommandChannel::~BOBCommandChannel ()
{
Stop ();
for (auto it: m_Destinations)
delete it.second;
}
void BOBCommandChannel::Start ()
{
Accept ();
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&BOBCommandChannel::Run, this));
}
void BOBCommandChannel::Stop ()
{
m_IsRunning = false;
for (auto it: m_Destinations)
it.second->Stop ();
m_Acceptor.cancel ();
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
void BOBCommandChannel::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint (eLogError, "BOB: ", ex.what ());
}
}
}
void BOBCommandChannel::AddDestination (const std::string& name, BOBDestination * dest)
{
m_Destinations[name] = dest;
}
void BOBCommandChannel::DeleteDestination (const std::string& name)
{
auto it = m_Destinations.find (name);
if (it != m_Destinations.end ())
{
it->second->Stop ();
delete it->second;
m_Destinations.erase (it);
}
}
BOBDestination * BOBCommandChannel::FindDestination (const std::string& name)
{
auto it = m_Destinations.find (name);
if (it != m_Destinations.end ())
return it->second;
return nullptr;
}
void BOBCommandChannel::Accept ()
{
auto newSession = std::make_shared<BOBCommandSession> (*this);
m_Acceptor.async_accept (newSession->GetSocket (), std::bind (&BOBCommandChannel::HandleAccept, this,
std::placeholders::_1, newSession));
}
void BOBCommandChannel::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<BOBCommandSession> session)
{
if (ecode != boost::asio::error::operation_aborted)
Accept ();
if (!ecode)
{
LogPrint (eLogInfo, "New BOB command connection from ", session->GetSocket ().remote_endpoint ());
session->SendVersion ();
}
else
LogPrint (eLogError, "BOB accept error: ", ecode.message ());
}
}
}

237
client/BOB.h Normal file
View file

@ -0,0 +1,237 @@
#ifndef BOB_H__
#define BOB_H__
#include <inttypes.h>
#include <thread>
#include <memory>
#include <map>
#include <string>
#include <boost/asio.hpp>
#include "I2PTunnel.h"
#include "I2PService.h"
#include "Identity.h"
#include "LeaseSet.h"
namespace i2p
{
namespace client
{
const size_t BOB_COMMAND_BUFFER_SIZE = 1024;
const char BOB_COMMAND_ZAP[] = "zap";
const char BOB_COMMAND_QUIT[] = "quit";
const char BOB_COMMAND_START[] = "start";
const char BOB_COMMAND_STOP[] = "stop";
const char BOB_COMMAND_SETNICK[] = "setnick";
const char BOB_COMMAND_GETNICK[] = "getnick";
const char BOB_COMMAND_NEWKEYS[] = "newkeys";
const char BOB_COMMAND_GETKEYS[] = "getkeys";
const char BOB_COMMAND_SETKEYS[] = "setkeys";
const char BOB_COMMAND_GETDEST[] = "getdest";
const char BOB_COMMAND_OUTHOST[] = "outhost";
const char BOB_COMMAND_OUTPORT[] = "outport";
const char BOB_COMMAND_INHOST[] = "inhost";
const char BOB_COMMAND_INPORT[] = "inport";
const char BOB_COMMAND_QUIET[] = "quiet";
const char BOB_COMMAND_LOOKUP[] = "lookup";
const char BOB_COMMAND_CLEAR[] = "clear";
const char BOB_COMMAND_LIST[] = "list";
const char BOB_COMMAND_OPTION[] = "option";
const char BOB_VERSION[] = "BOB 00.00.10\nOK\n";
const char BOB_REPLY_OK[] = "OK %s\n";
const char BOB_REPLY_ERROR[] = "ERROR %s\n";
const char BOB_DATA[] = "NICKNAME %s\n";
class BOBI2PTunnel: public I2PService
{
public:
BOBI2PTunnel (std::shared_ptr<ClientDestination> localDestination):
I2PService (localDestination) {};
virtual void Start () {};
virtual void Stop () {};
};
class BOBI2PInboundTunnel: public BOBI2PTunnel
{
struct AddressReceiver
{
std::shared_ptr<boost::asio::ip::tcp::socket> socket;
char buffer[BOB_COMMAND_BUFFER_SIZE + 1]; // for destination base64 address
uint8_t * data; // pointer to buffer
size_t dataLen, bufferOffset;
AddressReceiver (): data (nullptr), dataLen (0), bufferOffset (0) {};
};
public:
BOBI2PInboundTunnel (int port, std::shared_ptr<ClientDestination> localDestination);
~BOBI2PInboundTunnel ();
void Start ();
void Stop ();
private:
void Accept ();
void HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<AddressReceiver> receiver);
void ReceiveAddress (std::shared_ptr<AddressReceiver> receiver);
void HandleReceivedAddress (const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<AddressReceiver> receiver);
void HandleDestinationRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, std::shared_ptr<AddressReceiver> receiver);
void CreateConnection (std::shared_ptr<AddressReceiver> receiver, std::shared_ptr<const i2p::data::LeaseSet> leaseSet);
private:
boost::asio::ip::tcp::acceptor m_Acceptor;
};
class BOBI2POutboundTunnel: public BOBI2PTunnel
{
public:
BOBI2POutboundTunnel (const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, bool quiet);
void Start ();
void Stop ();
void SetQuiet () { m_IsQuiet = true; };
private:
void Accept ();
void HandleAccept (std::shared_ptr<i2p::stream::Stream> stream);
private:
boost::asio::ip::tcp::endpoint m_Endpoint;
bool m_IsQuiet;
};
class BOBDestination
{
public:
BOBDestination (std::shared_ptr<ClientDestination> localDestination);
~BOBDestination ();
void Start ();
void Stop ();
void StopTunnels ();
void CreateInboundTunnel (int port);
void CreateOutboundTunnel (const std::string& address, int port, bool quiet);
const i2p::data::PrivateKeys& GetKeys () const { return m_LocalDestination->GetPrivateKeys (); };
std::shared_ptr<ClientDestination> GetLocalDestination () const { return m_LocalDestination; };
private:
std::shared_ptr<ClientDestination> m_LocalDestination;
BOBI2POutboundTunnel * m_OutboundTunnel;
BOBI2PInboundTunnel * m_InboundTunnel;
};
class BOBCommandChannel;
class BOBCommandSession: public std::enable_shared_from_this<BOBCommandSession>
{
public:
BOBCommandSession (BOBCommandChannel& owner);
~BOBCommandSession ();
void Terminate ();
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
void SendVersion ();
// command handlers
void ZapCommandHandler (const char * operand, size_t len);
void QuitCommandHandler (const char * operand, size_t len);
void StartCommandHandler (const char * operand, size_t len);
void StopCommandHandler (const char * operand, size_t len);
void SetNickCommandHandler (const char * operand, size_t len);
void GetNickCommandHandler (const char * operand, size_t len);
void NewkeysCommandHandler (const char * operand, size_t len);
void SetkeysCommandHandler (const char * operand, size_t len);
void GetkeysCommandHandler (const char * operand, size_t len);
void GetdestCommandHandler (const char * operand, size_t len);
void OuthostCommandHandler (const char * operand, size_t len);
void OutportCommandHandler (const char * operand, size_t len);
void InhostCommandHandler (const char * operand, size_t len);
void InportCommandHandler (const char * operand, size_t len);
void QuietCommandHandler (const char * operand, size_t len);
void LookupCommandHandler (const char * operand, size_t len);
void ClearCommandHandler (const char * operand, size_t len);
void ListCommandHandler (const char * operand, size_t len);
void OptionCommandHandler (const char * operand, size_t len);
private:
void Receive ();
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void Send (size_t len);
void HandleSent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void SendReplyOK (const char * msg);
void SendReplyError (const char * msg);
void SendData (const char * nickname);
private:
BOBCommandChannel& m_Owner;
boost::asio::ip::tcp::socket m_Socket;
char m_ReceiveBuffer[BOB_COMMAND_BUFFER_SIZE + 1], m_SendBuffer[BOB_COMMAND_BUFFER_SIZE + 1];
size_t m_ReceiveBufferOffset;
bool m_IsOpen, m_IsQuiet;
std::string m_Nickname, m_Address;
int m_InPort, m_OutPort;
i2p::data::PrivateKeys m_Keys;
std::map<std::string, std::string> m_Options;
BOBDestination * m_CurrentDestination;
};
typedef void (BOBCommandSession::*BOBCommandHandler)(const char * operand, size_t len);
class BOBCommandChannel
{
public:
BOBCommandChannel(const std::string& address, int port);
~BOBCommandChannel ();
void Start ();
void Stop ();
boost::asio::io_service& GetService () { return m_Service; };
void AddDestination (const std::string& name, BOBDestination * dest);
void DeleteDestination (const std::string& name);
BOBDestination * FindDestination (const std::string& name);
private:
void Run ();
void Accept ();
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<BOBCommandSession> session);
private:
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::ip::tcp::acceptor m_Acceptor;
std::map<std::string, BOBDestination *> m_Destinations;
std::map<std::string, BOBCommandHandler> m_CommandHandlers;
public:
const decltype(m_CommandHandlers)& GetCommandHandlers () const { return m_CommandHandlers; };
const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; };
};
}
}
#endif

61
client/CMakeLists.txt Normal file
View file

@ -0,0 +1,61 @@
set(CLIENT_SRC
"BOB.cpp"
"ClientContext.cpp"
"Daemon.cpp"
"HTTPProxy.cpp"
"HTTPServer.cpp"
"I2PService.cpp"
"i2pcontrol/I2PControl.cpp"
"i2pcontrol/I2PControlServer.cpp"
"I2PTunnel.cpp"
"SAM.cpp"
"SOCKS.cpp"
"i2p.cpp"
)
include_directories(".")
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND CLIENT_SRC "DaemonLinux.cpp")
elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
list(APPEND CLIENT_SRC "DaemonLinux.cpp")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
list(APPEND CLIENT_SRC "DaemonLinux.cpp")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
list(APPEND CLIENT_SRC "DaemonWin32.cpp")
list(APPEND CLIENT_SRC "Win32Service.cpp")
endif()
if(WITH_BINARY)
add_executable(${CLIENT_NAME} ${CLIENT_SRC})
if(NOT MSVC) # FIXME: incremental linker file name (.ilk) collision for dll & exe
set_target_properties(${CLIENT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}")
if(WITH_STATIC)
set_target_properties(${CLIENT_NAME} PROPERTIES LINK_FLAGS "-static" )
endif()
endif()
if(WITH_HARDENING AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set_target_properties(${CLIENT_NAME} PROPERTIES LINK_FLAGS "-z relro -z now")
endif()
# FindBoost pulls pthread for thread which is broken for static linking
# (at least on Ubuntu 15.04)
list(GET Boost_LIBRARIES -1 LAST_Boost_LIBRARIES)
if(${LAST_Boost_LIBRARIES} MATCHES ".*pthread.*")
list(REMOVE_AT Boost_LIBRARIES -1)
endif()
target_link_libraries(
${CLIENT_NAME} ${CORE_NAME}
${DL_LIB} ${Boost_LIBRARIES} ${CRYPTO++_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}
)
install(TARGETS
${CLIENT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
if(MSVC)
install(FILES
$<TARGET_PDB_FILE:${CLIENT_NAME}> DESTINATION "bin" CONFIGURATIONS DEBUG
)
endif()
endif()

363
client/ClientContext.cpp Normal file
View file

@ -0,0 +1,363 @@
#include <fstream>
#include <iostream>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include "util/util.h"
#include "util/Log.h"
#include "Identity.h"
#include "ClientContext.h"
namespace i2p
{
namespace client
{
ClientContext context;
ClientContext::ClientContext (): m_SharedLocalDestination (nullptr),
m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr),
m_BOBCommandChannel (nullptr), m_I2PControlService (nullptr)
{
}
ClientContext::~ClientContext ()
{
delete m_HttpProxy;
delete m_SocksProxy;
delete m_SamBridge;
delete m_BOBCommandChannel;
delete m_I2PControlService;
}
void ClientContext::Start ()
{
if (!m_SharedLocalDestination)
{
m_SharedLocalDestination = CreateNewLocalDestination (); // non-public, DSA
m_Destinations[m_SharedLocalDestination->GetIdentity ().GetIdentHash ()] = m_SharedLocalDestination;
m_SharedLocalDestination->Start ();
}
std::shared_ptr<ClientDestination> localDestination;
// proxies
std::string proxyKeys = i2p::util::config::GetArg("-proxykeys", "");
if (proxyKeys.length () > 0)
localDestination = LoadLocalDestination (proxyKeys, false);
m_HttpProxy = new i2p::proxy::HTTPProxy(
i2p::util::config::GetArg("-httpproxyaddress", "127.0.0.1"),
i2p::util::config::GetArg("-httpproxyport", 4446),
localDestination
);
m_HttpProxy->Start();
LogPrint("HTTP Proxy started");
m_SocksProxy = new i2p::proxy::SOCKSProxy(
i2p::util::config::GetArg("-socksproxyaddress", "127.0.0.1"),
i2p::util::config::GetArg("-socksproxyport", 4447),
localDestination
);
m_SocksProxy->Start();
LogPrint("SOCKS Proxy Started");
// I2P tunnels
std::string ircDestination = i2p::util::config::GetArg("-ircdest", "");
if (ircDestination.length () > 0) // ircdest is presented
{
localDestination = nullptr;
std::string ircKeys = i2p::util::config::GetArg("-irckeys", "");
if (ircKeys.length () > 0)
localDestination = LoadLocalDestination (ircKeys, false);
auto ircPort = i2p::util::config::GetArg("-ircport", 6668);
auto ircTunnel = new I2PClientTunnel(
ircDestination, i2p::util::config::GetArg("-ircaddress", "127.0.0.1"),
ircPort, localDestination
);
ircTunnel->Start ();
// TODO: allow muliple tunnels on the same port (but on a different address)
m_ClientTunnels.insert(std::make_pair(
ircPort, std::unique_ptr<I2PClientTunnel>(ircTunnel)
));
LogPrint("IRC tunnel started");
}
std::string eepKeys = i2p::util::config::GetArg("-eepkeys", "");
if (eepKeys.length () > 0) // eepkeys file is presented
{
localDestination = LoadLocalDestination (eepKeys, true);
auto serverTunnel = new I2PServerTunnel (i2p::util::config::GetArg("-eepaddress", "127.0.0.1"),
i2p::util::config::GetArg("-eepport", 80), localDestination);
serverTunnel->Start ();
m_ServerTunnels.insert (std::make_pair(localDestination->GetIdentHash (), std::unique_ptr<I2PServerTunnel>(serverTunnel)));
LogPrint("Server tunnel started");
}
ReadTunnels ();
// SAM
int samPort = i2p::util::config::GetArg("-samport", 0);
if (samPort)
{
m_SamBridge = new SAMBridge(
i2p::util::config::GetArg("-samaddress", "127.0.0.1"), samPort
);
m_SamBridge->Start ();
LogPrint("SAM bridge started");
}
// BOB
int bobPort = i2p::util::config::GetArg("-bobport", 0);
if (bobPort)
{
m_BOBCommandChannel = new BOBCommandChannel(
i2p::util::config::GetArg("-bobaddress", "127.0.0.1"), bobPort
);
m_BOBCommandChannel->Start ();
LogPrint("BOB command channel started");
}
// I2P Control
int i2pcontrolPort = i2p::util::config::GetArg("-i2pcontrolport", 0);
if(i2pcontrolPort) {
m_I2PControlService = new I2PControlService(
i2p::util::config::GetArg("-i2pcontroladdress", "127.0.0.1"),
i2pcontrolPort,
i2p::util::config::GetArg("-i2pcontrolpassword", I2P_CONTROL_DEFAULT_PASSWORD)
);
m_I2PControlService->Start();
LogPrint("I2PControl started");
}
m_AddressBook.Start (m_SharedLocalDestination.get());
}
void ClientContext::Stop ()
{
m_HttpProxy->Stop();
delete m_HttpProxy;
m_HttpProxy = nullptr;
LogPrint("HTTP Proxy stopped");
m_SocksProxy->Stop();
delete m_SocksProxy;
m_SocksProxy = nullptr;
LogPrint("SOCKS Proxy stopped");
for (auto& it: m_ClientTunnels)
{
it.second->Stop ();
LogPrint("I2P client tunnel on port ", it.first, " stopped");
}
m_ClientTunnels.clear ();
for (auto& it: m_ServerTunnels)
{
it.second->Stop ();
LogPrint("I2P server tunnel stopped");
}
m_ServerTunnels.clear ();
if (m_SamBridge)
{
m_SamBridge->Stop ();
delete m_SamBridge;
m_SamBridge = nullptr;
LogPrint("SAM brdige stopped");
}
if (m_BOBCommandChannel)
{
m_BOBCommandChannel->Stop ();
delete m_BOBCommandChannel;
m_BOBCommandChannel = nullptr;
LogPrint("BOB command channel stopped");
}
if (m_I2PControlService)
{
m_I2PControlService->Stop ();
delete m_I2PControlService;
m_I2PControlService = nullptr;
LogPrint("I2PControl stopped");
}
m_AddressBook.Stop ();
for (auto it: m_Destinations)
it.second->Stop ();
m_Destinations.clear ();
m_SharedLocalDestination = nullptr;
}
std::shared_ptr<ClientDestination> ClientContext::LoadLocalDestination (const std::string& filename, bool isPublic)
{
i2p::data::PrivateKeys keys;
std::string fullPath = i2p::util::filesystem::GetFullPath (filename);
std::ifstream s(fullPath.c_str (), std::ifstream::binary);
if (s.is_open ())
{
s.seekg (0, std::ios::end);
size_t len = s.tellg();
s.seekg (0, std::ios::beg);
uint8_t * buf = new uint8_t[len];
s.read ((char *)buf, len);
keys.FromBuffer (buf, len);
delete[] buf;
LogPrint ("Local address ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " loaded");
}
else
{
LogPrint ("Can't open file ", fullPath, " Creating new one");
keys = i2p::data::PrivateKeys::CreateRandomKeys (i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256);
std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out);
size_t len = keys.GetFullLen ();
uint8_t * buf = new uint8_t[len];
len = keys.ToBuffer (buf, len);
f.write ((char *)buf, len);
delete[] buf;
LogPrint ("New private keys file ", fullPath, " for ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " created");
}
std::shared_ptr<ClientDestination> localDestination = nullptr;
std::unique_lock<std::mutex> l(m_DestinationsMutex);
auto it = m_Destinations.find (keys.GetPublic ().GetIdentHash ());
if (it != m_Destinations.end ())
{
LogPrint (eLogWarning, "Local destination ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " alreday exists");
localDestination = it->second;
}
else
{
localDestination = std::make_shared<ClientDestination> (keys, isPublic);
m_Destinations[localDestination->GetIdentHash ()] = localDestination;
localDestination->Start ();
}
return localDestination;
}
std::shared_ptr<ClientDestination> ClientContext::CreateNewLocalDestination (bool isPublic, i2p::data::SigningKeyType sigType,
const std::map<std::string, std::string> * params)
{
i2p::data::PrivateKeys keys = i2p::data::PrivateKeys::CreateRandomKeys (sigType);
auto localDestination = std::make_shared<ClientDestination> (keys, isPublic, params);
std::unique_lock<std::mutex> l(m_DestinationsMutex);
m_Destinations[localDestination->GetIdentHash ()] = localDestination;
localDestination->Start ();
return localDestination;
}
void ClientContext::DeleteLocalDestination (std::shared_ptr<ClientDestination> destination)
{
if (!destination) return;
auto it = m_Destinations.find (destination->GetIdentHash ());
if (it != m_Destinations.end ())
{
auto d = it->second;
{
std::unique_lock<std::mutex> l(m_DestinationsMutex);
m_Destinations.erase (it);
}
d->Stop ();
}
}
std::shared_ptr<ClientDestination> ClientContext::CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic,
const std::map<std::string, std::string> * params)
{
auto it = m_Destinations.find (keys.GetPublic ().GetIdentHash ());
if (it != m_Destinations.end ())
{
LogPrint ("Local destination ", m_AddressBook.ToAddress(keys.GetPublic ().GetIdentHash ()), " exists");
if (!it->second->IsRunning ())
{
it->second->Start ();
return it->second;
}
return nullptr;
}
auto localDestination = std::make_shared<ClientDestination> (keys, isPublic, params);
std::unique_lock<std::mutex> l(m_DestinationsMutex);
m_Destinations[keys.GetPublic ().GetIdentHash ()] = localDestination;
localDestination->Start ();
return localDestination;
}
std::shared_ptr<ClientDestination> ClientContext::FindLocalDestination (const i2p::data::IdentHash& destination) const
{
auto it = m_Destinations.find (destination);
if (it != m_Destinations.end ())
return it->second;
return nullptr;
}
void ClientContext::ReadTunnels ()
{
boost::property_tree::ptree pt;
std::string pathTunnelsConfigFile = i2p::util::filesystem::GetTunnelsConfigFile().string();
try {
boost::property_tree::read_ini(
pathTunnelsConfigFile,
pt
);
} catch(const std::exception& ex) {
LogPrint(eLogWarning, "Can't read ", pathTunnelsConfigFile, ": ", ex.what ());
return;
}
int numClientTunnels = 0, numServerTunnels = 0;
for(auto& section: pt) {
std::string name = section.first;
try {
std::string type = section.second.get<std::string> (I2P_TUNNELS_SECTION_TYPE);
if(type == I2P_TUNNELS_SECTION_TYPE_CLIENT) {
// mandatory params
std::string dest = section.second.get<std::string> (I2P_CLIENT_TUNNEL_DESTINATION);
int port = section.second.get<int> (I2P_CLIENT_TUNNEL_PORT);
// optional params
std::string address = section.second.get(
I2P_CLIENT_TUNNEL_ADDRESS, "127.0.0.1"
);
std::string keys = section.second.get(I2P_CLIENT_TUNNEL_KEYS, "");
int destinationPort = section.second.get(I2P_CLIENT_TUNNEL_DESTINATION_PORT, 0);
std::shared_ptr<ClientDestination> localDestination = nullptr;
if(keys.length () > 0)
localDestination = LoadLocalDestination (keys, false);
auto clientTunnel = new I2PClientTunnel(
dest, address, port, localDestination, destinationPort
);
// TODO: allow muliple tunnels on the same port (but on a different address)
if(m_ClientTunnels.insert(std::make_pair(port, std::unique_ptr<I2PClientTunnel>(clientTunnel))).second)
clientTunnel->Start ();
else
LogPrint (eLogError, "I2P client tunnel with port ", port, " already exists");
numClientTunnels++;
} else if(type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP)
{
// mandatory params
std::string host = section.second.get<std::string> (I2P_SERVER_TUNNEL_HOST);
int port = section.second.get<int> (I2P_SERVER_TUNNEL_PORT);
std::string keys = section.second.get<std::string> (I2P_SERVER_TUNNEL_KEYS);
// optional params
int inPort = section.second.get (I2P_SERVER_TUNNEL_INPORT, 0);
std::string accessList = section.second.get (I2P_SERVER_TUNNEL_ACCESS_LIST, "");
auto localDestination = LoadLocalDestination (keys, true);
I2PServerTunnel * serverTunnel = (type == I2P_TUNNELS_SECTION_TYPE_HTTP) ? new I2PServerTunnelHTTP (host, port, localDestination, inPort) : new I2PServerTunnel (host, port, localDestination, inPort);
if (accessList.length () > 0) {
std::set<i2p::data::IdentHash> idents;
size_t pos = 0, comma;
do {
comma = accessList.find (',', pos);
i2p::data::IdentHash ident;
ident.FromBase32 (accessList.substr (pos, comma != std::string::npos ? comma - pos : std::string::npos));
idents.insert (ident);
pos = comma + 1;
} while (comma != std::string::npos);
serverTunnel->SetAccessList (idents);
}
if (m_ServerTunnels.insert (std::make_pair (localDestination->GetIdentHash (), std::unique_ptr<I2PServerTunnel>(serverTunnel))).second)
serverTunnel->Start ();
else
LogPrint (eLogError, "I2P server tunnel for destination ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), " already exists");
numServerTunnels++;
} else
LogPrint (eLogWarning, "Unknown section type=", type, " of ", name, " in ", pathTunnelsConfigFile);
} catch (const std::exception& ex) {
LogPrint (eLogError, "Can't read tunnel ", name, " params: ", ex.what ());
}
}
LogPrint (eLogInfo, numClientTunnels, " I2P client tunnels created");
LogPrint (eLogInfo, numServerTunnels, " I2P server tunnels created");
}
}
}

86
client/ClientContext.h Normal file
View file

@ -0,0 +1,86 @@
#ifndef CLIENT_CONTEXT_H__
#define CLIENT_CONTEXT_H__
#include <map>
#include <mutex>
#include <memory>
#include "Destination.h"
#include "HTTPProxy.h"
#include "SOCKS.h"
#include "I2PTunnel.h"
#include "SAM.h"
#include "BOB.h"
#include "AddressBook.h"
#include "i2pcontrol/I2PControlServer.h"
namespace i2p
{
namespace client
{
const char I2P_TUNNELS_SECTION_TYPE[] = "type";
const char I2P_TUNNELS_SECTION_TYPE_CLIENT[] = "client";
const char I2P_TUNNELS_SECTION_TYPE_SERVER[] = "server";
const char I2P_TUNNELS_SECTION_TYPE_HTTP[] = "http";
const char I2P_CLIENT_TUNNEL_PORT[] = "port";
const char I2P_CLIENT_TUNNEL_ADDRESS[] = "address";
const char I2P_CLIENT_TUNNEL_DESTINATION[] = "destination";
const char I2P_CLIENT_TUNNEL_KEYS[] = "keys";
const char I2P_CLIENT_TUNNEL_DESTINATION_PORT[] = "destinationport";
const char I2P_SERVER_TUNNEL_HOST[] = "host";
const char I2P_SERVER_TUNNEL_PORT[] = "port";
const char I2P_SERVER_TUNNEL_KEYS[] = "keys";
const char I2P_SERVER_TUNNEL_INPORT[] = "inport";
const char I2P_SERVER_TUNNEL_ACCESS_LIST[] = "accesslist";
class ClientContext
{
public:
ClientContext ();
~ClientContext ();
void Start ();
void Stop ();
std::shared_ptr<ClientDestination> GetSharedLocalDestination () const { return m_SharedLocalDestination; };
std::shared_ptr<ClientDestination> CreateNewLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1,
const std::map<std::string, std::string> * params = nullptr); // transient
std::shared_ptr<ClientDestination> CreateNewLocalDestination (const i2p::data::PrivateKeys& keys, bool isPublic = true,
const std::map<std::string, std::string> * params = nullptr);
void DeleteLocalDestination (std::shared_ptr<ClientDestination> destination);
std::shared_ptr<ClientDestination> FindLocalDestination (const i2p::data::IdentHash& destination) const;
std::shared_ptr<ClientDestination> LoadLocalDestination (const std::string& filename, bool isPublic);
AddressBook& GetAddressBook () { return m_AddressBook; };
const SAMBridge * GetSAMBridge () const { return m_SamBridge; };
private:
void ReadTunnels ();
private:
std::mutex m_DestinationsMutex;
std::map<i2p::data::IdentHash, std::shared_ptr<ClientDestination> > m_Destinations;
std::shared_ptr<ClientDestination> m_SharedLocalDestination;
AddressBook m_AddressBook;
i2p::proxy::HTTPProxy * m_HttpProxy;
i2p::proxy::SOCKSProxy * m_SocksProxy;
std::map<int, std::unique_ptr<I2PClientTunnel> > m_ClientTunnels; // port->tunnel
std::map<i2p::data::IdentHash, std::unique_ptr<I2PServerTunnel> > m_ServerTunnels; // destination->tunnel
SAMBridge * m_SamBridge;
BOBCommandChannel * m_BOBCommandChannel;
I2PControlService * m_I2PControlService;
public:
// for HTTP
const decltype(m_Destinations)& GetDestinations () const { return m_Destinations; };
};
extern ClientContext context;
}
}
#endif

146
client/Daemon.cpp Normal file
View file

@ -0,0 +1,146 @@
#include <thread>
#include "Daemon.h"
#include "util/Log.h"
#include "version.h"
#include "transport/Transports.h"
#include "transport/NTCPSession.h"
#include "RouterInfo.h"
#include "RouterContext.h"
#include "tunnel/Tunnel.h"
#include "NetDb.h"
#include "Garlic.h"
#include "util/util.h"
#include "Streaming.h"
#include "Destination.h"
#include "HTTPServer.h"
#include "ClientContext.h"
namespace i2p
{
namespace util
{
class Daemon_Singleton::Daemon_Singleton_Private
{
public:
Daemon_Singleton_Private() : httpServer(nullptr)
{};
~Daemon_Singleton_Private()
{
delete httpServer;
};
i2p::util::HTTPServer *httpServer;
};
Daemon_Singleton::Daemon_Singleton() : running(1), d(*new Daemon_Singleton_Private()) {};
Daemon_Singleton::~Daemon_Singleton() {
delete &d;
};
bool Daemon_Singleton::IsService () const
{
#ifndef _WIN32
return i2p::util::config::GetArg("-service", 0);
#else
return false;
#endif
}
bool Daemon_Singleton::init(int argc, char* argv[])
{
i2p::util::config::OptionParser(argc, argv);
i2p::context.Init ();
LogPrint("\n\n\n\ni2pd starting\n");
LogPrint("Version ", VERSION);
LogPrint("data directory: ", i2p::util::filesystem::GetDataDir().string());
i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs);
isDaemon = i2p::util::config::GetArg("-daemon", 0);
isLogging = i2p::util::config::GetArg("-log", 1);
int port = i2p::util::config::GetArg("-port", 0);
if (port)
i2p::context.UpdatePort (port);
const char * host = i2p::util::config::GetCharArg("-host", "");
if (host && host[0])
i2p::context.UpdateAddress (boost::asio::ip::address::from_string (host));
i2p::context.SetSupportsV6 (i2p::util::config::GetArg("-v6", 0));
i2p::context.SetFloodfill (i2p::util::config::GetArg("-floodfill", 0));
auto bandwidth = i2p::util::config::GetArg("-bandwidth", "");
if (bandwidth.length () > 0)
{
if (bandwidth[0] > 'L')
i2p::context.SetHighBandwidth ();
else
i2p::context.SetLowBandwidth ();
}
LogPrint("CMD parameters:");
for (int i = 0; i < argc; ++i)
LogPrint(i, " ", argv[i]);
return true;
}
bool Daemon_Singleton::start()
{
// initialize log
if (isLogging)
{
if (isDaemon)
{
std::string logfile_path = IsService () ? "/var/log" : i2p::util::filesystem::GetDataDir().string();
#ifndef _WIN32
logfile_path.append("/i2pd.log");
#else
logfile_path.append("\\i2pd.log");
#endif
StartLog (logfile_path);
}
else
StartLog (""); // write to stdout
}
d.httpServer = new i2p::util::HTTPServer(
i2p::util::config::GetArg("-httpaddress", "127.0.0.1"),
i2p::util::config::GetArg("-httpport", 7070)
);
d.httpServer->Start();
LogPrint("HTTP Server started");
i2p::data::netdb.Start();
LogPrint("NetDB started");
i2p::transport::transports.Start();
LogPrint("Transports started");
i2p::tunnel::tunnels.Start();
LogPrint("Tunnels started");
i2p::client::context.Start ();
LogPrint("Client started");
return true;
}
bool Daemon_Singleton::stop()
{
LogPrint("Shutdown started.");
i2p::client::context.Stop();
LogPrint("Client stopped");
i2p::tunnel::tunnels.Stop();
LogPrint("Tunnels stopped");
i2p::transport::transports.Stop();
LogPrint("Transports stopped");
i2p::data::netdb.Stop();
LogPrint("NetDB stopped");
d.httpServer->Stop();
LogPrint("HTTP Server stopped");
StopLog ();
delete d.httpServer; d.httpServer = nullptr;
return true;
}
}
}

73
client/Daemon.h Normal file
View file

@ -0,0 +1,73 @@
#pragma once
#include <string>
#ifdef _WIN32
#define Daemon i2p::util::DaemonWin32::Instance()
#else
#define Daemon i2p::util::DaemonLinux::Instance()
#endif
namespace i2p
{
namespace util
{
class Daemon_Singleton_Private;
class Daemon_Singleton
{
public:
virtual bool init(int argc, char* argv[]);
virtual bool start();
virtual bool stop();
int isLogging;
int isDaemon;
int running;
protected:
Daemon_Singleton();
virtual ~Daemon_Singleton();
bool IsService () const;
// d-pointer for httpServer, httpProxy, etc.
class Daemon_Singleton_Private;
Daemon_Singleton_Private &d;
};
#ifdef _WIN32
class DaemonWin32 : public Daemon_Singleton
{
public:
static DaemonWin32& Instance()
{
static DaemonWin32 instance;
return instance;
}
virtual bool init(int argc, char* argv[]);
virtual bool start();
virtual bool stop();
};
#else
class DaemonLinux : public Daemon_Singleton
{
public:
DaemonLinux() = default;
static DaemonLinux& Instance()
{
static DaemonLinux instance;
return instance;
}
virtual bool start();
virtual bool stop();
private:
std::string pidfile;
int pidFilehandle;
};
#endif
}
}

119
client/DaemonLinux.cpp Normal file
View file

@ -0,0 +1,119 @@
#include "Daemon.h"
#ifndef _WIN32
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "util/Log.h"
#include "util/util.h"
void handle_signal(int sig)
{
switch (sig)
{
case SIGHUP:
if (i2p::util::config::GetArg("daemon", 0) == 1)
{
static bool first=true;
if (first)
{
first=false;
return;
}
}
LogPrint("Reloading config.");
i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs);
break;
case SIGABRT:
case SIGTERM:
case SIGINT:
Daemon.running = 0; // Exit loop
break;
}
}
namespace i2p
{
namespace util
{
bool DaemonLinux::start()
{
if (isDaemon == 1)
{
pid_t pid;
pid = fork();
if (pid > 0) // parent
::exit (EXIT_SUCCESS);
if (pid < 0) // error
return false;
// child
umask(0);
int sid = setsid();
if (sid < 0)
{
LogPrint("Error, could not create process group.");
return false;
}
std::string d(i2p::util::filesystem::GetDataDir().string ()); // make a copy
chdir(d.c_str());
// close stdin/stdout/stderr descriptors
::close (0);
::open ("/dev/null", O_RDWR);
::close (1);
::open ("/dev/null", O_RDWR);
::close (2);
::open ("/dev/null", O_RDWR);
}
// Pidfile
pidfile = IsService () ? "/var/run" : i2p::util::filesystem::GetDataDir().string();
pidfile.append("/i2pd.pid");
pidFilehandle = open(pidfile.c_str(), O_RDWR | O_CREAT, 0600);
if (pidFilehandle == -1)
{
LogPrint("Error, could not create pid file (", pidfile, ")\nIs an instance already running?");
return false;
}
if (lockf(pidFilehandle, F_TLOCK, 0) == -1)
{
LogPrint("Error, could not lock pid file (", pidfile, ")\nIs an instance already running?");
return false;
}
char pid[10];
sprintf(pid, "%d\n", getpid());
write(pidFilehandle, pid, strlen(pid));
// Signal handler
struct sigaction sa;
sa.sa_handler = handle_signal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGHUP, &sa, 0);
sigaction(SIGABRT, &sa, 0);
sigaction(SIGTERM, &sa, 0);
sigaction(SIGINT, &sa, 0);
return Daemon_Singleton::start();
}
bool DaemonLinux::stop()
{
close(pidFilehandle);
unlink(pidfile.c_str());
return Daemon_Singleton::stop();
}
}
}
#endif

83
client/DaemonWin32.cpp Normal file
View file

@ -0,0 +1,83 @@
#include "Daemon.h"
#include "util/util.h"
#include "util/Log.h"
#ifdef _WIN32
#include "./Win32/Win32Service.h"
namespace i2p
{
namespace util
{
bool DaemonWin32::init(int argc, char* argv[])
{
setlocale(LC_CTYPE, "");
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
setlocale(LC_ALL, "Russian");
if (!Daemon_Singleton::init(argc, argv)) return false;
if (I2PService::isService())
isDaemon = 1;
else
isDaemon = 0;
std::string serviceControl = i2p::util::config::GetArg("-service", "none");
if (serviceControl == "install")
{
InstallService(
SERVICE_NAME, // Name of service
SERVICE_DISPLAY_NAME, // Name to display
SERVICE_START_TYPE, // Service start type
SERVICE_DEPENDENCIES, // Dependencies
SERVICE_ACCOUNT, // Service running account
SERVICE_PASSWORD // Password of the account
);
exit(0);
}
else if (serviceControl == "remove")
{
UninstallService(SERVICE_NAME);
exit(0);
}
else if (serviceControl != "none")
{
printf(" --service=install to install the service.\n");
printf(" --service=remove to remove the service.\n");
}
if (isDaemon == 1)
{
LogPrint("Service session");
I2PService service(SERVICE_NAME);
if (!I2PService::Run(service))
{
LogPrint("Service failed to run w/err 0x%08lx\n", GetLastError());
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
else
LogPrint("User session");
return true;
}
bool DaemonWin32::start()
{
setlocale(LC_CTYPE, "");
SetConsoleCP(1251);
SetConsoleOutputCP(1251);
setlocale(LC_ALL, "Russian");
return Daemon_Singleton::start();
}
bool DaemonWin32::stop()
{
return Daemon_Singleton::stop();
}
}
}
#endif

299
client/HTTPProxy.cpp Normal file
View file

@ -0,0 +1,299 @@
#include <cstring>
#include <cassert>
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
#include <string>
#include <atomic>
#include "HTTPProxy.h"
#include "util/util.h"
#include "Identity.h"
#include "Streaming.h"
#include "Destination.h"
#include "ClientContext.h"
#include "util/I2PEndian.h"
#include "I2PTunnel.h"
namespace i2p
{
namespace proxy
{
static const size_t http_buffer_size = 8192;
class HTTPProxyHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this<HTTPProxyHandler>
{
private:
enum state
{
GET_METHOD,
GET_HOSTNAME,
GET_HTTPV,
GET_HTTPVNL, //TODO: fallback to finding HOst: header if needed
DONE
};
void EnterState(state nstate);
bool HandleData(uint8_t *http_buff, std::size_t len);
void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
void Terminate();
void AsyncSockRead();
void HTTPRequestFailed(/*std::string message*/);
void ExtractRequest();
bool ValidateHTTPRequest();
void HandleJumpServices();
bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len);
void SentHTTPFailed(const boost::system::error_code & ecode);
void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
uint8_t m_http_buff[http_buffer_size];
std::shared_ptr<boost::asio::ip::tcp::socket> m_sock;
std::string m_request; //Data left to be sent
std::string m_url; //URL
std::string m_method; //Method
std::string m_version; //HTTP version
std::string m_address; //Address
std::string m_path; //Path
int m_port; //Port
state m_state;//Parsing state
public:
HTTPProxyHandler(HTTPProxyServer * parent, std::shared_ptr<boost::asio::ip::tcp::socket> sock) :
I2PServiceHandler(parent), m_sock(sock)
{ EnterState(GET_METHOD); }
~HTTPProxyHandler() { Terminate(); }
void Handle () { AsyncSockRead(); }
};
void HTTPProxyHandler::AsyncSockRead()
{
LogPrint(eLogDebug,"--- HTTP Proxy async sock read");
if(m_sock) {
m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size),
std::bind(&HTTPProxyHandler::HandleSockRecv, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
} else {
LogPrint(eLogError,"--- HTTP Proxy no socket for read");
}
}
void HTTPProxyHandler::Terminate() {
if (Kill()) return;
if (m_sock)
{
LogPrint(eLogDebug,"--- HTTP Proxy close sock");
m_sock->close();
m_sock = nullptr;
}
Done(shared_from_this());
}
/* All hope is lost beyond this point */
//TODO: handle this apropriately
void HTTPProxyHandler::HTTPRequestFailed(/*HTTPProxyHandler::errTypes error*/)
{
static std::string response = "HTTP/1.0 500 Internal Server Error\r\nContent-type: text/html\r\nContent-length: 0\r\n";
boost::asio::async_write(*m_sock, boost::asio::buffer(response,response.size()),
std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1));
}
void HTTPProxyHandler::EnterState(HTTPProxyHandler::state nstate)
{
m_state = nstate;
}
void HTTPProxyHandler::ExtractRequest()
{
LogPrint(eLogDebug,"--- HTTP Proxy method is: ", m_method, "\nRequest is: ", m_url);
std::string server="";
std::string port="80";
boost::regex rHTTP("http://(.*?)(:(\\d+))?(/.*)");
boost::smatch m;
std::string path;
if(boost::regex_search(m_url, m, rHTTP, boost::match_extra))
{
server=m[1].str();
if (m[2].str() != "") port=m[3].str();
path=m[4].str();
}
LogPrint(eLogDebug,"--- HTTP Proxy server is: ",server, " port is: ", port, "\n path is: ",path);
m_address = server;
m_port = boost::lexical_cast<int>(port);
m_path = path;
}
bool HTTPProxyHandler::ValidateHTTPRequest()
{
if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" )
{
LogPrint(eLogError,"--- HTTP Proxy unsupported version: ", m_version);
HTTPRequestFailed(); //TODO: send right stuff
return false;
}
return true;
}
void HTTPProxyHandler::HandleJumpServices()
{
static const char * helpermark1 = "?i2paddresshelper=";
static const char * helpermark2 = "&i2paddresshelper=";
size_t addressHelperPos1 = m_path.rfind (helpermark1);
size_t addressHelperPos2 = m_path.rfind (helpermark2);
size_t addressHelperPos;
if (addressHelperPos1 == std::string::npos)
{
if (addressHelperPos2 == std::string::npos)
return; //Not a jump service
else
addressHelperPos = addressHelperPos2;
}
else
{
if (addressHelperPos2 == std::string::npos)
addressHelperPos = addressHelperPos1;
else if ( addressHelperPos1 > addressHelperPos2 )
addressHelperPos = addressHelperPos1;
else
addressHelperPos = addressHelperPos2;
}
auto base64 = m_path.substr (addressHelperPos + strlen(helpermark1));
base64 = i2p::util::http::urlDecode(base64); //Some of the symbols may be urlencoded
LogPrint (eLogDebug,"Jump service for ", m_address, " found at ", base64, ". Inserting to address book");
//TODO: this is very dangerous and broken. We should ask the user before doing anything see http://pastethis.i2p/raw/pn5fL4YNJL7OSWj3Sc6N/
//TODO: we could redirect the user again to avoid dirtiness in the browser
i2p::client::context.GetAddressBook ().InsertAddress (m_address, base64);
m_path.erase(addressHelperPos);
}
bool HTTPProxyHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len)
{
ExtractRequest(); //TODO: parse earlier
if (!ValidateHTTPRequest()) return false;
HandleJumpServices();
m_request = m_method;
m_request.push_back(' ');
m_request += m_path;
m_request.push_back(' ');
m_request += m_version;
m_request.push_back('\r');
m_request.push_back('\n');
m_request.append("Connection: close\r\n");
m_request.append(reinterpret_cast<const char *>(http_buff),len);
return true;
}
bool HTTPProxyHandler::HandleData(uint8_t *http_buff, std::size_t len)
{
assert(len); // This should always be called with a least a byte left to parse
while (len > 0)
{
//TODO: fallback to finding HOst: header if needed
switch (m_state)
{
case GET_METHOD:
switch (*http_buff)
{
case ' ': EnterState(GET_HOSTNAME); break;
default: m_method.push_back(*http_buff); break;
}
break;
case GET_HOSTNAME:
switch (*http_buff)
{
case ' ': EnterState(GET_HTTPV); break;
default: m_url.push_back(*http_buff); break;
}
break;
case GET_HTTPV:
switch (*http_buff)
{
case '\r': EnterState(GET_HTTPVNL); break;
default: m_version.push_back(*http_buff); break;
}
break;
case GET_HTTPVNL:
switch (*http_buff)
{
case '\n': EnterState(DONE); break;
default:
LogPrint(eLogError,"--- HTTP Proxy rejected invalid request ending with: ", ((int)*http_buff));
HTTPRequestFailed(); //TODO: add correct code
return false;
}
break;
default:
LogPrint(eLogError,"--- HTTP Proxy invalid state: ", m_state);
HTTPRequestFailed(); //TODO: add correct code 500
return false;
}
http_buff++;
len--;
if (m_state == DONE)
return CreateHTTPRequest(http_buff,len);
}
return true;
}
void HTTPProxyHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len)
{
LogPrint(eLogDebug,"--- HTTP Proxy sock recv: ", len);
if(ecode)
{
LogPrint(eLogWarning," --- HTTP Proxy sock recv got error: ", ecode);
Terminate();
return;
}
if (HandleData(m_http_buff, len))
{
if (m_state == DONE)
{
LogPrint(eLogInfo,"--- HTTP Proxy requested: ", m_url);
GetOwner()->CreateStream (std::bind (&HTTPProxyHandler::HandleStreamRequestComplete,
shared_from_this(), std::placeholders::_1), m_address, m_port);
}
else
AsyncSockRead();
}
}
void HTTPProxyHandler::SentHTTPFailed(const boost::system::error_code & ecode)
{
if (!ecode)
Terminate();
else
{
LogPrint (eLogError,"--- HTTP Proxy Closing socket after sending failure because: ", ecode.message ());
Terminate();
}
}
void HTTPProxyHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
if (Kill()) return;
LogPrint (eLogInfo,"--- HTTP Proxy New I2PTunnel connection");
auto connection = std::make_shared<i2p::client::I2PTunnelConnection>(GetOwner(), m_sock, stream);
GetOwner()->AddHandler (connection);
connection->I2PConnect (reinterpret_cast<const uint8_t*>(m_request.data()), m_request.size());
Done(shared_from_this());
}
else
{
LogPrint (eLogError,"--- HTTP Proxy Issue when creating the stream, check the previous warnings for more info.");
HTTPRequestFailed(); // TODO: Send correct error message host unreachable
}
}
HTTPProxyServer::HTTPProxyServer(const std::string& address, int port, std::shared_ptr<i2p::client::ClientDestination> localDestination):
TCPIPAcceptor(address, port, localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination ())
{
}
std::shared_ptr<i2p::client::I2PServiceHandler> HTTPProxyServer::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
return std::make_shared<HTTPProxyHandler> (this, socket);
}
}
}

32
client/HTTPProxy.h Normal file
View file

@ -0,0 +1,32 @@
#ifndef HTTP_PROXY_H__
#define HTTP_PROXY_H__
#include <memory>
#include <set>
#include <boost/asio.hpp>
#include <mutex>
#include "I2PService.h"
#include "Destination.h"
namespace i2p
{
namespace proxy
{
class HTTPProxyServer: public i2p::client::TCPIPAcceptor
{
public:
HTTPProxyServer(const std::string& address, int port, std::shared_ptr<i2p::client::ClientDestination> localDestination = nullptr);
~HTTPProxyServer() {};
protected:
// Implements TCPIPAcceptor
std::shared_ptr<i2p::client::I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
const char* GetName() { return "HTTP Proxy"; }
};
typedef HTTPProxyServer HTTPProxy;
}
}
#endif

994
client/HTTPServer.cpp Normal file
View file

@ -0,0 +1,994 @@
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <ctime>
#include "util/base64.h"
#include "util/Log.h"
#include "tunnel/Tunnel.h"
#include "tunnel/TransitTunnel.h"
#include "transport/Transports.h"
#include "NetDb.h"
#include "util/I2PEndian.h"
#include "Streaming.h"
#include "Destination.h"
#include "RouterContext.h"
#include "ClientContext.h"
#include "HTTPServer.h"
// For image and info
#include "version.h"
namespace i2p
{
namespace util
{
const std::string HTTPConnection::itoopieImage =
"<img alt=\"ICToopie Icon\" src=\"data:image/png;base64,"
"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\" />";
const std::string HTTPConnection::itoopieFavicon =
"data:image/vnd.microsoft.icon;base64,"
"AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAABsAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgQAAAIMAAACEAAAAg"
"wAAAIAAAACCAAAASQAAACUAAAAmAAAAOwAAAFIAAABgAAAAVAAAACcAAAAVAAAADQAAAAcAAAADAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjAAAA/wAAAP4AAAD+AAAA/gAAAP4AAAD+A"
"AAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/"
"AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAACbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAAP8AA"
"AD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4A"
"AAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/wAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAKQAAAAAAAAAAAAAAAADbAAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP8AAAD/AAAA/gAA"
"AP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAIA/gAAAPwAAAD/AAAA/gAAAP4AAAD+AAAA/gA"
"AAP4AAAD+AAAA/wAAAFkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApAAAAAAAAAAAAHAAAAP8AAAD+AAAA/gAAAPwACwD9ABEA"
"/QASAPgAFgD4ABUA+AATAP0ADQD8AAIA/AAAAP8AAAD+AAAA/gAAAP8ADQD7ACMA/QAlAP8AJwD/ACI"
"A/QATAPwAAAD8AAAA/gAAAP4AAAD+AAAA/gAAAKIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADe"
"AAAA/gAAAPwAJAD+AAwA+wAAAP8AAAD/AAAA/wAEAPwAEgD8ACQA/gAmAP8AHAD8AAEA/gAAAP4ACwD"
"8ACUA/wAkAP8AFwD8AAkA/AAQAP0AIwD+ACQA/wANAPsAAAD+AAAA/gAAAP8AAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAEAAABNAAAA/wAAAP8AGAD8AAAA/QAYAPMARQDyAFEA9wBOAPIAPAD0ABEA8QAAAP8"
"AGgD7ACMA/wAVAP0AAAD9ACMA/QAkAP8ADwD9AAgA+ABBAPUALADyAAAA/AAUAPwAJgD/ABgA/AAAAP"
"0AAAD/AAAAdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAP4ADQD8AAgA/wAqAPAAcAD/AGoA/wB"
"qAP8AagD/AGoA/wBvAP8ATwDvAAAA/QAbAP0AEwD8AAYA/QAkAP8AGAD8AAsA9wBuAPgAagD/AGsA/w"
"BjAPkADAD3AAQA/QAiAP4AGgD9AAAA/gAAAP8AAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZgAAAP8AAAD+ABY"
"A+wAQAPQAcQD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBTAPEAAAD/AAcA/AAMAPwAIQD9AA"
"AA+gBoAPgAagD/AGoA/wBqAP8AagD/AHAA/gAuAO0AAAD/AB8A/QAPAPwAAAD+AAAA+AAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAANIAAAD+AAwA+QAAAP0AZQD1AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8Abg"
"D/ABcA8QAAAP4ACAD9AAAA+QBeAPkAagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AbQD/ADkA7wAAAP8AJ"
"AD9AAAA/gAAAP8AAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAALi8AAAAAAAAAAAAAAAD/AAAA/QAAAP8AKwD1AGsA/wBqAP8AagD/AGoA/wBqAP"
"8AagD/AGoA/wBqAP8AagD/AGoA/wA9APUAAAD/AAAA/wBKAPMAagD/AGoA/wBqAP8AagD/AGoA/wBqA"
"P8AagD/AGoA/wBxAP8ADgD1ABMA/AAHAPsAAAD/AAAARQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzPAIAAAAgAAAA/wAAAPwAAAD/AGQA9A"
"BqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8ASQD1AAAA/wASAPMAcAD/AGoA/"
"wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AawD/ACsA+QAOAP4ADQD8AAAA/wAAAEUAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA7gAAAAAAAADgwAQAYLwAA//8AAA"
"ICIwAAAP8AAAD+AAYA8wBwAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/A"
"EIA9QAAAP8AMADvAGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGsA/wApAP0AEQD8"
"AAIA/QAAAP8AAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAVgEAAA"
"AAACJ5AQA2ygAAND8BAHV+AgAAAAAAAAH/AAAA/gAcAPQAbAD/AGoA/wBqAP8AagD/AGoA/wBqAP8Aa"
"gD/AGoA/wBqAP8AagD/AGsA/wApAPYAAAD+AB4A+gBsAP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8A"
"agD/AGoA/wBrAP8ALADxAAAA/AAAAP4AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAABYAhQAfAE8ABgBWAQAAAAAAIoABAAAAAAA5UgEAAAAAAAAA+AAAAP4AJQD2AGsA/wBqA"
"P8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBsAPgAAAD6AAAA/gANAPAAbgD/AGoA/wBq"
"AP8AagD/AGoA/wBqAP8AagD/AGoA/wBqAP8AagD/ADsA+QAAAP8AAAD+AAAA4AAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtQAAAAAAAABYABgAZgEGAIMBAAAAAAA+fgEAAAAAAAAAA"
"AACA8wAAAH+ACkA9wBgAPgARQDwADoA9gA5APYASgD0AHAA9wBrAP8AagD/AGoA/wBxAP0AFQD0AAAA"
"/gAAAP4AAAD5AHMA/QBqAP8AagD/AGoA/wBqAP8AagD/AGoA/wBvAP0ATgDxAEwA9AA1AOkAAAD/AAA"
"A/gAAAP8AAAB3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAvAAzAKcBAADXAAAAAAAcAFYBF"
"gB4ASAAZwEAXT8BADpdAgAAAAAAAQG+AAEB/gAAAf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAPsAJAD1"
"AEQA9ABEAPMACQD3AAAA/wAAAP4AAAD+AAAA/wAiAPMASAD4AE8A9gBuAPgAbQD/AGoA/wBvAP8AFQD"
"yAAAA/wAAAP8AAAD/AAAA/gAAAP4AAAD+AAAA/wAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"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"
"f7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf8/wCq5v4AERj+AAEA/gAAAP4AAAD+A"
"AAA/gAAAP4AAAD/AAAAdAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOgAAAD+AAAA/gAAAP4Aeqf9ALz//wC3+/8At/"
"v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At"
"/r/ALz//gAcJvwAAQH+AAAA/gAAAP4AAAD/AAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI0AAAD/AAAA/gAAAP"
"4Ac579ALn+/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+"
"/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Avf/+ABcg/QAAAf4AAAD+AAAA/wAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAABIAAAD/AAAA/gAAAP4AP1X9AL7//wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/"
"wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wCy8v4AAAD+AAAA"
"/gAAAP8AAACKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAA/QAAAP4ABAb9AL7//wC3+/8At/v/ALf7/wC3+/8At/v/A"
"Lf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/"
"ALf7/wC3+/8Atvr/AHmm/QAAAP4AAAD+AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAA/wAAAP4AAAD+AHWh/QC2+v8At"
"/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8A"
"t/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wDD//8AGSP9AAAA/gAAAP8AAABuAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbQAAA"
"P8AAAD+AAUH/QDE//8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3"
"+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Atvv/AH+s/gA"
"AAP4AAAD+AAAA2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAALMAAAD+AAAA/gBZd/0At/z/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7"
"/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf"
"7/wC3+/8At/v/ALf7/wDF//8AAAD9AAAA/gAAAP8AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAA/gAAAP4Al9D9ALf7/wC3+/8At/v/"
"ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v"
"/ALf7/wC3+/8At/v/ALf7/wC3+/8At/v/ALf7/wC3+/8Auf7/AERe/QAAAP4AAAD/AAAAUQAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP4A"
"AAD9AMb//gC3+/8At/v/ALf7/wC3+/8Auf7/AMb//gC2+f4AwP/+AMT//wC4/f8At/v/ALf7/wC3+/8"
"At/v/ALf7/wC3+/8At/v/ALf7/wC8//8Avf//ALn+/wC3+/8At/v/ALf7/wC3+/8At/v/ALb7/wBvlf"
"4AAAD+AAAA/gAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAACgAAAP8AAAD+ABgg/QDA//8At/v/ALf7/wC2+v8AuPz+AEVe/QAAAP0AAAD+AAAA/gA"
"EBv0AU2/9ALz//wC3+/8At/v/ALf7/wC3+/8Atvr/AL7//wBmi/0ALj/9ACQx/QBJZP0Ai739AMD//w"
"C3+/8At/v/ALf7/wC3+/8Aksj+AAAA/gAAAP4AAACZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABcAAAD/AAAA/gAtPf4AvP//ALf7/wC3+/8As/T+AAo"
"O/QAAAP0SEhLtAAAA/gAAAP4AAAD+AAAA/gAWH/0Av///ALf7/wC3+/8At/v/ALHx/gANEv0AAAD+AA"
"AA/gAAAP4AAAD/AAAA/wAuP/0Avv//ALf7/wC3+/8At/v/AKjm/QAAAP4AAAD+AAAApwAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATAAAA/wAAAP4AL0H"
"+ALz//wC3+/8AvP//ADJE/QAAAPfc3Nz1TExM6wAAAP4AAAD+AAAA/gAAAP8AAAD+AGeN/gC3+/8At/"
"v/AML//wAeKf0AAAH/AAAA/gAAAP4AAAD+cXFx3YSEhOoAAAD9AEVe/QC6//8At/v/ALf7/wCs7P8AA"
"AD+AAAA/gAAAKEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAP8AAAD+ACAt/QC+//8At/v/ALP1/gAAAP95eXns/////2FhYeoBAQH/AAAA/gEBAf"
"9eXl7lDQ0N9gAgLP0Av///ALf7/wCNwf4AAAD/NDQ03AAAAP8AAAD+AAAA/6Ojo+b/////eXl55AAAA"
"P8AtPf+ALf7/wC3+/8AoNv9AAAA/gAAAP4AAACHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/gAAAP0Ax///ALf7/wCIu/0AAAD/ysrK6/"
"/////8/Pz6Hx8f6wAAAP8kJCTm/f39/WVlZe4ABAX8AMT//wC3/P8AV3X9ERER7/////9MTEzrAAAA6"
"ElJSeb///////////X19e8AAAD/AHuo/QC3+/8At/v/AIW2/gAAAP4AAAD/AAAAZAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0wAAAP4AAAD+AK"
"fl/gC3+/8AjcH+AAAA/83Nze/////////////////////w//////////9gYGDsAA8V/QDC//8Auv//A"
"EJa/UFBQeb////////////////////////////////////vAAAA/wBvlfwAt/v/ALf7/wBdff0AAAD+"
"AAAA/wAAAC0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAI4AAAD+AAAA/gBnjP0At/v/ALz//gAAAP+CgoLx/////////////////////////////"
"///CAkJ8QBCXP4Auf7/ALj8/wBScf0DAwPw////////////////////////////////tra26AAAAP8A"
"hrf+ALf7/wDA//8AHCf9AAAA/gAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAA/wAAAP4AEBb9AMT//wC6//8AQVn+AAAA9+Pj4"
"/L/////////////////////cnJy1gAAAP8AlMr9ALf7/wC3+/8AhLT+AAAA/5mZmeL/////////////"
"/////////////R0dHesAAAD9AL///gC3+/8ApuX+AAAA/gAAAP4AAAC1AAAAAAAAAAAAAAAAAAAAAAA"
"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"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIMAAAD/AAAA/gAAAP4AAAD9AFl3/QCt7/4AwP//ALj9/w"
"C2+/8At/v/ALf7/wC3+/8At/v/ALz//wDB//4AeKP+ABki/QAAAP4AAAD+AAAA/wAAAOEAAAASAAAAA"
"QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAOUAAAD/AA"
"AA/gAAAP4AAAD+ABcg/QBQbv0AbJD9AH2s/gCBsP0Ac5v+AFt6/QAtPv0AAAD9AAAA/gAAAP4AAAD+A"
"AAA/wAAAIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAWgAAAOIAAAD/AAAA/gAAAP4AAAD+AAAA/gAAAP4AAAD+AAAA/gAAAP4AA"
"AD+AAAA/gAAAP8AAAD/AAAAmQAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAIgAAADRAAAA/wAAA"
"P8AAAD/AAAA/wAAAP8AAAD/AAAA5QAAAJoAAABWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAIAAAACQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///gAf///////4AAAAD/////wAAAAP////"
"/AAAAB/////+AAAAH/////4AAAA//////gAAAD/////8AAAAH/////wAAAAP////+AAAAA/////4AAA"
"AD/////gAAAAP////+AAAAA/////4AAAAD/////gAAAAP////+AAAAA/////4AAAAB/////gAAAAD//"
"//+AAAAAP////wAAAAA////+AAAAAD////wAAAAAP///8AAAAAA////gAAAAAB///8AAAAAAH///gAA"
"AAAAf//+AAAAAAA///4AAAAAAD///4AAAAAAP///wAAAAAAf///wAAAAAD////gAAAAAP////gAAAAB"
"/////AAAAAP////+AAAAD/////wAAAAf////+AAAAB/////4AAAAD/////AAAAAP////8AAAAA/////"
"wAAAAB////+AAAAAH////4AAAAAf////gAAAAA////+AAAAAD////4AAAAAP////gAAAAA////+AAAA"
"AD////4AAAAAf////gAAAAB////+AAAAAH////8AAAAAf////wAAAAD/////gAAAAP////+AAAAB///"
"//8AAAAH/////wAAAA//////gAAAH//////AAAA//////+AAAH//////+AAA///////+AAP///////+"
"AH//////////////8=";
const char HTTP_COMMAND_TUNNELS[] = "tunnels";
const char HTTP_COMMAND_TRANSIT_TUNNELS[] = "transit_tunnels";
const char HTTP_COMMAND_TRANSPORTS[] = "transports";
const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels";
const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels";
const char HTTP_COMMAND_LOCAL_DESTINATIONS[] = "local_destinations";
const char HTTP_COMMAND_LOCAL_DESTINATION[] = "local_destination";
const char HTTP_PARAM_BASE32_ADDRESS[] = "b32";
const char HTTP_COMMAND_SAM_SESSIONS[] = "sam_sessions";
const char HTTP_COMMAND_SAM_SESSION[] = "sam_session";
const char HTTP_PARAM_SAM_SESSION_ID[] = "id";
namespace misc_strings
{
const char name_value_separator[] = { ':', ' ' };
const char crlf[] = { '\r', '\n' };
} // namespace misc_strings
std::vector<boost::asio::const_buffer> HTTPConnection::reply::to_buffers(int status)
{
std::vector<boost::asio::const_buffer> buffers;
if (headers.size () > 0)
{
buffers.push_back(boost::asio::buffer("HTTP/1.1 ", 9));
buffers.push_back(boost::asio::buffer(boost::lexical_cast<std::string>(status), 3));
buffers.push_back(boost::asio::buffer(" ", 1));
std::string status_string;
switch (status)
{
case 105: status_string = "Name Not Resolved"; break;
case 200: status_string = "OK"; break;
case 400: status_string = "Bad Request"; break;
case 404: status_string = "Not Found"; break;
case 408: status_string = "Request Timeout"; break;
case 500: status_string = "Internal Server Error"; break;
case 502: status_string = "Bad Gateway"; break;
case 503: status_string = "Not Implemented"; break;
case 504: status_string = "Gateway Timeout"; break;
}
buffers.push_back(boost::asio::buffer(status_string, status_string.size()));
buffers.push_back(boost::asio::buffer(misc_strings::crlf));
for (std::size_t i = 0; i < headers.size(); ++i)
{
header& h = headers[i];
buffers.push_back(boost::asio::buffer(h.name));
buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator));
buffers.push_back(boost::asio::buffer(h.value));
buffers.push_back(boost::asio::buffer(misc_strings::crlf));
}
buffers.push_back(boost::asio::buffer(misc_strings::crlf));
}
buffers.push_back(boost::asio::buffer(content));
return buffers;
}
void HTTPConnection::Terminate ()
{
m_Socket->close ();
}
void HTTPConnection::Receive ()
{
m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE),
std::bind(&HTTPConnection::HandleReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if(!ecode) {
m_Buffer[bytes_transferred] = 0;
m_BufferLen = bytes_transferred;
RunRequest();
}
else if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
void HTTPConnection::RunRequest ()
{
auto address = ExtractAddress ();
if (address.length () > 1 && address[1] != '?') // not just '/' or '/?'
return; // TODO: error handling
HandleRequest (address);
}
std::string HTTPConnection::ExtractAddress ()
{
char * get = strstr (m_Buffer, "GET");
if (get)
{
char * http = strstr (get, "HTTP");
if (http)
return std::string (get + 4, http - get - 5);
}
return "";
}
void HTTPConnection::ExtractParams (const std::string& str, std::map<std::string, std::string>& params)
{
if (str[0] != '&') return;
size_t pos = 1, end;
do
{
end = str.find ('&', pos);
std::string param = str.substr (pos, end - pos);
LogPrint (param);
size_t e = param.find ('=');
if (e != std::string::npos)
params[param.substr(0, e)] = param.substr(e+1);
pos = end + 1;
}
while (end != std::string::npos);
}
void HTTPConnection::HandleWriteReply (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
boost::system::error_code ignored_ec;
m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec);
Terminate ();
}
}
void HTTPConnection::HandleRequest (const std::string& address)
{
std::stringstream s;
// Html5 head start
s << "<!DOCTYPE html>\n<html lang=\"en\">"; // TODO: Add support for locale.
s << "<head><meta charset=\"utf-8\" />"; // TODO: Find something to parse html/template system. This is horrible.
s << "<link rel='shortcut icon' href='";
s << itoopieFavicon;
s << "' /><title>Purple I2P " << VERSION " Webconsole</title></head>";
// Head end
if (address.length () > 1)
HandleCommand (address.substr (2), s);
else
FillContent (s);
s << "</html>";
SendReply (s.str ());
}
void HTTPConnection::FillContent (std::stringstream& s)
{
s << "<h2>Welcome to the Webconsole!</h2><br>";
s << "<b>Uptime:</b> " << boost::posix_time::to_simple_string (
boost::posix_time::time_duration (boost::posix_time::seconds (
i2p::context.GetUptime ()))) << "<br>";
s << "<b>Status:</b> ";
switch (i2p::context.GetStatus ())
{
case eRouterStatusOK: s << "OK"; break;
case eRouterStatusTesting: s << "Testing"; break;
case eRouterStatusFirewalled: s << "Firewalled"; break;
default: s << "Unknown";
}
s << "<br>";
s << "<b>Tunnel creation success rate:</b> " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%<br>";
s << "<b>Received:</b> " << i2p::transport::transports.GetTotalReceivedBytes ()/1000 << "K";
s << " (" << i2p::transport::transports.GetInBandwidth () <<" Bps)<br>";
s << "<b>Sent:</b> " << i2p::transport::transports.GetTotalSentBytes ()/1000 << "K";
s << " (" << i2p::transport::transports.GetOutBandwidth () <<" Bps)<br>";
s << "<b>Data path:</b> " << i2p::util::filesystem::GetDataDir().string() << "<br><br>";
s << "<b>Our external address:</b>" << "<br>" ;
for (auto& address : i2p::context.GetRouterInfo().GetAddresses())
{
switch (address.transportStyle)
{
case i2p::data::RouterInfo::eTransportNTCP:
if (address.host.is_v6 ())
s << "NTCP6&nbsp;&nbsp;";
else
s << "NTCP&nbsp;&nbsp;";
break;
case i2p::data::RouterInfo::eTransportSSU:
if (address.host.is_v6 ())
s << "SSU6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
else
s << "SSU&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
break;
default:
s << "Unknown&nbsp;&nbsp;";
}
s << address.host.to_string() << ":" << address.port << "<br>";
}
s << "<br><b>Routers:</b> <i>" << i2p::data::netdb.GetNumRouters () << "</i> ";
s << "<b>Floodfills:</b> <i>" << i2p::data::netdb.GetNumFloodfills () << "</i> ";
s << "<b>LeaseSets:</b> <i>" << i2p::data::netdb.GetNumLeaseSets () << "</i><br>";
s << "<br><b><a href=/?" << HTTP_COMMAND_LOCAL_DESTINATIONS << ">Local destinations</a></b>";
s << "<br><b><a href=/?" << HTTP_COMMAND_TUNNELS << ">Tunnels</a></b>";
s << "<br><b><a href=/?" << HTTP_COMMAND_TRANSIT_TUNNELS << ">Transit tunnels</a></b>";
s << "<br><b><a href=/?" << HTTP_COMMAND_TRANSPORTS << ">Transports</a></b>";
if (i2p::client::context.GetSAMBridge ())
s << "<br><b><a href=/?" << HTTP_COMMAND_SAM_SESSIONS << ">SAM sessions</a></b>";
s << "<br>";
if (i2p::context.AcceptsTunnels ())
s << "<br><b><a href=/?" << HTTP_COMMAND_STOP_ACCEPTING_TUNNELS << ">Stop accepting tunnels</a></b><br>";
else
s << "<br><b><a href=/?" << HTTP_COMMAND_START_ACCEPTING_TUNNELS << ">Start accepting tunnels</a></b><br>";
}
void HTTPConnection::HandleCommand (const std::string& command, std::stringstream& s)
{
size_t paramsPos = command.find('&');
std::string cmd = command.substr (0, paramsPos);
if (cmd == HTTP_COMMAND_TRANSPORTS)
ShowTransports (s);
else if (cmd == HTTP_COMMAND_TUNNELS)
ShowTunnels (s);
else if (cmd == HTTP_COMMAND_TRANSIT_TUNNELS)
ShowTransitTunnels (s);
else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS)
StartAcceptingTunnels (s);
else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS)
StopAcceptingTunnels (s);
else if (cmd == HTTP_COMMAND_LOCAL_DESTINATIONS)
ShowLocalDestinations (s);
else if (cmd == HTTP_COMMAND_LOCAL_DESTINATION)
{
std::map<std::string, std::string> params;
ExtractParams (command.substr (paramsPos), params);
auto b32 = params[HTTP_PARAM_BASE32_ADDRESS];
ShowLocalDestination (b32, s);
}
else if (cmd == HTTP_COMMAND_SAM_SESSIONS)
ShowSAMSessions (s);
else if (cmd == HTTP_COMMAND_SAM_SESSION)
{
std::map<std::string, std::string> params;
ExtractParams (command.substr (paramsPos), params);
auto id = params[HTTP_PARAM_SAM_SESSION_ID];
ShowSAMSession (id, s);
}
}
void HTTPConnection::ShowTransports (std::stringstream& s)
{
auto ntcpServer = i2p::transport::transports.GetNTCPServer ();
if (ntcpServer)
{
s << "NTCP<br>";
for (auto it: ntcpServer->GetNTCPSessions ())
{
if (it.second && it.second->IsEstablished ())
{
// incoming connection doesn't have remote RI
auto outgoing = it.second->GetRemoteRouter ();
if (outgoing) s << "-->";
s << it.second->GetRemoteIdentity ().GetIdentHash ().ToBase64 ().substr (0, 4) << ": "
<< it.second->GetSocket ().remote_endpoint().address ().to_string ();
if (!outgoing) s << "-->";
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
s << "<br>";
}
s << std::endl;
}
}
auto ssuServer = i2p::transport::transports.GetSSUServer ();
if (ssuServer)
{
s << "<br>SSU<br>";
for (auto it: ssuServer->GetSessions ())
{
// incoming connections don't have remote router
auto outgoing = it.second->GetRemoteRouter ();
auto endpoint = it.second->GetRemoteEndpoint ();
if (outgoing) s << "-->";
s << endpoint.address ().to_string () << ":" << endpoint.port ();
if (!outgoing) s << "-->";
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
if (it.second->GetRelayTag ())
s << " [itag:" << it.second->GetRelayTag () << "]";
s << "<br>";
s << std::endl;
}
}
}
void HTTPConnection::ShowTunnels (std::stringstream& s)
{
s << "Queue size:" << i2p::tunnel::tunnels.GetQueueSize () << "<br>";
for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ())
{
it->GetTunnelConfig ()->Print (s);
auto state = it->GetState ();
if (state == i2p::tunnel::eTunnelStateFailed)
s << " " << "Failed";
else if (state == i2p::tunnel::eTunnelStateExpiring)
s << " " << "Exp";
s << " " << (int)it->GetNumSentBytes () << "<br>";
s << std::endl;
}
for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ())
{
it.second->GetTunnelConfig ()->Print (s);
auto state = it.second->GetState ();
if (state == i2p::tunnel::eTunnelStateFailed)
s << " " << "Failed";
else if (state == i2p::tunnel::eTunnelStateExpiring)
s << " " << "Exp";
s << " " << (int)it.second->GetNumReceivedBytes () << "<br>";
s << std::endl;
}
}
void HTTPConnection::ShowTransitTunnels (std::stringstream& s)
{
for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ())
{
if (dynamic_cast<i2p::tunnel::TransitTunnelGateway *>(it.second))
s << it.second->GetTunnelID () << "-->";
else if (dynamic_cast<i2p::tunnel::TransitTunnelEndpoint *>(it.second))
s << "-->" << it.second->GetTunnelID ();
else
s << "-->" << it.second->GetTunnelID () << "-->";
s << " " << it.second->GetNumTransmittedBytes () << "<br>";
}
}
void HTTPConnection::ShowLocalDestinations (std::stringstream& s)
{
for (auto& it: i2p::client::context.GetDestinations ())
{
auto ident = it.second->GetIdentHash ();;
s << "<a href=/?" << HTTP_COMMAND_LOCAL_DESTINATION;
s << "&" << HTTP_PARAM_BASE32_ADDRESS << "=" << ident.ToBase32 () << ">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a><br>" << std::endl;
}
}
void HTTPConnection::ShowLocalDestination (const std::string& b32, std::stringstream& s)
{
i2p::data::IdentHash ident;
ident.FromBase32 (b32);
auto dest = i2p::client::context.FindLocalDestination (ident);
if (dest)
{
s << "<b>Base64:</b><br>" << dest->GetIdentity ().ToBase64 () << "<br><br>";
s << "<b>LeaseSets:</b> <i>" << dest->GetNumRemoteLeaseSets () << "</i><br>";
auto pool = dest->GetTunnelPool ();
if (pool)
{
s << "<b>Tunnels:</b><br>";
for (auto it: pool->GetOutboundTunnels ())
{
it->GetTunnelConfig ()->Print (s);
auto state = it->GetState ();
if (state == i2p::tunnel::eTunnelStateFailed)
s << " " << "Failed";
else if (state == i2p::tunnel::eTunnelStateExpiring)
s << " " << "Exp";
s << "<br>" << std::endl;
}
for (auto it: pool->GetInboundTunnels ())
{
it->GetTunnelConfig ()->Print (s);
auto state = it->GetState ();
if (state == i2p::tunnel::eTunnelStateFailed)
s << " " << "Failed";
else if (state == i2p::tunnel::eTunnelStateExpiring)
s << " " << "Exp";
s << "<br>" << std::endl;
}
}
s << "<br><b>Streams:</b><br>";
for (auto it: dest->GetStreamingDestination ()->GetStreams ())
{
s << it.first << "->" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << " ";
s << " [" << it.second->GetNumSentBytes () << ":" << it.second->GetNumReceivedBytes () << "]";
s << " [out:" << it.second->GetSendQueueSize () << "][in:" << it.second->GetReceiveQueueSize () << "]";
s << "[buf:" << it.second->GetSendBufferSize () << "]";
s << "[RTT:" << it.second->GetRTT () << "]";
s << "[Window:" << it.second->GetWindowSize () << "]";
s << "[Status:" << (int)it.second->GetStatus () << "]";
s << "<br>"<< std::endl;
}
}
}
void HTTPConnection::ShowSAMSessions (std::stringstream& s)
{
auto sam = i2p::client::context.GetSAMBridge ();
if (sam)
{
for (auto& it: sam->GetSessions ())
{
s << "<a href=/?" << HTTP_COMMAND_SAM_SESSION;
s << "&" << HTTP_PARAM_SAM_SESSION_ID << "=" << it.first << ">";
s << it.first << "</a><br>" << std::endl;
}
}
}
void HTTPConnection::ShowSAMSession (const std::string& id, std::stringstream& s)
{
auto sam = i2p::client::context.GetSAMBridge ();
if (sam)
{
auto session = sam->FindSession (id);
if (session)
{
auto& ident = session->localDestination->GetIdentHash();
s << "<a href=/?" << HTTP_COMMAND_LOCAL_DESTINATION;
s << "&" << HTTP_PARAM_BASE32_ADDRESS << "=" << ident.ToBase32 () << ">";
s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a><br>" << std::endl;
s << "<b>Streams:</b><br>";
for (auto it: session->sockets)
{
switch (it->GetSocketType ())
{
case i2p::client::eSAMSocketTypeSession:
s << "session";
break;
case i2p::client::eSAMSocketTypeStream:
s << "stream";
break;
case i2p::client::eSAMSocketTypeAcceptor:
s << "acceptor";
break;
default:
s << "unknown";
}
s << " [" << it->GetSocket ().remote_endpoint() << "]";
s << "<br>" << std::endl;
}
}
}
}
void HTTPConnection::StartAcceptingTunnels (std::stringstream& s)
{
i2p::context.SetAcceptsTunnels (true);
s << "Accepting tunnels started" << std::endl;
}
void HTTPConnection::StopAcceptingTunnels (std::stringstream& s)
{
i2p::context.SetAcceptsTunnels (false);
s << "Accepting tunnels stopped" << std::endl;
}
void HTTPConnection::SendReply (const std::string& content, int status)
{
m_Reply.content = content;
m_Reply.headers.resize(3);
// we need the date header to be compliant with HTTP 1.1
std::time_t time_now = std::time(nullptr);
char time_buff[128];
if ( std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)) ) {
m_Reply.headers[0].name = "Date";
m_Reply.headers[0].value = std::string(time_buff);
m_Reply.headers[1].name = "Content-Length";
m_Reply.headers[1].value = boost::lexical_cast<std::string>(m_Reply.content.size());
m_Reply.headers[2].name = "Content-Type";
m_Reply.headers[2].value = "text/html";
}
boost::asio::async_write (*m_Socket, m_Reply.to_buffers(status),
std::bind (&HTTPConnection::HandleWriteReply, shared_from_this (), std::placeholders::_1));
}
HTTPServer::HTTPServer (const std::string& address, int port):
m_Thread (nullptr), m_Work (m_Service),
m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(address), port)),
m_NewSocket (nullptr)
{
}
HTTPServer::~HTTPServer ()
{
Stop ();
}
void HTTPServer::Start ()
{
m_Thread = new std::thread (std::bind (&HTTPServer::Run, this));
m_Acceptor.listen ();
Accept ();
}
void HTTPServer::Stop ()
{
m_Acceptor.close();
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
void HTTPServer::Run ()
{
m_Service.run ();
}
void HTTPServer::Accept ()
{
m_NewSocket = new boost::asio::ip::tcp::socket (m_Service);
m_Acceptor.async_accept (*m_NewSocket, boost::bind (&HTTPServer::HandleAccept, this,
boost::asio::placeholders::error));
}
void HTTPServer::HandleAccept(const boost::system::error_code& ecode)
{
if (!ecode)
{
CreateConnection(m_NewSocket);
Accept ();
}
}
void HTTPServer::CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket)
{
auto conn = std::make_shared<HTTPConnection> (m_NewSocket);
conn->Receive ();
}
}
}

127
client/HTTPServer.h Normal file
View file

@ -0,0 +1,127 @@
#ifndef HTTP_SERVER_H__
#define HTTP_SERVER_H__
#include <sstream>
#include <thread>
#include <memory>
#include <boost/asio.hpp>
#include <boost/array.hpp>
namespace i2p
{
namespace util
{
const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192;
const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds
class HTTPConnection: public std::enable_shared_from_this<HTTPConnection>
{
protected:
struct header
{
std::string name;
std::string value;
};
struct request
{
std::string method;
std::string uri;
std::string host;
int port;
int http_version_major;
int http_version_minor;
std::vector<header> headers;
};
struct reply
{
std::vector<header> headers;
std::string content;
std::vector<boost::asio::const_buffer> to_buffers (int status);
};
public:
HTTPConnection (boost::asio::ip::tcp::socket * socket):
m_Socket (socket), m_Timer (socket->get_io_service ()),
m_BufferLen (0) {};
~HTTPConnection() { delete m_Socket; }
void Receive ();
private:
void Terminate ();
void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleWriteReply(const boost::system::error_code& ecode);
void SendReply (const std::string& content, int status = 200);
void HandleRequest (const std::string& address);
void HandleCommand (const std::string& command, std::stringstream& s);
void ShowTransports (std::stringstream& s);
void ShowTunnels (std::stringstream& s);
void ShowTransitTunnels (std::stringstream& s);
void ShowLocalDestinations (std::stringstream& s);
void ShowLocalDestination (const std::string& b32, std::stringstream& s);
void ShowSAMSessions (std::stringstream& s);
void ShowSAMSession (const std::string& id, std::stringstream& s);
void StartAcceptingTunnels (std::stringstream& s);
void StopAcceptingTunnels (std::stringstream& s);
void FillContent (std::stringstream& s);
std::string ExtractAddress ();
void ExtractParams (const std::string& str, std::map<std::string, std::string>& params);
protected:
boost::asio::ip::tcp::socket * m_Socket;
boost::asio::deadline_timer m_Timer;
char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1], m_StreamBuffer[HTTP_CONNECTION_BUFFER_SIZE + 1];
size_t m_BufferLen;
request m_Request;
reply m_Reply;
protected:
virtual void RunRequest ();
public:
static const std::string itoopieImage;
static const std::string itoopieFavicon;
};
class HTTPServer
{
public:
HTTPServer (const std::string& address, int port);
virtual ~HTTPServer ();
void Start ();
void Stop ();
private:
void Run ();
void Accept ();
void HandleAccept(const boost::system::error_code& ecode);
private:
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::io_service::work m_Work;
boost::asio::ip::tcp::acceptor m_Acceptor;
boost::asio::ip::tcp::socket * m_NewSocket;
protected:
virtual void CreateConnection(boost::asio::ip::tcp::socket * m_NewSocket);
};
}
}
#endif

79
client/I2PService.cpp Normal file
View file

@ -0,0 +1,79 @@
#include "Destination.h"
#include "Identity.h"
#include "ClientContext.h"
#include "I2PService.h"
namespace i2p
{
namespace client
{
static const i2p::data::SigningKeyType I2P_SERVICE_DEFAULT_KEY_TYPE = i2p::data::SIGNING_KEY_TYPE_ECDSA_SHA256_P256;
I2PService::I2PService (std::shared_ptr<ClientDestination> localDestination):
m_LocalDestination (localDestination ? localDestination :
i2p::client::context.CreateNewLocalDestination (false, I2P_SERVICE_DEFAULT_KEY_TYPE))
{
}
I2PService::I2PService (i2p::data::SigningKeyType kt):
m_LocalDestination (i2p::client::context.CreateNewLocalDestination (false, kt))
{
}
void I2PService::CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port) {
assert(streamRequestComplete);
i2p::data::IdentHash identHash;
if (i2p::client::context.GetAddressBook ().GetIdentHash (dest, identHash))
m_LocalDestination->CreateStream (streamRequestComplete, identHash, port);
else
{
LogPrint (eLogWarning, "Remote destination ", dest, " not found");
streamRequestComplete (nullptr);
}
}
void TCPIPAcceptor::Start ()
{
m_Acceptor.listen ();
Accept ();
}
void TCPIPAcceptor::Stop ()
{
m_Acceptor.close();
m_Timer.cancel ();
ClearHandlers();
}
void TCPIPAcceptor::Accept ()
{
auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (GetService ());
m_Acceptor.async_accept (*newSocket, std::bind (&TCPIPAcceptor::HandleAccept, this,
std::placeholders::_1, newSocket));
}
void TCPIPAcceptor::HandleAccept (const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
if (!ecode)
{
LogPrint(eLogDebug,"--- ",GetName()," accepted");
auto handler = CreateHandler(socket);
if (handler)
{
AddHandler(handler);
handler->Handle();
}
else
socket->close();
Accept();
}
else
{
if (ecode != boost::asio::error::operation_aborted)
LogPrint (eLogError,"--- ",GetName()," Closing socket on accept because: ", ecode.message ());
}
}
}
}

107
client/I2PService.h Normal file
View file

@ -0,0 +1,107 @@
#ifndef I2PSERVICE_H__
#define I2PSERVICE_H__
#include <atomic>
#include <mutex>
#include <unordered_set>
#include <memory>
#include <boost/asio.hpp>
#include "Destination.h"
#include "Identity.h"
namespace i2p
{
namespace client
{
class I2PServiceHandler;
class I2PService
{
public:
I2PService (std::shared_ptr<ClientDestination> localDestination = nullptr);
I2PService (i2p::data::SigningKeyType kt);
virtual ~I2PService () { ClearHandlers (); }
inline void AddHandler (std::shared_ptr<I2PServiceHandler> conn)
{
std::unique_lock<std::mutex> l(m_HandlersMutex);
m_Handlers.insert(conn);
}
inline void RemoveHandler (std::shared_ptr<I2PServiceHandler> conn)
{
std::unique_lock<std::mutex> l(m_HandlersMutex);
m_Handlers.erase(conn);
}
inline void ClearHandlers ()
{
std::unique_lock<std::mutex> l(m_HandlersMutex);
m_Handlers.clear();
}
inline std::shared_ptr<ClientDestination> GetLocalDestination () { return m_LocalDestination; }
inline void SetLocalDestination (std::shared_ptr<ClientDestination> dest) { m_LocalDestination = dest; }
void CreateStream (StreamRequestComplete streamRequestComplete, const std::string& dest, int port = 0);
inline boost::asio::io_service& GetService () { return m_LocalDestination->GetService (); }
virtual void Start () = 0;
virtual void Stop () = 0;
virtual const char* GetName() { return "Generic I2P Service"; }
private:
std::shared_ptr<ClientDestination> m_LocalDestination;
std::unordered_set<std::shared_ptr<I2PServiceHandler> > m_Handlers;
std::mutex m_HandlersMutex;
};
/*Simple interface for I2PHandlers, allows detection of finalization amongst other things */
class I2PServiceHandler
{
public:
I2PServiceHandler(I2PService * parent) : m_Service(parent), m_Dead(false) { }
virtual ~I2PServiceHandler() { }
//If you override this make sure you call it from the children
virtual void Handle() {}; //Start handling the socket
protected:
// Call when terminating or handing over to avoid race conditions
inline bool Kill () { return m_Dead.exchange(true); }
// Call to know if the handler is dead
inline bool Dead () { return m_Dead; }
// Call when done to clean up (make sure Kill is called first)
inline void Done (std::shared_ptr<I2PServiceHandler> me) { if(m_Service) m_Service->RemoveHandler(me); }
// Call to talk with the owner
inline I2PService * GetOwner() { return m_Service; }
private:
I2PService *m_Service;
std::atomic<bool> m_Dead; //To avoid cleaning up multiple times
};
/* TODO: support IPv6 too */
//This is a service that listens for connections on the IP network and interacts with I2P
class TCPIPAcceptor: public I2PService
{
public:
TCPIPAcceptor(const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination = nullptr)
: I2PService(localDestination), m_Acceptor(GetService(), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)),
m_Timer (GetService()) {}
TCPIPAcceptor(const std::string& address, int port, i2p::data::SigningKeyType kt)
: I2PService(kt), m_Acceptor(GetService(), boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port)),
m_Timer(GetService()) {}
virtual ~TCPIPAcceptor () { TCPIPAcceptor::Stop(); }
//If you override this make sure you call it from the children
void Start ();
//If you override this make sure you call it from the children
void Stop ();
protected:
virtual std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket) = 0;
virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; }
private:
void Accept();
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
boost::asio::ip::tcp::acceptor m_Acceptor;
boost::asio::deadline_timer m_Timer;
};
}
}
#endif

405
client/I2PTunnel.cpp Normal file
View file

@ -0,0 +1,405 @@
#include <cassert>
#include "util/Log.h"
#include "Destination.h"
#include "ClientContext.h"
#include "I2PTunnel.h"
namespace i2p
{
namespace client
{
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<const i2p::data::LeaseSet> leaseSet, int port):
I2PServiceHandler(owner), m_Socket (socket), m_RemoteEndpoint (socket->remote_endpoint ()),
m_IsQuiet (true)
{
m_Stream = GetOwner()->GetLocalDestination ()->CreateStream (leaseSet, port);
}
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner,
std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<i2p::stream::Stream> stream):
I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream),
m_RemoteEndpoint (socket->remote_endpoint ()), m_IsQuiet (true)
{
}
I2PTunnelConnection::I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
std::shared_ptr<boost::asio::ip::tcp::socket> socket, const boost::asio::ip::tcp::endpoint& target, bool quiet):
I2PServiceHandler(owner), m_Socket (socket), m_Stream (stream),
m_RemoteEndpoint (target), m_IsQuiet (quiet)
{
}
I2PTunnelConnection::~I2PTunnelConnection ()
{
}
void I2PTunnelConnection::I2PConnect (const uint8_t * msg, size_t len)
{
if (m_Stream)
{
if (msg)
m_Stream->Send (msg, len); // connect and send
else
m_Stream->Send (m_Buffer, 0); // connect
}
StreamReceive ();
Receive ();
}
void I2PTunnelConnection::Connect ()
{
if (m_Socket)
m_Socket->async_connect (m_RemoteEndpoint, std::bind (&I2PTunnelConnection::HandleConnect,
shared_from_this (), std::placeholders::_1));
}
void I2PTunnelConnection::Terminate ()
{
if (Kill()) return;
if (m_Stream)
{
m_Stream->Close ();
m_Stream.reset ();
}
m_Socket->close ();
Done(shared_from_this ());
}
void I2PTunnelConnection::Receive ()
{
m_Socket->async_read_some (boost::asio::buffer(m_Buffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE),
std::bind(&I2PTunnelConnection::HandleReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void I2PTunnelConnection::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint ("I2PTunnel read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
if (m_Stream)
{
auto s = shared_from_this ();
m_Stream->AsyncSend (m_Buffer, bytes_transferred,
[s](const boost::system::error_code& ecode)
{
if (!ecode)
s->Receive ();
else
s->Terminate ();
});
}
}
}
void I2PTunnelConnection::HandleWrite (const boost::system::error_code& ecode)
{
if (ecode)
{
LogPrint ("I2PTunnel write error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
StreamReceive ();
}
void I2PTunnelConnection::StreamReceive ()
{
if (m_Stream)
m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, I2P_TUNNEL_CONNECTION_BUFFER_SIZE),
std::bind (&I2PTunnelConnection::HandleStreamReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2),
I2P_TUNNEL_CONNECTION_MAX_IDLE);
}
void I2PTunnelConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint ("I2PTunnel stream read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
Write (m_StreamBuffer, bytes_transferred);
}
void I2PTunnelConnection::Write (const uint8_t * buf, size_t len)
{
m_Socket->async_send (boost::asio::buffer (buf, len),
std::bind (&I2PTunnelConnection::HandleWrite, shared_from_this (), std::placeholders::_1));
}
void I2PTunnelConnection::HandleConnect (const boost::system::error_code& ecode)
{
if (ecode)
{
LogPrint ("I2PTunnel connect error: ", ecode.message ());
Terminate ();
}
else
{
LogPrint ("I2PTunnel connected");
if (m_IsQuiet)
StreamReceive ();
else
{
// send destination first like received from I2P
std::string dest = m_Stream->GetRemoteIdentity ().ToBase64 ();
dest += "\n";
memcpy (m_StreamBuffer, dest.c_str (), dest.size ());
HandleStreamReceive (boost::system::error_code (), dest.size ());
}
Receive ();
}
}
I2PTunnelConnectionHTTP::I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
const boost::asio::ip::tcp::endpoint& target, const std::string& host):
I2PTunnelConnection (owner, stream, socket, target), m_Host (host), m_HeaderSent (false)
{
}
void I2PTunnelConnectionHTTP::Write (const uint8_t * buf, size_t len)
{
if (m_HeaderSent)
I2PTunnelConnection::Write (buf, len);
else
{
m_InHeader.clear ();
m_InHeader.write ((const char *)buf, len);
std::string line;
bool endOfHeader = false;
while (!endOfHeader)
{
std::getline(m_InHeader, line);
if (!m_InHeader.fail ())
{
if (line.find ("Host:") != std::string::npos)
m_OutHeader << "Host: " << m_Host << "\r\n";
else
m_OutHeader << line << "\n";
if (line == "\r") endOfHeader = true;
}
else
break;
}
if (endOfHeader)
{
m_OutHeader << m_InHeader.str (); // data right after header
m_HeaderSent = true;
I2PTunnelConnection::Write ((uint8_t *)m_OutHeader.str ().c_str (), m_OutHeader.str ().length ());
}
}
}
/* This handler tries to stablish a connection with the desired server and dies if it fails to do so */
class I2PClientTunnelHandler: public I2PServiceHandler, public std::enable_shared_from_this<I2PClientTunnelHandler>
{
public:
I2PClientTunnelHandler (I2PClientTunnel * parent, i2p::data::IdentHash destination,
int destinationPort, std::shared_ptr<boost::asio::ip::tcp::socket> socket):
I2PServiceHandler(parent), m_DestinationIdentHash(destination),
m_DestinationPort (destinationPort), m_Socket(socket) {};
void Handle();
void Terminate();
private:
void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
i2p::data::IdentHash m_DestinationIdentHash;
int m_DestinationPort;
std::shared_ptr<boost::asio::ip::tcp::socket> m_Socket;
};
void I2PClientTunnelHandler::Handle()
{
GetOwner()->GetLocalDestination ()->CreateStream (
std::bind (&I2PClientTunnelHandler::HandleStreamRequestComplete, shared_from_this(), std::placeholders::_1),
m_DestinationIdentHash, m_DestinationPort);
}
void I2PClientTunnelHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
if (Kill()) return;
LogPrint (eLogInfo,"New I2PTunnel connection");
auto connection = std::make_shared<I2PTunnelConnection>(GetOwner(), m_Socket, stream);
GetOwner()->AddHandler (connection);
connection->I2PConnect ();
Done(shared_from_this());
}
else
{
LogPrint (eLogError,"I2P Client Tunnel Issue when creating the stream, check the previous warnings for more info.");
Terminate();
}
}
void I2PClientTunnelHandler::Terminate()
{
if (Kill()) return;
if (m_Socket)
{
m_Socket->close();
m_Socket = nullptr;
}
Done(shared_from_this());
}
I2PClientTunnel::I2PClientTunnel(
const std::string& destination, const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, int destinationPort
)
: TCPIPAcceptor(address, port, localDestination), m_Destination(destination),
m_DestinationIdentHash(nullptr), m_DestinationPort(destinationPort)
{}
void I2PClientTunnel::Start ()
{
TCPIPAcceptor::Start ();
GetIdentHash();
}
void I2PClientTunnel::Stop ()
{
TCPIPAcceptor::Stop();
auto *originalIdentHash = m_DestinationIdentHash;
m_DestinationIdentHash = nullptr;
delete originalIdentHash;
}
/* HACK: maybe we should create a caching IdentHash provider in AddressBook */
const i2p::data::IdentHash * I2PClientTunnel::GetIdentHash ()
{
if (!m_DestinationIdentHash)
{
i2p::data::IdentHash identHash;
if (i2p::client::context.GetAddressBook ().GetIdentHash (m_Destination, identHash))
m_DestinationIdentHash = new i2p::data::IdentHash (identHash);
else
LogPrint (eLogWarning,"Remote destination ", m_Destination, " not found");
}
return m_DestinationIdentHash;
}
std::shared_ptr<I2PServiceHandler> I2PClientTunnel::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
const i2p::data::IdentHash *identHash = GetIdentHash();
if (identHash)
return std::make_shared<I2PClientTunnelHandler>(this, *identHash, m_DestinationPort, socket);
else
return nullptr;
}
I2PServerTunnel::I2PServerTunnel (const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, int inport):
I2PService (localDestination), m_Address (address), m_Port (port), m_IsAccessList (false)
{
m_PortDestination = localDestination->CreateStreamingDestination (inport > 0 ? inport : port);
}
void I2PServerTunnel::Start ()
{
m_Endpoint.port (m_Port);
boost::system::error_code ec;
auto addr = boost::asio::ip::address::from_string (m_Address, ec);
if (!ec)
{
m_Endpoint.address (addr);
Accept ();
}
else
{
auto resolver = std::make_shared<boost::asio::ip::tcp::resolver>(GetService ());
resolver->async_resolve (boost::asio::ip::tcp::resolver::query (m_Address, ""),
std::bind (&I2PServerTunnel::HandleResolve, this,
std::placeholders::_1, std::placeholders::_2, resolver));
}
}
void I2PServerTunnel::Stop ()
{
ClearHandlers ();
}
void I2PServerTunnel::HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
std::shared_ptr<boost::asio::ip::tcp::resolver> resolver)
{
if (!ecode)
{
auto addr = (*it).endpoint ().address ();
LogPrint (eLogInfo, "server tunnel ", (*it).host_name (), " has been resolved to ", addr);
m_Endpoint.address (addr);
Accept ();
}
else
LogPrint (eLogError, "Unable to resolve server tunnel address: ", ecode.message ());
}
void I2PServerTunnel::SetAccessList (const std::set<i2p::data::IdentHash>& accessList)
{
m_AccessList = accessList;
m_IsAccessList = true;
}
void I2PServerTunnel::Accept ()
{
if (m_PortDestination)
m_PortDestination->SetAcceptor (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1));
auto localDestination = GetLocalDestination ();
if (localDestination)
{
if (!localDestination->IsAcceptingStreams ()) // set it as default if not set yet
localDestination->AcceptStreams (std::bind (&I2PServerTunnel::HandleAccept, this, std::placeholders::_1));
}
else
LogPrint ("Local destination not set for server tunnel");
}
void I2PServerTunnel::HandleAccept (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
if (m_IsAccessList)
{
if (!m_AccessList.count (stream->GetRemoteIdentity ().GetIdentHash ()))
{
LogPrint (eLogWarning, "Address ", stream->GetRemoteIdentity ().GetIdentHash ().ToBase32 (), " is not in white list. Incoming connection dropped");
stream->Close ();
return;
}
}
CreateI2PConnection (stream);
}
}
void I2PServerTunnel::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
{
auto conn = std::make_shared<I2PTunnelConnection> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint ());
AddHandler (conn);
conn->Connect ();
}
I2PServerTunnelHTTP::I2PServerTunnelHTTP (const std::string& address, int port, std::shared_ptr<ClientDestination> localDestination, int inport):
I2PServerTunnel (address, port, localDestination, inport)
{
}
void I2PServerTunnelHTTP::CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream)
{
auto conn = std::make_shared<I2PTunnelConnectionHTTP> (this, stream, std::make_shared<boost::asio::ip::tcp::socket> (GetService ()), GetEndpoint (), GetAddress ());
AddHandler (conn);
conn->Connect ();
}
}
}

156
client/I2PTunnel.h Normal file
View file

@ -0,0 +1,156 @@
#ifndef I2PTUNNEL_H__
#define I2PTUNNEL_H__
#include <inttypes.h>
#include <string>
#include <set>
#include <memory>
#include <sstream>
#include <boost/asio.hpp>
#include "Identity.h"
#include "Destination.h"
#include "Streaming.h"
#include "I2PService.h"
namespace i2p
{
namespace client
{
const size_t I2P_TUNNEL_CONNECTION_BUFFER_SIZE = 8192;
const int I2P_TUNNEL_CONNECTION_MAX_IDLE = 3600; // in seconds
const int I2P_TUNNEL_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds
class I2PTunnelConnection: public I2PServiceHandler, public std::enable_shared_from_this<I2PTunnelConnection>
{
public:
I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<const i2p::data::LeaseSet> leaseSet, int port = 0); // to I2P
I2PTunnelConnection (I2PService * owner, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<i2p::stream::Stream> stream); // to I2P using simplified API
I2PTunnelConnection (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
const boost::asio::ip::tcp::endpoint& target, bool quiet = true); // from I2P
~I2PTunnelConnection ();
void I2PConnect (const uint8_t * msg = nullptr, size_t len = 0);
void Connect ();
protected:
void Terminate ();
void Receive ();
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
virtual void Write (const uint8_t * buf, size_t len); // can be overloaded
void HandleWrite (const boost::system::error_code& ecode);
void StreamReceive ();
void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleConnect (const boost::system::error_code& ecode);
private:
uint8_t m_Buffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE], m_StreamBuffer[I2P_TUNNEL_CONNECTION_BUFFER_SIZE];
std::shared_ptr<boost::asio::ip::tcp::socket> m_Socket;
std::shared_ptr<i2p::stream::Stream> m_Stream;
boost::asio::ip::tcp::endpoint m_RemoteEndpoint;
bool m_IsQuiet; // don't send destination
};
class I2PTunnelConnectionHTTP: public I2PTunnelConnection
{
public:
I2PTunnelConnectionHTTP (I2PService * owner, std::shared_ptr<i2p::stream::Stream> stream,
std::shared_ptr<boost::asio::ip::tcp::socket> socket,
const boost::asio::ip::tcp::endpoint& target, const std::string& host);
protected:
void Write (const uint8_t * buf, size_t len);
private:
std::string m_Host;
std::stringstream m_InHeader, m_OutHeader;
bool m_HeaderSent;
};
class I2PClientTunnel: public TCPIPAcceptor
{
protected:
// Implements TCPIPAcceptor
std::shared_ptr<I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
const char* GetName() { return "I2P Client Tunnel"; }
public:
I2PClientTunnel(
const std::string& destination, const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, int destinationPort = 0
);
~I2PClientTunnel () {}
void Start ();
void Stop ();
private:
const i2p::data::IdentHash * GetIdentHash ();
std::string m_Destination;
const i2p::data::IdentHash * m_DestinationIdentHash;
int m_DestinationPort;
};
class I2PServerTunnel: public I2PService
{
public:
I2PServerTunnel (const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, int inport = 0);
void Start ();
void Stop ();
void SetAccessList (const std::set<i2p::data::IdentHash>& accessList);
const std::string& GetAddress() const { return m_Address; }
int GetPort () const { return m_Port; };
const boost::asio::ip::tcp::endpoint& GetEndpoint () const { return m_Endpoint; }
private:
void HandleResolve (const boost::system::error_code& ecode, boost::asio::ip::tcp::resolver::iterator it,
std::shared_ptr<boost::asio::ip::tcp::resolver> resolver);
void Accept ();
void HandleAccept (std::shared_ptr<i2p::stream::Stream> stream);
virtual void CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream);
private:
std::string m_Address;
int m_Port;
boost::asio::ip::tcp::endpoint m_Endpoint;
std::shared_ptr<i2p::stream::StreamingDestination> m_PortDestination;
std::set<i2p::data::IdentHash> m_AccessList;
bool m_IsAccessList;
};
class I2PServerTunnelHTTP: public I2PServerTunnel
{
public:
I2PServerTunnelHTTP (const std::string& address, int port,
std::shared_ptr<ClientDestination> localDestination, int inport = 0);
private:
void CreateI2PConnection (std::shared_ptr<i2p::stream::Stream> stream);
};
}
}
#endif

874
client/SAM.cpp Normal file
View file

@ -0,0 +1,874 @@
#include <string.h>
#include <stdio.h>
#ifdef _MSC_VER
#include <stdlib.h>
#endif
#include <boost/lexical_cast.hpp>
#include "util/base64.h"
#include "Identity.h"
#include "util/Log.h"
#include "Destination.h"
#include "ClientContext.h"
#include "SAM.h"
namespace i2p
{
namespace client
{
SAMSocket::SAMSocket (SAMBridge& owner):
m_Owner (owner), m_Socket (m_Owner.GetService ()), m_Timer (m_Owner.GetService ()),
m_BufferOffset (0), m_SocketType (eSAMSocketTypeUnknown), m_IsSilent (false),
m_Stream (nullptr), m_Session (nullptr)
{
}
SAMSocket::~SAMSocket ()
{
Terminate ();
}
void SAMSocket::CloseStream ()
{
if (m_Stream)
{
m_Stream->Close ();
m_Stream.reset ();
}
}
void SAMSocket::Terminate ()
{
CloseStream ();
switch (m_SocketType)
{
case eSAMSocketTypeSession:
m_Owner.CloseSession (m_ID);
break;
case eSAMSocketTypeStream:
{
if (m_Session)
m_Session->sockets.remove (shared_from_this ());
break;
}
case eSAMSocketTypeAcceptor:
{
if (m_Session)
{
m_Session->sockets.remove (shared_from_this ());
m_Session->localDestination->StopAcceptingStreams ();
}
break;
}
default:
;
}
m_SocketType = eSAMSocketTypeTerminated;
m_Socket.close ();
}
void SAMSocket::ReceiveHandshake ()
{
m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE),
std::bind(&SAMSocket::HandleHandshakeReceived, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
void SAMSocket::HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint ("SAM handshake read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
m_Buffer[bytes_transferred] = 0;
char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred);
if (eol)
*eol = 0;
LogPrint ("SAM handshake ", m_Buffer);
char * separator = strchr (m_Buffer, ' ');
if (separator)
{
separator = strchr (separator + 1, ' ');
if (separator)
*separator = 0;
}
if (!strcmp (m_Buffer, SAM_HANDSHAKE))
{
std::string version("3.0");
// try to find MIN and MAX, 3.0 if not found
if (separator)
{
separator++;
std::map<std::string, std::string> params;
ExtractParams (separator, params);
auto it = params.find (SAM_PARAM_MAX);
// TODO: check MIN as well
if (it != params.end ())
version = it->second;
}
if (version[0] == '3') // we support v3 (3.0 and 3.1) only
{
#ifdef _MSC_VER
size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ());
#else
size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_HANDSHAKE_REPLY, version.c_str ());
#endif
boost::asio::async_write (m_Socket, boost::asio::buffer (m_Buffer, l), boost::asio::transfer_all (),
std::bind(&SAMSocket::HandleHandshakeReplySent, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
else
SendMessageReply (SAM_HANDSHAKE_I2P_ERROR, strlen (SAM_HANDSHAKE_I2P_ERROR), true);
}
else
{
LogPrint ("SAM handshake mismatch");
Terminate ();
}
}
}
void SAMSocket::HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint ("SAM handshake reply send error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
m_Socket.async_read_some (boost::asio::buffer(m_Buffer, SAM_SOCKET_BUFFER_SIZE),
std::bind(&SAMSocket::HandleMessage, shared_from_this (),
std::placeholders::_1, std::placeholders::_2));
}
}
void SAMSocket::SendMessageReply (const char * msg, size_t len, bool close)
{
if (!m_IsSilent)
boost::asio::async_write (m_Socket, boost::asio::buffer (msg, len), boost::asio::transfer_all (),
std::bind(&SAMSocket::HandleMessageReplySent, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, close));
else
{
if (close)
Terminate ();
else
Receive ();
}
}
void SAMSocket::HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close)
{
if (ecode)
{
LogPrint ("SAM reply send error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
if (close)
Terminate ();
else
Receive ();
}
}
void SAMSocket::HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint ("SAM read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
bytes_transferred += m_BufferOffset;
m_BufferOffset = 0;
m_Buffer[bytes_transferred] = 0;
char * eol = (char *)memchr (m_Buffer, '\n', bytes_transferred);
if (eol)
{
*eol = 0;
char * separator = strchr (m_Buffer, ' ');
if (separator)
{
separator = strchr (separator + 1, ' ');
if (separator)
*separator = 0;
else
separator = eol;
if (!strcmp (m_Buffer, SAM_SESSION_CREATE))
ProcessSessionCreate (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_STREAM_CONNECT))
ProcessStreamConnect (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_STREAM_ACCEPT))
ProcessStreamAccept (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_DEST_GENERATE))
ProcessDestGenerate ();
else if (!strcmp (m_Buffer, SAM_NAMING_LOOKUP))
ProcessNamingLookup (separator + 1, bytes_transferred - (separator - m_Buffer) - 1);
else if (!strcmp (m_Buffer, SAM_DATAGRAM_SEND))
{
size_t len = bytes_transferred - (separator - m_Buffer) - 1;
size_t processed = ProcessDatagramSend (separator + 1, len, eol + 1);
if (processed < len)
{
m_BufferOffset = len - processed;
if (processed > 0)
memmove (m_Buffer, separator + 1 + processed, m_BufferOffset);
else
{
// restore string back
*separator = ' ';
*eol = '\n';
}
}
// since it's SAM v1 reply is not expected
Receive ();
}
else
{
LogPrint (eLogError, "SAM unexpected message ", m_Buffer);
Terminate ();
}
}
else
{
LogPrint (eLogError, "SAM malformed message ", m_Buffer);
Terminate ();
}
}
else
{
LogPrint (eLogWarning, "SAM incomplete message ", bytes_transferred);
m_BufferOffset = bytes_transferred;
// try to receive remaining message
Receive ();
}
}
}
void SAMSocket::ProcessSessionCreate (char * buf, size_t len)
{
LogPrint ("SAM session create: ", buf);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
std::string& style = params[SAM_PARAM_STYLE];
std::string& id = params[SAM_PARAM_ID];
std::string& destination = params[SAM_PARAM_DESTINATION];
m_ID = id;
if (m_Owner.FindSession (id))
{
// session exists
SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_ID, strlen(SAM_SESSION_CREATE_DUPLICATED_ID), true);
return;
}
// create destination
m_Session = m_Owner.CreateSession (id, destination == SAM_VALUE_TRANSIENT ? "" : destination, &params);
if (m_Session)
{
m_SocketType = eSAMSocketTypeSession;
if (style == SAM_VALUE_DATAGRAM)
{
auto dest = m_Session->localDestination->CreateDatagramDestination ();
dest->SetReceiver (std::bind (&SAMSocket::HandleI2PDatagramReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
}
if (m_Session->localDestination->IsReady ())
SendSessionCreateReplyOk ();
else
{
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL));
m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
shared_from_this (), std::placeholders::_1));
}
}
else
SendMessageReply (SAM_SESSION_CREATE_DUPLICATED_DEST, strlen(SAM_SESSION_CREATE_DUPLICATED_DEST), true);
}
void SAMSocket::HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode)
{
if (ecode != boost::asio::error::operation_aborted)
{
if (m_Session->localDestination->IsReady ())
SendSessionCreateReplyOk ();
else
{
m_Timer.expires_from_now (boost::posix_time::seconds(SAM_SESSION_READINESS_CHECK_INTERVAL));
m_Timer.async_wait (std::bind (&SAMSocket::HandleSessionReadinessCheckTimer,
shared_from_this (), std::placeholders::_1));
}
}
}
void SAMSocket::SendSessionCreateReplyOk ()
{
uint8_t buf[1024];
char priv[1024];
size_t l = m_Session->localDestination->GetPrivateKeys ().ToBuffer (buf, 1024);
size_t l1 = i2p::util::ByteStreamToBase64 (buf, l, priv, 1024);
priv[l1] = 0;
#ifdef _MSC_VER
size_t l2 = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
#else
size_t l2 = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_SESSION_CREATE_REPLY_OK, priv);
#endif
SendMessageReply (m_Buffer, l2, false);
}
void SAMSocket::ProcessStreamConnect (char * buf, size_t len)
{
LogPrint (eLogDebug, "SAM stream connect: ", buf);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
std::string& id = params[SAM_PARAM_ID];
std::string& destination = params[SAM_PARAM_DESTINATION];
std::string& silent = params[SAM_PARAM_SILENT];
if (silent == SAM_VALUE_TRUE) m_IsSilent = true;
m_ID = id;
m_Session = m_Owner.FindSession (id);
if (m_Session)
{
i2p::data::IdentityEx dest;
size_t len = dest.FromBase64(destination);
if (len > 0)
{
context.GetAddressBook().InsertAddress(dest);
auto leaseSet = m_Session->localDestination->FindLeaseSet(dest.GetIdentHash());
if (leaseSet)
Connect(leaseSet);
else
{
m_Session->localDestination->RequestDestination(dest.GetIdentHash(),
std::bind(&SAMSocket::HandleConnectLeaseSetRequestComplete,
shared_from_this(), std::placeholders::_1));
}
}
else
SendMessageReply(SAM_SESSION_STATUS_INVALID_KEY, strlen(SAM_SESSION_STATUS_INVALID_KEY), true);
}
else
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
}
void SAMSocket::Connect (std::shared_ptr<const i2p::data::LeaseSet> remote)
{
m_SocketType = eSAMSocketTypeStream;
m_Session->sockets.push_back (shared_from_this ());
m_Stream = m_Session->localDestination->CreateStream (remote);
m_Stream->Send ((uint8_t *)m_Buffer, 0); // connect
I2PReceive ();
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
}
void SAMSocket::HandleConnectLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet)
{
if (leaseSet)
Connect (leaseSet);
else
{
LogPrint ("SAM destination to connect not found");
SendMessageReply (SAM_STREAM_STATUS_CANT_REACH_PEER, strlen(SAM_STREAM_STATUS_CANT_REACH_PEER), true);
}
}
void SAMSocket::ProcessStreamAccept (char * buf, size_t len)
{
LogPrint (eLogDebug, "SAM stream accept: ", buf);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
std::string& id = params[SAM_PARAM_ID];
std::string& silent = params[SAM_PARAM_SILENT];
if (silent == SAM_VALUE_TRUE) m_IsSilent = true;
m_ID = id;
m_Session = m_Owner.FindSession (id);
if (m_Session)
{
if (!m_Session->localDestination->IsAcceptingStreams ())
{
m_SocketType = eSAMSocketTypeAcceptor;
m_Session->sockets.push_back (shared_from_this ());
m_Session->localDestination->AcceptStreams (std::bind (&SAMSocket::HandleI2PAccept, shared_from_this (), std::placeholders::_1));
SendMessageReply (SAM_STREAM_STATUS_OK, strlen(SAM_STREAM_STATUS_OK), false);
}
else
SendMessageReply (SAM_STREAM_STATUS_I2P_ERROR, strlen(SAM_STREAM_STATUS_I2P_ERROR), true);
}
else
SendMessageReply (SAM_STREAM_STATUS_INVALID_ID, strlen(SAM_STREAM_STATUS_INVALID_ID), true);
}
size_t SAMSocket::ProcessDatagramSend (char * buf, size_t len, const char * data)
{
LogPrint (eLogDebug, "SAM datagram send: ", buf, " ", len);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
size_t size = boost::lexical_cast<int>(params[SAM_PARAM_SIZE]), offset = data - buf;
if (offset + size <= len)
{
if (m_Session)
{
auto d = m_Session->localDestination->GetDatagramDestination ();
if (d)
{
i2p::data::IdentityEx dest;
dest.FromBase64 (params[SAM_PARAM_DESTINATION]);
d->SendDatagramTo ((const uint8_t *)data, size, dest.GetIdentHash ());
}
else
LogPrint (eLogError, "SAM missing datagram destination");
}
else
LogPrint (eLogError, "SAM session is not created from DATAGRAM SEND");
}
else
{
LogPrint (eLogWarning, "SAM sent datagram size ", size, " exceeds buffer ", len - offset);
return 0; // try to receive more
}
return offset + size;
}
void SAMSocket::ProcessDestGenerate ()
{
LogPrint (eLogDebug, "SAM dest generate");
auto keys = i2p::data::PrivateKeys::CreateRandomKeys ();
#ifdef _MSC_VER
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY,
keys.GetPublic ().ToBase64 ().c_str (), keys.ToBase64 ().c_str ());
#else
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_DEST_REPLY,
keys.GetPublic ().ToBase64 ().c_str (), keys.ToBase64 ().c_str ());
#endif
SendMessageReply (m_Buffer, len, false);
}
void SAMSocket::ProcessNamingLookup (char * buf, size_t len)
{
LogPrint (eLogDebug, "SAM naming lookup: ", buf);
std::map<std::string, std::string> params;
ExtractParams (buf, params);
std::string& name = params[SAM_PARAM_NAME];
i2p::data::IdentityEx identity;
i2p::data::IdentHash ident;
if (name == "ME")
SendNamingLookupReply (m_Session->localDestination->GetIdentity ());
else if (context.GetAddressBook ().GetAddress (name, identity))
SendNamingLookupReply (identity);
else if (m_Session && m_Session->localDestination &&
context.GetAddressBook ().GetIdentHash (name, ident))
{
auto leaseSet = m_Session->localDestination->FindLeaseSet (ident);
if (leaseSet)
SendNamingLookupReply (leaseSet->GetIdentity ());
else
m_Session->localDestination->RequestDestination (ident,
std::bind (&SAMSocket::HandleNamingLookupLeaseSetRequestComplete,
shared_from_this (), std::placeholders::_1, ident));
}
else
{
LogPrint ("SAM naming failed. Unknown address ", name);
#ifdef _MSC_VER
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str());
#else
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY, name.c_str());
#endif
SendMessageReply (m_Buffer, len, false);
}
}
void SAMSocket::HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, i2p::data::IdentHash ident)
{
if (leaseSet)
{
context.GetAddressBook ().InsertAddress (leaseSet->GetIdentity ());
SendNamingLookupReply (leaseSet->GetIdentity ());
}
else
{
LogPrint (eLogInfo, "SAM naming lookup failed. LeaseSet for ", ident.ToBase32 (), " not found");
#ifdef _MSC_VER
size_t len = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY,
context.GetAddressBook ().ToAddress (ident).c_str());
#else
size_t len = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY_INVALID_KEY,
context.GetAddressBook ().ToAddress (ident).c_str());
#endif
SendMessageReply (m_Buffer, len, false);
}
}
void SAMSocket::SendNamingLookupReply (const i2p::data::IdentityEx& identity)
{
auto base64 = identity.ToBase64 ();
#ifdef _MSC_VER
size_t l = sprintf_s (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ());
#else
size_t l = snprintf (m_Buffer, SAM_SOCKET_BUFFER_SIZE, SAM_NAMING_REPLY, base64.c_str ());
#endif
SendMessageReply (m_Buffer, l, false);
}
void SAMSocket::ExtractParams (char * buf, std::map<std::string, std::string>& params)
{
char * separator;
do
{
separator = strchr (buf, ' ');
if (separator) *separator = 0;
char * value = strchr (buf, '=');
if (value)
{
*value = 0;
value++;
params[buf] = value;
}
buf = separator + 1;
}
while (separator);
}
void SAMSocket::Receive ()
{
if (m_BufferOffset >= SAM_SOCKET_BUFFER_SIZE)
{
LogPrint (eLogError, "Buffer is full. Terminate");
Terminate ();
return;
}
m_Socket.async_read_some (boost::asio::buffer(m_Buffer + m_BufferOffset, SAM_SOCKET_BUFFER_SIZE - m_BufferOffset),
std::bind((m_SocketType == eSAMSocketTypeStream) ? &SAMSocket::HandleReceived : &SAMSocket::HandleMessage,
shared_from_this (), std::placeholders::_1, std::placeholders::_2));
}
void SAMSocket::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint ("SAM read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
if (m_Stream)
{
auto s = shared_from_this ();
m_Stream->AsyncSend ((uint8_t *)m_Buffer, bytes_transferred,
[s](const boost::system::error_code& ecode)
{
if (!ecode)
s->Receive ();
else
s->Terminate ();
});
}
}
}
void SAMSocket::I2PReceive ()
{
if (m_Stream)
m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE),
std::bind (&SAMSocket::HandleI2PReceive, shared_from_this (),
std::placeholders::_1, std::placeholders::_2),
SAM_SOCKET_CONNECTION_MAX_IDLE);
}
void SAMSocket::HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (ecode)
{
LogPrint ("SAM stream read error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
{
boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred),
std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1));
}
}
void SAMSocket::HandleWriteI2PData (const boost::system::error_code& ecode)
{
if (ecode)
{
LogPrint ("SAM socket write error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Terminate ();
}
else
I2PReceive ();
}
void SAMSocket::HandleI2PAccept (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
LogPrint ("SAM incoming I2P connection for session ", m_ID);
m_Stream = stream;
context.GetAddressBook ().InsertAddress (stream->GetRemoteIdentity ());
auto session = m_Owner.FindSession (m_ID);
if (session)
session->localDestination->StopAcceptingStreams ();
m_SocketType = eSAMSocketTypeStream;
if (!m_IsSilent)
{
// send remote peer address
uint8_t ident[1024];
size_t l = stream->GetRemoteIdentity ().ToBuffer (ident, 1024);
size_t l1 = i2p::util::ByteStreamToBase64 (ident, l, (char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE);
m_StreamBuffer[l1] = '\n';
HandleI2PReceive (boost::system::error_code (), l1 +1); // we send identity like it has been received from stream
}
else
I2PReceive ();
}
else
LogPrint (eLogInfo, "SAM I2P acceptor has been reset");
}
void SAMSocket::HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len)
{
LogPrint (eLogDebug, "SAM datagram received ", len);
auto base64 = from.ToBase64 ();
#ifdef _MSC_VER
size_t l = sprintf_s ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), len);
#else
size_t l = snprintf ((char *)m_StreamBuffer, SAM_SOCKET_BUFFER_SIZE, SAM_DATAGRAM_RECEIVED, base64.c_str (), len);
#endif
if (len < SAM_SOCKET_BUFFER_SIZE - l)
{
memcpy (m_StreamBuffer + l, buf, len);
boost::asio::async_write (m_Socket, boost::asio::buffer (m_StreamBuffer, len + l),
std::bind (&SAMSocket::HandleWriteI2PData, shared_from_this (), std::placeholders::_1));
}
else
LogPrint (eLogWarning, "SAM received datagram size ", len," exceeds buffer");
}
SAMSession::SAMSession (std::shared_ptr<ClientDestination> dest):
localDestination (dest)
{
}
SAMSession::~SAMSession ()
{
for (auto it: sockets)
it->SetSocketType (eSAMSocketTypeTerminated);
i2p::client::context.DeleteLocalDestination (localDestination);
}
void SAMSession::CloseStreams ()
{
for (auto it: sockets)
{
it->CloseStream ();
it->SetSocketType (eSAMSocketTypeTerminated);
}
sockets.clear ();
}
SAMBridge::SAMBridge(const std::string& address, int port)
: m_IsRunning (false), m_Thread (nullptr),
m_Acceptor(m_Service, boost::asio::ip::tcp::endpoint(
boost::asio::ip::address::from_string(address), port)
),
m_DatagramEndpoint(boost::asio::ip::address::from_string(address), port - 1),
m_DatagramSocket(m_Service, m_DatagramEndpoint)
{
}
SAMBridge::~SAMBridge ()
{
if (m_IsRunning)
Stop ();
}
void SAMBridge::Start ()
{
Accept ();
ReceiveDatagram ();
m_IsRunning = true;
m_Thread = new std::thread (std::bind (&SAMBridge::Run, this));
}
void SAMBridge::Stop ()
{
m_IsRunning = false;
m_Acceptor.cancel ();
for (auto it: m_Sessions)
delete it.second;
m_Sessions.clear ();
m_Service.stop ();
if (m_Thread)
{
m_Thread->join ();
delete m_Thread;
m_Thread = nullptr;
}
}
void SAMBridge::Run ()
{
while (m_IsRunning)
{
try
{
m_Service.run ();
}
catch (std::exception& ex)
{
LogPrint ("SAM: ", ex.what ());
}
}
}
void SAMBridge::Accept ()
{
auto newSocket = std::make_shared<SAMSocket> (*this);
m_Acceptor.async_accept (newSocket->GetSocket (), std::bind (&SAMBridge::HandleAccept, this,
std::placeholders::_1, newSocket));
}
void SAMBridge::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<SAMSocket> socket)
{
if (!ecode)
{
boost::system::error_code ec;
auto ep = socket->GetSocket ().remote_endpoint (ec);
if (!ec)
{
LogPrint ("New SAM connection from ", ep);
socket->ReceiveHandshake ();
}
else
LogPrint (eLogError, "SAM connection from error ", ec.message ());
}
else
LogPrint ("SAM accept error: ", ecode.message ());
if (ecode != boost::asio::error::operation_aborted)
Accept ();
}
SAMSession * SAMBridge::CreateSession (const std::string& id, const std::string& destination,
const std::map<std::string, std::string> * params)
{
std::shared_ptr<ClientDestination> localDestination = nullptr;
if (destination != "")
{
i2p::data::PrivateKeys keys;
keys.FromBase64 (destination);
localDestination = i2p::client::context.CreateNewLocalDestination (keys, true, params);
}
else // transient
{
// extract signature type
i2p::data::SigningKeyType signatureType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1;
if (params)
{
auto it = params->find (SAM_PARAM_SIGNATURE_TYPE);
if (it != params->end ())
// TODO: extract string values
signatureType = boost::lexical_cast<int> (it->second);
}
localDestination = i2p::client::context.CreateNewLocalDestination (true, signatureType, params);
}
if (localDestination)
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
auto ret = m_Sessions.insert (std::pair<std::string, SAMSession *>(id, new SAMSession (localDestination)));
if (!ret.second)
LogPrint ("Session ", id, " already exists");
return ret.first->second;
}
return nullptr;
}
void SAMBridge::CloseSession (const std::string& id)
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
auto it = m_Sessions.find (id);
if (it != m_Sessions.end ())
{
auto session = it->second;
session->localDestination->StopAcceptingStreams ();
session->CloseStreams ();
m_Sessions.erase (it);
delete session;
}
}
SAMSession * SAMBridge::FindSession (const std::string& id) const
{
std::unique_lock<std::mutex> l(m_SessionsMutex);
auto it = m_Sessions.find (id);
if (it != m_Sessions.end ())
return it->second;
return nullptr;
}
void SAMBridge::ReceiveDatagram ()
{
m_DatagramSocket.async_receive_from (
boost::asio::buffer (m_DatagramReceiveBuffer, i2p::datagram::MAX_DATAGRAM_SIZE),
m_SenderEndpoint,
std::bind (&SAMBridge::HandleReceivedDatagram, this, std::placeholders::_1, std::placeholders::_2));
}
void SAMBridge::HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred)
{
if (!ecode)
{
m_DatagramReceiveBuffer[bytes_transferred] = 0;
char * eol = strchr ((char *)m_DatagramReceiveBuffer, '\n');
*eol = 0; eol++;
size_t payloadLen = bytes_transferred - ((uint8_t *)eol - m_DatagramReceiveBuffer);
LogPrint ("SAM datagram received ", m_DatagramReceiveBuffer," size=", payloadLen);
char * sessionID = strchr ((char *)m_DatagramReceiveBuffer, ' ');
if (sessionID)
{
sessionID++;
char * destination = strchr (sessionID, ' ');
if (destination)
{
*destination = 0; destination++;
auto session = FindSession (sessionID);
if (session)
{
i2p::data::IdentityEx dest;
dest.FromBase64 (destination);
session->localDestination->GetDatagramDestination ()->
SendDatagramTo ((uint8_t *)eol, payloadLen, dest.GetIdentHash ());
}
else
LogPrint ("Session ", sessionID, " not found");
}
else
LogPrint ("Missing destination key");
}
else
LogPrint ("Missing sessionID");
ReceiveDatagram ();
}
else
LogPrint ("SAM datagram receive error: ", ecode.message ());
}
}
}

192
client/SAM.h Normal file
View file

@ -0,0 +1,192 @@
#ifndef SAM_H__
#define SAM_H__
#include <inttypes.h>
#include <string>
#include <map>
#include <list>
#include <thread>
#include <mutex>
#include <memory>
#include <boost/asio.hpp>
#include "Identity.h"
#include "LeaseSet.h"
#include "Streaming.h"
#include "Destination.h"
namespace i2p
{
namespace client
{
const size_t SAM_SOCKET_BUFFER_SIZE = 8192;
const int SAM_SOCKET_CONNECTION_MAX_IDLE = 3600; // in seconds
const int SAM_SESSION_READINESS_CHECK_INTERVAL = 20; // in seconds
const char SAM_HANDSHAKE[] = "HELLO VERSION";
const char SAM_HANDSHAKE_REPLY[] = "HELLO REPLY RESULT=OK VERSION=%s\n";
const char SAM_HANDSHAKE_I2P_ERROR[] = "HELLO REPLY RESULT=I2P_ERROR\n";
const char SAM_SESSION_CREATE[] = "SESSION CREATE";
const char SAM_SESSION_CREATE_REPLY_OK[] = "SESSION STATUS RESULT=OK DESTINATION=%s\n";
const char SAM_SESSION_CREATE_DUPLICATED_ID[] = "SESSION STATUS RESULT=DUPLICATED_ID\n";
const char SAM_SESSION_CREATE_DUPLICATED_DEST[] = "SESSION STATUS RESULT=DUPLICATED_DEST\n";
const char SAM_SESSION_STATUS_INVALID_KEY[] = "SESSION STATUS RESULT=INVALID_KEY\n";
const char SAM_STREAM_CONNECT[] = "STREAM CONNECT";
const char SAM_STREAM_STATUS_OK[] = "STREAM STATUS RESULT=OK\n";
const char SAM_STREAM_STATUS_INVALID_ID[] = "STREAM STATUS RESULT=INVALID_ID\n";
const char SAM_STREAM_STATUS_CANT_REACH_PEER[] = "STREAM STATUS RESULT=CANT_REACH_PEER\n";
const char SAM_STREAM_STATUS_I2P_ERROR[] = "STREAM STATUS RESULT=I2P_ERROR\n";
const char SAM_STREAM_ACCEPT[] = "STREAM ACCEPT";
const char SAM_DATAGRAM_SEND[] = "DATAGRAM SEND";
const char SAM_DEST_GENERATE[] = "DEST GENERATE";
const char SAM_DEST_REPLY[] = "DEST REPLY PUB=%s PRIV=%s\n";
const char SAM_DEST_REPLY_I2P_ERROR[] = "DEST REPLY RESULT=I2P_ERROR\n";
const char SAM_NAMING_LOOKUP[] = "NAMING LOOKUP";
const char SAM_NAMING_REPLY[] = "NAMING REPLY RESULT=OK NAME=ME VALUE=%s\n";
const char SAM_DATAGRAM_RECEIVED[] = "DATAGRAM RECEIVED DESTINATION=%s SIZE=%lu\n";
const char SAM_NAMING_REPLY_INVALID_KEY[] = "NAMING REPLY RESULT=INVALID_KEY NAME=%s\n";
const char SAM_NAMING_REPLY_KEY_NOT_FOUND[] = "NAMING REPLY RESULT=INVALID_KEY_NOT_FOUND NAME=%s\n";
const char SAM_PARAM_MIN[] = "MIN";
const char SAM_PARAM_MAX[] = "MAX";
const char SAM_PARAM_STYLE[] = "STYLE";
const char SAM_PARAM_ID[] = "ID";
const char SAM_PARAM_SILENT[] = "SILENT";
const char SAM_PARAM_DESTINATION[] = "DESTINATION";
const char SAM_PARAM_NAME[] = "NAME";
const char SAM_PARAM_SIGNATURE_TYPE[] = "SIGNATURE_TYPE";
const char SAM_PARAM_SIZE[] = "SIZE";
const char SAM_VALUE_TRANSIENT[] = "TRANSIENT";
const char SAM_VALUE_STREAM[] = "STREAM";
const char SAM_VALUE_DATAGRAM[] = "DATAGRAM";
const char SAM_VALUE_RAW[] = "RAW";
const char SAM_VALUE_TRUE[] = "true";
const char SAM_VALUE_FALSE[] = "false";
enum SAMSocketType
{
eSAMSocketTypeUnknown,
eSAMSocketTypeSession,
eSAMSocketTypeStream,
eSAMSocketTypeAcceptor,
eSAMSocketTypeTerminated
};
class SAMBridge;
struct SAMSession;
class SAMSocket: public std::enable_shared_from_this<SAMSocket>
{
public:
SAMSocket (SAMBridge& owner);
~SAMSocket ();
void CloseStream (); // TODO: implement it better
boost::asio::ip::tcp::socket& GetSocket () { return m_Socket; };
void ReceiveHandshake ();
void SetSocketType (SAMSocketType socketType) { m_SocketType = socketType; };
SAMSocketType GetSocketType () const { return m_SocketType; };
private:
void Terminate ();
void HandleHandshakeReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleHandshakeReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleMessage (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void SendMessageReply (const char * msg, size_t len, bool close);
void HandleMessageReplySent (const boost::system::error_code& ecode, std::size_t bytes_transferred, bool close);
void Receive ();
void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void I2PReceive ();
void HandleI2PReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred);
void HandleI2PAccept (std::shared_ptr<i2p::stream::Stream> stream);
void HandleWriteI2PData (const boost::system::error_code& ecode);
void HandleI2PDatagramReceive (const i2p::data::IdentityEx& from, uint16_t fromPort, uint16_t toPort, const uint8_t * buf, size_t len);
void ProcessSessionCreate (char * buf, size_t len);
void ProcessStreamConnect (char * buf, size_t len);
void ProcessStreamAccept (char * buf, size_t len);
void ProcessDestGenerate ();
void ProcessNamingLookup (char * buf, size_t len);
size_t ProcessDatagramSend (char * buf, size_t len, const char * data); // from SAM 1.0
void ExtractParams (char * buf, std::map<std::string, std::string>& params);
void Connect (std::shared_ptr<const i2p::data::LeaseSet> remote);
void HandleConnectLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet);
void SendNamingLookupReply (const i2p::data::IdentityEx& identity);
void HandleNamingLookupLeaseSetRequestComplete (std::shared_ptr<i2p::data::LeaseSet> leaseSet, i2p::data::IdentHash ident);
void HandleSessionReadinessCheckTimer (const boost::system::error_code& ecode);
void SendSessionCreateReplyOk ();
private:
SAMBridge& m_Owner;
boost::asio::ip::tcp::socket m_Socket;
boost::asio::deadline_timer m_Timer;
char m_Buffer[SAM_SOCKET_BUFFER_SIZE + 1];
size_t m_BufferOffset;
uint8_t m_StreamBuffer[SAM_SOCKET_BUFFER_SIZE];
SAMSocketType m_SocketType;
std::string m_ID; // nickname
bool m_IsSilent;
std::shared_ptr<i2p::stream::Stream> m_Stream;
SAMSession * m_Session;
};
struct SAMSession
{
std::shared_ptr<ClientDestination> localDestination;
std::list<std::shared_ptr<SAMSocket> > sockets;
SAMSession (std::shared_ptr<ClientDestination> dest);
~SAMSession ();
void CloseStreams ();
};
class SAMBridge
{
public:
SAMBridge(const std::string& address, int port);
~SAMBridge ();
void Start ();
void Stop ();
boost::asio::io_service& GetService () { return m_Service; };
SAMSession * CreateSession (const std::string& id, const std::string& destination, // empty string means transient
const std::map<std::string, std::string> * params);
void CloseSession (const std::string& id);
SAMSession * FindSession (const std::string& id) const;
private:
void Run ();
void Accept ();
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<SAMSocket> socket);
void ReceiveDatagram ();
void HandleReceivedDatagram (const boost::system::error_code& ecode, std::size_t bytes_transferred);
private:
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::ip::tcp::acceptor m_Acceptor;
boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint;
boost::asio::ip::udp::socket m_DatagramSocket;
mutable std::mutex m_SessionsMutex;
std::map<std::string, SAMSession *> m_Sessions;
uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1];
public:
// for HTTP
const decltype(m_Sessions)& GetSessions () const { return m_Sessions; };
};
}
}
#endif

577
client/SOCKS.cpp Normal file
View file

@ -0,0 +1,577 @@
#include <cstring>
#include <cassert>
#include <string>
#include <atomic>
#include "SOCKS.h"
#include "Identity.h"
#include "Streaming.h"
#include "Destination.h"
#include "ClientContext.h"
#include "util/I2PEndian.h"
#include "I2PTunnel.h"
namespace i2p
{
namespace proxy
{
static const size_t socks_buffer_size = 8192;
static const size_t max_socks_hostname_size = 255; // Limit for socks5 and bad idea to traverse
struct SOCKSDnsAddress
{
uint8_t size;
char value[max_socks_hostname_size];
void FromString (std::string str)
{
size = str.length();
if (str.length() > max_socks_hostname_size) size = max_socks_hostname_size;
memcpy(value,str.c_str(),size);
}
std::string ToString() { return std::string(value, size); }
void push_back (char c) { value[size++] = c; }
};
class SOCKSServer;
class SOCKSHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this<SOCKSHandler>
{
private:
enum state
{
GET_SOCKSV,
GET_COMMAND,
GET_PORT,
GET_IPV4,
GET4_IDENT,
GET4A_HOST,
GET5_AUTHNUM,
GET5_AUTH,
GET5_REQUESTV,
GET5_GETRSV,
GET5_GETADDRTYPE,
GET5_IPV6,
GET5_HOST_SIZE,
GET5_HOST,
DONE
};
enum authMethods
{
AUTH_NONE = 0, //No authentication, skip to next step
AUTH_GSSAPI = 1, //GSSAPI authentication
AUTH_USERPASSWD = 2, //Username and password
AUTH_UNACCEPTABLE = 0xff //No acceptable method found
};
enum addrTypes
{
ADDR_IPV4 = 1, //IPv4 address (4 octets)
ADDR_DNS = 3, // DNS name (up to 255 octets)
ADDR_IPV6 = 4 //IPV6 address (16 octets)
};
enum errTypes
{
SOCKS5_OK = 0, // No error for SOCKS5
SOCKS5_GEN_FAIL = 1, // General server failure
SOCKS5_RULE_DENIED = 2, // Connection disallowed by ruleset
SOCKS5_NET_UNREACH = 3, // Network unreachable
SOCKS5_HOST_UNREACH = 4, // Host unreachable
SOCKS5_CONN_REFUSED = 5, // Connection refused by the peer
SOCKS5_TTL_EXPIRED = 6, // TTL Expired
SOCKS5_CMD_UNSUP = 7, // Command unsuported
SOCKS5_ADDR_UNSUP = 8, // Address type unsuported
SOCKS4_OK = 90, // No error for SOCKS4
SOCKS4_FAIL = 91, // Failed establishing connecting or not allowed
SOCKS4_IDENTD_MISSING = 92, // Couldn't connect to the identd server
SOCKS4_IDENTD_DIFFER = 93 // The ID reported by the application and by identd differ
};
enum cmdTypes
{
CMD_CONNECT = 1, // TCP Connect
CMD_BIND = 2, // TCP Bind
CMD_UDP = 3 // UDP associate
};
enum socksVersions
{
SOCKS4 = 4, // SOCKS4
SOCKS5 = 5 // SOCKS5
};
union address
{
uint32_t ip;
SOCKSDnsAddress dns;
uint8_t ipv6[16];
};
void EnterState(state nstate, uint8_t parseleft = 1);
bool HandleData(uint8_t *sock_buff, std::size_t len);
bool ValidateSOCKSRequest();
void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered);
void Terminate();
void AsyncSockRead();
boost::asio::const_buffers_1 GenerateSOCKS5SelectAuth(authMethods method);
boost::asio::const_buffers_1 GenerateSOCKS4Response(errTypes error, uint32_t ip, uint16_t port);
boost::asio::const_buffers_1 GenerateSOCKS5Response(errTypes error, addrTypes type, const address &addr, uint16_t port);
bool Socks5ChooseAuth();
void SocksRequestFailed(errTypes error);
void SocksRequestSuccess();
void SentSocksFailed(const boost::system::error_code & ecode);
void SentSocksDone(const boost::system::error_code & ecode);
void SentSocksResponse(const boost::system::error_code & ecode);
void HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream);
uint8_t m_sock_buff[socks_buffer_size];
std::shared_ptr<boost::asio::ip::tcp::socket> m_sock;
std::shared_ptr<i2p::stream::Stream> m_stream;
uint8_t *m_remaining_data; //Data left to be sent
uint8_t m_response[7+max_socks_hostname_size];
address m_address; //Address
std::size_t m_remaining_data_len; //Size of the data left to be sent
uint32_t m_4aip; //Used in 4a requests
uint16_t m_port;
uint8_t m_command;
uint8_t m_parseleft; //Octets left to parse
authMethods m_authchosen; //Authentication chosen
addrTypes m_addrtype; //Address type chosen
socksVersions m_socksv; //Socks version
cmdTypes m_cmd; // Command requested
state m_state;
public:
SOCKSHandler(SOCKSServer * parent, std::shared_ptr<boost::asio::ip::tcp::socket> sock) :
I2PServiceHandler(parent), m_sock(sock), m_stream(nullptr),
m_authchosen(AUTH_UNACCEPTABLE), m_addrtype(ADDR_IPV4)
{ m_address.ip = 0; EnterState(GET_SOCKSV); }
~SOCKSHandler() { Terminate(); }
void Handle() { AsyncSockRead(); }
};
void SOCKSHandler::AsyncSockRead()
{
LogPrint(eLogDebug,"--- SOCKS async sock read");
if(m_sock)
m_sock->async_receive(boost::asio::buffer(m_sock_buff, socks_buffer_size),
std::bind(&SOCKSHandler::HandleSockRecv, shared_from_this(),
std::placeholders::_1, std::placeholders::_2));
else
LogPrint(eLogError,"--- SOCKS no socket for read");
}
void SOCKSHandler::Terminate()
{
if (Kill()) return;
if (m_sock)
{
LogPrint(eLogDebug,"--- SOCKS close sock");
m_sock->close();
m_sock = nullptr;
}
if (m_stream)
{
LogPrint(eLogDebug,"--- SOCKS close stream");
m_stream.reset ();
}
Done(shared_from_this());
}
boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS4Response(SOCKSHandler::errTypes error, uint32_t ip, uint16_t port)
{
assert(error >= SOCKS4_OK);
m_response[0] = '\x00'; //Version
m_response[1] = error; //Response code
htobe16buf(m_response+2,port); //Port
htobe32buf(m_response+4,ip); //IP
return boost::asio::const_buffers_1(m_response,8);
}
boost::asio::const_buffers_1 SOCKSHandler::GenerateSOCKS5Response(SOCKSHandler::errTypes error, SOCKSHandler::addrTypes type, const SOCKSHandler::address &addr, uint16_t port)
{
size_t size = 6;
assert(error <= SOCKS5_ADDR_UNSUP);
m_response[0] = '\x05'; //Version
m_response[1] = error; //Response code
m_response[2] = '\x00'; //RSV
m_response[3] = type; //Address type
switch (type)
{
case ADDR_IPV4:
size = 10;
htobe32buf(m_response+4,addr.ip);
break;
case ADDR_IPV6:
size = 22;
memcpy(m_response+4,addr.ipv6, 16);
break;
case ADDR_DNS:
size = 7+addr.dns.size;
m_response[4] = addr.dns.size;
memcpy(m_response+5,addr.dns.value, addr.dns.size);
break;
}
htobe16buf(m_response+size-2,port); //Port
return boost::asio::const_buffers_1(m_response,size);
}
bool SOCKSHandler::Socks5ChooseAuth()
{
m_response[0] = '\x05'; //Version
m_response[1] = m_authchosen; //Response code
boost::asio::const_buffers_1 response(m_response,2);
if (m_authchosen == AUTH_UNACCEPTABLE)
{
LogPrint(eLogWarning,"--- SOCKS5 authentication negotiation failed");
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed,
shared_from_this(), std::placeholders::_1));
return false;
}
else
{
LogPrint(eLogDebug,"--- SOCKS5 choosing authentication method: ", m_authchosen);
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksResponse,
shared_from_this(), std::placeholders::_1));
return true;
}
}
/* All hope is lost beyond this point */
void SOCKSHandler::SocksRequestFailed(SOCKSHandler::errTypes error)
{
boost::asio::const_buffers_1 response(nullptr,0);
assert(error != SOCKS4_OK && error != SOCKS5_OK);
switch (m_socksv)
{
case SOCKS4:
LogPrint(eLogWarning,"--- SOCKS4 failed: ", error);
if (error < SOCKS4_OK) error = SOCKS4_FAIL; //Transparently map SOCKS5 errors
response = GenerateSOCKS4Response(error, m_4aip, m_port);
break;
case SOCKS5:
LogPrint(eLogWarning,"--- SOCKS5 failed: ", error);
response = GenerateSOCKS5Response(error, m_addrtype, m_address, m_port);
break;
}
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksFailed,
shared_from_this(), std::placeholders::_1));
}
void SOCKSHandler::SocksRequestSuccess()
{
boost::asio::const_buffers_1 response(nullptr,0);
//TODO: this should depend on things like the command type and callbacks may change
switch (m_socksv)
{
case SOCKS4:
LogPrint(eLogInfo,"--- SOCKS4 connection success");
response = GenerateSOCKS4Response(SOCKS4_OK, m_4aip, m_port);
break;
case SOCKS5:
LogPrint(eLogInfo,"--- SOCKS5 connection success");
auto s = i2p::client::context.GetAddressBook().ToAddress(GetOwner()->GetLocalDestination()->GetIdentHash());
address ad; ad.dns.FromString(s);
//HACK only 16 bits passed in port as SOCKS5 doesn't allow for more
response = GenerateSOCKS5Response(SOCKS5_OK, ADDR_DNS, ad, m_stream->GetRecvStreamID());
break;
}
boost::asio::async_write(*m_sock, response, std::bind(&SOCKSHandler::SentSocksDone,
shared_from_this(), std::placeholders::_1));
}
void SOCKSHandler::EnterState(SOCKSHandler::state nstate, uint8_t parseleft) {
switch (nstate)
{
case GET_PORT: parseleft = 2; break;
case GET_IPV4: m_addrtype = ADDR_IPV4; m_address.ip = 0; parseleft = 4; break;
case GET4_IDENT: m_4aip = m_address.ip; break;
case GET4A_HOST:
case GET5_HOST: m_addrtype = ADDR_DNS; m_address.dns.size = 0; break;
case GET5_IPV6: m_addrtype = ADDR_IPV6; parseleft = 16; break;
default:;
}
m_parseleft = parseleft;
m_state = nstate;
}
bool SOCKSHandler::ValidateSOCKSRequest()
{
if ( m_cmd != CMD_CONNECT )
{
//TODO: we need to support binds and other shit!
LogPrint(eLogError,"--- SOCKS unsupported command: ", m_cmd);
SocksRequestFailed(SOCKS5_CMD_UNSUP);
return false;
}
//TODO: we may want to support other address types!
if ( m_addrtype != ADDR_DNS )
{
switch (m_socksv)
{
case SOCKS5:
LogPrint(eLogError,"--- SOCKS5 unsupported address type: ", m_addrtype);
break;
case SOCKS4:
LogPrint(eLogError,"--- SOCKS4a rejected because it's actually SOCKS4");
break;
}
SocksRequestFailed(SOCKS5_ADDR_UNSUP);
return false;
}
//TODO: we may want to support other domains
if(m_addrtype == ADDR_DNS && m_address.dns.ToString().find(".i2p") == std::string::npos)
{
LogPrint(eLogError,"--- SOCKS invalid hostname: ", m_address.dns.ToString());
SocksRequestFailed(SOCKS5_ADDR_UNSUP);
return false;
}
return true;
}
bool SOCKSHandler::HandleData(uint8_t *sock_buff, std::size_t len)
{
assert(len); // This should always be called with a least a byte left to parse
while (len > 0)
{
switch (m_state)
{
case GET_SOCKSV:
m_socksv = (SOCKSHandler::socksVersions) *sock_buff;
switch (*sock_buff)
{
case SOCKS4:
EnterState(GET_COMMAND); //Initialize the parser at the right position
break;
case SOCKS5:
EnterState(GET5_AUTHNUM); //Initialize the parser at the right position
break;
default:
LogPrint(eLogError,"--- SOCKS rejected invalid version: ", ((int)*sock_buff));
Terminate();
return false;
}
break;
case GET5_AUTHNUM:
EnterState(GET5_AUTH, *sock_buff);
break;
case GET5_AUTH:
m_parseleft --;
if (*sock_buff == AUTH_NONE)
m_authchosen = AUTH_NONE;
if ( m_parseleft == 0 )
{
if (!Socks5ChooseAuth()) return false;
EnterState(GET5_REQUESTV);
}
break;
case GET_COMMAND:
switch (*sock_buff)
{
case CMD_CONNECT:
case CMD_BIND:
break;
case CMD_UDP:
if (m_socksv == SOCKS5) break;
default:
LogPrint(eLogError,"--- SOCKS invalid command: ", ((int)*sock_buff));
SocksRequestFailed(SOCKS5_GEN_FAIL);
return false;
}
m_cmd = (SOCKSHandler::cmdTypes)*sock_buff;
switch (m_socksv)
{
case SOCKS5: EnterState(GET5_GETRSV); break;
case SOCKS4: EnterState(GET_PORT); break;
}
break;
case GET_PORT:
m_port = (m_port << 8)|((uint16_t)*sock_buff);
m_parseleft--;
if (m_parseleft == 0)
{
switch (m_socksv)
{
case SOCKS5: EnterState(DONE); break;
case SOCKS4: EnterState(GET_IPV4); break;
}
}
break;
case GET_IPV4:
m_address.ip = (m_address.ip << 8)|((uint32_t)*sock_buff);
m_parseleft--;
if (m_parseleft == 0)
{
switch (m_socksv)
{
case SOCKS5: EnterState(GET_PORT); break;
case SOCKS4: EnterState(GET4_IDENT); m_4aip = m_address.ip; break;
}
}
break;
case GET4_IDENT:
if (!*sock_buff)
{
if( m_4aip == 0 || m_4aip > 255 )
EnterState(DONE);
else
EnterState(GET4A_HOST);
}
break;
case GET4A_HOST:
if (!*sock_buff)
{
EnterState(DONE);
break;
}
if (m_address.dns.size >= max_socks_hostname_size)
{
LogPrint(eLogError,"--- SOCKS4a destination is too large");
SocksRequestFailed(SOCKS4_FAIL);
return false;
}
m_address.dns.push_back(*sock_buff);
break;
case GET5_REQUESTV:
if (*sock_buff != SOCKS5)
{
LogPrint(eLogError,"--- SOCKS5 rejected unknown request version: ", ((int)*sock_buff));
SocksRequestFailed(SOCKS5_GEN_FAIL);
return false;
}
EnterState(GET_COMMAND);
break;
case GET5_GETRSV:
if ( *sock_buff != 0 )
{
LogPrint(eLogError,"--- SOCKS5 unknown reserved field: ", ((int)*sock_buff));
SocksRequestFailed(SOCKS5_GEN_FAIL);
return false;
}
EnterState(GET5_GETADDRTYPE);
break;
case GET5_GETADDRTYPE:
switch (*sock_buff)
{
case ADDR_IPV4: EnterState(GET_IPV4); break;
case ADDR_IPV6: EnterState(GET5_IPV6); break;
case ADDR_DNS : EnterState(GET5_HOST_SIZE); break;
default:
LogPrint(eLogError,"--- SOCKS5 unknown address type: ", ((int)*sock_buff));
SocksRequestFailed(SOCKS5_GEN_FAIL);
return false;
}
break;
case GET5_IPV6:
m_address.ipv6[16-m_parseleft] = *sock_buff;
m_parseleft--;
if (m_parseleft == 0) EnterState(GET_PORT);
break;
case GET5_HOST_SIZE:
EnterState(GET5_HOST, *sock_buff);
break;
case GET5_HOST:
m_address.dns.push_back(*sock_buff);
m_parseleft--;
if (m_parseleft == 0) EnterState(GET_PORT);
break;
default:
LogPrint(eLogError,"--- SOCKS parse state?? ", m_state);
Terminate();
return false;
}
sock_buff++;
len--;
if (m_state == DONE)
{
m_remaining_data_len = len;
m_remaining_data = sock_buff;
return ValidateSOCKSRequest();
}
}
return true;
}
void SOCKSHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len)
{
LogPrint(eLogDebug,"--- SOCKS sock recv: ", len);
if(ecode)
{
LogPrint(eLogWarning," --- SOCKS sock recv got error: ", ecode);
Terminate();
return;
}
if (HandleData(m_sock_buff, len))
{
if (m_state == DONE)
{
LogPrint(eLogInfo,"--- SOCKS requested ", m_address.dns.ToString(), ":" , m_port);
GetOwner()->CreateStream ( std::bind (&SOCKSHandler::HandleStreamRequestComplete,
shared_from_this(), std::placeholders::_1), m_address.dns.ToString(), m_port);
}
else
AsyncSockRead();
}
}
void SOCKSHandler::SentSocksFailed(const boost::system::error_code & ecode)
{
if (!ecode)
Terminate();
else
{
LogPrint (eLogError,"--- SOCKS Closing socket after sending failure because: ", ecode.message ());
Terminate();
}
}
void SOCKSHandler::SentSocksDone(const boost::system::error_code & ecode)
{
if (!ecode)
{
if (Kill()) return;
LogPrint (eLogInfo,"--- SOCKS New I2PTunnel connection");
auto connection = std::make_shared<i2p::client::I2PTunnelConnection>(GetOwner(), m_sock, m_stream);
GetOwner()->AddHandler (connection);
connection->I2PConnect (m_remaining_data,m_remaining_data_len);
Done(shared_from_this());
}
else
{
LogPrint (eLogError,"--- SOCKS Closing socket after completion reply because: ", ecode.message ());
Terminate();
}
}
void SOCKSHandler::SentSocksResponse(const boost::system::error_code & ecode)
{
if (ecode)
{
LogPrint (eLogError,"--- SOCKS Closing socket after sending reply because: ", ecode.message ());
Terminate();
}
}
void SOCKSHandler::HandleStreamRequestComplete (std::shared_ptr<i2p::stream::Stream> stream)
{
if (stream)
{
m_stream = stream;
SocksRequestSuccess();
}
else
{
LogPrint (eLogError,"--- SOCKS Issue when creating the stream, check the previous warnings for more info.");
SocksRequestFailed(SOCKS5_HOST_UNREACH);
}
}
SOCKSServer::SOCKSServer(const std::string& address, int port, std::shared_ptr<i2p::client::ClientDestination> localDestination)
: TCPIPAcceptor(
address, port,
localDestination ? localDestination : i2p::client::context.GetSharedLocalDestination()
)
{
}
std::shared_ptr<i2p::client::I2PServiceHandler> SOCKSServer::CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
return std::make_shared<SOCKSHandler> (this, socket);
}
}
}

33
client/SOCKS.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef SOCKS_H__
#define SOCKS_H__
#include <memory>
#include <set>
#include <boost/asio.hpp>
#include <mutex>
#include "I2PService.h"
namespace i2p
{
namespace proxy
{
class SOCKSServer: public i2p::client::TCPIPAcceptor
{
public:
SOCKSServer(const std::string& address, int port,
std::shared_ptr<i2p::client::ClientDestination> localDestination = nullptr);
~SOCKSServer() {};
protected:
// Implements TCPIPAcceptor
std::shared_ptr<i2p::client::I2PServiceHandler> CreateHandler(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
const char* GetName() { return "SOCKS"; }
};
typedef SOCKSServer SOCKSProxy;
}
}
#endif

462
client/Win32Service.cpp Normal file
View file

@ -0,0 +1,462 @@
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS // to use freopen
#endif
#include "Win32Service.h"
#include <assert.h>
#include <strsafe.h>
#include <windows.h>
#include "Daemon.h"
#include "Log.h"
I2PService *I2PService::s_service = NULL;
BOOL I2PService::isService()
{
BOOL bIsService = FALSE;
HWINSTA hWinStation = GetProcessWindowStation();
if (hWinStation != NULL)
{
USEROBJECTFLAGS uof = { 0 };
if (GetUserObjectInformation(hWinStation, UOI_FLAGS, &uof, sizeof(USEROBJECTFLAGS), NULL) && ((uof.dwFlags & WSF_VISIBLE) == 0))
{
bIsService = TRUE;
}
}
return bIsService;
}
BOOL I2PService::Run(I2PService &service)
{
s_service = &service;
SERVICE_TABLE_ENTRY serviceTable[] =
{
{ service.m_name, ServiceMain },
{ NULL, NULL }
};
return StartServiceCtrlDispatcher(serviceTable);
}
void WINAPI I2PService::ServiceMain(DWORD dwArgc, PSTR *pszArgv)
{
assert(s_service != NULL);
s_service->m_statusHandle = RegisterServiceCtrlHandler(
s_service->m_name, ServiceCtrlHandler);
if (s_service->m_statusHandle == NULL)
{
throw GetLastError();
}
s_service->Start(dwArgc, pszArgv);
}
void WINAPI I2PService::ServiceCtrlHandler(DWORD dwCtrl)
{
switch (dwCtrl)
{
case SERVICE_CONTROL_STOP: s_service->Stop(); break;
case SERVICE_CONTROL_PAUSE: s_service->Pause(); break;
case SERVICE_CONTROL_CONTINUE: s_service->Continue(); break;
case SERVICE_CONTROL_SHUTDOWN: s_service->Shutdown(); break;
case SERVICE_CONTROL_INTERROGATE: break;
default: break;
}
}
I2PService::I2PService(PSTR pszServiceName,
BOOL fCanStop,
BOOL fCanShutdown,
BOOL fCanPauseContinue)
{
m_name = (pszServiceName == NULL) ? "" : pszServiceName;
m_statusHandle = NULL;
m_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
m_status.dwCurrentState = SERVICE_START_PENDING;
DWORD dwControlsAccepted = 0;
if (fCanStop)
dwControlsAccepted |= SERVICE_ACCEPT_STOP;
if (fCanShutdown)
dwControlsAccepted |= SERVICE_ACCEPT_SHUTDOWN;
if (fCanPauseContinue)
dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE;
m_status.dwControlsAccepted = dwControlsAccepted;
m_status.dwWin32ExitCode = NO_ERROR;
m_status.dwServiceSpecificExitCode = 0;
m_status.dwCheckPoint = 0;
m_status.dwWaitHint = 0;
m_fStopping = FALSE;
// Create a manual-reset event that is not signaled at first to indicate
// the stopped signal of the service.
m_hStoppedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (m_hStoppedEvent == NULL)
{
throw GetLastError();
}
}
I2PService::~I2PService(void)
{
if (m_hStoppedEvent)
{
CloseHandle(m_hStoppedEvent);
m_hStoppedEvent = NULL;
}
}
void I2PService::Start(DWORD dwArgc, PSTR *pszArgv)
{
try
{
SetServiceStatus(SERVICE_START_PENDING);
OnStart(dwArgc, pszArgv);
SetServiceStatus(SERVICE_RUNNING);
}
catch (DWORD dwError)
{
LogPrint("Win32Service Start", dwError);
SetServiceStatus(SERVICE_STOPPED, dwError);
}
catch (...)
{
LogPrint("Win32Service failed to start.", EVENTLOG_ERROR_TYPE);
SetServiceStatus(SERVICE_STOPPED);
}
}
void I2PService::OnStart(DWORD dwArgc, PSTR *pszArgv)
{
LogPrint("Win32Service in OnStart",
EVENTLOG_INFORMATION_TYPE);
Daemon.start();
//i2p::util::config::OptionParser(dwArgc, pszArgv);
//i2p::util::filesystem::ReadConfigFile(i2p::util::config::mapArgs, i2p::util::config::mapMultiArgs);
//i2p::context.OverrideNTCPAddress(i2p::util::config::GetCharArg("-host", "127.0.0.1"),
// i2p::util::config::GetArg("-port", 17070));
_worker = new std::thread(std::bind(&I2PService::WorkerThread, this));
}
void I2PService::WorkerThread()
{
while (!m_fStopping)
{
::Sleep(1000); // Simulate some lengthy operations.
}
// Signal the stopped event.
SetEvent(m_hStoppedEvent);
}
void I2PService::Stop()
{
DWORD dwOriginalState = m_status.dwCurrentState;
try
{
SetServiceStatus(SERVICE_STOP_PENDING);
OnStop();
SetServiceStatus(SERVICE_STOPPED);
}
catch (DWORD dwError)
{
LogPrint("Win32Service Stop", dwError);
SetServiceStatus(dwOriginalState);
}
catch (...)
{
LogPrint("Win32Service failed to stop.", EVENTLOG_ERROR_TYPE);
SetServiceStatus(dwOriginalState);
}
}
void I2PService::OnStop()
{
// Log a service stop message to the Application log.
LogPrint("Win32Service in OnStop", EVENTLOG_INFORMATION_TYPE);
Daemon.stop();
m_fStopping = TRUE;
if (WaitForSingleObject(m_hStoppedEvent, INFINITE) != WAIT_OBJECT_0)
{
throw GetLastError();
}
_worker->join();
delete _worker;
}
void I2PService::Pause()
{
try
{
SetServiceStatus(SERVICE_PAUSE_PENDING);
OnPause();
SetServiceStatus(SERVICE_PAUSED);
}
catch (DWORD dwError)
{
LogPrint("Win32Service Pause", dwError);
SetServiceStatus(SERVICE_RUNNING);
}
catch (...)
{
LogPrint("Win32Service failed to pause.", EVENTLOG_ERROR_TYPE);
SetServiceStatus(SERVICE_RUNNING);
}
}
void I2PService::OnPause()
{
}
void I2PService::Continue()
{
try
{
SetServiceStatus(SERVICE_CONTINUE_PENDING);
OnContinue();
SetServiceStatus(SERVICE_RUNNING);
}
catch (DWORD dwError)
{
LogPrint("Win32Service Continue", dwError);
SetServiceStatus(SERVICE_PAUSED);
}
catch (...)
{
LogPrint("Win32Service failed to resume.", EVENTLOG_ERROR_TYPE);
SetServiceStatus(SERVICE_PAUSED);
}
}
void I2PService::OnContinue()
{
}
void I2PService::Shutdown()
{
try
{
OnShutdown();
SetServiceStatus(SERVICE_STOPPED);
}
catch (DWORD dwError)
{
LogPrint("Win32Service Shutdown", dwError);
}
catch (...)
{
LogPrint("Win32Service failed to shut down.", EVENTLOG_ERROR_TYPE);
}
}
void I2PService::OnShutdown()
{
}
void I2PService::SetServiceStatus(DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwWaitHint)
{
static DWORD dwCheckPoint = 1;
m_status.dwCurrentState = dwCurrentState;
m_status.dwWin32ExitCode = dwWin32ExitCode;
m_status.dwWaitHint = dwWaitHint;
m_status.dwCheckPoint =
((dwCurrentState == SERVICE_RUNNING) ||
(dwCurrentState == SERVICE_STOPPED)) ?
0 : dwCheckPoint++;
::SetServiceStatus(m_statusHandle, &m_status);
}
//*****************************************************************************
void FreeHandles(SC_HANDLE schSCManager, SC_HANDLE schService)
{
if (schSCManager)
{
CloseServiceHandle(schSCManager);
schSCManager = NULL;
}
if (schService)
{
CloseServiceHandle(schService);
schService = NULL;
}
}
void InstallService(PSTR pszServiceName,
PSTR pszDisplayName,
DWORD dwStartType,
PSTR pszDependencies,
PSTR pszAccount,
PSTR pszPassword)
{
printf("Try to install Win32Service (%s).\n", pszServiceName);
char szPath[MAX_PATH];
SC_HANDLE schSCManager = NULL;
SC_HANDLE schService = NULL;
if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath)) == 0)
{
printf("GetModuleFileName failed w/err 0x%08lx\n", GetLastError());
FreeHandles(schSCManager, schService);
return;
}
// Open the local default service control manager database
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT |
SC_MANAGER_CREATE_SERVICE);
if (schSCManager == NULL)
{
printf("OpenSCManager failed w/err 0x%08lx\n", GetLastError());
FreeHandles(schSCManager, schService);
return;
}
// Install the service into SCM by calling CreateService
schService = CreateService(
schSCManager, // SCManager database
pszServiceName, // Name of service
pszDisplayName, // Name to display
SERVICE_QUERY_STATUS, // Desired access
SERVICE_WIN32_OWN_PROCESS, // Service type
dwStartType, // Service start type
SERVICE_ERROR_NORMAL, // Error control type
szPath, // Service's binary
NULL, // No load ordering group
NULL, // No tag identifier
pszDependencies, // Dependencies
pszAccount, // Service running account
pszPassword // Password of the account
);
if (schService == NULL)
{
printf("CreateService failed w/err 0x%08lx\n", GetLastError());
FreeHandles(schSCManager, schService);
return;
}
printf("Win32Service is installed as %s.\n", pszServiceName);
// Centralized cleanup for all allocated resources.
FreeHandles(schSCManager, schService);
}
void UninstallService(PSTR pszServiceName)
{
printf("Try to uninstall Win32Service (%s).\n", pszServiceName);
SC_HANDLE schSCManager = NULL;
SC_HANDLE schService = NULL;
SERVICE_STATUS ssSvcStatus = {};
// Open the local default service control manager database
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (schSCManager == NULL)
{
printf("OpenSCManager failed w/err 0x%08lx\n", GetLastError());
FreeHandles(schSCManager, schService);
return;
}
// Open the service with delete, stop, and query status permissions
schService = OpenService(schSCManager, pszServiceName, SERVICE_STOP |
SERVICE_QUERY_STATUS | DELETE);
if (schService == NULL)
{
printf("OpenService failed w/err 0x%08lx\n", GetLastError());
FreeHandles(schSCManager, schService);
return;
}
// Try to stop the service
if (ControlService(schService, SERVICE_CONTROL_STOP, &ssSvcStatus))
{
printf("Stopping %s.\n", pszServiceName);
Sleep(1000);
while (QueryServiceStatus(schService, &ssSvcStatus))
{
if (ssSvcStatus.dwCurrentState == SERVICE_STOP_PENDING)
{
printf(".");
Sleep(1000);
}
else break;
}
if (ssSvcStatus.dwCurrentState == SERVICE_STOPPED)
{
printf("\n%s is stopped.\n", pszServiceName);
}
else
{
printf("\n%s failed to stop.\n", pszServiceName);
}
}
// Now remove the service by calling DeleteService.
if (!DeleteService(schService))
{
printf("DeleteService failed w/err 0x%08lx\n", GetLastError());
FreeHandles(schSCManager, schService);
return;
}
printf("%s is removed.\n", pszServiceName);
// Centralized cleanup for all allocated resources.
FreeHandles(schSCManager, schService);
}

85
client/Win32Service.h Normal file
View file

@ -0,0 +1,85 @@
#ifndef WIN_32_SERVICE_H__
#define WIN_32_SERVICE_H__
#include <thread>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifdef _WIN32
// Internal name of the service
#define SERVICE_NAME "i2pService"
// Displayed name of the service
#define SERVICE_DISPLAY_NAME "i2p router service"
// Service start options.
#define SERVICE_START_TYPE SERVICE_DEMAND_START
// List of service dependencies - "dep1\0dep2\0\0"
#define SERVICE_DEPENDENCIES ""
// The name of the account under which the service should run
#define SERVICE_ACCOUNT "NT AUTHORITY\\LocalService"
// The password to the service account name
#define SERVICE_PASSWORD NULL
#endif
class I2PService
{
public:
I2PService(PSTR pszServiceName,
BOOL fCanStop = TRUE,
BOOL fCanShutdown = TRUE,
BOOL fCanPauseContinue = FALSE);
virtual ~I2PService(void);
static BOOL isService();
static BOOL Run(I2PService &service);
void Stop();
protected:
virtual void OnStart(DWORD dwArgc, PSTR *pszArgv);
virtual void OnStop();
virtual void OnPause();
virtual void OnContinue();
virtual void OnShutdown();
void SetServiceStatus(DWORD dwCurrentState,
DWORD dwWin32ExitCode = NO_ERROR,
DWORD dwWaitHint = 0);
private:
static void WINAPI ServiceMain(DWORD dwArgc, LPSTR *lpszArgv);
static void WINAPI ServiceCtrlHandler(DWORD dwCtrl);
void WorkerThread();
void Start(DWORD dwArgc, PSTR *pszArgv);
void Pause();
void Continue();
void Shutdown();
static I2PService* s_service;
PSTR m_name;
SERVICE_STATUS m_status;
SERVICE_STATUS_HANDLE m_statusHandle;
BOOL m_fStopping;
HANDLE m_hStoppedEvent;
std::thread* _worker;
};
void InstallService(PSTR pszServiceName,
PSTR pszDisplayName,
DWORD dwStartType,
PSTR pszDependencies,
PSTR pszAccount,
PSTR pszPassword);
void UninstallService(PSTR pszServiceName);
#endif // WIN_32_SERVICE_H__

19
client/i2p.cpp Normal file
View file

@ -0,0 +1,19 @@
#include <thread>
#include <stdlib.h>
#include "Daemon.h"
#include "Reseed.h"
int main( int argc, char* argv[] )
{
Daemon.init(argc, argv);
if (Daemon.start())
{
while (Daemon.running)
{
//TODO Meeh: Find something better to do here.
std::this_thread::sleep_for (std::chrono::seconds(1));
}
}
Daemon.stop();
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,404 @@
// There is bug in boost 1.49 with gcc 4.7 coming with Debian Wheezy
// #define GCC47_BOOST149 ((BOOST_VERSION == 104900) && (__GNUC__ == 4) && (__GNUC_MINOR__ == 7))
// TODO: handle this somewhere, but definitely not here
#include "I2PControl.h"
#include <iomanip>
#include <sstream>
#include <cryptopp/osrng.h>
#include <cryptopp/hex.h>
#include <cryptopp/filters.h>
#include <boost/property_tree/json_parser.hpp>
#include "util/Log.h"
#include "util/Timestamp.h"
#include "transport/Transports.h"
#include "tunnel/Tunnel.h"
#include "NetDb.h"
#include "version.h"
#include "Daemon.h"
namespace i2p {
namespace client {
I2PControlSession::Response::Response(const std::string& version)
: id(), version(version), error(ErrorCode::None), parameters()
{
}
std::string I2PControlSession::Response::toJsonString() const
{
std::ostringstream oss;
oss << "{\"id\":" << id << ",\"result\":{";
for(auto it = parameters.begin(); it != parameters.end(); ++it) {
if(it != parameters.begin())
oss << ',';
oss << '"' << it->first << "\":" << it->second;
}
oss << "},\"jsonrpc\":\"" << version << '"';
if(error != ErrorCode::None)
oss << ",\"error\":{\"code\":" << -static_cast<int>(error)
<< ",\"message\":\"" << getErrorMsg() << "\"" << "}";
oss << "}";
return oss.str();
}
std::string I2PControlSession::Response::getErrorMsg() const
{
switch(error) {
case ErrorCode::MethodNotFound:
return "Method not found.";
case ErrorCode::InvalidParameters:
return "Invalid parameters.";
case ErrorCode::InvalidRequest:
return "Invalid request.";
case ErrorCode::ParseError:
return "Json parse error.";
case ErrorCode::InvalidPassword:
return "Invalid password.";
case ErrorCode::NoToken:
return "No authentication token given.";
case ErrorCode::NonexistentToken:
return "Nonexistent authentication token given.";
case ErrorCode::ExpiredToken:
return "Exipred authentication token given.";
case ErrorCode::UnspecifiedVersion:
return "Version not specified.";
case ErrorCode::UnsupportedVersion:
return "Version not supported.";
default:
return "";
};
}
void I2PControlSession::Response::setParam(const std::string& param, const std::string& value)
{
parameters[param] = value.empty() ? "null" : "\"" + value + "\"";
}
void I2PControlSession::Response::setParam(const std::string& param, int value)
{
parameters[param] = std::to_string(value);
}
void I2PControlSession::Response::setParam(const std::string& param, double value)
{
std::ostringstream oss;
oss << std::fixed << std::setprecision(2) << value;
parameters[param] = oss.str();
}
void I2PControlSession::Response::setError(ErrorCode code)
{
error = code;
}
void I2PControlSession::Response::setId(const std::string& identifier)
{
id = identifier;
}
I2PControlSession::I2PControlSession(boost::asio::io_service& ios, const std::string& pass)
: password(pass), tokens(), tokensMutex(),
service(ios), shutdownTimer(ios), expireTokensTimer(ios)
{
// Method handlers
methodHandlers[I2P_CONTROL_METHOD_AUTHENTICATE] = &I2PControlSession::handleAuthenticate;
methodHandlers[I2P_CONTROL_METHOD_ECHO] = &I2PControlSession::handleEcho;
methodHandlers[I2P_CONTROL_METHOD_I2PCONTROL] = &I2PControlSession::handleI2PControl;
methodHandlers[I2P_CONTROL_METHOD_ROUTER_INFO] = &I2PControlSession::handleRouterInfo;
methodHandlers[I2P_CONTROL_METHOD_ROUTER_MANAGER] = &I2PControlSession::handleRouterManager;
methodHandlers[I2P_CONTROL_METHOD_NETWORK_SETTING] = &I2PControlSession::handleNetworkSetting;
// RouterInfo handlers
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_UPTIME] = &I2PControlSession::handleUptime;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_VERSION] = &I2PControlSession::handleVersion;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_STATUS] = &I2PControlSession::handleStatus;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS]= &I2PControlSession::handleNetDbKnownPeers;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlSession::handleNetDbActivePeers;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_NET_STATUS] = &I2PControlSession::handleNetStatus;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlSession::handleTunnelsParticipating;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_IB_1S] = &I2PControlSession::handleInBandwidth1S;
routerInfoHandlers[I2P_CONTROL_ROUTER_INFO_BW_OB_1S] = &I2PControlSession::handleOutBandwidth1S;
// RouterManager handlers
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN] = &I2PControlSession::handleShutdown;
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = &I2PControlSession::handleShutdownGraceful;
routerManagerHandlers[I2P_CONTROL_ROUTER_MANAGER_RESEED] = &I2PControlSession::handleReseed;
}
void I2PControlSession::start()
{
startExpireTokensJob();
}
void I2PControlSession::stop()
{
boost::system::error_code e; // Make sure this doesn't throw
shutdownTimer.cancel(e);
expireTokensTimer.cancel(e);
}
I2PControlSession::Response I2PControlSession::handleRequest(std::stringstream& request)
{
boost::property_tree::ptree pt;
boost::property_tree::read_json(request, pt);
Response response;
try {
response.setId(pt.get<std::string>(I2P_CONTROL_PROPERTY_ID));
std::string method = pt.get<std::string>(I2P_CONTROL_PROPERTY_METHOD);
auto it = methodHandlers.find(method);
if(it == methodHandlers.end()) { // Not found
LogPrint(eLogWarning, "Unknown I2PControl method ", method);
response.setError(ErrorCode::MethodNotFound);
return response;
}
PropertyTree params = pt.get_child(I2P_CONTROL_PROPERTY_PARAMS);
if(method != I2P_CONTROL_METHOD_AUTHENTICATE && !authenticate(params, response)) {
LogPrint(eLogWarning, "I2PControl invalid token presented");
return response;
}
// Call the appropriate handler
(this->*(it->second))(params, response);
} catch(const boost::property_tree::ptree_error& error) {
response.setError(ErrorCode::ParseError);
} catch(...) {
response.setError(ErrorCode::InternalError);
}
return response;
}
bool I2PControlSession::authenticate(const PropertyTree& pt, Response& response)
{
try {
std::string token = pt.get<std::string>(I2P_CONTROL_PARAM_TOKEN);
std::lock_guard<std::mutex> lock(tokensMutex);
auto it = tokens.find(token);
if(it == tokens.end()) {
response.setError(ErrorCode::NonexistentToken);
return false;
} else if(util::GetSecondsSinceEpoch() - it->second > I2P_CONTROL_TOKEN_LIFETIME) {
response.setError(ErrorCode::ExpiredToken);
return false;
}
} catch(const boost::property_tree::ptree_error& error) {
response.setError(ErrorCode::NoToken);
return false;
}
return true;
}
std::string I2PControlSession::generateToken() const
{
byte random_data[I2P_CONTROL_TOKEN_SIZE] = {};
CryptoPP::AutoSeededRandomPool rng;
rng.GenerateBlock(random_data, I2P_CONTROL_TOKEN_SIZE);
std::string token;
CryptoPP::StringSource ss(
random_data, I2P_CONTROL_TOKEN_SIZE, true,
new CryptoPP::HexEncoder(new CryptoPP::StringSink(token))
);
return token;
}
void I2PControlSession::handleAuthenticate(const PropertyTree& pt, Response& response)
{
const int api = pt.get<int>(I2P_CONTROL_PARAM_API);
const std::string given_pass = pt.get<std::string>(I2P_CONTROL_PARAM_PASSWORD);
LogPrint(eLogDebug, "I2PControl Authenticate API = ", api, " Password = ", given_pass);
if(given_pass != password) {
LogPrint(
eLogError, "I2PControl Authenticate Invalid password ", given_pass,
" expected ", password
);
response.setError(ErrorCode::InvalidPassword);
return;
}
const std::string token = generateToken();
response.setParam(I2P_CONTROL_PARAM_API, api);
response.setParam(I2P_CONTROL_PARAM_TOKEN, token);
std::lock_guard<std::mutex> lock(tokensMutex);
tokens.insert(std::make_pair(token, util::GetSecondsSinceEpoch()));
}
void I2PControlSession::handleEcho(const PropertyTree& pt, Response& response)
{
const std::string echo = pt.get<std::string>(I2P_CONTROL_PARAM_ECHO);
LogPrint(eLogDebug, "I2PControl Echo Echo = ", echo);
response.setParam(I2P_CONTROL_PARAM_RESULT, echo);
}
void I2PControlSession::handleI2PControl(const PropertyTree& pt, Response& response)
{
LogPrint(eLogDebug, "I2PControl I2PControl");
// TODO: implement
}
void I2PControlSession::handleRouterInfo(const PropertyTree& pt, Response& response)
{
LogPrint(eLogDebug, "I2PControl RouterInfo");
for(const auto& pair : pt) {
if(pair.first == I2P_CONTROL_PARAM_TOKEN)
continue;
LogPrint(eLogDebug, pair.first);
auto it = routerInfoHandlers.find(pair.first);
if(it != routerInfoHandlers.end()) {
(this->*(it->second))(response);
} else {
LogPrint(eLogError, "I2PControl RouterInfo unknown request ", pair.first);
response.setError(ErrorCode::InvalidRequest);
}
}
}
void I2PControlSession::handleRouterManager(const PropertyTree& pt, Response& response)
{
LogPrint(eLogDebug, "I2PControl RouterManager");
for(const auto& pair : pt) {
if(pair.first == I2P_CONTROL_PARAM_TOKEN)
continue;
LogPrint(eLogDebug, pair.first);
auto it = routerManagerHandlers.find(pair.first);
if(it != routerManagerHandlers.end()) {
(this->*(it->second))(response);
} else {
LogPrint(eLogError, "I2PControl RouterManager unknown request ", pair.first);
response.setError(ErrorCode::InvalidRequest);
}
}
}
void I2PControlSession::handleNetworkSetting(const PropertyTree& pt, Response& response)
{
}
void I2PControlSession::handleUptime(Response& response)
{
response.setParam(I2P_CONTROL_ROUTER_INFO_UPTIME, (int)i2p::context.GetUptime()*1000);
}
void I2PControlSession::handleVersion(Response& response)
{
response.setParam(I2P_CONTROL_ROUTER_INFO_VERSION, VERSION);
}
void I2PControlSession::handleStatus(Response& response)
{
response.setParam(I2P_CONTROL_ROUTER_INFO_STATUS, "???"); // TODO:
}
void I2PControlSession::handleNetDbKnownPeers(Response& response)
{
response.setParam(
I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS, i2p::data::netdb.GetNumRouters()
);
}
void I2PControlSession::handleNetDbActivePeers(Response& response)
{
response.setParam(
I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS,
(int)i2p::transport::transports.GetPeers().size()
);
}
void I2PControlSession::handleNetStatus(Response& response)
{
response.setParam(
I2P_CONTROL_ROUTER_INFO_NET_STATUS, (int)i2p::context.GetStatus()
);
}
void I2PControlSession::handleTunnelsParticipating(Response& response)
{
response.setParam(
I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING,
(int)i2p::tunnel::tunnels.GetTransitTunnels().size()
);
}
void I2PControlSession::handleInBandwidth1S(Response& response)
{
response.setParam(
I2P_CONTROL_ROUTER_INFO_BW_IB_1S,
(double)i2p::transport::transports.GetInBandwidth()
);
}
void I2PControlSession::handleOutBandwidth1S(Response& response)
{
response.setParam(
I2P_CONTROL_ROUTER_INFO_BW_OB_1S,
(double)i2p::transport::transports.GetOutBandwidth()
);
}
void I2PControlSession::handleShutdown(Response& response)
{
LogPrint(eLogInfo, "Shutdown requested");
response.setParam(I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, "");
// 1 second to make sure response has been sent
shutdownTimer.expires_from_now(boost::posix_time::seconds(1));
shutdownTimer.async_wait([](const boost::system::error_code& ecode) {
Daemon.running = 0;
});
}
void I2PControlSession::handleShutdownGraceful(Response& response)
{
i2p::context.SetAcceptsTunnels(false);
int timeout = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout();
LogPrint(eLogInfo, "Graceful shutdown requested. Will shutdown after ", timeout, " seconds");
response.setParam(I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL, "");
shutdownTimer.expires_from_now(boost::posix_time::seconds(timeout + 1));
shutdownTimer.async_wait([](const boost::system::error_code& ecode) {
Daemon.running = 0;
});
}
void I2PControlSession::handleReseed(Response& response)
{
LogPrint(eLogInfo, "Reseed requested");
response.setParam(I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN, "");
i2p::data::netdb.Reseed();
}
void I2PControlSession::expireTokens(const boost::system::error_code& error)
{
if(error == boost::asio::error::operation_aborted)
return; // Do not restart timer, shutting down
startExpireTokensJob();
LogPrint(eLogDebug, "I2PControl is expiring tokens.");
const uint64_t now = util::GetSecondsSinceEpoch();
std::lock_guard<std::mutex> lock(tokensMutex);
for(auto it = tokens.begin(); it != tokens.end(); ) {
if(now - it->second > I2P_CONTROL_TOKEN_LIFETIME)
it = tokens.erase(it);
else
++it;
}
}
void I2PControlSession::startExpireTokensJob()
{
expireTokensTimer.expires_from_now(boost::posix_time::seconds(I2P_CONTROL_TOKEN_LIFETIME));
expireTokensTimer.async_wait(std::bind(
&I2PControlSession::expireTokens, shared_from_this(), std::placeholders::_1
));
}
}
}

View file

@ -0,0 +1,204 @@
#ifndef I2PCONTROL_H__
#define I2PCONTROL_H__
#include <boost/property_tree/ptree.hpp>
#include <string>
#include <map>
#include <functional>
#include <mutex>
#include <boost/asio.hpp>
namespace i2p {
namespace client {
const char I2P_CONTROL_DEFAULT_PASSWORD[] = "itoopie";
const uint64_t I2P_CONTROL_TOKEN_LIFETIME = 600; // Token lifetime in seconds
const std::size_t I2P_CONTROL_TOKEN_SIZE = 8; // Token size in bytes
const char I2P_CONTROL_PROPERTY_ID[] = "id";
const char I2P_CONTROL_PROPERTY_METHOD[] = "method";
const char I2P_CONTROL_PROPERTY_PARAMS[] = "params";
const char I2P_CONTROL_PROPERTY_RESULT[] = "result";
// methods
const char I2P_CONTROL_METHOD_AUTHENTICATE[] = "Authenticate";
const char I2P_CONTROL_METHOD_ECHO[] = "Echo";
const char I2P_CONTROL_METHOD_I2PCONTROL[] = "I2PControl";
const char I2P_CONTROL_METHOD_ROUTER_INFO[] = "RouterInfo";
const char I2P_CONTROL_METHOD_ROUTER_MANAGER[] = "RouterManager";
const char I2P_CONTROL_METHOD_NETWORK_SETTING[] = "NetworkSetting";
// params
const char I2P_CONTROL_PARAM_API[] = "API";
const char I2P_CONTROL_PARAM_PASSWORD[] = "Password";
const char I2P_CONTROL_PARAM_TOKEN[] = "Token";
const char I2P_CONTROL_PARAM_ECHO[] = "Echo";
const char I2P_CONTROL_PARAM_RESULT[] = "Result";
// I2PControl
const char I2P_CONTROL_I2PCONTROL_ADDRESS[] = "i2pcontrol.address";
const char I2P_CONTROL_I2PCONTROL_PASSWORD[] = "i2pcontrol.password";
const char I2P_CONTROL_I2PCONTROL_PORT[] = "i2pcontrol.port";
// RouterInfo requests
const char I2P_CONTROL_ROUTER_INFO_UPTIME[] = "i2p.router.uptime";
const char I2P_CONTROL_ROUTER_INFO_VERSION[] = "i2p.router.version";
const char I2P_CONTROL_ROUTER_INFO_STATUS[] = "i2p.router.status";
const char I2P_CONTROL_ROUTER_INFO_NETDB_KNOWNPEERS[] = "i2p.router.netdb.knownpeers";
const char I2P_CONTROL_ROUTER_INFO_NETDB_ACTIVEPEERS[] = "i2p.router.netdb.activepeers";
const char I2P_CONTROL_ROUTER_INFO_NET_STATUS[] = "i2p.router.net.status";
const char I2P_CONTROL_ROUTER_INFO_TUNNELS_PARTICIPATING[] = "i2p.router.net.tunnels.participating";
const char I2P_CONTROL_ROUTER_INFO_BW_IB_1S[] = "i2p.router.net.bw.inbound.1s";
const char I2P_CONTROL_ROUTER_INFO_BW_OB_1S[] = "i2p.router.net.bw.outbound.1s";
// RouterManager requests
const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN[] = "Shutdown";
const char I2P_CONTROL_ROUTER_MANAGER_SHUTDOWN_GRACEFUL[] = "ShutdownGraceful";
const char I2P_CONTROL_ROUTER_MANAGER_RESEED[] = "Reseed";
/**
* "Null" I2P control implementation, does not do actual networking.
* @note authentication tokens are per-session
* @note I2PControlSession must always be used as a std::shared_ptr
* @warning an I2PControlSession must be destroyed before its io_service
*/
class I2PControlSession : public std::enable_shared_from_this<I2PControlSession> {
public:
enum class ErrorCode {
None = 0,
// JSON-RPC2
MethodNotFound = 32601,
InvalidParameters = 32602,
InvalidRequest = 32600,
InternalError = 32603,
ParseError = 32700,
// I2PControl specific
InvalidPassword = 32001,
NoToken = 32002,
NonexistentToken = 32003,
ExpiredToken = 32004,
UnspecifiedVersion = 32005,
UnsupportedVersion = 32006
};
class Response {
std::string id;
std::string version;
ErrorCode error;
std::map<std::string, std::string> parameters;
public:
Response(const std::string& version = "2.0");
std::string toJsonString() const;
/**
* Set an ouptut parameter to a specified string.
* @todo escape quotes
*/
void setParam(const std::string& param, const std::string& value);
void setParam(const std::string& param, int value);
void setParam(const std::string& param, double value);
void setError(ErrorCode code);
void setId(const std::string& identifier);
std::string getErrorMsg() const;
};
/**
* Sets up the appropriate handlers.
* @param pass the password required to authenticate (i.e. obtains a token)
* @param ios the parent io_service object, must remain valid throughout
* the lifetime of this I2PControlSession.
*/
I2PControlSession(boost::asio::io_service& ios,
const std::string& pass = I2P_CONTROL_DEFAULT_PASSWORD);
/**
* Starts the I2PControlSession.
* In essence, this starts the expireTokensTimer.
* @note should always be called after construction
*/
void start();
/**
* Cancels all operations that are waiting.
* @note it's a good idea to call this before destruction (shared_ptr reset)
*/
void stop();
/**
* Handle a json string with I2PControl instructions.
*/
Response handleRequest(std::stringstream& request);
private:
// For convenience
typedef boost::property_tree::ptree PropertyTree;
// Handler types
typedef void (I2PControlSession::*MethodHandler)(
const PropertyTree& pt, Response& results
);
typedef void (I2PControlSession::*RequestHandler)(Response& results);
/**
* Tries to authenticate by checking whether the given token is valid.
* Sets the appropriate error code in the given response.
*/
bool authenticate(const PropertyTree& pt, Response& response);
/**
* Generate a random authentication token.
* @return 8 random bytes as a hexadecimal string
*/
std::string generateToken() const;
void startExpireTokensJob();
/**
* Expire tokens that are too old.
*/
void expireTokens(const boost::system::error_code& error);
// Method handlers
void handleAuthenticate(const PropertyTree& pt, Response& response);
void handleEcho(const PropertyTree& pt, Response& response);
void handleI2PControl(const PropertyTree& pt, Response& response);
void handleRouterInfo(const PropertyTree& pt, Response& response);
void handleRouterManager(const PropertyTree& pt, Response& response);
void handleNetworkSetting(const PropertyTree& pt, Response& response);
// RouterInfo handlers
void handleUptime(Response& response);
void handleVersion(Response& response);
void handleStatus(Response& response);
void handleNetDbKnownPeers(Response& response);
void handleNetDbActivePeers(Response& response);
void handleNetStatus(Response& response);
void handleTunnelsParticipating(Response& response);
void handleInBandwidth1S(Response& response);
void handleOutBandwidth1S(Response& response);
// RouterManager handlers
void handleShutdown(Response& response);
void handleShutdownGraceful(Response& response);
void handleReseed(Response& response);
std::string password;
std::map<std::string, uint64_t> tokens;
std::mutex tokensMutex;
std::map<std::string, MethodHandler> methodHandlers;
std::map<std::string, RequestHandler> routerInfoHandlers;
std::map<std::string, RequestHandler> routerManagerHandlers;
std::map<std::string, RequestHandler> networkSettingHandlers;
boost::asio::io_service& service;
boost::asio::deadline_timer shutdownTimer;
boost::asio::deadline_timer expireTokensTimer;
};
}
}
#endif // I2PCONTROL_H__

View file

@ -0,0 +1,170 @@
#include "I2PControlServer.h"
#include <sstream>
#include <boost/date_time/local_time/local_time.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "util/Log.h"
#include "util/Timestamp.h"
#include "version.h"
namespace i2p {
namespace client {
I2PControlService::I2PControlService(const std::string& address, int port, const std::string& pass)
: m_Session(std::make_shared<I2PControlSession>(m_Service, pass)),
m_IsRunning(false), m_Thread(nullptr),
m_Acceptor(m_Service, boost::asio::ip::tcp::endpoint(
boost::asio::ip::address::from_string(address), port)
)
{
}
I2PControlService::~I2PControlService()
{
Stop();
}
void I2PControlService::Start()
{
if(!m_IsRunning) {
Accept();
m_Session->start();
m_IsRunning = true;
m_Thread = new std::thread(std::bind(&I2PControlService::Run, this));
}
}
void I2PControlService::Stop()
{
if(m_IsRunning) {
m_IsRunning = false;
m_Acceptor.cancel();
m_Session->stop();
// Release ownership before the io_service is stopped and destroyed
m_Session.reset();
m_Service.stop();
if(m_Thread)
{
m_Thread->join();
delete m_Thread;
m_Thread = nullptr;
}
}
}
void I2PControlService::Run()
{
while(m_IsRunning) {
try {
m_Service.run();
} catch(const std::exception& ex) {
LogPrint(eLogError, "I2PControl: ", ex.what());
}
}
}
void I2PControlService::Accept()
{
auto newSocket = std::make_shared<boost::asio::ip::tcp::socket>(m_Service);
m_Acceptor.async_accept(*newSocket, std::bind(&I2PControlService::HandleAccept, this,
std::placeholders::_1, newSocket));
}
void I2PControlService::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
if(ecode != boost::asio::error::operation_aborted)
Accept();
if(!ecode)
{
LogPrint(eLogInfo, "New I2PControl request from ", socket->remote_endpoint());
std::this_thread::sleep_for(std::chrono::milliseconds(5));
ReadRequest(socket);
}
else
LogPrint(eLogError, "I2PControl accept error: ", ecode.message());
}
void I2PControlService::ReadRequest(std::shared_ptr<boost::asio::ip::tcp::socket> socket)
{
auto request = std::make_shared<I2PControlBuffer>();
socket->async_read_some(
#if BOOST_VERSION >= 104900
boost::asio::buffer(*request),
#else
boost::asio::buffer(request->data(), request->size()),
#endif
std::bind(&I2PControlService::HandleRequestReceived, this,
std::placeholders::_1, std::placeholders::_2, socket, request));
}
void I2PControlService::HandleRequestReceived(const boost::system::error_code& ecode,
size_t bytes_transferred, std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<I2PControlBuffer> buf)
{
if(ecode) {
LogPrint(eLogError, "I2PControl read error: ", ecode.message());
return;
}
try {
bool isHtml = !memcmp(buf->data(), "POST", 4);
std::stringstream ss;
ss.write(buf->data(), bytes_transferred);
if(isHtml) {
std::string header;
while(!ss.eof() && header != "\r")
std::getline(ss, header);
if(ss.eof()) {
LogPrint(eLogError, "Malformed I2PControl request. HTTP header expected");
return; // TODO:
}
}
I2PControlSession::Response response = m_Session->handleRequest(ss);
SendResponse(socket, buf, response.toJsonString(), isHtml);
} catch(const std::exception& ex) {
LogPrint(eLogError, "I2PControl handle request: ", ex.what());
} catch(...) {
LogPrint(eLogError, "I2PControl handle request unknown exception");
}
}
void I2PControlService::SendResponse(std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<I2PControlBuffer> buf, const std::string& response, bool isHtml)
{
size_t len = response.length(), offset = 0;
if(isHtml) {
std::ostringstream header;
header << "HTTP/1.1 200 OK\r\n";
header << "Connection: close\r\n";
header << "Content-Length: " << boost::lexical_cast<std::string>(len) << "\r\n";
header << "Content-Type: application/json\r\n";
header << "Date: ";
auto facet = new boost::local_time::local_time_facet("%a, %d %b %Y %H:%M:%S GMT");
header.imbue(std::locale(header.getloc(), facet));
header << boost::posix_time::second_clock::local_time() << "\r\n";
header << "\r\n";
offset = header.str().size();
memcpy(buf->data(), header.str().c_str(), offset);
}
memcpy(buf->data() + offset, response.c_str(), len);
boost::asio::async_write(
*socket, boost::asio::buffer(buf->data(), offset + len),
boost::asio::transfer_all(), std::bind(
&I2PControlService::HandleResponseSent, this,
std::placeholders::_1, std::placeholders::_2, socket, buf
)
);
}
void I2PControlService::HandleResponseSent(const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<I2PControlBuffer> buf)
{
if(ecode)
LogPrint(eLogError, "I2PControl write error: ", ecode.message());
socket->close();
}
}
}

View file

@ -0,0 +1,56 @@
#ifndef I2P_CONTROL_SERVER_H__
#define I2P_CONTROL_SERVER_H__
#include "I2PControl.h"
#include <inttypes.h>
#include <thread>
#include <memory>
#include <array>
#include <string>
#include <sstream>
#include <boost/asio.hpp>
namespace i2p {
namespace client {
const size_t I2P_CONTROL_MAX_REQUEST_SIZE = 1024;
typedef std::array<char, I2P_CONTROL_MAX_REQUEST_SIZE> I2PControlBuffer;
class I2PControlService {
public:
I2PControlService(const std::string& address, int port, const std::string& pass);
~I2PControlService();
void Start();
void Stop();
private:
void Run();
void Accept();
void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
void ReadRequest(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
void HandleRequestReceived(const boost::system::error_code& ecode, size_t bytes_transferred,
std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<I2PControlBuffer> buf);
void SendResponse(std::shared_ptr<boost::asio::ip::tcp::socket> socket,
std::shared_ptr<I2PControlBuffer> buf, const std::string& response, bool isHtml);
void HandleResponseSent(const boost::system::error_code& ecode, std::size_t bytes_transferred,
std::shared_ptr<boost::asio::ip::tcp::socket> socket, std::shared_ptr<I2PControlBuffer> buf);
private:
bool m_IsRunning;
std::thread * m_Thread;
boost::asio::io_service m_Service;
boost::asio::ip::tcp::acceptor m_Acceptor;
std::shared_ptr<I2PControlSession> m_Session;
};
}
}
#endif