// 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 #include #include #include #include #include #include "util/util.h" #include "util/Log.h" #include "util/Timestamp.h" #include "transport/Transports.h" #include "tunnel/Tunnel.h" #include "NetworkDatabase.h" #include "version.h" #include "Daemon.h" namespace i2p { namespace client { namespace i2pcontrol { JsonObject::JsonObject(const std::string& value) : children(), value("\"" + value + "\"") { } JsonObject::JsonObject(int value) : children(), value(std::to_string(value)) { } JsonObject::JsonObject(double v) : children(), value() { std::ostringstream oss; oss << std::fixed << std::setprecision(2) << v; value = oss.str(); } JsonObject& JsonObject::operator[](const std::string& key) { return children[key]; } std::string JsonObject::toString() const { if(children.empty()) return value; std::ostringstream oss; oss << '{'; for(auto it = children.begin(); it != children.end(); ++it) { if(it != children.begin()) oss << ','; oss << '"' << it->first << "\":" << it->second.toString(); } oss << '}'; return oss.str(); } JsonObject tunnelToJsonObject(i2p::tunnel::Tunnel* tunnel) { JsonObject obj; std::stringstream ss; tunnel->GetTunnelConfig()->Print(ss); // TODO: use a JsonObject obj["layout"] = JsonObject(ss.str()); const auto state = tunnel->GetState(); if(state == i2p::tunnel::eTunnelStateFailed) obj["state"] = JsonObject("failed"); else if(state == i2p::tunnel::eTunnelStateExpiring) obj["state"] = JsonObject("expiring"); return obj; } 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(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 "Expired 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::setParam(const std::string& param, const JsonObject& value) { parameters[param] = value.toString(); } 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) { using namespace i2p::client::i2pcontrol::constants; // Method handlers methodHandlers[METHOD_AUTHENTICATE] = &I2PControlSession::handleAuthenticate; methodHandlers[METHOD_ECHO] = &I2PControlSession::handleEcho; methodHandlers[METHOD_I2PCONTROL] = &I2PControlSession::handleI2PControl; methodHandlers[METHOD_ROUTER_INFO] = &I2PControlSession::handleRouterInfo; methodHandlers[METHOD_ROUTER_MANAGER] = &I2PControlSession::handleRouterManager; methodHandlers[METHOD_NETWORK_SETTING] = &I2PControlSession::handleNetworkSetting; // RouterInfo handlers routerInfoHandlers[ROUTER_INFO_UPTIME] = &I2PControlSession::handleUptime; routerInfoHandlers[ROUTER_INFO_VERSION] = &I2PControlSession::handleVersion; routerInfoHandlers[ROUTER_INFO_STATUS] = &I2PControlSession::handleStatus; routerInfoHandlers[ROUTER_INFO_DATAPATH] = &I2PControlSession::handleDatapath; routerInfoHandlers[ROUTER_INFO_NETDB_KNOWNPEERS]= &I2PControlSession::handleNetDbKnownPeers; routerInfoHandlers[ROUTER_INFO_NETDB_ACTIVEPEERS] = &I2PControlSession::handleNetDbActivePeers; routerInfoHandlers[ROUTER_INFO_NETDB_LEASESETS] = &I2PControlSession::handleNetDbLeaseSets; routerInfoHandlers[ROUTER_INFO_NETDB_FLOODFILLS] = &I2PControlSession::handleNetDbFloodfills; routerInfoHandlers[ROUTER_INFO_NET_STATUS] = &I2PControlSession::handleNetStatus; routerInfoHandlers[ROUTER_INFO_TUNNELS_PARTICIPATING] = &I2PControlSession::handleTunnelsParticipating; routerInfoHandlers[ROUTER_INFO_TUNNELS_CREATION_SUCCESS] = &I2PControlSession::handleTunnelsCreationSuccess; routerInfoHandlers[ROUTER_INFO_TUNNELS_IN_LIST] = &I2PControlSession::handleTunnelsInList; routerInfoHandlers[ROUTER_INFO_TUNNELS_OUT_LIST] = &I2PControlSession::handleTunnelsOutList; routerInfoHandlers[ROUTER_INFO_BW_IB_1S] = &I2PControlSession::handleInBandwidth1S; routerInfoHandlers[ROUTER_INFO_BW_OB_1S] = &I2PControlSession::handleOutBandwidth1S; // RouterManager handlers routerManagerHandlers[ROUTER_MANAGER_SHUTDOWN] = &I2PControlSession::handleShutdown; routerManagerHandlers[ROUTER_MANAGER_SHUTDOWN_GRACEFUL] = &I2PControlSession::handleShutdownGraceful; routerManagerHandlers[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(constants::PROPERTY_ID)); std::string method = pt.get(constants::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(constants::PROPERTY_PARAMS); if(method != constants::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(constants::PARAM_TOKEN); std::lock_guard lock(tokensMutex); auto it = tokens.find(token); if(it == tokens.end()) { response.setError(ErrorCode::NonexistentToken); return false; } else if(util::GetSecondsSinceEpoch() - it->second > constants::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[constants::TOKEN_SIZE] = {}; CryptoPP::AutoSeededRandomPool rng; rng.GenerateBlock(random_data, constants::TOKEN_SIZE); std::string token; CryptoPP::StringSource ss( random_data, constants::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(constants::PARAM_API); const std::string given_pass = pt.get(constants::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(constants::PARAM_API, api); response.setParam(constants::PARAM_TOKEN, token); std::lock_guard lock(tokensMutex); tokens.insert(std::make_pair(token, util::GetSecondsSinceEpoch())); } void I2PControlSession::handleEcho(const PropertyTree& pt, Response& response) { const std::string echo = pt.get(constants::PARAM_ECHO); LogPrint(eLogDebug, "I2PControl Echo Echo = ", echo); response.setParam(constants::PARAM_RESULT, echo); } void I2PControlSession::handleI2PControl(const PropertyTree&, 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 == constants::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 == constants::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&, Response&) { // TODO: implement } void I2PControlSession::handleUptime(Response& response) { response.setParam(constants::ROUTER_INFO_UPTIME, (int)i2p::context.GetUptime()*1000); } void I2PControlSession::handleVersion(Response& response) { response.setParam(constants::ROUTER_INFO_VERSION, VERSION); } void I2PControlSession::handleStatus(Response& response) { response.setParam(constants::ROUTER_INFO_STATUS, "???"); // TODO: } void I2PControlSession::handleDatapath(Response& response) { response.setParam( constants::ROUTER_INFO_DATAPATH, i2p::util::filesystem::GetDefaultDataDir().string() ); } void I2PControlSession::handleNetDbKnownPeers(Response& response) { response.setParam( constants::ROUTER_INFO_NETDB_KNOWNPEERS, i2p::data::netdb.GetNumRouters() ); } void I2PControlSession::handleNetDbActivePeers(Response& response) { response.setParam( constants::ROUTER_INFO_NETDB_ACTIVEPEERS, (int)i2p::transport::transports.GetPeers().size() ); } void I2PControlSession::handleNetDbFloodfills(Response& response) { response.setParam( constants::ROUTER_INFO_NETDB_FLOODFILLS, (int)i2p::data::netdb.GetNumFloodfills() ); } void I2PControlSession::handleNetDbLeaseSets(Response& response) { response.setParam( constants::ROUTER_INFO_NETDB_LEASESETS, (int)i2p::data::netdb.GetNumLeaseSets() ); } void I2PControlSession::handleNetStatus(Response& response) { response.setParam( constants::ROUTER_INFO_NET_STATUS, (int)i2p::context.GetStatus() ); } void I2PControlSession::handleTunnelsParticipating(Response& response) { response.setParam( constants::ROUTER_INFO_TUNNELS_PARTICIPATING, (int)i2p::tunnel::tunnels.GetTransitTunnels().size() ); } void I2PControlSession::handleTunnelsCreationSuccess(Response& response) { response.setParam( constants::ROUTER_INFO_TUNNELS_CREATION_SUCCESS, i2p::tunnel::tunnels.GetTunnelCreationSuccessRate() ); } void I2PControlSession::handleTunnelsInList(Response& response) { JsonObject list; for(auto pair : i2p::tunnel::tunnels.GetInboundTunnels()) { const std::string id = std::to_string(pair.first); list[id] = tunnelToJsonObject(pair.second.get()); list[id]["bytes"] = JsonObject( static_cast(pair.second->GetNumReceivedBytes()) ); } response.setParam(constants::ROUTER_INFO_TUNNELS_IN_LIST, list); } void I2PControlSession::handleTunnelsOutList(Response& response) { JsonObject list; for(auto tunnel : i2p::tunnel::tunnels.GetOutboundTunnels()) { const std::string id = std::to_string(tunnel->GetTunnelID()); list[id] = tunnelToJsonObject(tunnel.get()); list[id]["bytes"] = JsonObject( static_cast(tunnel->GetNumSentBytes()) ); } response.setParam(constants::ROUTER_INFO_TUNNELS_OUT_LIST, list); } void I2PControlSession::handleInBandwidth1S(Response& response) { response.setParam( constants::ROUTER_INFO_BW_IB_1S, (double)i2p::transport::transports.GetInBandwidth() ); } void I2PControlSession::handleOutBandwidth1S(Response& response) { response.setParam( constants::ROUTER_INFO_BW_OB_1S, (double)i2p::transport::transports.GetOutBandwidth() ); } void I2PControlSession::handleShutdown(Response& response) { LogPrint(eLogInfo, "Shutdown requested"); response.setParam(constants::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&) { 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(constants::ROUTER_MANAGER_SHUTDOWN_GRACEFUL, ""); shutdownTimer.expires_from_now(boost::posix_time::seconds(timeout + 1)); shutdownTimer.async_wait([](const boost::system::error_code&) { Daemon.running = 0; }); } void I2PControlSession::handleReseed(Response& response) { LogPrint(eLogInfo, "Reseed requested"); response.setParam(constants::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 lock(tokensMutex); for(auto it = tokens.begin(); it != tokens.end(); ) { if(now - it->second > constants::TOKEN_LIFETIME) it = tokens.erase(it); else ++it; } } void I2PControlSession::startExpireTokensJob() { expireTokensTimer.expires_from_now(boost::posix_time::seconds(constants::TOKEN_LIFETIME)); expireTokensTimer.async_wait(std::bind( &I2PControlSession::expireTokens, shared_from_this(), std::placeholders::_1 )); } } } }