From eb77f921ec46d86d98085f428f83861c5c07b6ef Mon Sep 17 00:00:00 2001 From: Gioacchino Mazzurco Date: Wed, 19 Sep 2018 21:28:26 +0200 Subject: [PATCH 1/6] Implement JSON API HTTP Basic authentication jsonapi-generator is now capable of generating API for headers outside retroshare/ directory jsonapi-generator do a bit of methods parameter sanity check JsonApiServer is now integrated in the rsinit hell like other services Add *::exportGPGKeyPairToString to a bunch of classes in cascade RsControl is now capable of calling back a function when retroshare is almost completely stopped, this is useful when running retroshare toghether with externally managed runloop such as QCoreApplication Expose a bunch of methods through JSON API retroshare-nogui remove some dead code and fix stopping from the RetroShare API --- jsonapi-generator/README.adoc | 58 +++- .../async-method-wrapper-template.cpp.tmpl | 2 +- .../src/jsonapi-generator-doxygen.conf | 1 + jsonapi-generator/src/jsonapi-generator.cpp | 61 ++++- .../src/method-wrapper-template.cpp.tmpl | 2 +- libresapi/src/api/RsControlModule.cpp | 11 +- libresapi/src/api/RsControlModule.h | 2 + libretroshare/src/jsonapi/jsonapi.cpp | 257 +++++++++++++++--- libretroshare/src/jsonapi/jsonapi.h | 149 ++++++++-- libretroshare/src/jsonapi/jsonapiitems.h | 69 +++++ libretroshare/src/libretroshare.pro | 2 +- libretroshare/src/pgp/pgphandler.cc | 30 ++ libretroshare/src/pgp/pgphandler.h | 3 + libretroshare/src/pqi/authgpg.cc | 9 +- libretroshare/src/pqi/authgpg.h | 3 + libretroshare/src/pqi/p3servicecontrol.cc | 8 +- libretroshare/src/pqi/p3servicecontrol.h | 4 +- libretroshare/src/retroshare/rsfiles.h | 2 +- libretroshare/src/retroshare/rsgxschannels.h | 4 +- libretroshare/src/retroshare/rsiface.h | 59 ++-- libretroshare/src/retroshare/rsinit.h | 46 +++- .../src/retroshare/rsservicecontrol.h | 9 +- libretroshare/src/rsitems/rsserviceids.h | 1 + libretroshare/src/rsserver/p3face-config.cc | 8 +- libretroshare/src/rsserver/p3face-server.cc | 3 + libretroshare/src/rsserver/p3face.h | 26 +- libretroshare/src/rsserver/rsaccounts.cc | 16 ++ libretroshare/src/rsserver/rsaccounts.h | 3 + libretroshare/src/rsserver/rsinit.cc | 172 ++++++------ retroshare-android-service/src/service.cpp | 67 +---- retroshare-nogui/src/retroshare.cc | 125 ++------- .../librssimulator/peer/FakeServiceControl.h | 2 +- 32 files changed, 816 insertions(+), 398 deletions(-) create mode 100644 libretroshare/src/jsonapi/jsonapiitems.h diff --git a/jsonapi-generator/README.adoc b/jsonapi-generator/README.adoc index 9142a2e0f..7d7d5be04 100644 --- a/jsonapi-generator/README.adoc +++ b/jsonapi-generator/README.adoc @@ -55,7 +55,7 @@ extern RsGxsChannels* rsGxsChannels; .Calling the JSON API with curl on the terminal [source,bash] -------------------------------------------------------------------------------- -curl --data @paramethers.json http://127.0.0.1:9092/rsGxsChannels/createGroup +curl -u $API_USER --data @paramethers.json http://127.0.0.1:9092/rsGxsChannels/createGroup -------------------------------------------------------------------------------- .JSON API call result @@ -76,7 +76,7 @@ least in two different ways. .Calling the JSON API with GET method with curl on the terminal [source,bash] -------------------------------------------------------------------------------- -curl --get --data-urlencode jsonData@paramethers.json \ +curl -u $API_USER --get --data-urlencode jsonData@paramethers.json \ http://127.0.0.1:9092/rsGxsChannels/createGroup -------------------------------------------------------------------------------- @@ -85,12 +85,53 @@ equivalent to the following. .Calling the JSON API with GET method and pre-encoded data with curl on the terminal -------------------------------------------------------------------------------- -curl http://127.0.0.1:9092/rsGxsChannels/createGroup?jsonData=%7B%0A%20%20%20%20%22group%22%3A%7B%0A%20%20%20%20%20%20%20%20%22mMeta%22%3A%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupName%22%3A%22JSON%20test%20group%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupFlags%22%3A4%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mSignFlags%22%3A520%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%22mDescription%22%3A%22JSON%20test%20group%20description%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22caller_data%22%3A%22Here%20can%20go%20any%20kind%20of%20JSON%20data%20%28even%20objects%29%20that%20the%20caller%20want%20to%20get%20back%20together%20with%20the%20response%22%0A%7D +curl -u $API_USER http://127.0.0.1:9092/rsGxsChannels/createGroup?jsonData=%7B%0A%20%20%20%20%22group%22%3A%7B%0A%20%20%20%20%20%20%20%20%22mMeta%22%3A%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupName%22%3A%22JSON%20test%20group%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupFlags%22%3A4%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mSignFlags%22%3A520%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%22mDescription%22%3A%22JSON%20test%20group%20description%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22caller_data%22%3A%22Here%20can%20go%20any%20kind%20of%20JSON%20data%20%28even%20objects%29%20that%20the%20caller%20want%20to%20get%20back%20together%20with%20the%20response%22%0A%7D -------------------------------------------------------------------------------- Note that using +GET+ method +?jsonData=+ and then the JSON data URL encoded are added after the path in the HTTP request. + +== JSON API authentication + +Most of JSON API methods require authentication as they give access to +RetroShare user data, and we don't want any application running on the system +eventually by other users be able to access private data indiscriminately. +JSON API support HTTP Basic as authentication scheme, this is enough as JSON API +server is intented for usage on the same system (127.0.0.1) not over an +untrusted network. +If you need to use JSON API over an untrusted network consider using a reverse +proxy with HTTPS such as NGINX in front of JSON API server. +If RetroShare login has been effectuated through the JSON API you can use your +location SSLID as username and your PGP password as credential for the JSON API, +but we suggests you use specific meaningful and human readable credentials for +each JSON API client so the human user can have better control over which client +can access the JSON API. + +.NewToken.json +[source,json] +-------------------------------------------------------------------------------- +{ + "token": "myNewUser:myNewPassword" +} +-------------------------------------------------------------------------------- + +.An authenticated client can authorize new tokens like this +-------------------------------------------------------------------------------- +curl -u $API_USER --data @NewToken.json http://127.0.0.1:9092/jsonApiServer/authorizeToken +-------------------------------------------------------------------------------- + +.An unauthenticated JSON API client can request access with +-------------------------------------------------------------------------------- +curl --data @NewToken.json http://127.0.0.1:9092/jsonApiServer/requestNewTokenAutorization +-------------------------------------------------------------------------------- + +When an unauthenticated client request his token to be authorized, JSON API +server will try to ask confirmation to the human user if possible through ++mNewAccessRequestCallback+, if it is not possible or the user didn't authorized +the token +false+ is returned. + + == Offer new RetroShare services through JSON API To offer a retroshare service through the JSON API, first of all one need find @@ -231,6 +272,17 @@ data: {"results":[{"size":668,"hash":"e8845280912ebf3779e400000000000000000000", -------------------------------------------------------------------------------- +By default JSON API methods requires client authentication and their wrappers +are automatically generated by +json-api-generator+. +In some cases methods need do be accessible without authentication such as ++rsLoginHelper/getLocations+ so in the doxygen documentaion they have the custom +command +@jsonapi{RS_VERSION,unauthenticated}+. +Other methods such as +/rsControl/rsGlobalShutDown+ need special care so they +are marked with the custom doxygen command +@jsonapi{RS_VERSION,manualwrapper}+ +and their wrappers are not automatically generated but written manually into ++JsonApiServer::JsonApiServer(...)+. + + == A bit of history === First writings about this diff --git a/jsonapi-generator/src/async-method-wrapper-template.cpp.tmpl b/jsonapi-generator/src/async-method-wrapper-template.cpp.tmpl index d5ca5c828..402f32975 100644 --- a/jsonapi-generator/src/async-method-wrapper-template.cpp.tmpl +++ b/jsonapi-generator/src/async-method-wrapper-template.cpp.tmpl @@ -61,5 +61,5 @@ $%outputParamsSerialization%$ session->yield(message.str()); $%sessionDelayedClose%$ } ); -}); +}, $%requiresAuth%$); diff --git a/jsonapi-generator/src/jsonapi-generator-doxygen.conf b/jsonapi-generator/src/jsonapi-generator-doxygen.conf index d7fbafebb..0edd1de3e 100644 --- a/jsonapi-generator/src/jsonapi-generator-doxygen.conf +++ b/jsonapi-generator/src/jsonapi-generator-doxygen.conf @@ -2,6 +2,7 @@ DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "libretroshare" ALIASES += jsonapi{1}="\xmlonly\endxmlonly" +ALIASES += jsonapi{2}="\xmlonly\endxmlonly" # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. diff --git a/jsonapi-generator/src/jsonapi-generator.cpp b/jsonapi-generator/src/jsonapi-generator.cpp index 8bdf44c64..2a19a3853 100644 --- a/jsonapi-generator/src/jsonapi-generator.cpp +++ b/jsonapi-generator/src/jsonapi-generator.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include struct MethodParam { @@ -40,7 +42,10 @@ struct MethodParam int main(int argc, char *argv[]) { if(argc != 3) - qFatal("Usage: jsonapi-generator SOURCE_PATH OUTPUT_PATH"); + { + qDebug() << "Usage: jsonapi-generator SOURCE_PATH OUTPUT_PATH"; + return EINVAL; + } QString sourcePath(argv[1]); QString outputPath(argv[2]); @@ -59,6 +64,20 @@ int main(int argc, char *argv[]) qFatal(QString("Can't open: " + cppApiIncludesFilePath).toLatin1().data()); QSet cppApiIncludesSet; + auto fatalError = [&]( + std::initializer_list errors, int ernum = EINVAL ) + { + QString errorMsg; + for(const QVariant& error: errors) + errorMsg += error.toString() + " "; + errorMsg.chop(1); + QByteArray cppError(QString("#error "+errorMsg+"\n").toLocal8Bit()); + wrappersDefFile.write(cppError); + cppApiIncludesFile.write(cppError); + qDebug() << errorMsg; + return ernum; + }; + QDirIterator it(doxPrefix, QStringList() << "*8h.xml", QDir::Files); while(it.hasNext()) { @@ -74,9 +93,10 @@ int main(int argc, char *argv[]) continue; } - QFileInfo hfi(hFile); - QString headerFileName(hfi.fileName()); - headerFileName.replace(QString("_8h.xml"), QString(".h")); + QFileInfo headerFileInfo(hDoc.elementsByTagName("location").at(0) + .attributes().namedItem("file").nodeValue()); + QString headerRelPath = headerFileInfo.dir().dirName() + "/" + + headerFileInfo.fileName(); QDomNodeList sectiondefs = hDoc.elementsByTagName("memberdef"); for(int j = 0; j < sectiondefs.size(); ++j) @@ -112,6 +132,7 @@ int main(int argc, char *argv[]) QDomNode member = members.item(i); QString refid(member.attributes().namedItem("refid").nodeValue()); QString methodName(member.firstChildElement("name").toElement().text()); + bool requiresAuth = true; QString defFilePath(doxPrefix + refid.split('_')[0] + ".xml"); qDebug() << "Looking for" << typeName << methodName << "into" @@ -134,10 +155,21 @@ int main(int argc, char *argv[]) QDomElement tmpMBD = memberdefs.item(k).toElement(); QString tmpId = tmpMBD.attributes().namedItem("id").nodeValue(); QString tmpKind = tmpMBD.attributes().namedItem("kind").nodeValue(); - bool hasJsonApi = !tmpMBD.elementsByTagName("jsonapi").isEmpty(); - if( tmpId == refid && tmpKind == "function" && hasJsonApi ) + QDomNodeList tmpJsonApiTagList(tmpMBD.elementsByTagName("jsonapi")); + + if( tmpJsonApiTagList.count() && tmpId == refid && + tmpKind == "function") { - memberdef = tmpMBD; + QDomElement tmpJsonApiTag = tmpJsonApiTagList.item(0).toElement(); + QString tmpAccessValue; + if(tmpJsonApiTag.hasAttribute("access")) + tmpAccessValue = tmpJsonApiTag.attributeNode("access").nodeValue(); + + requiresAuth = "unauthenticated" != tmpAccessValue; + + if("manualwrapper" != tmpAccessValue) + memberdef = tmpMBD; + break; } } @@ -189,6 +221,7 @@ int main(int argc, char *argv[]) pType.replace(QString("&"), QString()); pType.replace(QString(" "), QString()); } + paramsMap.insert(tmpParam.name, tmpParam); orderedParamNames.push_back(tmpParam.name); } @@ -211,6 +244,16 @@ int main(int argc, char *argv[]) } } + // Params sanity check + for( const MethodParam& pm : paramsMap) + if( !(pm.isMultiCallback || pm.isSingleCallback + || pm.in || pm.out) ) + return fatalError( + { "Parameter:", pm.name, "of:", apiPath, + "declared in:", headerRelPath, + "miss doxygen parameter direction attribute!", + defFile.fileName() }); + QString functionCall("\t\t"); if(retvalType != "void") { @@ -325,7 +368,6 @@ int main(int argc, char *argv[]) substitutionsMap.insert("inputParamsDeserialization", inputParamsDeserialization); substitutionsMap.insert("outputParamsSerialization", outputParamsSerialization); substitutionsMap.insert("instanceName", instanceName); - substitutionsMap.insert("headerFileName", headerFileName); substitutionsMap.insert("functionCall", functionCall); substitutionsMap.insert("apiPath", apiPath); substitutionsMap.insert("sessionEarlyClose", sessionEarlyClose); @@ -334,6 +376,7 @@ int main(int argc, char *argv[]) substitutionsMap.insert("callbackName", callbackName); substitutionsMap.insert("callbackParams", callbackParams); substitutionsMap.insert("callbackParamsSerialization", callbackParamsSerialization); + substitutionsMap.insert("requiresAuth", requiresAuth ? "true" : "false"); QString templFilePath(sourcePath); if(hasMultiCallback || hasSingleCallback) @@ -351,7 +394,7 @@ int main(int argc, char *argv[]) wrappersDefFile.write(wrapperDef.toLocal8Bit()); - cppApiIncludesSet.insert("#include \"retroshare/" + headerFileName + "\"\n"); + cppApiIncludesSet.insert("#include \"" + headerRelPath + "\"\n"); } } } diff --git a/jsonapi-generator/src/method-wrapper-template.cpp.tmpl b/jsonapi-generator/src/method-wrapper-template.cpp.tmpl index 4125e1b69..bdcef3aee 100644 --- a/jsonapi-generator/src/method-wrapper-template.cpp.tmpl +++ b/jsonapi-generator/src/method-wrapper-template.cpp.tmpl @@ -44,5 +44,5 @@ $%outputParamsSerialization%$ // return them to the API caller DEFAULT_API_CALL_JSON_RETURN(rb::OK); } ); -}); +}, $%requiresAuth%$); diff --git a/libresapi/src/api/RsControlModule.cpp b/libresapi/src/api/RsControlModule.cpp index 5caca6b8b..66ea7caf6 100644 --- a/libresapi/src/api/RsControlModule.cpp +++ b/libresapi/src/api/RsControlModule.cpp @@ -432,9 +432,8 @@ void RsControlModule::handleLogin(Request &req, Response &resp) void RsControlModule::handleShutdown(Request &, Response &resp) { - RS_STACK_MUTEX(mExitFlagMtx); // ********** LOCKED ********** - mProcessShouldExit = true; - resp.setOk(); + requestShutdown(); + resp.setOk(); } void RsControlModule::handleImportPgp(Request &req, Response &resp) @@ -582,6 +581,12 @@ bool RsControlModule::askForDeferredSelfSignature(const void *data, const uint32 } } +bool RsControlModule::requestShutdown() +{ + RS_STACK_MUTEX(mExitFlagMtx); + mProcessShouldExit = true; +} + void RsControlModule::setRunState(RunState s, std::string errstr) { RS_STACK_MUTEX(mDataMtx); // ********** LOCKED ********** diff --git a/libresapi/src/api/RsControlModule.h b/libresapi/src/api/RsControlModule.h index cb3b7834d..e9886f7bc 100644 --- a/libresapi/src/api/RsControlModule.h +++ b/libresapi/src/api/RsControlModule.h @@ -62,6 +62,8 @@ public: virtual bool askForPassword(const std::string &title, const std::string& key_details, bool prev_is_bad , std::string& password,bool& canceled) override; virtual bool askForDeferredSelfSignature(const void *data, const uint32_t len, unsigned char *sign, unsigned int *signlen,int& signature_result, std::string reason = "") override; + virtual bool requestShutdown(); + protected: // from RsThread // wee need a thread to call into things which block like askForPassword() diff --git a/libretroshare/src/jsonapi/jsonapi.cpp b/libretroshare/src/jsonapi/jsonapi.cpp index a75e08de4..4c68370f9 100644 --- a/libretroshare/src/jsonapi/jsonapi.cpp +++ b/libretroshare/src/jsonapi/jsonapi.cpp @@ -18,6 +18,7 @@ #include "jsonapi.h" +#include #include #include #include @@ -27,10 +28,17 @@ #include "retroshare/rsfiles.h" #include "util/radix64.h" #include "retroshare/rsversion.h" +#include "retroshare/rsinit.h" +#include "util/rsnet.h" +#include "retroshare/rsiface.h" +#include "retroshare/rsinit.h" +#include "util/rsurl.h" // Generated at compile time #include "jsonapi-includes.inl" +/*extern*/ JsonApiServer* jsonApiServer = nullptr; + #define INITIALIZE_API_CALL_JSON_CONTEXT \ RsGenericSerializer::SerializeContext cReq( \ nullptr, 0, \ @@ -71,10 +79,10 @@ static bool checkRsServicePtrReady( { if(serviceInstance) return true; - std::string jsonApiError; + std::string jsonApiError = __PRETTY_FUNCTION__; jsonApiError += "Service: "; jsonApiError += serviceName; - jsonApiError += " not initialized! Are you sure you logged in already?"; + jsonApiError += " not initialized!"; RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON); RS_SERIAL_PROCESS(jsonApiError); @@ -84,13 +92,53 @@ static bool checkRsServicePtrReady( return false; } - -JsonApiServer::JsonApiServer( - uint16_t port, const std::string& bindAddress, - const std::function shutdownCallback ) : - mPort(port), mBindAddress(bindAddress), mShutdownCallback(shutdownCallback) +JsonApiServer::JsonApiServer(uint16_t port, const std::string& bindAddress, + const std::function newAccessRequestCallback ) : + mPort(port), mBindAddress(bindAddress), + mNewAccessRequestCallback(newAccessRequestCallback), + configMutex("JsonApiServer config") { - registerHandler("/jsonApiServer/shutdown", + registerHandler("/rsLoginHelper/attemptLogin", + [this](const std::shared_ptr session) + { + size_t reqSize = session->get_request()->get_header("Content-Length", 0); + session->fetch( reqSize, [this]( + const std::shared_ptr session, + const rb::Bytes& body ) + { + INITIALIZE_API_CALL_JSON_CONTEXT; + + RsPeerId account; + std::string password; + + // deserialize input parameters from JSON + { + RsGenericSerializer::SerializeContext& ctx(cReq); + RsGenericSerializer::SerializeJob j(RsGenericSerializer::FROM_JSON); + RS_SERIAL_PROCESS(account); + RS_SERIAL_PROCESS(password); + } + + // call retroshare C++ API + RsInit::LoadCertificateStatus retval = + rsLoginHelper->attemptLogin(account, password); + + if( retval == RsInit::OK ) + authorizeToken(account.toStdString()+":"+password); + + // serialize out parameters and return value to JSON + { + RsGenericSerializer::SerializeContext& ctx(cAns); + RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON); + RS_SERIAL_PROCESS(retval); + } + + // return them to the API caller + DEFAULT_API_CALL_JSON_RETURN(rb::OK); + } ); + }, false); + + registerHandler("/rsControl/rsGlobalShutDown", [this](const std::shared_ptr session) { size_t reqSize = session->get_request()->get_header("Content-Length", 0); @@ -100,37 +148,9 @@ JsonApiServer::JsonApiServer( { INITIALIZE_API_CALL_JSON_CONTEXT; DEFAULT_API_CALL_JSON_RETURN(rb::OK); - shutdown(); + rsControl->rsGlobalShutDown(); } ); - }); - - registerHandler("/jsonApiServer/version", - [](const std::shared_ptr session) - { - size_t reqSize = session->get_request()->get_header("Content-Length", 0); - session->fetch( reqSize, []( - const std::shared_ptr session, - const rb::Bytes& body ) - { - INITIALIZE_API_CALL_JSON_CONTEXT; - - uint32_t major = RS_MAJOR_VERSION; - uint32_t minor = RS_MINOR_VERSION; - uint32_t mini = RS_MINI_VERSION; - std::string extra = RS_EXTRA_VERSION; - std::string human = RS_HUMAN_READABLE_VERSION; - - RsGenericSerializer::SerializeContext& ctx(cAns); - RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON); - RS_SERIAL_PROCESS(major); - RS_SERIAL_PROCESS(minor); - RS_SERIAL_PROCESS(mini); - RS_SERIAL_PROCESS(extra); - RS_SERIAL_PROCESS(human); - - DEFAULT_API_CALL_JSON_RETURN(rb::OK); - } ); - }); + }, true); registerHandler("/rsFiles/getFileData", [](const std::shared_ptr session) @@ -186,7 +206,7 @@ JsonApiServer::JsonApiServer( DEFAULT_API_CALL_JSON_RETURN(rb::OK); } ); - }); + }, true); // Generated at compile time #include "jsonapi-wrappers.inl" @@ -198,22 +218,173 @@ void JsonApiServer::run() settings->set_port(mPort); settings->set_bind_address(mBindAddress); settings->set_default_header("Cache-Control", "no-cache"); + + { + sockaddr_storage tmp; + sockaddr_storage_inet_pton(tmp, mBindAddress); + sockaddr_storage_setport(tmp, mPort); + sockaddr_storage_ipv6_to_ipv4(tmp); + RsUrl tmpUrl(sockaddr_storage_tostring(tmp)); + tmpUrl.setScheme("http"); + + std::cerr << "JSON API listening on " << tmpUrl.toString() + << std::endl; + } + mService.start(settings); } void JsonApiServer::registerHandler( const std::string& path, - const std::function)>& handler) + const std::function)>& handler, + bool requiresAutentication ) { std::shared_ptr resource(new rb::Resource); resource->set_path(path); resource->set_method_handler("GET", handler); resource->set_method_handler("POST", handler); + + if(requiresAutentication) + resource->set_authentication_handler( + [this]( + const std::shared_ptr session, + const std::function)>& callback ) + { + if(!rsLoginHelper->isLoggedIn()) + { + session->close(rb::CONFLICT); + return; + } + + std::istringstream authHeader; + authHeader.str(session->get_request()->get_header("Authorization")); + + std::string authToken; + std::getline(authHeader, authToken, ' '); + + if(authToken != "Basic") + { + session->close(rb::UNAUTHORIZED); + return; + } + + std::getline(authHeader, authToken, ' '); + authToken = decodeToken(authToken); + + if(isAuthTokenValid(authToken)) callback(session); + else session->close(rb::UNAUTHORIZED); + } ); + mService.publish(resource); } -void JsonApiServer::shutdown(int exitCode) +void JsonApiServer::setNewAccessRequestCallback( + const std::function& callback ) +{ mNewAccessRequestCallback = callback; } + +void JsonApiServer::shutdown() { mService.stop(); } + +bool JsonApiServer::requestNewTokenAutorization(const std::string& token) { - mService.stop(); - mShutdownCallback(exitCode); + if(rsLoginHelper->isLoggedIn() && mNewAccessRequestCallback(token)) + return authorizeToken(token); + return false; } + +bool JsonApiServer::isAuthTokenValid(const std::string& token) +{ + RS_STACK_MUTEX(configMutex); + return mAuthTokenStorage.mAuthorizedTokens.count(token); +} + +std::set JsonApiServer::getAuthorizedTokens() +{ + RS_STACK_MUTEX(configMutex); + return mAuthTokenStorage.mAuthorizedTokens; +} + +bool JsonApiServer::revokeAuthToken(const std::string& token) +{ + RS_STACK_MUTEX(configMutex); + if(mAuthTokenStorage.mAuthorizedTokens.erase(token)) + { + IndicateConfigChanged(); + return true; + } + return false; +} + +bool JsonApiServer::authorizeToken(const std::string& token) +{ + if(token.empty()) return false; + + RS_STACK_MUTEX(configMutex); + if(mAuthTokenStorage.mAuthorizedTokens.insert(token).second) + { + IndicateConfigChanged(); + return true; + } + return false; +} + +/*static*/ std::string JsonApiServer::decodeToken(const std::string& token) +{ + std::vector decodedVect(Radix64::decode(token)); + std::string decodedToken( + reinterpret_cast(&decodedVect[0]), + decodedVect.size() ); + return decodedToken; +} + +/*static*/ std::string JsonApiServer::encondeToken(const std::string& token) +{ + std::string encoded; + Radix64::encode( reinterpret_cast(token.c_str()), + token.length(), encoded ); + return encoded; +} + +/*static*/ void JsonApiServer::version( + uint32_t& major, uint32_t& minor, uint32_t& mini, std::string& extra, + std::string& human ) +{ + major = RS_MAJOR_VERSION; + minor = RS_MINOR_VERSION; + mini = RS_MINI_VERSION; + extra = RS_EXTRA_VERSION; + human = RS_HUMAN_READABLE_VERSION; +} + +RsSerialiser* JsonApiServer::setupSerialiser() +{ + RsSerialiser* rss = new RsSerialiser; + rss->addSerialType(new JsonApiConfigSerializer); + return rss; +} + +bool JsonApiServer::saveList(bool& cleanup, std::list& saveItems) +{ + cleanup = false; + configMutex.lock(); + saveItems.push_back(&mAuthTokenStorage); + return true; +} + +bool JsonApiServer::loadList(std::list& loadList) +{ + for(RsItem* it : loadList) + switch (static_cast(it->PacketSubType())) + { + case JsonApiItemsType::AuthTokenItem: + mAuthTokenStorage = *static_cast(it); + delete it; + break; + default: + delete it; + break; + } + return true; +} + +void JsonApiServer::saveDone() { configMutex.unlock(); } + diff --git a/libretroshare/src/jsonapi/jsonapi.h b/libretroshare/src/jsonapi/jsonapi.h index 7595bc11d..5315054ea 100644 --- a/libretroshare/src/jsonapi/jsonapi.h +++ b/libretroshare/src/jsonapi/jsonapi.h @@ -15,36 +15,59 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +#pragma once #include #include #include +#include +#include "util/rsthreads.h" +#include "pqi/p3cfgmgr.h" +#include "rsitems/rsitem.h" +#include "jsonapi/jsonapiitems.h" #include "util/rsthreads.h" namespace rb = restbed; +struct JsonApiServer; + +/** + * Pointer to global instance of JsonApiServer + * @jsonapi{development} + */ +extern JsonApiServer* jsonApiServer; /** * Simple usage * \code{.cpp} * JsonApiServer jas(9092); + * jsonApiServer = &jas; * jas.start("JsonApiServer"); * \endcode + * Uses p3Config to securely store persistent JSON API authorization tokens */ -struct JsonApiServer : RsSingleJobThread +struct JsonApiServer : RsSingleJobThread, p3Config { + /** + * @brief construct a JsonApiServer instance with given parameters + * @param[in] port listening port fpt the JSON API socket + * @param[in] bindAddress binding address for the JSON API socket + * @param newAccessRequestCallback called when a new auth token is asked to + * be authorized via JSON API, the auth token is passed as parameter, and + * the callback should return true if the new token get access granted and + * false otherwise, this usually requires user interacion to confirm access + */ JsonApiServer( uint16_t port = 9092, const std::string& bindAddress = "127.0.0.1", - const std::function shutdownCallback = [](int){} ); - - /// @see RsSingleJobThread - virtual void run(); + const std::function newAccessRequestCallback = [](const std::string&){return false;} ); /** * @param[in] path Path itno which publish the API call * @param[in] handler function which will be called to handle the requested + * @param[in] requiresAutentication specify if the API call must be + * autenticated or not * path, the function must be declared like: * \code{.cpp} * void functionName(const shared_ptr session) @@ -52,24 +75,118 @@ struct JsonApiServer : RsSingleJobThread */ void registerHandler( const std::string& path, - const std::function)>& handler ); + const std::function)>& handler, + bool requiresAutentication = true ); /** - * @brief Shutdown the JSON API server and call shutdownCallback - * @jsonapi{development} - * Beware that this method shout down only the JSON API server instance not - * the whole RetroShare instance, this behaviour can be altered via - * shutdownCallback paramether of @see JsonApiServer::JsonApiServer - * This method is made available also via JSON API with path - * /jsonApiServer/shutdown - * @param exitCode just passed down to the shutdownCallback + * @brief Set new access request callback + * @param callback function to call when a new JSON API access is requested */ - void shutdown(int exitCode = 0); + void setNewAccessRequestCallback( + const std::function& callback ); + + /** + * @brief Shutdown the JSON API server + * Beware that this method shout down only the JSON API server instance not + */ + void shutdown(); + + /** + * @brief This function should be used by JSON API clients that aren't + * authenticated yet, to ask their token to be authorized, the success or + * failure will depend on mNewAccessRequestCallback return value, and it + * will likely need human user interaction in the process. + * @jsonapi{development,unauthenticated} + * @param[in] token token to autorize + * @return true if authorization succeded, false otherwise. + */ + bool requestNewTokenAutorization(const std::string& token); + + /** + * @brief Check if given JSON API auth token is authorized + * @jsonapi{development} + * @param[in] token decoded + * @return tru if authorized, false otherwise + */ + bool isAuthTokenValid(const std::string& token); + + /** + * @brief Get uthorized tokens + * @jsonapi{development} + * @return the set of authorized encoded tokens + */ + std::set getAuthorizedTokens(); + + /** + * @brief Revoke given auth token + * @jsonapi{development} + * @param[in] token decoded + * @return true if the token has been revoked, false otherwise + */ + bool revokeAuthToken(const std::string& token); + + /** + * @brief Add new auth token to the authorized set + * @jsonapi{development} + * @param[in] token toke to autorize decoded + * @return true if the token has been added to authorized, false if error + * occurred or if the token was already authorized + */ + bool authorizeToken(const std::string& token); + + /** + * @brief Get decoded version of the given encoded token + * @jsonapi{development,unauthenticated} + * @param[in] token encoded + * @return token decoded + */ + static std::string decodeToken(const std::string& token); + + /** + * @brief Get encoded version of the given decoded token + * @jsonapi{development,unauthenticated} + * @param[in] token decoded + * @return token encoded + */ + static std::string encondeToken(const std::string& token); + + /** + * @brief Write version information to given paramethers + * @jsonapi{development,unauthenticated} + * @param[out] major storage + * @param[out] minor storage + * @param[out] mini storage + * @param[out] extra storage + * @param[out] human storage + */ + static void version( uint32_t& major, uint32_t& minor, uint32_t& mini, + std::string& extra, std::string&human ); + + /// @see RsSingleJobThread + virtual void run(); private: + /// @see p3Config::setupSerialiser + virtual RsSerialiser* setupSerialiser(); + + /// @see p3Config::saveList + virtual bool saveList(bool &cleanup, std::list& saveItems); + + /// @see p3Config::loadList + virtual bool loadList(std::list& loadList); + + /// @see p3Config::saveDone + virtual void saveDone(); + const uint16_t mPort; const std::string mBindAddress; rb::Service mService; - const std::function mShutdownCallback; + + /// Called when new JSON API auth token is requested to be authorized + std::function mNewAccessRequestCallback; + + /// Encrypted persistent storage for authorized JSON API tokens + JsonApiServerAuthTokenStorage mAuthTokenStorage; + RsMutex configMutex; }; diff --git a/libretroshare/src/jsonapi/jsonapiitems.h b/libretroshare/src/jsonapi/jsonapiitems.h new file mode 100644 index 000000000..7e61d4de8 --- /dev/null +++ b/libretroshare/src/jsonapi/jsonapiitems.h @@ -0,0 +1,69 @@ +/* + * RetroShare JSON API + * Copyright (C) 2018 Gioacchino Mazzurco + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include +#include +#include + +#include "rsitems/rsitem.h" +#include "rsitems/rsserviceids.h" +#include "serialiser/rsserializer.h" +#include "serialiser/rsserializable.h" + +enum class JsonApiItemsType : uint8_t { AuthTokenItem = 0 }; + +struct JsonApiServerAuthTokenStorage : RsItem +{ + JsonApiServerAuthTokenStorage() : + RsItem( RS_PKT_VERSION_SERVICE, RS_SERVICE_TYPE_JSONAPI, + static_cast(JsonApiItemsType::AuthTokenItem) ) {} + + /// @see RsSerializable + virtual void serial_process(RsGenericSerializer::SerializeJob j, + RsGenericSerializer::SerializeContext& ctx) + { + RS_SERIAL_PROCESS(mAuthorizedTokens); + } + + /// @see RsItem + virtual void clear() { mAuthorizedTokens.clear(); } + + std::set mAuthorizedTokens; +}; + + +struct JsonApiConfigSerializer : RsServiceSerializer +{ + JsonApiConfigSerializer() : RsServiceSerializer(RS_SERVICE_TYPE_JSONAPI) {} + virtual ~JsonApiConfigSerializer() {} + + RsItem* create_item(uint16_t service_id, uint8_t item_sub_id) const + { + if(service_id != RS_SERVICE_TYPE_JSONAPI) return nullptr; + + switch(static_cast(item_sub_id)) + { + case JsonApiItemsType::AuthTokenItem: return new JsonApiServerAuthTokenStorage(); + default: return nullptr; + } + } +}; + + diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index e2527f27a..76d8fa30d 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -909,7 +909,7 @@ rs_jsonapi { # Force recalculation of libretroshare dependencies see https://stackoverflow.com/a/47884045 QMAKE_EXTRA_TARGETS += libretroshare - HEADERS += jsonapi/jsonapi.h + HEADERS += jsonapi/jsonapi.h jsonapi/jsonapiitems.h SOURCES += jsonapi/jsonapi.cpp } diff --git a/libretroshare/src/pgp/pgphandler.cc b/libretroshare/src/pgp/pgphandler.cc index f5ca9fa1e..bc80f322f 100644 --- a/libretroshare/src/pgp/pgphandler.cc +++ b/libretroshare/src/pgp/pgphandler.cc @@ -709,6 +709,36 @@ bool PGPHandler::exportGPGKeyPair(const std::string& filename,const RsPgpId& exp return true ; } +bool PGPHandler::exportGPGKeyPairToString( + std::string& data, const RsPgpId& exportedKeyId, + bool includeSignatures, std::string& errorMsg ) const +{ + RS_STACK_MUTEX(pgphandlerMtx); + + const ops_keydata_t *pubkey = locked_getPublicKey(exportedKeyId,false); + + if(!pubkey) + { + errorMsg = "Cannot output key " + exportedKeyId.toStdString() + + ": not found in public keyring."; + return false; + } + const ops_keydata_t *seckey = locked_getSecretKey(exportedKeyId); + + if(!seckey) + { + errorMsg = "Cannot output key " + exportedKeyId.toStdString() + + ": not found in secret keyring."; + return false; + } + + data = makeRadixEncodedPGPKey(pubkey, includeSignatures); + data += "\n"; + data += makeRadixEncodedPGPKey(seckey, includeSignatures); + data += "\n"; + return true; +} + bool PGPHandler::getGPGDetailsFromBinaryBlock(const unsigned char *mem_block,size_t mem_size,RsPgpId& key_id, std::string& name, std::list& signers) const { ops_keyring_t *tmp_keyring = allocateOPSKeyring(); diff --git a/libretroshare/src/pgp/pgphandler.h b/libretroshare/src/pgp/pgphandler.h index 9fe4abfbb..c227ec018 100644 --- a/libretroshare/src/pgp/pgphandler.h +++ b/libretroshare/src/pgp/pgphandler.h @@ -99,6 +99,9 @@ class PGPHandler bool importGPGKeyPair(const std::string& filename,RsPgpId& imported_id,std::string& import_error) ; bool importGPGKeyPairFromString(const std::string& data,RsPgpId& imported_id,std::string& import_error) ; bool exportGPGKeyPair(const std::string& filename,const RsPgpId& exported_id) const ; + bool exportGPGKeyPairToString( + std::string& data, const RsPgpId& exportedKeyId, + bool includeSignatures, std::string& errorMsg ) const; bool availableGPGCertificatesWithPrivateKeys(std::list& ids); bool GeneratePGPCertificate(const std::string& name, const std::string& email, const std::string& passwd, RsPgpId& pgpId, const int keynumbits, std::string& errString) ; diff --git a/libretroshare/src/pqi/authgpg.cc b/libretroshare/src/pqi/authgpg.cc index 318397b2c..70735ee83 100644 --- a/libretroshare/src/pqi/authgpg.cc +++ b/libretroshare/src/pqi/authgpg.cc @@ -327,6 +327,14 @@ bool AuthGPG::exportProfile(const std::string& fname,const RsPgpId& exported_id) return PGPHandler::exportGPGKeyPair(fname,exported_id) ; } +bool AuthGPG::exportIdentityToString( + std::string& data, const RsPgpId& pgpId, bool includeSignatures, + std::string& errorMsg ) +{ + return PGPHandler::exportGPGKeyPairToString( + data, pgpId, includeSignatures, errorMsg); +} + bool AuthGPG::importProfile(const std::string& fname,RsPgpId& imported_id,std::string& import_error) { return PGPHandler::importGPGKeyPair(fname,imported_id,import_error) ; @@ -337,7 +345,6 @@ bool AuthGPG::importProfileFromString(const std::string &data, RsPgpId &gpg_id, return PGPHandler::importGPGKeyPairFromString(data, gpg_id, import_error); } - bool AuthGPG::active() { RsStackMutex stack(gpgMtxData); /******* LOCKED ******/ diff --git a/libretroshare/src/pqi/authgpg.h b/libretroshare/src/pqi/authgpg.h index bc01b1cd0..6d3337580 100644 --- a/libretroshare/src/pqi/authgpg.h +++ b/libretroshare/src/pqi/authgpg.h @@ -158,6 +158,9 @@ public: virtual bool importProfile(const std::string& filename,RsPgpId& gpg_id,std::string& import_error) ; virtual bool importProfileFromString(const std::string& data,RsPgpId& gpg_id,std::string& import_error) ; virtual bool exportProfile(const std::string& filename,const RsPgpId& gpg_id) ; + virtual bool exportIdentityToString( + std::string& data, const RsPgpId& pgpId, bool includeSignatures, + std::string& errorMsg ); virtual bool removeKeysFromPGPKeyring(const std::set &pgp_ids,std::string& backup_file,uint32_t& error_code) ; diff --git a/libretroshare/src/pqi/p3servicecontrol.cc b/libretroshare/src/pqi/p3servicecontrol.cc index 5207b9829..4db44960e 100644 --- a/libretroshare/src/pqi/p3servicecontrol.cc +++ b/libretroshare/src/pqi/p3servicecontrol.cc @@ -987,9 +987,9 @@ void p3ServiceControl::filterChangeAdded_locked(const RsPeerId &peerId, uint32_t -void p3ServiceControl::getPeersConnected(const uint32_t serviceId, std::set &peerSet) +void p3ServiceControl::getPeersConnected(uint32_t serviceId, std::set &peerSet) { - RsStackMutex stack(mCtrlMtx); /***** LOCK STACK MUTEX ****/ + RS_STACK_MUTEX(mCtrlMtx); std::map >::iterator mit; mit = mServicePeerMap.find(serviceId); @@ -1004,9 +1004,9 @@ void p3ServiceControl::getPeersConnected(const uint32_t serviceId, std::set >::iterator mit; mit = mServicePeerMap.find(serviceId); diff --git a/libretroshare/src/pqi/p3servicecontrol.h b/libretroshare/src/pqi/p3servicecontrol.h index 6cabf1193..fa4e4f37e 100644 --- a/libretroshare/src/pqi/p3servicecontrol.h +++ b/libretroshare/src/pqi/p3servicecontrol.h @@ -95,8 +95,8 @@ virtual bool getServicePermissions(uint32_t serviceId, RsServicePermissions &per virtual bool updateServicePermissions(uint32_t serviceId, const RsServicePermissions &permissions); // Get List of Peers using this Service. -virtual void getPeersConnected(const uint32_t serviceId, std::set &peerSet); -virtual bool isPeerConnected(const uint32_t serviceId, const RsPeerId &peerId); +virtual void getPeersConnected(uint32_t serviceId, std::set &peerSet); +virtual bool isPeerConnected(uint32_t serviceId, const RsPeerId &peerId); // Gets the list of items used by that service virtual bool getServiceItemNames(uint32_t serviceId,std::map& names) ; diff --git a/libretroshare/src/retroshare/rsfiles.h b/libretroshare/src/retroshare/rsfiles.h index 9a312683f..1a77cb829 100644 --- a/libretroshare/src/retroshare/rsfiles.h +++ b/libretroshare/src/retroshare/rsfiles.h @@ -204,7 +204,7 @@ public: * as available or a sensible maximum. Expect a block size of around 1MiB. * To get more data, call this function repeatedly with different offsets. * - * jsonapi{development} + * @jsonapi{development,manualwrapper} * note the missing @ the wrapper for this is written manually not * autogenerated @see JsonApiServer. * diff --git a/libretroshare/src/retroshare/rsgxschannels.h b/libretroshare/src/retroshare/rsgxschannels.h index 383ebc7df..31bad28b4 100644 --- a/libretroshare/src/retroshare/rsgxschannels.h +++ b/libretroshare/src/retroshare/rsgxschannels.h @@ -200,8 +200,8 @@ public: * @brief Share channel publishing key * This can be used to authorize other peers to post on the channel * @jsonapi{development} - * param[in] groupId Channel id - * param[in] peers peers to which share the key + * @param[in] groupId Channel id + * @param[in] peers peers to which share the key * @return false on error, true otherwise */ virtual bool groupShareKeys( diff --git a/libretroshare/src/retroshare/rsiface.h b/libretroshare/src/retroshare/rsiface.h index 61111402a..83eda2eae 100644 --- a/libretroshare/src/retroshare/rsiface.h +++ b/libretroshare/src/retroshare/rsiface.h @@ -19,24 +19,22 @@ * along with this program. If not, see . * * * *******************************************************************************/ -#ifndef RETROSHARE_GUI_INTERFACE_H -#define RETROSHARE_GUI_INTERFACE_H +#pragma once #include "retroshare/rsnotify.h" #include "rstypes.h" #include +#include -class NotifyBase; class RsServer; class RsInit; class RsPeerCryptoParams; -struct TurtleFileInfo ; +class RsControl; -/* RsInit -> Configuration Parameters for RetroShare Startup - */ +/// RsInit -> Configuration Parameters for RetroShare Startup +RsInit* InitRsConfig(); -RsInit *InitRsConfig(); /* extract various options for GUI */ const char *RsConfigDirectory(RsInit *config); bool RsConfigStartMinimised(RsInit *config); @@ -46,35 +44,50 @@ void CleanupRsConfig(RsInit *); // Called First... (handles comandline options) int InitRetroShare(int argc, char **argv, RsInit *config); -class RsControl /* The Main Interface Class - for controlling the server */ +/** + * Pointer to global instance of RsControl needed to expose JSON API + * @jsonapi{development} + */ +extern RsControl* rsControl; + +/** The Main Interface Class - for controlling the server */ +class RsControl { public: /// TODO: This should return a reference instead of a pointer! - static RsControl *instance(); - static void earlyInitNotificationSystem() { instance(); } + static RsControl* instance(); + static void earlyInitNotificationSystem() { rsControl = instance(); } /* Real Startup Fn */ virtual int StartupRetroShare() = 0; - /** Check if core is fully ready, true only after - * StartupRetroShare() finish and before rsGlobalShutDown() begin + /** + * @brief Check if core is fully ready, true only after StartupRetroShare() + * finish and before rsGlobalShutDown() begin + * @jsonapi{development} */ virtual bool isReady() = 0; - /****************************************/ - /* Config */ + virtual void ConfigFinalSave() = 0; - virtual void ConfigFinalSave( ) = 0; - virtual void rsGlobalShutDown( ) = 0; + /** + * @brief Turn off RetroShare + * @jsonapi{development,manualwrapper} + */ + virtual void rsGlobalShutDown() = 0; - /****************************************/ + virtual bool getPeerCryptoDetails( + const RsPeerId& ssl_id,RsPeerCryptoParams& params ) = 0; + virtual void getLibraries(std::list &libraries) = 0; - virtual bool getPeerCryptoDetails(const RsPeerId& ssl_id,RsPeerCryptoParams& params) = 0; - virtual void getLibraries(std::list &libraries) = 0; + /** + * @brief Set shutdown callback, useful if main runlop is controlled by + * another entity such as QCoreApplication + * @param callback function to call when shutdown is almost complete + */ + virtual void setShutdownCallback(const std::function& callback) = 0; - protected: - RsControl() {} // should not be used, hence it's private. +protected: + RsControl() {} // should not be used, hence it's private. }; - -#endif diff --git a/libretroshare/src/retroshare/rsinit.h b/libretroshare/src/retroshare/rsinit.h index 731b111cc..5dfade3f8 100644 --- a/libretroshare/src/retroshare/rsinit.h +++ b/libretroshare/src/retroshare/rsinit.h @@ -72,7 +72,7 @@ public: * PreLogin * Call before init retroshare, initialises rsinitconfig's public attributes */ - static void InitRsConfig(); + static void InitRsConfig(); /*! * Should be called to load up ssl cert and private key, and intialises gpg @@ -166,15 +166,45 @@ public: static std::string systemDataDirectory(bool check = true); static std::string PGPDirectory(); - // PGP Accounts. - static int GetPGPLogins(std::list &pgpIds); + /** + * @brief Get available PGP identities id list + * @jsonapi{development,unauthenticated} + * @param[out] pgpIds storage for PGP id list + * @return true on success, false otherwise + */ + static int GetPGPLogins(std::list &pgpIds); static int GetPGPLoginDetails(const RsPgpId& id, std::string &name, std::string &email); static bool GeneratePGPCertificate(const std::string&, const std::string& email, const std::string& passwd, RsPgpId &pgpId, const int keynumbits, std::string &errString); // PGP Support Functions. static bool ExportIdentity(const std::string& fname,const RsPgpId& pgp_id) ; static bool ImportIdentity(const std::string& fname,RsPgpId& imported_pgp_id,std::string& import_error) ; - static bool ImportIdentityFromString(const std::string& data,RsPgpId& imported_pgp_id,std::string& import_error) ; + + /** + * @brief Import full encrypted PGP identity from string + * @jsonapi{development,unauthenticated} + * @param[in] data certificate string + * @param[out] pgpId storage for the PGP fingerprint of the imported key + * @param[out] errorMsg storage for eventual human readable error message + * @return true on success, false otherwise + */ + static bool ImportIdentityFromString( + const std::string& data, RsPgpId& pgpId, + std::string& errorMsg ); + + /** + * @brief Export full encrypted PGP identity to string + * @jsonapi{development} + * @param[out] data storage for certificate string + * @param[in] pgpId PGP id to export + * @param[in] includeSignatures true to include signatures + * @param[out] errorMsg storage for eventual human readable error message + * @return true on success, false otherwise + */ + static bool exportIdentityToString( + std::string& data, const RsPgpId& pgpId, std::string& errorMsg, + bool includeSignatures = true ); + static void GetUnsupportedKeys(std::map > &unsupported_keys); static bool CopyGnuPGKeyrings() ; @@ -233,7 +263,7 @@ struct RsLoginHelper { /** * @brief Normal way to attempt login - * @jsonapi{development} + * @jsonapi{development,manualwrapper} * @param[in] account Id of the account to which attempt login * @param[in] password Password for the given account * @return RsInit::OK if login attempt success, error code otherwhise @@ -255,14 +285,14 @@ struct RsLoginHelper /** * @brief Get locations and associated information - * @jsonapi{development} + * @jsonapi{development,unauthenticated} * @param[out] locations storage for the retrived locations */ void getLocations(std::vector& locations); /** * @brief Creates a new RetroShare location, and log in once is created - * @jsonapi{development} + * @jsonapi{development,unauthenticated} * @param[inout] location provide input information to generate the location * and storage to output the data of the generated location * @param[in] password to protect and unlock the associated PGP key @@ -280,7 +310,7 @@ struct RsLoginHelper /** * @brief Check if RetroShare is already logged in, this usually return true * after a successfull attemptLogin() and before closeSession() - * @jsonapi{development} + * @jsonapi{development,unauthenticated} * @return true if already logged in, false otherwise */ bool isLoggedIn(); diff --git a/libretroshare/src/retroshare/rsservicecontrol.h b/libretroshare/src/retroshare/rsservicecontrol.h index 726b3efb4..f588e9719 100644 --- a/libretroshare/src/retroshare/rsservicecontrol.h +++ b/libretroshare/src/retroshare/rsservicecontrol.h @@ -36,7 +36,7 @@ class RsServiceControl; * Pointer to global instance of RsServiceControl service implementation * @jsonapi{development} */ -extern RsServiceControl *rsServiceControl; +extern RsServiceControl* rsServiceControl; struct RsServiceInfo : RsSerializable { @@ -131,9 +131,9 @@ public: virtual ~RsServiceControl(){} /** - * @brief getOwnServices return a map off all services. + * @brief get a map off all services. * @jsonapi{development} - * @param[out] info + * @param[out] info storage for service information * @return always true */ virtual bool getOwnServices(RsPeerServiceInfo &info) = 0; @@ -197,7 +197,8 @@ public: * @param[in] serviceId service to look up. * @param[out] peerSet set of peers using this service. */ - virtual void getPeersConnected(const uint32_t serviceId, std::set &peerSet) = 0; + virtual void getPeersConnected( uint32_t serviceId, + std::set& peerSet ) = 0; }; #endif diff --git a/libretroshare/src/rsitems/rsserviceids.h b/libretroshare/src/rsitems/rsserviceids.h index 4c7f26fd8..69ec0962c 100644 --- a/libretroshare/src/rsitems/rsserviceids.h +++ b/libretroshare/src/rsitems/rsserviceids.h @@ -79,6 +79,7 @@ const uint16_t RS_SERVICE_GXS_TYPE_GXSCIRCLE = 0x0218; const uint16_t RS_SERVICE_GXS_TYPE_REPUTATION = 0x0219; const uint16_t RS_SERVICE_TYPE_GXS_RECOGN = 0x0220; const uint16_t RS_SERVICE_TYPE_GXS_TRANS = 0x0230; +const uint16_t RS_SERVICE_TYPE_JSONAPI = 0x0240; const uint16_t RS_SERVICE_GXS_TYPE_FORUMS_CONFIG = 0x0315; const uint16_t RS_SERVICE_GXS_TYPE_CHANNELS_CONFIG = 0x0317; diff --git a/libretroshare/src/rsserver/p3face-config.cc b/libretroshare/src/rsserver/p3face-config.cc index bb1ca6b04..348d0bb8f 100644 --- a/libretroshare/src/rsserver/p3face-config.cc +++ b/libretroshare/src/rsserver/p3face-config.cc @@ -33,7 +33,7 @@ #include "retroshare/rsinit.h" #include "plugins/pluginmanager.h" #include "util/rsdebug.h" -//const int p3facemsgzone = 11453; +#include "jsonapi/jsonapi.h" #include #include @@ -92,6 +92,10 @@ void RsServer::rsGlobalShutDown() mNetMgr->shutdown(); /* Handles UPnP */ +#ifdef RS_JSONAPI + if(jsonApiServer) jsonApiServer->shutdown(); +#endif + rsAutoProxyMonitor::instance()->stopAllRSShutdown(); fullstop() ; @@ -117,4 +121,6 @@ void RsServer::rsGlobalShutDown() // #endif AuthGPG::exit(); + + mShutdownCallback(0); } diff --git a/libretroshare/src/rsserver/p3face-server.cc b/libretroshare/src/rsserver/p3face-server.cc index 336279fcd..b8d1552dc 100644 --- a/libretroshare/src/rsserver/p3face-server.cc +++ b/libretroshare/src/rsserver/p3face-server.cc @@ -55,6 +55,9 @@ int rsserverzone = 101; #include #endif + +/*extern*/ RsControl* rsControl = nullptr; + static double getCurrentTS() { diff --git a/libretroshare/src/rsserver/p3face.h b/libretroshare/src/rsserver/p3face.h index fdad62a86..01a51caeb 100644 --- a/libretroshare/src/rsserver/p3face.h +++ b/libretroshare/src/rsserver/p3face.h @@ -19,8 +19,9 @@ * along with this program. If not, see . * * * *******************************************************************************/ -#ifndef MRK_P3RS_INTERFACE_H -#define MRK_P3RS_INTERFACE_H +#pragma once + +#include //#include "server/filedexserver.h" #include "ft/ftserver.h" @@ -74,19 +75,19 @@ class RsPluginManager; class RsServer: public RsControl, public RsTickingThread { - public: - /****************************************/ - /* p3face-startup.cc: init... */ - virtual int StartupRetroShare(); +public: + RsServer(); + virtual ~RsServer(); - /****************************************/ - /* p3face.cc: main loop / util fns / locking. */ + virtual int StartupRetroShare(); /// @see RsControl::isReady() virtual bool isReady() { return coreReady; } - RsServer() ; - virtual ~RsServer(); + /// @see RsControl::setShutdownCallback + void setShutdownCallback(const std::function& callback) + { mShutdownCallback = callback; } + /* Thread Fn: Run the Core */ virtual void data_tick(); @@ -202,6 +203,9 @@ class RsServer: public RsControl, public RsTickingThread static const double maxTimeDelta; static const double kickLimit; + /// @see RsControl::setShutdownCallback + std::function mShutdownCallback; + /** Keep track of the core being fully ready, true only after * StartupRetroShare() finish and before rsGlobalShutDown() begin */ @@ -213,5 +217,3 @@ class RsServer: public RsControl, public RsTickingThread */ std::string make_path_unix(std::string winpath); - -#endif diff --git a/libretroshare/src/rsserver/rsaccounts.cc b/libretroshare/src/rsserver/rsaccounts.cc index 5bcd04c8e..557981dcc 100644 --- a/libretroshare/src/rsserver/rsaccounts.cc +++ b/libretroshare/src/rsserver/rsaccounts.cc @@ -930,6 +930,14 @@ bool RsAccountsDetail::importIdentityFromString(const std::string &data, RsPgpId return AuthGPG::getAuthGPG()->importProfileFromString(data, imported_pgp_id, import_error); } +bool RsAccountsDetail::exportIdentityToString( + std::string& data, const RsPgpId& pgpId, bool includeSignatures, + std::string& errorMsg ) +{ + return AuthGPG::getAuthGPG()->exportIdentityToString( + data, pgpId, includeSignatures, errorMsg ); +} + bool RsAccountsDetail::copyGnuPGKeyrings() { std::string pgp_dir = PathPGPDirectory() ; @@ -1344,6 +1352,14 @@ bool RsAccounts::ImportIdentityFromString(const std::string& data,RsPgpId& im return rsAccountsDetails->importIdentityFromString(data,imported_pgp_id,import_error); } +/*static*/ bool RsAccounts::exportIdentityToString( + std::string& data, const RsPgpId& pgpId, std::string& errorMsg, + bool includeSignatures ) +{ + return rsAccountsDetails->exportIdentityToString( + data, pgpId, includeSignatures, errorMsg); +} + void RsAccounts::GetUnsupportedKeys(std::map > &unsupported_keys) { return rsAccountsDetails->getUnsupportedKeys(unsupported_keys); diff --git a/libretroshare/src/rsserver/rsaccounts.h b/libretroshare/src/rsserver/rsaccounts.h index 518885a47..2db4552a7 100644 --- a/libretroshare/src/rsserver/rsaccounts.h +++ b/libretroshare/src/rsserver/rsaccounts.h @@ -99,6 +99,9 @@ class RsAccountsDetail // PGP Support Functions. bool exportIdentity(const std::string& fname,const RsPgpId& pgp_id) ; bool importIdentity(const std::string& fname,RsPgpId& imported_pgp_id,std::string& import_error) ; + bool exportIdentityToString( + std::string& data, const RsPgpId& pgpId, bool includeSignatures, + std::string& errorMsg ); bool importIdentityFromString(const std::string& data,RsPgpId& imported_pgp_id,std::string& import_error) ; void getUnsupportedKeys(std::map > &unsupported_keys); bool copyGnuPGKeyrings() ; diff --git a/libretroshare/src/rsserver/rsinit.cc b/libretroshare/src/rsserver/rsinit.cc index e334895cd..2a398bc0d 100644 --- a/libretroshare/src/rsserver/rsinit.cc +++ b/libretroshare/src/rsserver/rsinit.cc @@ -94,6 +94,10 @@ RsDht *rsDht = NULL ; # include "gxstrans/p3gxstrans.h" #endif +#ifdef RS_JSONAPI +# include "jsonapi/jsonapi.h" +#endif + // #define GPG_DEBUG // #define AUTHSSL_DEBUG // #define FIM_DEBUG @@ -105,15 +109,15 @@ RsLoginHelper* rsLoginHelper = nullptr; RsAccounts* rsAccounts = nullptr; -class RsInitConfig +struct RsInitConfig { - public: + RsInitConfig() : jsonApiPort(0), jsonApiBindAddress("127.0.0.1") {} - RsFileHash main_executable_hash; + RsFileHash main_executable_hash; #ifdef WINDOWS_SYS - bool portable; - bool isWindowsXP; + bool portable; + bool isWindowsXP; #endif rs_lock_handle_t lockHandle; @@ -146,31 +150,25 @@ class RsInitConfig int debugLevel; std::string logfname; - bool load_trustedpeer; - std::string load_trustedpeer_file; - bool udpListenerOnly; std::string opModeStr; + + uint16_t jsonApiPort; + std::string jsonApiBindAddress; }; -static RsInitConfig *rsInitConfig = NULL; - -//const int p3facestartupzone = 47238; - -// initial configuration bootstrapping... -//static const std::string configInitFile = "default_cert.txt"; -//static const std::string configConfFile = "config.rs"; -//static const std::string configCertDir = "friends"; -//static const std::string configKeyDir = "keys"; -//static const std::string configCaFile = "cacerts.pem"; -//static const std::string configHelpName = "retro.htm"; +static RsInitConfig* rsInitConfig = nullptr; static const std::string configLogFileName = "retro.log"; static const int SSLPWD_LEN = 64; void RsInit::InitRsConfig() { - rsInitConfig = new RsInitConfig ; + rsInitConfig = new RsInitConfig; + + + /* TODO almost all of this should be moved to RsInitConfig::RsInitConfig + * initializers */ /* Directories */ #ifdef WINDOWS_SYS @@ -181,14 +179,13 @@ void RsInit::InitRsConfig() rsInitConfig->hiddenNodeSet = false; + // This doesn't seems a configuration... #ifndef WINDOWS_SYS rsInitConfig->lockHandle = -1; #else rsInitConfig->lockHandle = NULL; #endif - - rsInitConfig->load_trustedpeer = false; rsInitConfig->port = 0 ; rsInitConfig->forceLocalAddr = false; rsInitConfig->haveLogFile = false; @@ -204,9 +201,6 @@ void RsInit::InitRsConfig() rsInitConfig->udpListenerOnly = false; rsInitConfig->opModeStr = std::string(""); - /* setup the homePath (default save location) */ - // rsInitConfig->homePath = getHomePath(); - #ifdef WINDOWS_SYS // test for portable version if (GetFileAttributes(L"portable") != (DWORD) -1) { @@ -243,31 +237,7 @@ void RsInit::InitRsConfig() } #endif - /* Setup the Debugging */ - // setup debugging for desired zones. - setOutputLevel(RsLog::Warning); // default to Warnings. - - // For Testing purposes. - // We can adjust everything under Linux. - //setZoneLevel(PQL_DEBUG_BASIC, 38422); // pqipacket. - //setZoneLevel(PQL_DEBUG_BASIC, 96184); // pqinetwork; - //setZoneLevel(PQL_DEBUG_BASIC, 82371); // pqiperson. - //setZoneLevel(PQL_DEBUG_BASIC, 34283); // pqihandler. - //setZoneLevel(PQL_DEBUG_BASIC, 44863); // discItems. - //setZoneLevel(PQL_DEBUG_BASIC, 1728); // pqi/p3proxy - //setZoneLevel(PQL_DEBUG_BASIC, 1211); // sslroot. - //setZoneLevel(PQL_DEBUG_BASIC, 37714); // pqissl. - //setZoneLevel(PQL_DEBUG_BASIC, 8221); // pqistreamer. - //setZoneLevel(PQL_DEBUG_BASIC, 9326); // pqiarchive - //setZoneLevel(PQL_DEBUG_BASIC, 3334); // p3channel. - //setZoneLevel(PQL_DEBUG_BASIC, 354); // pqipersongrp. - //setZoneLevel(PQL_DEBUG_BASIC, 6846); // pqiudpproxy - //setZoneLevel(PQL_DEBUG_BASIC, 3144); // pqissludp; - //setZoneLevel(PQL_DEBUG_BASIC, 86539); // pqifiler. - //setZoneLevel(PQL_DEBUG_BASIC, 91393); // Funky_Browser. - //setZoneLevel(PQL_DEBUG_BASIC, 25915); // fltkserver - //setZoneLevel(PQL_DEBUG_BASIC, 47659); // fldxsrvr - //setZoneLevel(PQL_DEBUG_BASIC, 49787); // pqissllistener + setOutputLevel(RsLog::Warning); } /******** @@ -294,69 +264,60 @@ bool doPortRestrictions = false; int RsInit::InitRetroShare(int argc, char **argv, bool /* strictCheck */) { #ifdef DEBUG_RSINIT - for(int i=0; ilogfname = "" ; - //rsInitConfig->inet = "" ; + std::string prefUserString = ""; + std::string opt_base_dir; #ifdef __APPLE__ - /* HACK to avoid stupid OSX Finder behaviour + // TODO: is this still needed with argstream? + /* HACK to avoid stupid OSX Finder behaviour * remove the commandline arguments - if we detect we are launched from Finder, * and we have the unparsable "-psn_0_12332" option. * this is okay, as you cannot pass commandline arguments via Finder anyway */ - if ((argc >= 2) && (0 == strncmp(argv[1], "-psn", 4))) - { - argc = 1; - } + if ((argc >= 2) && (0 == strncmp(argv[1], "-psn", 4))) argc = 1; #endif - argstream as(argc,argv) ; + argstream as(argc,argv); + as >> option('m',"minimized" ,rsInitConfig->startMinimised ,"Start minimized." ) + >> option('s',"stderr" ,rsInitConfig->outStderr ,"output to stderr instead of log file." ) + >> option('u',"udp" ,rsInitConfig->udpListenerOnly,"Only listen to UDP." ) + >> option('e',"external-port" ,rsInitConfig->forceExtPort ,"Use a forwarded external port." ) + >> parameter('l',"log-file" ,rsInitConfig->logfname ,"logfile" ,"Set Log filename." ,false) + >> parameter('d',"debug-level" ,rsInitConfig->debugLevel ,"level" ,"Set debug level." ,false) + >> parameter('i',"ip-address" ,rsInitConfig->inet ,"nnn.nnn.nnn.nnn", "Force IP address to use (if cannot be detected)." ,false) + >> parameter('o',"opmode" ,rsInitConfig->opModeStr ,"opmode" ,"Set Operating mode (Full, NoTurtle, Gaming, Minimal)." ,false) + >> parameter('p',"port" ,rsInitConfig->port ,"port", "Set listenning port to use." ,false) + >> parameter('c',"base-dir" ,opt_base_dir ,"directory", "Set base directory." ,false) + >> parameter('U',"user-id" ,prefUserString ,"ID", "[ocation Id] Sets Account to Use, Useful when Autologin is enabled.",false); +#ifdef RS_JSONAPI + as >> parameter( + "jsonApiPort", rsInitConfig->jsonApiPort, "jsonApiPort", + "Enable JSON API on the specified port", false ) + >> parameter( + "jsonApiBindAddress", rsInitConfig->jsonApiBindAddress, + "jsonApiBindAddress", "JSON API Bind Address.", false); +#endif // ifdef RS_JSONAPI - as -#ifdef RS_AUTOLOGIN - >> option('a',"auto-login" ,rsInitConfig->autoLogin ,"AutoLogin (Windows Only) + StartMinimised") -#endif - >> option('m',"minimized" ,rsInitConfig->startMinimised ,"Start minimized." ) - >> option('s',"stderr" ,rsInitConfig->outStderr ,"output to stderr instead of log file." ) - >> option('u',"udp" ,rsInitConfig->udpListenerOnly,"Only listen to UDP." ) - >> option('e',"external-port" ,rsInitConfig->forceExtPort ,"Use a forwarded external port." ) - - >> parameter('l',"log-file" ,rsInitConfig->logfname ,"logfile" ,"Set Log filename." ,false) - >> parameter('d',"debug-level" ,rsInitConfig->debugLevel ,"level" ,"Set debug level." ,false) -#ifdef TO_REMOVE - // This was removed because it is not used anymore. - >> parameter('w',"password" ,rsInitConfig->passwd ,"password" ,"Set Login Password." ,false) -#endif - >> parameter('i',"ip-address" ,rsInitConfig->inet ,"nnn.nnn.nnn.nnn", "Force IP address to use (if cannot be detected)." ,false) - >> parameter('o',"opmode" ,rsInitConfig->opModeStr ,"opmode" ,"Set Operating mode (Full, NoTurtle, Gaming, Minimal)." ,false) - >> parameter('p',"port" ,rsInitConfig->port ,"port", "Set listenning port to use." ,false) - >> parameter('c',"base-dir" ,opt_base_dir ,"directory", "Set base directory." ,false) - >> parameter('U',"user-id" ,prefUserString ,"ID", "[ocation Id] Sets Account to Use, Useful when Autologin is enabled.",false) - // by rshare 'r' "link" "Link" "Open RsLink with protocol retroshare://" - // by rshare 'f' "rsfile" "RsFile" "Open RsFile like RsCollection" #ifdef LOCALNET_TESTING - >> parameter('R',"restrict-port" ,portRestrictions ,"port1-port2","Apply port restriction" ,false) -#endif - >> help('h',"help","Display this Help") ; + as >> parameter('R',"restrict-port" ,portRestrictions ,"port1-port2","Apply port restriction" ,false); +#endif // ifdef LOCALNET_TESTING - as.defaultErrorHandling(true,true) ; +#ifdef RS_AUTOLOGIN + as >> option('a',"auto-login" ,rsInitConfig->autoLogin ,"AutoLogin (Windows Only) + StartMinimised"); +#endif // ifdef RS_AUTOLOGIN + + as >> help('h',"help","Display this Help"); + as.defaultErrorHandling(true,true); if(rsInitConfig->autoLogin) rsInitConfig->startMinimised = true ; if(rsInitConfig->outStderr) rsInitConfig->haveLogFile = false ; @@ -477,6 +438,16 @@ int RsInit::InitRetroShare(int argc, char **argv, bool /* strictCheck */) } #endif +#ifdef RS_JSONAPI + if(rsInitConfig->jsonApiPort) + { + jsonApiServer = new JsonApiServer( + rsInitConfig->jsonApiPort, + rsInitConfig->jsonApiBindAddress ); + jsonApiServer->start("JSON API Server"); + } +#endif // ifdef RS_JSONAPI + return RS_INIT_OK; } @@ -1277,6 +1248,14 @@ int RsServer::StartupRetroShare() // mPluginsManager->loadPlugins(programatically_inserted_plugins) ; +#ifdef RS_JSONAPI + { + mConfigMgr->addConfiguration("jsonApi.cfg", jsonApiServer); + RsFileHash dummyHash; + jsonApiServer->loadConfiguration(dummyHash); + } +#endif + /**** Reputation system ****/ p3GxsReputation *mReputations = new p3GxsReputation(mLinkMgr) ; @@ -1940,6 +1919,7 @@ int RsServer::StartupRetroShare() RsInit::LoadCertificateStatus RsLoginHelper::attemptLogin( const RsPeerId& account, const std::string& password) { + if(isLoggedIn()) return RsInit::ERR_ALREADY_RUNNING; if(!rsNotify->cachePgpPassphrase(password)) return RsInit::ERR_UNKOWN; if(!rsNotify->setDisableAskPassword(true)) return RsInit::ERR_UNKOWN; if(!RsAccounts::SelectAccount(account)) return RsInit::ERR_UNKOWN; @@ -1973,6 +1953,8 @@ bool RsLoginHelper::createLocation( RsLoginHelper::Location& l, const std::string& password, bool makeHidden, bool makeAutoTor, std::string& errorMessage ) { + if(isLoggedIn()) return (errorMessage="Already Running", false); + if(l.mLocationName.empty()) { errorMessage = "Location name is needed"; diff --git a/retroshare-android-service/src/service.cpp b/retroshare-android-service/src/service.cpp index 9aa54f519..7f453ead6 100644 --- a/retroshare-android-service/src/service.cpp +++ b/retroshare-android-service/src/service.cpp @@ -90,6 +90,7 @@ int main(int argc, char *argv[]) RsInit::InitRsConfig(); RsInit::InitRetroShare(argc, argv, true); RsControl::earlyInitNotificationSystem(); + rsControl->setShutdownCallback(QCoreApplication::exit); QObject::connect( &app, &QCoreApplication::aboutToQuit, [](){ @@ -97,71 +98,5 @@ int main(int argc, char *argv[]) RsControl::instance()->rsGlobalShutDown(); } ); #endif // ifdef LIBRESAPI_LOCAL_SERVER -#ifdef RS_JSONAPI - uint16_t jsonApiPort = 9092; - std::string jsonApiBindAddress = "127.0.0.1"; - - { - QCommandLineOption jsonApiPortOpt( - "jsonApiPort", "JSON API listening port.", "port", "9092"); - QCommandLineOption jsonApiBindAddressOpt( - "jsonApiBindAddress", "JSON API Bind Address.", - "IP Address", "127.0.0.1"); - - QCommandLineParser cmdParser; - cmdParser.addHelpOption(); - cmdParser.addOption(jsonApiPortOpt); - cmdParser.addOption(jsonApiBindAddressOpt); - - cmdParser.parse(app.arguments()); - - if(cmdParser.isSet(jsonApiPortOpt)) - { - QString jsonApiPortStr = cmdParser.value(jsonApiPortOpt); - bool portOk; - jsonApiPort = jsonApiPortStr.toUShort(&portOk); - if(!portOk) - { - std::cerr << "ERROR: jsonApiPort option value must be a valid " - << "TCP port!" << std::endl; - cmdParser.showHelp(); - QCoreApplication::exit(EINVAL); - } - } - - if(cmdParser.isSet(jsonApiBindAddressOpt)) - { - sockaddr_storage tmp; - jsonApiBindAddress = - cmdParser.value(jsonApiBindAddressOpt).toStdString(); - if(!sockaddr_storage_inet_pton(tmp, jsonApiBindAddress)) - { - std::cerr << "ERROR: jsonApiBindAddress option value must " - << "be a valid IP address!" << std::endl; - cmdParser.showHelp(); - QCoreApplication::exit(EINVAL); - } - } - } - - JsonApiServer jas( jsonApiPort, jsonApiBindAddress, - [](int ec) { QCoreApplication::exit(ec); } ); - jas.start(); - - { - sockaddr_storage tmp; - sockaddr_storage_inet_pton(tmp, jsonApiBindAddress); - sockaddr_storage_setport(tmp, jsonApiPort); - sockaddr_storage_ipv6_to_ipv4(tmp); - RsUrl tmpUrl(sockaddr_storage_tostring(tmp)); - tmpUrl.setScheme("http"); - - std::cerr << "JSON API listening on " - << tmpUrl.toString() - << std::endl; - } - -#endif // ifdef RS_JSONAPI - return app.exec(); } diff --git a/retroshare-nogui/src/retroshare.cc b/retroshare-nogui/src/retroshare.cc index c9ff35022..cbc86355d 100644 --- a/retroshare-nogui/src/retroshare.cc +++ b/retroshare-nogui/src/retroshare.cc @@ -24,15 +24,15 @@ * */ -#include /* definition of iface */ -#include /* definition of iface */ - +#include "retroshare/rsiface.h" +#include "retroshare/rsinit.h" #include "notifytxt.h" - -#include #include "util/argstream.h" #include "util/rstime.h" + +#include #include + #ifdef WINDOWS_SYS #include #endif @@ -43,18 +43,12 @@ #ifdef ENABLE_WEBUI #include +#include #include "api/ApiServerMHD.h" #include "api/RsControlModule.h" #include "TerminalApiClient.h" #endif -#ifdef RS_JSONAPI -# include -# include "jsonapi/jsonapi.h" -# include "util/rsnet.h" -# include "util/rsurl.h" -#endif // RS_JSONAPI - /* Basic instructions for running libretroshare as background thread. * ******************************************************************* * * This allows your program to communicate with authenticated peers. @@ -67,45 +61,6 @@ int main(int argc, char **argv) { -#ifdef RS_JSONAPI - JsonApiServer* jsonApiServer = nullptr; - uint16_t jsonApiPort = 0; - std::string jsonApiBindAddress = "127.0.0.1"; - - { - argstream jsonApiArgs(argc, argv); - jsonApiArgs >> parameter( - "jsonApiPort", jsonApiPort, "jsonApiPort", - "Enable JSON API on the specified port", false ); - jsonApiArgs >> parameter( - "jsonApiBindAddress", jsonApiBindAddress, - "jsonApiBindAddress", "JSON API Bind Address.", false); - jsonApiArgs >> help('h', "help", "Display this Help"); - - if (jsonApiArgs.helpRequested()) - std::cerr << jsonApiArgs.usage() << std::endl; - } - - if(jsonApiPort) - { - jsonApiServer = new JsonApiServer( - jsonApiPort, jsonApiBindAddress, - [](int /*ec*/) { std::raise(SIGTERM); } ); - - jsonApiServer->start("JSON API Server"); - - sockaddr_storage tmp; - sockaddr_storage_inet_pton(tmp, jsonApiBindAddress); - sockaddr_storage_setport(tmp, jsonApiPort); - sockaddr_storage_ipv6_to_ipv4(tmp); - RsUrl tmpUrl(sockaddr_storage_tostring(tmp)); - tmpUrl.setScheme("http"); - - std::cerr << "JSON API listening on " << tmpUrl.toString() - << std::endl; - } -#endif // RS_JSONAPI - #ifdef ENABLE_WEBUI std::string docroot = resource_api::getDefaultDocroot(); @@ -146,11 +101,14 @@ int main(int argc, char **argv) httpd->start(); } - resource_api::TerminalApiClient tac(&api); + RsControl::earlyInitNotificationSystem(); + rsControl->setShutdownCallback([](int){std::raise(SIGTERM);}); + + resource_api::TerminalApiClient tac(&api); tac.start(); bool already = false ; - while(ctrl_mod.processShouldExit() == false) + while(!ctrl_mod.processShouldExit()) { rstime::rs_usleep(1000*1000); @@ -170,28 +128,6 @@ int main(int argc, char **argv) return 0; #endif - /* Retroshare startup is configured using an RsInit object. - * This is an opaque class, which the user cannot directly tweak - * If you want to peek at whats happening underneath look in - * libretroshare/src/rsserver/p3face-startup.cc - * - * You create it with InitRsConfig(), and delete with CleanupRsConfig() - * InitRetroshare(argv, argc, config) parses the command line options, - * and initialises the config paths. - * - * *** There are several functions that I should add to modify - * **** the config the moment these can only be set via the commandline - * - RsConfigDirectory(...) is probably the most useful. - * - RsConfigNetAddr(...) for setting port, etc. - * - RsConfigOutput(...) for logging and debugging. - * - * Next you need to worry about loading your certificate, or making - * a new one: - * - * RsGenerateCertificate(...) To create a new key, certificate - * LoadPassword(...) set password for existing certificate. - **/ - bool strictCheck = true; RsInit::InitRsConfig(); int initResult = RsInit::InitRetroShare(argc, argv, strictCheck); @@ -220,10 +156,13 @@ int main(int argc, char **argv) * if you want to receive notifications of events */ // This is needed to allocate rsNotify, so that it can be used to ask for PGP passphrase - // - RsControl::earlyInitNotificationSystem() ; + RsControl::earlyInitNotificationSystem(); - NotifyTxt *notify = new NotifyTxt() ; + // an atomic might be safer but is probably unneded for this simple usage + bool keepRunning = true; + rsControl->setShutdownCallback([&](int){keepRunning = false;}); + + NotifyTxt *notify = new NotifyTxt(); rsNotify->registerNotifyClient(notify); /* PreferredId => Key + Certificate are loaded into libretroshare */ @@ -244,35 +183,19 @@ int main(int argc, char **argv) } /* Start-up libretroshare server threads */ - RsControl::instance() -> StartupRetroShare(); + RsControl::instance()->StartupRetroShare(); #ifdef RS_INTRO_SERVER RsIntroServer rsIS; #endif - - /* pass control to the GUI */ - while(1) - { - //std::cerr << "GUI Tick()" << std::endl; + while(keepRunning) + { #ifdef RS_INTRO_SERVER rsIS.tick(); #endif - - int rt = 0; - // If we have a MenuTerminal ... - // only want to sleep if there is no input. (rt == 0). - if (rt == 0) - { -#ifndef WINDOWS_SYS - sleep(1); -#else - Sleep(1000); -#endif - } - - rstime::rs_usleep(1000); - + rstime::rs_usleep(10*1000); } - return 1; -} + + return 0; +} diff --git a/tests/librssimulator/peer/FakeServiceControl.h b/tests/librssimulator/peer/FakeServiceControl.h index f1ef73d3b..7bd47acd6 100644 --- a/tests/librssimulator/peer/FakeServiceControl.h +++ b/tests/librssimulator/peer/FakeServiceControl.h @@ -15,7 +15,7 @@ class FakeServiceControl: public p3ServiceControl { } - virtual void getPeersConnected(const uint32_t serviceId, std::set &peerSet) + virtual void getPeersConnected(uint32_t serviceId, std::set &peerSet) { (void) serviceId; std::list ids ; From add97dede08cd3cb7427f83338ab95184b0d8e42 Mon Sep 17 00:00:00 2001 From: Gioacchino Mazzurco Date: Wed, 19 Sep 2018 22:02:07 +0200 Subject: [PATCH 2/6] p3face include jsonapi.h only if enabled --- libretroshare/src/rsserver/p3face-config.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libretroshare/src/rsserver/p3face-config.cc b/libretroshare/src/rsserver/p3face-config.cc index 348d0bb8f..f01d4589b 100644 --- a/libretroshare/src/rsserver/p3face-config.cc +++ b/libretroshare/src/rsserver/p3face-config.cc @@ -33,7 +33,10 @@ #include "retroshare/rsinit.h" #include "plugins/pluginmanager.h" #include "util/rsdebug.h" -#include "jsonapi/jsonapi.h" + +#ifdef RS_JSONAPI +# include "jsonapi/jsonapi.h" +#endif // ifdef RS_JSONAPI #include #include From bf981661975b2dda594d17ae367adeb6f873ec71 Mon Sep 17 00:00:00 2001 From: Gioacchino Mazzurco Date: Fri, 21 Sep 2018 01:39:48 +0200 Subject: [PATCH 3/6] RsServer fix crash on stop if shutdown callback not set --- libretroshare/src/rsserver/p3face-server.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libretroshare/src/rsserver/p3face-server.cc b/libretroshare/src/rsserver/p3face-server.cc index b8d1552dc..9be996372 100644 --- a/libretroshare/src/rsserver/p3face-server.cc +++ b/libretroshare/src/rsserver/p3face-server.cc @@ -83,7 +83,8 @@ const double RsServer::kickLimit = 0.15; RsServer::RsServer() : - coreMutex("RsServer"), coreReady(false) + coreMutex("RsServer"), mShutdownCallback([](int){}), + coreReady(false) { // This is needed asap. // From 1c7f02655ec390b0a04b0c6c137e5643f9f4fff5 Mon Sep 17 00:00:00 2001 From: Gioacchino Mazzurco Date: Fri, 21 Sep 2018 01:40:26 +0200 Subject: [PATCH 4/6] Fix crash at startup if jsonapi not enabled --- libretroshare/src/rsserver/rsinit.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/libretroshare/src/rsserver/rsinit.cc b/libretroshare/src/rsserver/rsinit.cc index 2a398bc0d..fe2f4c991 100644 --- a/libretroshare/src/rsserver/rsinit.cc +++ b/libretroshare/src/rsserver/rsinit.cc @@ -1249,6 +1249,7 @@ int RsServer::StartupRetroShare() mPluginsManager->loadPlugins(programatically_inserted_plugins) ; #ifdef RS_JSONAPI + if(jsonApiServer) // JsonApiServer may be disabled at runtime { mConfigMgr->addConfiguration("jsonApi.cfg", jsonApiServer); RsFileHash dummyHash; From ccabf82e60b9ef1128f5f3b010d11ce43e0cd37d Mon Sep 17 00:00:00 2001 From: Gioacchino Mazzurco Date: Fri, 21 Sep 2018 01:58:38 +0200 Subject: [PATCH 5/6] retroshare-gui now handle JsonApiServer too --- .../src/gui/settings/JsonApiPage.cc | 173 ++++++++++++++++++ retroshare-gui/src/gui/settings/JsonApiPage.h | 58 ++++++ .../src/gui/settings/JsonApiPage.ui | 147 +++++++++++++++ retroshare-gui/src/gui/settings/WebuiPage.cpp | 18 -- retroshare-gui/src/gui/settings/WebuiPage.h | 7 - .../src/gui/settings/rsettingswin.cpp | 10 +- .../src/gui/settings/rsharesettings.cpp | 42 +++++ .../src/gui/settings/rsharesettings.h | 14 ++ retroshare-gui/src/main.cpp | 12 ++ retroshare-gui/src/retroshare-gui.pro | 6 + 10 files changed, 461 insertions(+), 26 deletions(-) create mode 100644 retroshare-gui/src/gui/settings/JsonApiPage.cc create mode 100644 retroshare-gui/src/gui/settings/JsonApiPage.h create mode 100644 retroshare-gui/src/gui/settings/JsonApiPage.ui diff --git a/retroshare-gui/src/gui/settings/JsonApiPage.cc b/retroshare-gui/src/gui/settings/JsonApiPage.cc new file mode 100644 index 000000000..f01b6e345 --- /dev/null +++ b/retroshare-gui/src/gui/settings/JsonApiPage.cc @@ -0,0 +1,173 @@ +/* + * RetroShare JSON API + * Copyright (C) 2018 Gioacchino Mazzurco + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "JsonApiPage.h" + +#include "rsharesettings.h" +#include "jsonapi/jsonapi.h" +#include "util/misc.h" + +#include +#include +#include + + +JsonApiPage::JsonApiPage(QWidget */*parent*/, Qt::WindowFlags /*flags*/) +{ + ui.setupUi(this); + connect( ui.addTokenPushButton, &QPushButton::clicked, + this, &JsonApiPage::addTokenClicked); + connect( ui.removeTokenPushButton, &QPushButton::clicked, + this, &JsonApiPage::removeTokenClicked ); + connect( ui.tokensListView, &QListView::clicked, + this, &JsonApiPage::tokenClicked ); + connect( ui.applyConfigPushButton, &QPushButton::clicked, + this, &JsonApiPage::onApplyClicked ); +} + +bool JsonApiPage::updateParams(QString &errmsg) +{ + bool ok = true; + bool changed = false; + + bool enabled = ui.enableCheckBox->isChecked(); + if( enabled != Settings->getJsonApiEnabled()) + { + Settings->setJsonApiEnabled(enabled); + changed = true; + } + + uint16_t port = ui.portSpinBox->value(); + if(port != Settings->getJsonApiPort()) + { + Settings->setJsonApiPort(port); + changed = true; + } + + QString listenAddress = ui.listenAddressLineEdit->text(); + if(listenAddress != Settings->getJsonApiListenAddress()) + { + Settings->setJsonApiListenAddress(listenAddress); + changed = true; + } + + if(changed) + { + checkShutdownJsonApi(); + ok = checkStartJsonApi(); + } + + if(!ok) errmsg = "Could not start JSON API Server!"; + return ok; +} + +void JsonApiPage::load() +{ + whileBlocking(ui.enableCheckBox)->setChecked(Settings->getJsonApiEnabled()); + whileBlocking(ui.portSpinBox)->setValue(Settings->getJsonApiPort()); + whileBlocking(ui.listenAddressLineEdit)->setText(Settings->getJsonApiListenAddress()); + whileBlocking(ui.tokensListView)->setModel(new QStringListModel(Settings->getJsonApiAuthTokens())); +} + +QString JsonApiPage::helpText() const { return ""; } + +/*static*/ bool JsonApiPage::checkStartJsonApi() +{ + checkShutdownJsonApi(); + + if(Settings->getJsonApiEnabled()) + { + jsonApiServer = new JsonApiServer( + Settings->getJsonApiPort(), + Settings->getJsonApiListenAddress().toStdString() ); + jsonApiServer->start("jsonApiServer"); + + for(const QString& token : Settings->getJsonApiAuthTokens()) + jsonApiServer->authorizeToken(token.toStdString()); + } + + return true; +} + +/*static*/ void JsonApiPage::checkShutdownJsonApi() +{ + if(jsonApiServer) + { + /* It is important to make a copy of +jsonApiServer+ pointer so the old + * object can be deleted later, while the original pointer is + * reassigned */ + JsonApiServer* oldJsonApiServer = jsonApiServer; + jsonApiServer = nullptr; + + oldJsonApiServer->shutdown(); + + QProgressDialog* pd = new QProgressDialog( + "Stopping JSON API Server", QString(), 0, 3000); + QTimer* prtm = new QTimer; + prtm->setInterval(16); // 60 FPS + connect( prtm, &QTimer::timeout, + pd, [=](){pd->setValue(pd->value()+16);} ); + pd->show(); + prtm->start(); + + /* Must wait for deletion because stopping of the server is async. + * It is important to capture a copy so it "survive" after + * safeStopJsonApiServer returns */ + QTimer::singleShot(3*1000, [=]() + { + delete oldJsonApiServer; + prtm->stop(); + pd->close(); + prtm->deleteLater(); + pd->deleteLater(); + }); + } +} + +void JsonApiPage::onApplyClicked(bool) +{ + QString errmsg; + updateParams(errmsg); +} + +void JsonApiPage::addTokenClicked(bool) +{ + QString token(ui.tokenLineEdit->text()); + if(jsonApiServer) jsonApiServer->authorizeToken(token.toStdString()); + QStringList newTk(Settings->getJsonApiAuthTokens()); + newTk.removeAll(token); + newTk.append(token); + Settings->setJsonApiAuthTokens(newTk); + whileBlocking(ui.tokensListView)->setModel(new QStringListModel(newTk)); +} + +void JsonApiPage::removeTokenClicked(bool) +{ + QString token(ui.tokenLineEdit->text()); + if(jsonApiServer) jsonApiServer->revokeAuthToken(token.toStdString()); + QStringList newTk(Settings->getJsonApiAuthTokens()); + newTk.removeAll(token); + Settings->setJsonApiAuthTokens(newTk); + whileBlocking(ui.tokensListView)->setModel( + new QStringListModel(Settings->getJsonApiAuthTokens()) ); +} + +void JsonApiPage::tokenClicked(const QModelIndex& index) +{ + ui.tokenLineEdit->setText(ui.tokensListView->model()->data(index).toString()); +} + diff --git a/retroshare-gui/src/gui/settings/JsonApiPage.h b/retroshare-gui/src/gui/settings/JsonApiPage.h new file mode 100644 index 000000000..d7733ffb3 --- /dev/null +++ b/retroshare-gui/src/gui/settings/JsonApiPage.h @@ -0,0 +1,58 @@ +/* + * RetroShare JSON API + * Copyright (C) 2018 Gioacchino Mazzurco + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include "ui_JsonApiPage.h" + +class JsonApiPage : public ConfigPage +{ + Q_OBJECT + +public: + + JsonApiPage(QWidget * parent = nullptr, Qt::WindowFlags flags = 0); + ~JsonApiPage() {} + + /** Loads the settings for this page */ + virtual void load(); + + virtual QPixmap iconPixmap() const + { return QPixmap(":/icons/svg/empty-circle.svg"); } + virtual QString pageName() const { return tr("JSON API"); } + virtual QString helpText() const; + + /** Call this after start of libretroshare/Retroshare + * checks the settings and starts JSON API if required */ + static bool checkStartJsonApi(); + + /** call this before shutdown of libretroshare + * it stops the JSON API if its running */ + static void checkShutdownJsonApi(); + +public slots: + void onApplyClicked(bool); + void addTokenClicked(bool); + void removeTokenClicked(bool); + void tokenClicked(const QModelIndex& index); + +private: + Ui::JsonApiPage ui; /// Qt Designer generated object + + bool updateParams(QString &errmsg); +}; diff --git a/retroshare-gui/src/gui/settings/JsonApiPage.ui b/retroshare-gui/src/gui/settings/JsonApiPage.ui new file mode 100644 index 000000000..0408c8403 --- /dev/null +++ b/retroshare-gui/src/gui/settings/JsonApiPage.ui @@ -0,0 +1,147 @@ + + + JsonApiPage + + + + 0 + 0 + 521 + 393 + + + + Form + + + + + + + 274 + 0 + + + + JSON API Server + + + + + + Enable RetroShare JSON API Server + + + + + + + + + Port: + + + + + + + 1024 + + + 65535 + + + 9092 + + + + + + + + + + + Listen Address: + + + + + + + 127.0.0.1 + + + + + + + + + + + Token: + + + + + + + <html><head/><body><p>ApiUser:ApiPassword</p></body></html> + + + + + + + Add + + + + + + + Remove + + + + + + + + + Authenticated Tokens + + + + + + + + + + + + + Restart JSON API Server to apply settings + + + + + + + Qt::Vertical + + + + 17 + 632 + + + + + + + + + diff --git a/retroshare-gui/src/gui/settings/WebuiPage.cpp b/retroshare-gui/src/gui/settings/WebuiPage.cpp index 808900625..8051abcdd 100644 --- a/retroshare-gui/src/gui/settings/WebuiPage.cpp +++ b/retroshare-gui/src/gui/settings/WebuiPage.cpp @@ -23,11 +23,6 @@ resource_api::ApiServerLocal* WebuiPage::apiServerLocal = 0; #endif resource_api::RsControlModule* WebuiPage::controlModule = 0; -#ifdef RS_JSONAPI -# include - -JsonApiServer* WebuiPage::jsonApiServer = nullptr; -#endif // ifdef RS_JSONAPI WebuiPage::WebuiPage(QWidget */*parent*/, Qt::WindowFlags /*flags*/) { @@ -113,15 +108,6 @@ QString WebuiPage::helpText() const apiServerLocal = new resource_api::ApiServerLocal(apiServer, resource_api::ApiServerLocal::serverPath()); #endif -#ifdef RS_JSONAPI - // Use same port of libresapi + 2 - jsonApiServer = new JsonApiServer( - Settings->getWebinterfacePort() + 2, - Settings->getWebinterfaceAllowAllIps() ? "::" : "127.0.0.1", - [](int /*ec*/) { std::raise(SIGTERM); } ); - jsonApiServer->start("WebuiPage::jsonApiServer"); -#endif // ifdef RS_JSONAPI - return ok; } @@ -142,10 +128,6 @@ QString WebuiPage::helpText() const delete controlModule; controlModule = 0; } -#ifdef RS_JSONAPI - delete jsonApiServer; - jsonApiServer = nullptr; -#endif } /*static*/ void WebuiPage::showWebui() diff --git a/retroshare-gui/src/gui/settings/WebuiPage.h b/retroshare-gui/src/gui/settings/WebuiPage.h index 78961c98d..66af0327b 100644 --- a/retroshare-gui/src/gui/settings/WebuiPage.h +++ b/retroshare-gui/src/gui/settings/WebuiPage.h @@ -3,10 +3,6 @@ #include #include "ui_WebuiPage.h" -#ifdef RS_JSONAPI -# include "jsonapi/jsonapi.h" -#endif - namespace resource_api{ class ApiServer; class ApiServerMHD; @@ -59,7 +55,4 @@ private: static resource_api::ApiServerLocal* apiServerLocal; #endif static resource_api::RsControlModule* controlModule; -#ifdef RS_JSONAPI - static JsonApiServer* jsonApiServer; -#endif }; diff --git a/retroshare-gui/src/gui/settings/rsettingswin.cpp b/retroshare-gui/src/gui/settings/rsettingswin.cpp index 7f2dcf115..e04b21c18 100644 --- a/retroshare-gui/src/gui/settings/rsettingswin.cpp +++ b/retroshare-gui/src/gui/settings/rsettingswin.cpp @@ -50,6 +50,10 @@ # include "WebuiPage.h" #endif +#ifdef RS_JSONAPI +# include "JsonApiPage.h" +#endif + #define IMAGE_GENERAL ":/images/kcmsystem24.png" #define ITEM_SPACING 2 @@ -165,8 +169,12 @@ SettingsPage::initStackedWidget() #ifdef ENABLE_WEBUI addPage(new WebuiPage() ); #endif // ENABLE_WEBUI - // add widgets from plugins +#ifdef RS_JSONAPI + addPage(new JsonApiPage()); +#endif + + // add widgets from plugins for(int i=0;inbPlugins();++i) { RsPlugin *pl = rsPlugins->plugin(i) ; diff --git a/retroshare-gui/src/gui/settings/rsharesettings.cpp b/retroshare-gui/src/gui/settings/rsharesettings.cpp index e37216e39..744c41a8b 100644 --- a/retroshare-gui/src/gui/settings/rsharesettings.cpp +++ b/retroshare-gui/src/gui/settings/rsharesettings.cpp @@ -1171,3 +1171,45 @@ void RshareSettings::setPageAlreadyDisplayed(const QString& page_name,bool b) { return setValueToGroup("PageAlreadyDisplayed",page_name,b); } + +#ifdef RS_JSONAPI +bool RshareSettings::getJsonApiEnabled() +{ + return valueFromGroup("JsonApi", "enabled", false).toBool(); +} + +void RshareSettings::setJsonApiEnabled(bool enabled) +{ + setValueToGroup("JsonApi", "enabled", enabled); +} + +uint16_t RshareSettings::getJsonApiPort() +{ + return valueFromGroup("JsonApi", "port", 9092).toUInt(); +} + +void RshareSettings::setJsonApiPort(uint16_t port) +{ + setValueToGroup("JsonApi", "port", port); +} + +QString RshareSettings::getJsonApiListenAddress() +{ + return valueFromGroup("JsonApi", "listenAddress", "127.0.0.1").toString(); +} + +void RshareSettings::setJsonApiListenAddress(const QString& listenAddress) +{ + setValueToGroup("JsonApi", "listenAddress", listenAddress); +} + +QStringList RshareSettings::getJsonApiAuthTokens() +{ + return valueFromGroup("JsonApi", "authTokens", QStringList()).toStringList(); +} + +void RshareSettings::setJsonApiAuthTokens(const QStringList& authTokens) +{ + setValueToGroup("JsonApi", "authTokens", authTokens); +} +#endif // RS_JSONAPI diff --git a/retroshare-gui/src/gui/settings/rsharesettings.h b/retroshare-gui/src/gui/settings/rsharesettings.h index 0dfd3dc7f..b8a856b13 100644 --- a/retroshare-gui/src/gui/settings/rsharesettings.h +++ b/retroshare-gui/src/gui/settings/rsharesettings.h @@ -341,6 +341,20 @@ public: bool getPageAlreadyDisplayed(const QString& page_code) ; void setPageAlreadyDisplayed(const QString& page_code,bool b) ; +#ifdef RS_JSONAPI + bool getJsonApiEnabled(); + void setJsonApiEnabled(bool enabled); + + uint16_t getJsonApiPort(); + void setJsonApiPort(uint16_t port); + + QString getJsonApiListenAddress(); + void setJsonApiListenAddress(const QString& listenAddress); + + QStringList getJsonApiAuthTokens(); + void setJsonApiAuthTokens(const QStringList& authTokens); +#endif // ifdef RS_JSONAPI + protected: /** Default constructor. */ RshareSettings(); diff --git a/retroshare-gui/src/main.cpp b/retroshare-gui/src/main.cpp index 17c8ee507..ae0019e80 100644 --- a/retroshare-gui/src/main.cpp +++ b/retroshare-gui/src/main.cpp @@ -51,6 +51,10 @@ # include "gui/settings/WebuiPage.h" #endif +#ifdef RS_JSONAPI +# include "gui/settings/JsonApiPage.h" +#endif // RS_JSONAPI + #include "TorControl/TorManager.h" #include "TorControl/TorControlWindow.h" @@ -536,6 +540,10 @@ feenableexcept(FE_INVALID | FE_DIVBYZERO); WebuiPage::checkStartWebui(); #endif // ENABLE_WEBUI +#ifdef RS_JSONAPI + JsonApiPage::checkStartJsonApi(); +#endif // RS_JSONAPI + // This is done using a timer, because the passphrase request from notify is asynchrouneous and therefore clearing the // passphrase here makes it request for a passphrase when creating the default chat identity. @@ -545,6 +553,10 @@ feenableexcept(FE_INVALID | FE_DIVBYZERO); int ti = rshare.exec(); delete w ; +#ifdef RS_JSONAPI + JsonApiPage::checkShutdownJsonApi(); +#endif // RS_JSONAPI + #ifdef ENABLE_WEBUI WebuiPage::checkShutdownWebui(); #endif // ENABLE_WEBUI diff --git a/retroshare-gui/src/retroshare-gui.pro b/retroshare-gui/src/retroshare-gui.pro index 7e9a5bda1..e6e75db9e 100644 --- a/retroshare-gui/src/retroshare-gui.pro +++ b/retroshare-gui/src/retroshare-gui.pro @@ -17,6 +17,12 @@ libresapihttpserver { FORMS *= gui/settings/WebuiPage.ui } +rs_jsonapi { + HEADERS *= gui/settings/JsonApiPage.h + SOURCES *= gui/settings/JsonApiPage.cc + FORMS *= gui/settings/JsonApiPage.ui +} + !include("../../libretroshare/src/use_libretroshare.pri"):error("Including") FORMS += TorControl/TorControlWindow.ui From 95e0fd338f9145f0d5f33e50006d7ce4b3a7fdc5 Mon Sep 17 00:00:00 2001 From: Gioacchino Mazzurco Date: Tue, 25 Sep 2018 17:15:06 +0200 Subject: [PATCH 6/6] JsonApiServer::isAuthTokenValid avoid timing attack Thanks Cyril for review :) --- libretroshare/src/jsonapi/jsonapi.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/libretroshare/src/jsonapi/jsonapi.cpp b/libretroshare/src/jsonapi/jsonapi.cpp index 4c68370f9..ad4fd801b 100644 --- a/libretroshare/src/jsonapi/jsonapi.cpp +++ b/libretroshare/src/jsonapi/jsonapi.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "util/rsjson.h" #include "retroshare/rsfiles.h" @@ -294,7 +295,25 @@ bool JsonApiServer::requestNewTokenAutorization(const std::string& token) bool JsonApiServer::isAuthTokenValid(const std::string& token) { RS_STACK_MUTEX(configMutex); - return mAuthTokenStorage.mAuthorizedTokens.count(token); + + // attempt avoiding +else CRYPTO_memcmp+ being optimized away + int noOptimiz = 1; + + /* Do not use mAuthTokenStorage.mAuthorizedTokens.count(token), because + * std::string comparison is usually not constant time on content to be + * faster, so an attacker may use timings to guess authorized tokens */ + for(const std::string& vTok : mAuthTokenStorage.mAuthorizedTokens) + { + if( token.size() == vTok.size() && + ( noOptimiz = CRYPTO_memcmp( token.data(), vTok.data(), + vTok.size() ) ) == 0 ) + return true; + // Make token size guessing harder + else noOptimiz = CRYPTO_memcmp(token.data(), token.data(), token.size()); + } + + // attempt avoiding +else CRYPTO_memcmp+ being optimized away + return static_cast(noOptimiz) + 1 == 0; } std::set JsonApiServer::getAuthorizedTokens()