diff --git a/bin/Makefile.am b/bin/Makefile.am index deb5c45591be8e44eadf9c43d64a008f8ed62452..bbe9d273e158ac60280bc5990c50412094a2a8c6 100644 --- a/bin/Makefile.am +++ b/bin/Makefile.am @@ -35,15 +35,15 @@ endif if RING_RESTCPP SUBDIRS=restcpp -sbin_PROGRAMS = dring +sbin_PROGRAMS = restdring -dring_SOURCES = main.cpp +restdring_SOURCES = main.cpp -dring_CXXFLAGS= -g \ +restdring_CXXFLAGS= -g \ -I$(top_srcdir)/src \ -I$(top_srcdir)/src/dring \ -DREST_API \ -DTOP_BUILDDIR=\"$$(cd "$(top_builddir)"; pwd)\" -dring_LDADD = restcpp/libclient_rest.la $(top_builddir)/src/libring.la +restdring_LDADD = restcpp/libclient_rest.la $(top_builddir)/src/libring.la endif diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml index 4bc665df95b2075a830185be2f0deb741ba1b9ec..76c36ca7537492b16cb1ed4af7948fe69a0616a0 100644 --- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml +++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml @@ -220,6 +220,118 @@ </arg> </method> + <method name="lookupName" tp:name-for-bindings="lookupName"> + <tp:docstring> + Performs name lookup with RingNS protocol for the specified account (if any) or using the default nameserver. + </tp:docstring> + <arg type="s" name="accountID" direction="in"> + The account to use. If empty, use the default RingNS server. + </arg> + <arg type="s" name="nameserverUri" direction="in"> + The name server URI to use, considered only if accountID is empty. + </arg> + <arg type="s" name="name" direction="in"> + </arg> + <arg type="b" name="success" direction="out"> + <tp:docstring> + True if the operation was initialized successfully. registeredNameFound will be trigered on completion. + </tp:docstring> + </arg> + </method> + <method name="lookupAddress" tp:name-for-bindings="lookupAddress"> + <tp:docstring> + Performs address lookup with RingNS protocol for the specified account (if any) or using the default nameserver. + </tp:docstring> + <arg type="s" name="accountID" direction="in"> + The account to use. If empty, use the default RingNS server. + </arg> + <arg type="s" name="nameserverUri" direction="in"> + The name server URI to use, considered only if accountID is empty. + </arg> + <arg type="s" name="address" direction="in"> + <tp:docstring> + Address to lookup for. Must not include spaces. + </tp:docstring> + </arg> + <arg type="b" name="success" direction="out"> + <tp:docstring> + True if the operation was initialized successfully. registeredNameFound will be trigered on completion. + False in case of operation initialization error. registeredNameFound won't be called in that case. + </tp:docstring> + </arg> + </method> + <signal name="registeredNameFound" tp:name-for-bindings="registeredNameFound"> + <tp:docstring> + Notify clients when a new registered address-name mapping is known. + If status is not success (0), requested field (name or address) is left empty. + </tp:docstring> + <arg type="s" name="accountID"> + </arg> + <arg type="i" name="status"> + <tp:docstring> + Status code: 0 for success + <ul> + <li>SUCCESS = 0 everything went fine. Name/address pair was found.</li> + <li>INVALID_NAME = 1 provided name is not valid.</li> + <li>NOT_FOUND = 2 everything went fine. Name/address pair was not found.</li> + <li>ERROR = 3 An error happened</li> + </ul> + </tp:docstring> + </arg> + <arg type="s" name="address"> + </arg> + <arg type="s" name="name"> + </arg> + </signal> + + <method name="registerName" tp:name-for-bindings="registerName"> + <tp:docstring> + Performs name registration with RingNS protocol for the specified account. + </tp:docstring> + <arg type="s" name="accountID" direction="in"> + </arg> + <arg type="s" name="password" direction="in"> + <tp:docstring> + Ring account main password. + </tp:docstring> + </arg> + <arg type="s" name="name" direction="in"> + <tp:docstring> + Name to register. Must be lower-case ASCII, digits or "-" or "_". + </tp:docstring> + </arg> + <arg type="b" name="success" direction="out"> + <tp:docstring> + True if the operation was initialized successfully. nameRegistrationEnded will be trigered on completion. + False in case of operation initialization error. nameRegistrationEnded won't be called in that case. + </tp:docstring> + </arg> + </method> + <signal name="nameRegistrationEnded" tp:name-for-bindings="nameRegistrationEnded"> + <tp:docstring> + Notify clients when the registerName operation ended. + </tp:docstring> + <arg type="s" name="accountID"> + </arg> + <arg type="i" name="status"> + <tp:docstring> + Status code: 0 for success + <ul> + <li>SUCCESS = 0 everything went fine. Name is now registered.</li> + <li>WRONG_PASSWORD = 1 registration failed: wrong password.</li> + <li>INVALID_NAME = 2 registration failed: invalid name.</li> + <li>ALREADY_TAKEN = 3 registration failed: name is already taken.</li> + <li>NETWORK_ERROR = 4 registration failed: network or server error.</li> + </ul> + </tp:docstring> + </arg> + <arg type="s" name="name"> + <tp:docstring> + The name that was attempted to register. + </tp:docstring> + </arg> + </signal> + <method name="testAccountICEInitialization" tp:name-for-bindings="testAccountICEInitialization"> <tp:docstring> Test initializing an ICE transport with the current account configuration. diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp index 6ef794a62ac0117c63cc007097a172f63e0a1c81..093998ac506c0f654ccdbec17ef2b356c437065f 100644 --- a/bin/dbus/dbusclient.cpp +++ b/bin/dbus/dbusclient.cpp @@ -178,6 +178,8 @@ DBusClient::initLibrary(int flags) exportable_callback<ConfigurationSignal::IncomingTrustRequest>(bind(&DBusConfigurationManager::incomingTrustRequest, confM, _1, _2, _3, _4 )), exportable_callback<ConfigurationSignal::ExportOnRingEnded>(bind(&DBusConfigurationManager::exportOnRingEnded, confM, _1, _2, _3 )), exportable_callback<ConfigurationSignal::KnownDevicesChanged>(bind(&DBusConfigurationManager::knownDevicesChanged, confM, _1, _2 )), + exportable_callback<ConfigurationSignal::NameRegistrationEnded>(bind(&DBusConfigurationManager::nameRegistrationEnded, confM, _1, _2, _3 )), + exportable_callback<ConfigurationSignal::RegisteredNameFound>(bind(&DBusConfigurationManager::registeredNameFound, confM, _1, _2, _3, _4 )), exportable_callback<ConfigurationSignal::CertificatePinned>(bind(&DBusConfigurationManager::certificatePinned, confM, _1 )), exportable_callback<ConfigurationSignal::CertificatePathPinned>(bind(&DBusConfigurationManager::certificatePathPinned, confM, _1, _2 )), exportable_callback<ConfigurationSignal::CertificateExpired>(bind(&DBusConfigurationManager::certificateExpired, confM, _1 )), diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp index 62dc9f8ddee4b38528bb734c49ec4bf92ad86d4d..fdf6f59bfe89f9d54ec318334818e7bb609d7152 100644 --- a/bin/dbus/dbusconfigurationmanager.cpp +++ b/bin/dbus/dbusconfigurationmanager.cpp @@ -86,6 +86,24 @@ DBusConfigurationManager::getKnownRingDevices(const std::string& accountID) -> d return DRing::getKnownRingDevices(accountID); } +auto +DBusConfigurationManager::lookupName(const std::string& account, const std::string& nameserver, const std::string& name) -> decltype(DRing::lookupName(account, nameserver, name)) +{ + return DRing::lookupName(account, nameserver, name); +} + +auto +DBusConfigurationManager::lookupAddress(const std::string& account, const std::string& nameserver, const std::string& address) -> decltype(DRing::lookupAddress(account, nameserver, address)) +{ + return DRing::lookupAddress(account, nameserver, address); +} + +auto +DBusConfigurationManager::registerName(const std::string& account, const std::string& password, const std::string& name) -> decltype(DRing::registerName(account, password, name)) +{ + return DRing::registerName(account, password, name); +} + void DBusConfigurationManager::removeAccount(const std::string& accountID) { diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h index c25be1eb1ee1c10a86a4cea6f23f1dc1bf9ec5b3..562c9ab4f91d03ca07bd5c9c17e70d6d5432fc5c 100644 --- a/bin/dbus/dbusconfigurationmanager.h +++ b/bin/dbus/dbusconfigurationmanager.h @@ -65,6 +65,9 @@ class DBusConfigurationManager : std::string addAccount(const std::map<std::string, std::string>& details); bool exportOnRing(const std::string& accountID, const std::string& password); std::map<std::string, std::string> getKnownRingDevices(const std::string& accountID); + bool lookupName(const std::string& account, const std::string& nameserver, const std::string& name); + bool lookupAddress(const std::string& account, const std::string& nameserver, const std::string& address); + bool registerName(const std::string& account, const std::string& password, const std::string& name); void removeAccount(const std::string& accoundID); std::vector<std::string> getAccountList(); void sendRegister(const std::string& accoundID, const bool& enable); diff --git a/configure.ac b/configure.ac index d4ede1d0cb9c8a6e1f16dfe6948602af89908797..fbc6a6aaa12c7c2a351898a3c5b819d860a23da1 100644 --- a/configure.ac +++ b/configure.ac @@ -385,20 +385,26 @@ AS_IF([test "x$with_dbus" = "xyes"], [ AM_CONDITIONAL(RING_DBUS, true)], AM_CONDITIONAL(RING_DBUS, false)); +dnl Ring name service is default-enabled +AC_ARG_ENABLE([ringns], AS_HELP_STRING([--disable-ringns], [Enable Ring Name Service])) +AM_CONDITIONAL([RINGNS], test "x$enable_ringns" != "xno", [Define if you use the Ring Name Service]) +AC_DEFINE_UNQUOTED([HAVE_RINGNS], `if test "x$enable_ringns" != "xno"; then echo 1; else echo 0; fi`, [Define if you use the Ring Name Service]) + # Rest C++ with restbed AC_ARG_WITH([restcpp], AS_HELP_STRING([--with-restcpp], [enable rest support with C++])) -AS_IF([test "x$with_restcpp" = "xyes"], [ - PKG_CHECK_MODULES(RESTBED, librestbed,, AC_MSG_WARN([Missing restbed files])) +AS_IF([test "x$enable_ringns" != "xno" || test "x$with_restcpp" = "xyes"], + AC_CHECK_LIB(restbed, exit,, AC_MSG_ERROR([Missing restbed files]))); +AS_IF([test "x$with_restcpp" = "xyes"], [ AS_AC_EXPAND(SBINDIR, $sbindir) AC_SUBST(SBINDIR) - AC_CONFIG_FILES([bin/restcpp/Makefile]) - - AM_CONDITIONAL(RING_RESTCPP, true)], - AM_CONDITIONAL(RING_RESTCPP, false)); + AM_CONDITIONAL(RING_RESTCPP, true) + ], + AM_CONDITIONAL(RING_RESTCPP, false) +); dnl Check for libav PKG_CHECK_MODULES(LIBAVCODEC, libavcodec >= 53.5.0,, AC_MSG_ERROR([Missing libavcodec development files])) diff --git a/src/Makefile.am b/src/Makefile.am index c9e4bf7773158eff29bef9010d0083d34c538c42..1c36d1799b7df73f92fd68e6cd54c7b4c40cdb2b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -55,7 +55,8 @@ libring_la_LDFLAGS = \ @GNUTLS_LIBS@ \ @OPENDHT_LIBS@ \ @ZLIB_LIBS@ \ - $(PCRE_LIBS) + $(PCRE_LIBS) \ + @LIBS@ if HAVE_WIN32 libring_la_LDFLAGS += -no-undefined -avoid-version diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp index 157ce84d599319862b3e6c3c7306a7c5795351ee..97132fec0a183d50f9990cda984883b97af736f6 100644 --- a/src/client/configurationmanager.cpp +++ b/src/client/configurationmanager.cpp @@ -61,6 +61,7 @@ namespace DRing { constexpr unsigned CODECS_NOT_LOADED = 0x1000; /** Codecs not found */ using ring::SIPAccount; +using ring::RingAccount; using ring::tls::TlsValidator; using ring::tls::CertificateStore; using ring::DeviceType; @@ -828,12 +829,52 @@ connectivityChanged() RING_ERR("UPnP context error: %s", e.what()); } - auto account_list = ring::Manager::instance().getAccountList(); - for (auto account_id : account_list) { - if (auto account = ring::Manager::instance().getAccount(account_id)) { - account->connectivityChanged(); - } + for (const auto &account : ring::Manager::instance().getAllAccounts()) { + account->connectivityChanged(); + } +} + +bool lookupName(const std::string& account, const std::string& nameserver, const std::string& name) +{ +#if HAVE_RINGNS + if (account.empty()) { + ring::NameDirectory::instance(nameserver).lookupName(name, [name](const std::string& result, ring::NameDirectory::Response response) { + ring::emitSignal<DRing::ConfigurationSignal::RegisteredNameFound>("", (int)response, result, name); + }); + return true; + } else if (auto acc = ring::Manager::instance().getAccount<RingAccount>(account)) { + acc->lookupName(name); + return true; + } +#endif + return false; +} + +bool lookupAddress(const std::string& account, const std::string& nameserver, const std::string& address) +{ +#if HAVE_RINGNS + if (account.empty()) { + ring::NameDirectory::instance(nameserver).lookupAddress(address, [address](const std::string& result, ring::NameDirectory::Response response) { + ring::emitSignal<DRing::ConfigurationSignal::RegisteredNameFound>("", (int)response, address, result); + }); + return true; + } else if (auto acc = ring::Manager::instance().getAccount<RingAccount>(account)) { + acc->lookupAddress(address); + return true; } +#endif + return false; +} + +bool registerName(const std::string& account, const std::string& password, const std::string& name) +{ +#if HAVE_RINGNS + if (auto acc = ring::Manager::instance().getAccount<RingAccount>(account)) { + acc->registerName(password, name); + return true; + } +#endif + return false; } } // namespace DRing diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp index 09c10202b0fd98375296572c2cfa6ade1eb7887a..e16243095807b3dba01d179b0747aaaa320b9cfc 100644 --- a/src/client/ring_signal.cpp +++ b/src/client/ring_signal.cpp @@ -65,6 +65,8 @@ getSignalHandlers() exported_callback<DRing::ConfigurationSignal::IncomingTrustRequest>(), exported_callback<DRing::ConfigurationSignal::ExportOnRingEnded>(), exported_callback<DRing::ConfigurationSignal::KnownDevicesChanged>(), + exported_callback<DRing::ConfigurationSignal::NameRegistrationEnded>(), + exported_callback<DRing::ConfigurationSignal::RegisteredNameFound>(), exported_callback<DRing::ConfigurationSignal::MediaParametersChanged>(), exported_callback<DRing::ConfigurationSignal::Error>(), #ifdef __ANDROID__ diff --git a/src/dring/account_const.h b/src/dring/account_const.h index 5ac7dfd56dd781a6913eee88667b491cb38fa8fd..9db2f649e654aadc3c01b0ed33aeb2fdcb84fd00 100644 --- a/src/dring/account_const.h +++ b/src/dring/account_const.h @@ -76,6 +76,7 @@ enum class testAccountICEInitializationStatus : int { namespace VolatileProperties { constexpr static const char ACTIVE [] = "Account.active"; +constexpr static const char REGISTERED_NAME [] = "Account.registredName"; // Volatile parameters namespace Registration { @@ -222,12 +223,12 @@ constexpr static const char ALLOW_FROM_TRUSTED [] = "DHT.AllowFromTrusted"; } //namespace DRing::Account::DHT -namespace ETH { +namespace RingNS { -constexpr static const char KEY_FILE [] = "ETH.keyFile"; -constexpr static const char ACCOUNT [] = "ETH.account"; +constexpr static const char URI [] = "RingNS.uri"; +constexpr static const char ACCOUNT [] = "RingNS.account"; -} //namespace DRing::Account::ETH +} //namespace DRing::Account::RingNS namespace CodecInfo { diff --git a/src/dring/call_const.h b/src/dring/call_const.h index 98486238a671e063acddd275099b161fa9f92846..9bd26b15e2d6c40eb231390a983b843c396ceeaa 100644 --- a/src/dring/call_const.h +++ b/src/dring/call_const.h @@ -44,6 +44,7 @@ namespace Details { constexpr static char CALL_TYPE [] = "CALL_TYPE" ; constexpr static char PEER_NUMBER [] = "PEER_NUMBER" ; +constexpr static char REGISTERED_NAME [] = "REGISTERED_NAME" ; constexpr static char DISPLAY_NAME [] = "DISPLAY_NAME" ; constexpr static char CALL_STATE [] = "CALL_STATE" ; constexpr static char CONF_ID [] = "CONF_ID" ; diff --git a/src/dring/configurationmanager_interface.h b/src/dring/configurationmanager_interface.h index dae105ed9b28a2be8bf6f2474085b4226d61e230..dff11f96833b3e08a2fda5f0d5c22ecd91011757 100644 --- a/src/dring/configurationmanager_interface.h +++ b/src/dring/configurationmanager_interface.h @@ -48,6 +48,10 @@ std::string addAccount(const std::map<std::string, std::string>& details); bool exportOnRing(const std::string& accountID, const std::string& password); std::map<std::string, std::string> getKnownRingDevices(const std::string& accountID); +bool lookupName(const std::string& account, const std::string& nameserver, const std::string& name); +bool lookupAddress(const std::string& account, const std::string& nameserver, const std::string& address); +bool registerName(const std::string& account, const std::string& password, const std::string& name); + void removeAccount(const std::string& accountID); void setAccountEnabled(const std::string& accountID, bool enable); std::vector<std::string> getAccountList(); @@ -216,10 +220,18 @@ struct ConfigurationSignal { constexpr static const char* name = "ExportOnRingEnded"; using cb_type = void(const std::string& /*account_id*/, int state, const std::string& pin); }; + struct NameRegistrationEnded { + constexpr static const char* name = "NameRegistrationEnded"; + using cb_type = void(const std::string& /*account_id*/, int state, const std::string& name); + }; struct KnownDevicesChanged { constexpr static const char* name = "KnownDevicesChanged"; using cb_type = void(const std::string& /*account_id*/, const std::map<std::string, std::string>& devices); }; + struct RegisteredNameFound { + constexpr static const char* name = "RegisteredNameFound"; + using cb_type = void(const std::string& /*account_id*/, int state, const std::string& /*address*/, const std::string& /*name*/); + }; struct CertificatePinned { constexpr static const char* name = "CertificatePinned"; using cb_type = void(const std::string& /*certId*/); diff --git a/src/ringdht/Makefile.am b/src/ringdht/Makefile.am index 4654df9a7379c1c95ef190c6c99d9d97d2e9f0ed..ecbc4963858a1b4160395eaf92cdd62e66d469cb 100644 --- a/src/ringdht/Makefile.am +++ b/src/ringdht/Makefile.am @@ -11,9 +11,7 @@ libringacc_la_CXXFLAGS = @CXXFLAGS@ @JSONCPP_CFLAGS@ libringacc_la_LIBADD = $(DHT_LIBS) \ $(BOOST_SYSTEM_LIB) \ - $(BOOST_FILESYSTEM_LIB) \ $(BOOST_RANDOM_LIB) \ - $(BOOST_THREAD_LIB) \ ./eth/libdevcore/libdevcore.la \ ./eth/libdevcrypto/libdevcrypto.la @@ -24,3 +22,9 @@ libringacc_la_SOURCES = \ sip_transport_ice.h \ sips_transport_ice.cpp \ sips_transport_ice.h + +if RINGNS +libringacc_la_SOURCES += \ + namedirectory.cpp \ + namedirectory.h +endif diff --git a/src/ringdht/namedirectory.cpp b/src/ringdht/namedirectory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0f6994948db80f90fc3a5e9f784396cf6324ceb8 --- /dev/null +++ b/src/ringdht/namedirectory.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2016 Savoir-faire Linux Inc. + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include "namedirectory.h" + +#include "logger.h" +#include "string_utils.h" +#include "thread_pool.h" + +#include <json/json.h> +#include <restbed> + +/* for visual studio */ +#include <ciso646> +#include <sstream> +#include <regex> + +namespace ring { + +constexpr const char* const QUERY_NAME {"/name/"}; +constexpr const char* const QUERY_ADDR {"/addr/"}; +const std::regex NAME_VALIDATOR {"^[a-z0-9-_]{3,32}$"}; +const std::regex URI_VALIDATOR {"^(:?[a-zA-Z]+://)?([a-zA-Z0-9\\-._~%!$&'()*+,;=:\\[\\]]+)"}; + +constexpr size_t MAX_RESPONSE_SIZE {1024 * 1024}; + +std::string hostFromUri(const std::string& uri) +{ + std::smatch pieces_match; + if (std::regex_search(uri, pieces_match, URI_VALIDATOR)) + if (pieces_match.size() == 3) + return pieces_match[2].str(); + return uri; +} + +NameDirectory::NameDirectory(const std::string& s) : serverUri_(s), serverHost_(hostFromUri(s)) +{} + +NameDirectory& NameDirectory::instance(const std::string& server) +{ + const std::string& s = server.empty() ? DEFAULT_SERVER_URI : server; + static std::map<std::string, NameDirectory> instances {}; + auto it = instances.emplace(s, NameDirectory{s}); + return it.first->second; +} + +void NameDirectory::lookupAddress(const std::string& addr, LookupCallback cb) +{ + auto cacheRes = nameCache_.find(addr); + if (cacheRes != nameCache_.end()) { + cb(cacheRes->second, Response::found); + return; + } + + restbed::Uri uri(serverUri_ + QUERY_ADDR + addr); + auto req = std::make_shared<restbed::Request>(uri); + req->set_header("Accept", "*/*"); + req->set_header("Host", serverHost_); + + RING_DBG("Address lookup for %s: %s", addr.c_str(), uri.to_string().c_str()); + + auto ret = restbed::Http::async(req, [this,cb,addr](const std::shared_ptr<restbed::Request>, + const std::shared_ptr<restbed::Response> reply) { + if (reply->get_status_code() == 200) { + size_t length = 0; + length = reply->get_header("Content-Length", length); + if (length > MAX_RESPONSE_SIZE) { + cb("", Response::error); + return; + } + restbed::Http::fetch(length, reply); + std::string body; + reply->get_body(body); + + Json::Value json; + Json::Reader reader; + if (!reader.parse(body, json)) { + RING_ERR("Address lookup for %s: can't parse server response: %s", addr.c_str(), body.c_str()); + cb("", Response::error); + return; + } + auto name = json["name"].asString(); + if (not name.empty()) { + RING_DBG("Found name for %s: %s", addr.c_str(), name.c_str()); + addrCache_.emplace(name, addr); + nameCache_.emplace(addr, name); + cb(name, Response::found); + } else { + cb("", Response::notFound); + } + } else { + cb("", Response::error); + } + }).share(); + + // avoid blocking on future destruction + ThreadPool::instance().run([ret](){ ret.get(); }); +} + +static const std::string HEX_PREFIX {"0x"}; + +void NameDirectory::lookupName(const std::string& name, LookupCallback cb) +{ + if (not validateName(name)) { + cb(name, Response::invalidName); + return; + } + + auto cacheRes = addrCache_.find(name); + if (cacheRes != addrCache_.end()) { + cb(cacheRes->second, Response::found); + return; + } + + restbed::Uri uri(serverUri_ + QUERY_NAME + name); + auto request = std::make_shared<restbed::Request>(std::move(uri)); + request->set_header("Accept", "*/*"); + request->set_header("Host", serverHost_); + + RING_DBG("Name lookup for %s: %s", name.c_str(), uri.to_string().c_str()); + + auto ret = restbed::Http::async(request, [this,cb,name](const std::shared_ptr<restbed::Request>, + const std::shared_ptr<restbed::Response> reply) { + auto code = reply->get_status_code(); + if (code != 200) + RING_DBG("Name lookup for %s: got reply code %d", name.c_str(), code); + if (code >= 200 && code < 300) { + size_t length = 0; + length = reply->get_header("Content-Length", length); + if (length > MAX_RESPONSE_SIZE) { + cb("", Response::error); + return; + } + restbed::Http::fetch(length, reply); + std::string body; + reply->get_body(body); + + Json::Value json; + Json::Reader reader; + if (!reader.parse(body, json)) { + RING_ERR("Name lookup for %s: can't parse server response: %s", name.c_str(), body.c_str()); + cb("", Response::error); + return; + } + auto addr = json["addr"].asString(); + if (!addr.compare(0, HEX_PREFIX.size(), HEX_PREFIX)) + addr = addr.substr(HEX_PREFIX.size()); + if (not addr.empty()) { + RING_DBG("Found address for %s: %s", name.c_str(), addr.c_str()); + addrCache_.emplace(name, addr); + nameCache_.emplace(addr, name); + cb(addr, Response::found); + } else { + cb("", Response::notFound); + } + } else if (code >= 400 && code < 500) { + cb("", Response::notFound); + } else { + cb("", Response::error); + } + }).share(); + + // avoid blocking on future destruction + ThreadPool::instance().run([ret](){ ret.get(); }); +} + +bool NameDirectory::validateName(const std::string& name) const +{ + return std::regex_match(name, NAME_VALIDATOR); +} + +void NameDirectory::registerName(const std::string& addr, const std::string& name, const std::string& owner, RegistrationCallback cb) +{ + if (not validateName(name)) { + cb(RegistrationResponse::invalidName); + return; + } + + auto cacheRes = addrCache_.find(name); + if (cacheRes != addrCache_.end()) { + if (cacheRes->second == addr) + cb(RegistrationResponse::success); + else + cb(RegistrationResponse::alreadyTaken); + return; + } + + auto request = std::make_shared<restbed::Request>(restbed::Uri(serverUri_ + QUERY_NAME + name)); + request->set_header("Accept", "*/*"); + request->set_header("Host", serverHost_); + request->set_header("Content-Type", "application/json"); + request->set_method("POST"); + std::string body; + { + std::stringstream ss; + ss << "{\"addr\":\"" << addr << "\",\"owner\":\"" << owner << "\"}"; + body = ss.str(); + } + request->set_body(body); + request->set_header("Content-Length", ring::to_string(body.size())); + + auto params = std::make_shared<restbed::Settings>(); + params->set_connection_timeout(std::chrono::seconds(60)); + + RING_WARN("registerName: sending request %s %s", addr.c_str(), name.c_str()); + auto ret = restbed::Http::async(request, + [this,cb,addr,name](const std::shared_ptr<restbed::Request>, + const std::shared_ptr<restbed::Response> reply) + { + auto code = reply->get_status_code(); + RING_DBG("Got reply for registration of %s -> %s: code %d", name.c_str(), addr.c_str(), code); + if (code >= 200 && code < 300) { + size_t length = 0; + length = reply->get_header("Content-Length", length); + if (length > MAX_RESPONSE_SIZE) { + cb(RegistrationResponse::error); + return; + } + restbed::Http::fetch(length, reply); + std::string body; + reply->get_body(body); + + Json::Value json; + Json::Reader reader; + if (!reader.parse(body, json)) { + cb(RegistrationResponse::error); + return; + } + auto success = json["success"].asBool(); + RING_DBG("Got reply for registration of %s -> %s: %s", name.c_str(), addr.c_str(), success ? "success" : "failure"); + if (success) { + addrCache_.emplace(name, addr); + nameCache_.emplace(addr, name); + } + cb(success ? RegistrationResponse::success : RegistrationResponse::error); + } else if (code >= 400 && code < 500) { + cb(RegistrationResponse::alreadyTaken); + } else { + cb(RegistrationResponse::error); + } + }, params).share(); + + // avoid blocking on future destruction + ThreadPool::instance().run([ret](){ ret.get(); }); +} + +} diff --git a/src/ringdht/namedirectory.h b/src/ringdht/namedirectory.h new file mode 100644 index 0000000000000000000000000000000000000000..e8acc6f2a4855878f3feb205967fa73c5f0fdf31 --- /dev/null +++ b/src/ringdht/namedirectory.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 Savoir-faire Linux Inc. + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <functional> +#include <map> +#include <string> + +namespace ring { + +class NameDirectory +{ +public: + NameDirectory() {} + NameDirectory(const std::string& s); + + static NameDirectory& instance(const std::string& server); + static NameDirectory& instance() { return instance(DEFAULT_SERVER_URI); } + + enum class Response : int { found = 0, invalidName, notFound, error }; + enum class RegistrationResponse : int { success = 0, invalidName, alreadyTaken, error }; + + using LookupCallback = std::function<void(const std::string& result, Response response)>; + void lookupAddress(const std::string& addr, LookupCallback cb); + void lookupName(const std::string& name, LookupCallback cb); + + using RegistrationCallback = std::function<void(RegistrationResponse response)>; + void registerName(const std::string& addr, const std::string& name, const std::string& owner, RegistrationCallback cb); + + const std::string& getServer() const { + return serverUri_; + } + +private: + constexpr static const char* const DEFAULT_SERVER_URI = "http://5.196.89.112:3000"; + + const std::string serverUri_ {DEFAULT_SERVER_URI}; + const std::string serverHost_ {}; + std::map<std::string, std::string> nameCache_; + std::map<std::string, std::string> addrCache_; + + bool validateName(const std::string& name) const; + +}; + +} diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp index edabcc145b9d5852c738fb88aacf7ffdd4541917..171fd80ac219b86795d5f2d69abade1362ee09e3 100644 --- a/src/ringdht/ringaccount.cpp +++ b/src/ringdht/ringaccount.cpp @@ -153,6 +153,9 @@ RingAccount::createIceTransport(const Args&... args) RingAccount::RingAccount(const std::string& accountID, bool /* presenceEnabled */) : SIPAccountBase(accountID), via_addr_(), +#if HAVE_RINGNS + nameDir_(NameDirectory::instance()), +#endif cachePath_(fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID()), dataPath_(cachePath_ + DIR_SEPARATOR_STR "values"), idPath_(fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID()) @@ -481,10 +484,13 @@ void RingAccount::serialize(YAML::Emitter &out) out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_CONTACT << YAML::Value << allowPeersFromContact_; out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_TRUSTED << YAML::Value << allowPeersFromTrusted_; +#if HAVE_RINGNS + out << YAML::Key << DRing::Account::ConfProperties::RingNS::URI << YAML::Value << nameDir_.get().getServer(); +#endif + out << YAML::Key << DRing::Account::ConfProperties::ARCHIVE_PATH << YAML::Value << archivePath_; out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT << YAML::Value << receipt_; out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT_SIG << YAML::Value << YAML::Binary(receiptSignature_.data(), receiptSignature_.size()); - out << YAML::Key << DRing::Account::ConfProperties::ETH::ACCOUNT << YAML::Value << ethAccount_; // tls submap out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap; @@ -503,12 +509,6 @@ void RingAccount::unserialize(const YAML::Node &node) parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_CONTACT, allowPeersFromContact_); parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_TRUSTED, allowPeersFromTrusted_); - try { - parseValue(node, DRing::Account::ConfProperties::ETH::ACCOUNT, ethAccount_); - } catch (const std::exception& e) { - RING_WARN("can't read eth account: %s", e.what()); - } - try { parseValue(node, DRing::Account::ConfProperties::ARCHIVE_PATH, archivePath_); } catch (const std::exception& e) { @@ -527,6 +527,12 @@ void RingAccount::unserialize(const YAML::Node &node) dhtPort_ = getRandomEvenPort(DHT_PORT_RANGE); dhtPortUsed_ = dhtPort_; +#if HAVE_RINGNS + std::string ringns_server; + parseValue(node, DRing::Account::ConfProperties::RingNS::URI, ringns_server); + nameDir_ = NameDirectory::instance(ringns_server); +#endif + parseValue(node, Conf::DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_); loadAccount(); @@ -650,12 +656,7 @@ RingAccount::hasSignedReceipt() ringDeviceId_ = identity_.first->getPublicKey().getId().toString(); username_ = RING_URI_PREFIX + id; announce_ = std::make_shared<dht::Value>(std::move(announce_val)); - - auto eth_addr = root["eth"].asString(); - if (eth_addr != ethAccount_) { - RING_WARN("hasSignedReceipt() eth_addr not matching"); - ethAccount_ = eth_addr; - } + ethAccount_ = root["eth"].asString(); RING_WARN("hasSignedReceipt() -> true"); return true; @@ -1124,6 +1125,12 @@ RingAccount::setAccountDetails(const std::map<std::string, std::string> &details std::transform(archive_pin.begin(), archive_pin.end(), archive_pin.begin(), ::toupper); parseString(details, DRing::Account::ConfProperties::ARCHIVE_PATH, archivePath_); +#if HAVE_RINGNS + std::string ringns_server; + parseString(details, DRing::Account::ConfProperties::RingNS::URI, ringns_server); + nameDir_ = NameDirectory::instance(ringns_server); +#endif + loadAccount(archive_password, archive_pin); } @@ -1154,7 +1161,10 @@ RingAccount::getAccountDetails() const a.emplace(Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, "-1"); //a.emplace(DRing::Account::ConfProperties::ETH::KEY_FILE, ethPath_); - a.emplace(DRing::Account::ConfProperties::ETH::ACCOUNT, ethAccount_); + a.emplace(DRing::Account::ConfProperties::RingNS::ACCOUNT, ethAccount_); +#if HAVE_RINGNS + a.emplace(DRing::Account::ConfProperties::RingNS::URI, nameDir_.get().getServer()); +#endif return a; } @@ -1164,9 +1174,50 @@ RingAccount::getVolatileAccountDetails() const { auto a = SIPAccountBase::getVolatileAccountDetails(); a.emplace(DRing::Account::VolatileProperties::InstantMessaging::OFF_CALL, TRUE_STR); +#if HAVE_RINGNS + if (not registeredName_.empty()) + a.emplace(DRing::Account::VolatileProperties::REGISTERED_NAME, registeredName_); +#endif return a; } +#if HAVE_RINGNS +void +RingAccount::lookupName(const std::string& name) +{ + auto acc = getAccountID(); + nameDir_.get().lookupName(name, [acc,name](const std::string& result, NameDirectory::Response response) { + emitSignal<DRing::ConfigurationSignal::RegisteredNameFound>(acc, 0, result, name); + }); +} + +void +RingAccount::lookupAddress(const std::string& addr) +{ + auto acc = getAccountID(); + nameDir_.get().lookupAddress(addr, [acc,addr](const std::string& result, NameDirectory::Response response) { + emitSignal<DRing::ConfigurationSignal::RegisteredNameFound>(acc, 0, addr, result); + }); +} + +void +RingAccount::registerName(const std::string& password, const std::string& name) +{ + auto acc = getAccountID(); + std::weak_ptr<RingAccount> w = std::static_pointer_cast<RingAccount>(shared_from_this()); + nameDir_.get().registerName(ringAccountId_, name, ethAccount_, [acc,name,w](NameDirectory::RegistrationResponse response){ + int res = (response == NameDirectory::RegistrationResponse::success) ? 0 : ( + (response == NameDirectory::RegistrationResponse::invalidName) ? 2 : ( + (response == NameDirectory::RegistrationResponse::alreadyTaken) ? 3 : 4)); + if (response == NameDirectory::RegistrationResponse::success) { + if (auto this_ = w.lock()) + this_->registeredName_ = name; + } + emitSignal<DRing::ConfigurationSignal::NameRegistrationEnded>(acc, res, name); + }); +} +#endif + void RingAccount::handleEvents() { @@ -1460,6 +1511,22 @@ RingAccount::doRegister_() dht_.join(); } + auto shared = std::static_pointer_cast<RingAccount>(shared_from_this()); + std::weak_ptr<RingAccount> w {shared}; + +#if HAVE_RINGNS + // Look for registered name on the blockchain + nameDir_.get().lookupAddress(ringAccountId_, [w](const std::string& result, const NameDirectory::Response& response) { + if (response == NameDirectory::Response::found) + if (auto this_ = w.lock()) { + if (this_->registeredName_ != result) { + this_->registeredName_ = result; + emitSignal<DRing::ConfigurationSignal::VolatileDetailsChanged>(this_->accountID_, this_->getVolatileAccountDetails()); + } + } + }); +#endif + dht_.setOnStatusChanged([this](dht::NodeStatus s4, dht::NodeStatus s6) { RING_WARN("Dht status : IPv4 %s; IPv6 %s", dhtStatusStr(s4), dhtStatusStr(s6)); RegistrationState state; @@ -1514,8 +1581,6 @@ RingAccount::doRegister_() if (not bootstrap.empty()) dht_.bootstrap(bootstrap); - auto shared = std::static_pointer_cast<RingAccount>(shared_from_this()); - // Put device annoucement if (announce_) { auto h = dht::InfoHash(ringAccountId_); @@ -1735,8 +1800,18 @@ RingAccount::replyToIncomingIceMsg(std::shared_ptr<SIPCall> call, dht::Value val { dht::IceCandidates(peer_ice_msg.id, ice->getLocalAttributesAndCandidates()) }; val.id = vid; - // Asynchronous DHT put of our local ICE data std::weak_ptr<SIPCall> wcall = call; +#if HAVE_RINGNS + auto from_acc_id = peer_cert ? (peer_cert->issuer ? peer_cert->issuer->getId().toString() : peer_cert->getId().toString()) : peer_ice_msg.from.toString(); + nameDir_.get().lookupAddress(from_acc_id, [wcall](const std::string& result, const NameDirectory::Response& response){ + if (response == NameDirectory::Response::found) + if (auto call = wcall.lock()) + call->setPeerRegistredName(result); + }); +#endif + + // Asynchronous DHT put of our local ICE data + auto shared_this = std::static_pointer_cast<RingAccount>(shared_from_this()); dht_.putEncrypted( callKey_, peer_ice_msg.from, @@ -1779,6 +1854,12 @@ RingAccount::replyToIncomingIceMsg(std::shared_ptr<SIPCall> call, void RingAccount::doUnregister(std::function<void(bool)> released_cb) { + if (registrationState_ == RegistrationState::INITIALIZING + || registrationState_ == RegistrationState::ERROR_NEED_MIGRATION) { + if (released_cb) released_cb(false); + return; + } + RING_WARN("doUnregister"); { std::lock_guard<std::mutex> lock(callsMutex_); diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h index b481fd4e916528587de8e56767f0572ccc6a21b1..cdf50d07a8afc7b498e2d93876fd25a2ea5aad22 100644 --- a/src/ringdht/ringaccount.h +++ b/src/ringdht/ringaccount.h @@ -42,6 +42,10 @@ #include <list> #include <future> +#if HAVE_RINGNS +#include "namedirectory.h" +#endif + /** * @file ringaccount.h * @brief Ring Account is build on top of SIPAccountBase and uses DHT to handle call connectivity. @@ -284,9 +288,15 @@ class RingAccount : public SIPAccountBase { void connectivityChanged() override; - public: // overloaded methods + // overloaded methods void flush() override; +#if HAVE_RINGNS + void lookupName(const std::string& name); + void lookupAddress(const std::string& address); + void registerName(const std::string& password, const std::string& name); +#endif + private: NON_COPYABLE(RingAccount); @@ -341,6 +351,11 @@ class RingAccount : public SIPAccountBase { MSGPACK_DEFINE_MAP(dev); }; +#if HAVE_RINGNS + std::reference_wrapper<NameDirectory> nameDir_; + std::string registeredName_; +#endif + /** * Compute archive encryption key and DHT storage location from password and PIN. */