diff --git a/src/jsonapi/jsonapi.cpp b/src/jsonapi/jsonapi.cpp index 2d91ee36f..078db5739 100644 --- a/src/jsonapi/jsonapi.cpp +++ b/src/jsonapi/jsonapi.cpp @@ -235,6 +235,8 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), std::string locationName; std::string pgpName; std::string password; + bool makeHidden = false; + bool makeAutoTor = false; // JSON API only std::string apiUser; @@ -249,6 +251,8 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), RS_SERIAL_PROCESS(locationName); RS_SERIAL_PROCESS(pgpName); RS_SERIAL_PROCESS(password); + RS_SERIAL_PROCESS(makeHidden); + RS_SERIAL_PROCESS(makeAutoTor); // JSON API only RS_SERIAL_PROCESS(apiUser); @@ -265,7 +269,8 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), if(!retval) // call retroshare C++ API retval = rsLoginHelper->createLocationV2( - locationId, pgpId, locationName, pgpName, password ); + locationId, pgpId, locationName, pgpName, password, + makeHidden, makeAutoTor ); if(!retval) retval = authorizeUser(apiUser, apiPass); diff --git a/src/retroshare/rsinit.h b/src/retroshare/rsinit.h index 494c1b57c..e5c02259e 100644 --- a/src/retroshare/rsinit.h +++ b/src/retroshare/rsinit.h @@ -498,7 +498,9 @@ public: RsPgpId& pgpId, const std::string& locationName, const std::string& pgpName, - const std::string& password + const std::string& password, + bool makeHidden = false, + bool makeAutoTor = false /* JSON API only * const std::string& apiUser * const std::string& apiPass */ ); diff --git a/src/retroshare/rstor.h b/src/retroshare/rstor.h index fe96348a6..92d34166c 100644 --- a/src/retroshare/rstor.h +++ b/src/retroshare/rstor.h @@ -128,6 +128,30 @@ public: */ static RsTorConnectivityStatus torConnectivityStatus() ; + /** + * Configure TorManager to attach to an already running Tor instance. + * This must be called before start(). The external process is never + * stopped or taken over by RetroShare. + * + * This endpoint is available before account login because the Tor + * connection must be configured before hidden-node initialization starts. + * + * @param[in] controlAddress Tor control listener address, usually + * `127.0.0.1` + * @param[in] controlPort Tor control listener TCP port + * @param[in] controlPassword Tor control password, or an empty string for + * null/cookie authentication + * @param[in] socksAddress Tor SOCKS listener address, usually `127.0.0.1` + * @param[in] socksPort Tor SOCKS listener TCP port + * @return true if the external Tor configuration was accepted + * + * @jsonapi{development,unauthenticated} + */ + static bool setExternalTorConnection( + const std::string& controlAddress, uint16_t controlPort, + const std::string& controlPassword, + const std::string& socksAddress, uint16_t socksPort ); + static void setTorDataDirectory(const std::string& dir); static void setHiddenServiceDirectory(const std::string& dir); @@ -162,12 +186,14 @@ public: /*! * \brief getHiddenServiceInfo * Gets information about the hidden service setup by RS to run. - * \param service_id - * \param service_onion_address - * \param service_port - * \param service_target_address - * \param target_port - * \return + * \param[out] service_id Tor service identifier without the `.onion` suffix + * \param[out] service_onion_address Complete generated onion hostname + * \param[out] service_port Public onion-service port + * \param[out] service_target_address Local address receiving Tor traffic + * \param[out] target_port Local port receiving Tor traffic + * \return true when an AutoTor hidden service is available + * + * @jsonapi{development} */ static bool getHiddenServiceInfo(std::string& service_id, std::string& service_onion_address, diff --git a/src/rsserver/rsinit.cc b/src/rsserver/rsinit.cc index 31d6e08c4..b475351f4 100644 --- a/src/rsserver/rsinit.cc +++ b/src/rsserver/rsinit.cc @@ -2162,6 +2162,15 @@ bool RsInit::startAutoTor() std::string service_id; RsTor::setupHiddenService(); + // If the connection to the Tor control port cannot be established at all + // (e.g. an external Tor that is not running, or a wrong control port), + // fail after a while instead of waiting forever. Once the control link + // has been seen up, wait indefinitely for Tor to bootstrap, as before. + + const time_t control_connect_timeout = 30; // seconds + time_t start_time = time(nullptr); + bool control_link_seen = false; + while(RsTor::torStatus() != RsTorStatus::READY && RsTor::getHiddenServiceStatus(service_id) != RsTorHiddenServiceStatus::ONLINE) // runs until some status is reached: either tor works, or it fails. { rstime::rs_usleep(0.5*1000*1000) ; @@ -2174,6 +2183,24 @@ bool RsInit::startAutoTor() std::cerr << "(EE) Tor hidden service cannot be started: " << error_msg << std::endl; return false; } + + if(!control_link_seen) + switch(RsTor::torConnectivityStatus()) + { + case RsTorConnectivityStatus::SOCKET_CONNECTED: + case RsTorConnectivityStatus::AUTHENTICATING: + case RsTorConnectivityStatus::AUTHENTICATED: + case RsTorConnectivityStatus::HIDDEN_SERVICE_READY: + control_link_seen = true; + break; + default: + if(time(nullptr) > start_time + control_connect_timeout) + { + std::cerr << "(EE) Cannot establish a connection to the Tor control port. Giving up." << std::endl; + return false; + } + break; + } // process Qt event loop to deal with messages of online/offline info // QCoreApplication::processEvents(); } @@ -2236,7 +2263,8 @@ void RsLoginHelper::getLocations(std::vector& store) std::error_condition RsLoginHelper::createLocationV2( RsPeerId& locationId, RsPgpId& pgpId, const std::string& locationName, const std::string& pgpName, - const std::string& password ) + const std::string& password, + bool makeHidden, bool makeAutoTor ) { if(isLoggedIn()) return RsInitErrorNum::ALREADY_LOGGED_IN; if(locationName.empty()) return RsInitErrorNum::INVALID_LOCATION_NAME; @@ -2256,7 +2284,7 @@ std::error_condition RsLoginHelper::createLocationV2( RsLoginHandler::cachePgpPassphrase(password); bool ret = RsAccounts::createNewAccount( - pgpId, "", locationName, "", false, false, sslPassword, + pgpId, "", locationName, "", makeHidden, makeAutoTor, sslPassword, locationId, errorMessage ); if(!ret) { diff --git a/src/tor/TorControl.cpp b/src/tor/TorControl.cpp index 106244b01..561bbe08a 100644 --- a/src/tor/TorControl.cpp +++ b/src/tor/TorControl.cpp @@ -211,6 +211,8 @@ void TorControl::connect(const std::string &address, uint16_t port) setStatus(SocketConnected); setTorStatus(TorOffline); // connected and running, but not yet ready } + else + setStatus(NotConnected); // so that the owner can try connecting again } void TorControl::reconnect() @@ -221,7 +223,14 @@ void TorControl::reconnect() return; setStatus(Connecting); - mSocket->connectToHost(mTorAddress, mControlPort); + + if(mSocket->connectToHost(mTorAddress, mControlPort)) + { + setStatus(SocketConnected); + setTorStatus(TorOffline); // connected and running, but not yet ready + } + else + setStatus(NotConnected); // so that the owner can try connecting again } void TorControl::authenticateReply(TorControlCommand *sender) diff --git a/src/tor/TorManager.cpp b/src/tor/TorManager.cpp index f4bb6d27e..aadb82121 100644 --- a/src/tor/TorManager.cpp +++ b/src/tor/TorManager.cpp @@ -38,12 +38,10 @@ #include "util/rsdir.h" #include "retroshare/rsinit.h" #include +#include #if !defined(_WIN32) && !defined(__MINGW32__) #include #endif -#ifdef __linux__ -#include -#endif #include "TorManager.h" #include "TorProcess.h" @@ -74,6 +72,13 @@ public: bool configNeeded; bool mVerbose; std::string userProvidedTorExecutablePath; + bool useExternalTor; + std::string externalControlAddress; + uint16_t externalControlPort; + ByteArray externalControlPassword; + std::string externalSocksAddress; + uint16_t externalSocksPort; + time_t lastControlConnectAttempt; HiddenService *hiddenService ; @@ -123,6 +128,10 @@ TorManagerPrivate::TorManagerPrivate(TorManager *parent) , control(new TorControl()) , configNeeded(false) , mVerbose(false) + , useExternalTor(false) + , externalControlPort(0) + , externalSocksPort(0) + , lastControlConnectAttempt(0) , hiddenService(NULL) { control->set_statusChanged_callback([this](int new_status,int /*old_status*/) { controlStatusChanged(new_status); }); @@ -371,6 +380,32 @@ std::string TorManager::torExecutablePath() const return (d==nullptr)?(std::string()):(d->torExecutablePath()); } +bool TorManager::setExternalTorConnection( + const std::string& controlAddress, uint16_t controlPort, + const std::string& controlPassword, + const std::string& socksAddress, uint16_t socksPort ) +{ + if(isRunning()) + { + d->setError("Cannot change Tor connection while TorManager is running."); + return false; + } + if(controlAddress.empty() || controlPort == 0 || + socksAddress.empty() || socksPort == 0) + { + d->setError("External Tor control and SOCKS endpoints must be valid."); + return false; + } + + d->useExternalTor = true; + d->externalControlAddress = controlAddress; + d->externalControlPort = controlPort; + d->externalControlPassword = ByteArray(controlPassword); + d->externalSocksAddress = socksAddress; + d->externalSocksPort = socksPort; + return true; +} + const std::list& TorManager::logMessages() const { return d->logMessages; @@ -394,43 +429,14 @@ bool TorManager::startTorManager() //emit errorChanged(); // not needed because there's no error to handle } -#ifdef TODO - SettingsObject settings("tor"); - - // If a control port is defined by config or environment, skip launching tor - if (!settings.read("controlPort").isUndefined() || - !qEnvironmentVariableIsEmpty("TOR_CONTROL_PORT")) + if(d->useExternalTor) { - QHostAddress address(settings.read("controlAddress").toString()); - quint16 port = (quint16)settings.read("controlPort").toInt(); - QByteArray password = settings.read("controlPassword").toString().toLatin1(); - - if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_HOST")) - address = QHostAddress(qgetenv("TOR_CONTROL_HOST")); - - if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_PORT")) { - bool ok = false; - port = qgetenv("TOR_CONTROL_PORT").toUShort(&ok); - if (!ok) - port = 0; - } - - if (!qEnvironmentVariableIsEmpty("TOR_CONTROL_PASSWD")) - password = qgetenv("TOR_CONTROL_PASSWD"); - - if (!port) { - d->setError("Invalid control port settings from environment or configuration"); - return false; - } - - if (address.isNull()) - address = QHostAddress::LocalHost; - - d->control->setAuthPassword(password); - d->control->connect(address, port); + RsInfo() << "Connecting to external Tor control port at " + << d->externalControlAddress << ":" << d->externalControlPort; + d->control->setAuthPassword(d->externalControlPassword); + d->control->connect(d->externalControlAddress, d->externalControlPort); } else -#endif { // Launch a bundled Tor instance std::string executable = d->torExecutablePath(); @@ -503,7 +509,8 @@ bool TorManager::startTorManager() void TorManager::run() { - d->process->start(); + if(d->process) + d->process->start(); while(!shouldStop()) { @@ -512,7 +519,8 @@ void TorManager::run() } d->control->shutdownSync(); - d->process->stop(); + if(d->process) + d->process->stop(); if(rsEvents) { @@ -526,10 +534,12 @@ void TorManager::threadTick() { static bool authenticated_msg_already_given = false; - d->process->tick(); - - if(d->process->state() != TorProcess::Ready) - return; + if(d->process) + { + d->process->tick(); + if(d->process->state() != TorProcess::Ready) + return; + } switch(d->control->status()) { @@ -538,8 +548,28 @@ void TorManager::threadTick() break; case TorControl::NotConnected: - RsInfo() << "Connecting to tor process at " << d->process->controlHost() << ":" << d->process->controlPort() << "..." ; - d->control->connect(d->process->controlHost(),d->process->controlPort()); + { + // The connection attempt is synchronous and threadTick() runs every + // 50ms, so throttle retries when the control port is unreachable + // (e.g. an external Tor that is not started yet). + + time_t now = time(nullptr); + + if(now < d->lastControlConnectAttempt + 2) + break; + d->lastControlConnectAttempt = now; + + if(d->useExternalTor) + { + RsInfo() << "Connecting to external tor at " << d->externalControlAddress << ":" << d->externalControlPort << "..." ; + d->control->connect(d->externalControlAddress,d->externalControlPort); + } + else + { + RsInfo() << "Connecting to tor process at " << d->process->controlHost() << ":" << d->process->controlPort() << "..." ; + d->control->connect(d->process->controlHost(),d->process->controlPort()); + } + } break; case TorControl::SocketConnected: @@ -551,7 +581,8 @@ void TorManager::threadTick() setupHiddenService(); } - d->control->setAuthPassword(d->process->controlPassword()); + d->control->setAuthPassword(d->useExternalTor + ? d->externalControlPassword : d->process->controlPassword()); d->control->authenticate(); RsInfo() << "Authenticating..." ; break; @@ -588,6 +619,12 @@ bool TorManager::getProxyServerInfo(std::string& proxy_server_adress,uint16_t& p proxy_server_adress = control()->socksAddress(); proxy_server_port = control()->socksPort(); + if(d->useExternalTor && (proxy_server_adress.empty() || proxy_server_port == 0)) + { + proxy_server_adress = d->externalSocksAddress; + proxy_server_port = d->externalSocksPort; + } + return proxy_server_port > 1023 ; } @@ -796,7 +833,17 @@ void TorManagerPrivate::setError(const std::string &message) bool RsTor::isTorAvailable() { - return !instance()->d->torExecutablePath().empty(); + return instance()->d->useExternalTor || !instance()->d->torExecutablePath().empty(); +} + +bool RsTor::setExternalTorConnection( + const std::string& controlAddress, uint16_t controlPort, + const std::string& controlPassword, + const std::string& socksAddress, uint16_t socksPort ) +{ + return instance()->setExternalTorConnection( + controlAddress, controlPort, controlPassword, + socksAddress, socksPort ); } bool RsTor::getHiddenServiceInfo(std::string& service_id, @@ -840,11 +887,17 @@ void RsTor::setTorExecutablePath(const std::string& e) std::string RsTor::socksAddress() { - return instance()->control()->socksAddress(); + std::string address; + uint16_t port = 0; + instance()->getProxyServerInfo(address, port); + return address; } uint16_t RsTor::socksPort() { - return instance()->control()->socksPort(); + std::string address; + uint16_t port = 0; + instance()->getProxyServerInfo(address, port); + return port; } static RsTorStatus torStatus(Tor::TorControl::TorStatus t) @@ -955,17 +1008,14 @@ void RsTor::setHiddenServiceDirectory(const std::string& dir) instance()->setHiddenServiceDirectory(dir); } -#ifdef __APPLE__ -#include -#endif - TorManager *RsTor::instance() { -#ifdef __APPLE__ - assert(pthread_main_np() != 0); // On macOS, ensure we are on the main thread -#elif defined(__linux__) - assert(getpid() == syscall(SYS_gettid)); // On Linux, ensure we are on the main thread -#endif + // RsTor methods are exposed through the JSON API, so this can be reached + // from any thread. Protect the lazy initialisation, which the former + // main-thread asserts used to guard. + + static std::mutex instanceMutex; + std::unique_lock lock(instanceMutex); if(rsTorMgr == nullptr) rsTorMgr = new TorManager; diff --git a/src/tor/TorManager.h b/src/tor/TorManager.h index a37d833c6..6d173fa4c 100644 --- a/src/tor/TorManager.h +++ b/src/tor/TorManager.h @@ -62,6 +62,11 @@ public: std::string torExecutablePath() const; void setTorExecutablePath(const std::string& tor_exe_full_path) ; + bool setExternalTorConnection( + const std::string& controlAddress, uint16_t controlPort, + const std::string& controlPassword, + const std::string& socksAddress, uint16_t socksPort ); + std::string hiddenServiceDirectory() const; void setHiddenServiceDirectory(const std::string &path);