Commit f062220c authored by Adrien Béraud's avatar Adrien Béraud

nameservice: add name directory

Change-Id: I89c26b87c29d8c9dd541583f67dd13dbdc3ff37a
Tuleap: #938
parent 7e42bf1e
......@@ -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
......@@ -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.
......
......@@ -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 )),
......
......@@ -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)
{
......
......@@ -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);
......
......@@ -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]))
......
......@@ -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
......
......@@ -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
......@@ -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__
......
......@@ -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 {
......
......@@ -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" ;
......
......@@ -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*/);
......
......@@ -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
/*
* 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", "*/*");