diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml index fb13b8835c5a614117b302721e690f7e342ac6f9..cdcbadd9b8247bcb6371beb5dcd1600f1818e7e4 100644 --- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml +++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml @@ -154,6 +154,44 @@ </arg> </method> + <method name="exportOnRing" tp:name-for-bindings="exportOnRing"> + <tp:docstring> + Export account on the DHT using the given password and generated PIN (returned through exportOnRingEnded signal). + </tp:docstring> + <arg type="s" name="accountID" direction="in"> + </arg> + <arg type="s" name="password" direction="in"> + </arg> + <arg type="b" name="success" direction="out"> + <tp:docstring> + True if the operation was initialized successfully. exportOnRingEnded will be trigered on completion. + </tp:docstring> + </arg> + </method> + + <signal name="exportOnRingEnded" tp:name-for-bindings="exportOnRingEnded"> + <tp:docstring> + Notify clients when the exportOnRing 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. PIN is set.</li> + <li>WRONG_PASSWORD = 1 wrong password provided.</li> + <li>NETWORK_ERROR = 2 can't publish archive on the network.</li> + </ul> + </tp:docstring> + </arg> + <arg type="s" name="PIN"> + <tp:docstring> + A PIN to show to the user to import the account from somewhere else. + </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 0c9714827302575c014aaa8e2a2d7b2ecb252645..73c1d937e50d871d09829ec954d7d4da9a61a780 100644 --- a/bin/dbus/dbusclient.cpp +++ b/bin/dbus/dbusclient.cpp @@ -176,6 +176,7 @@ DBusClient::initLibrary(int flags) exportable_callback<ConfigurationSignal::IncomingAccountMessage>(bind(&DBusConfigurationManager::incomingAccountMessage, confM, _1, _2, _3 )), exportable_callback<ConfigurationSignal::AccountMessageStatusChanged>(bind(&DBusConfigurationManager::accountMessageStatusChanged, confM, _1, _2, _3, _4 )), 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::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 129ba9ffdc55c1ca291d5f8a3f0708fb0c3a65dd..8546e74e0a5e1705082e4bc711f5c1e22a881e82 100644 --- a/bin/dbus/dbusconfigurationmanager.cpp +++ b/bin/dbus/dbusconfigurationmanager.cpp @@ -74,6 +74,12 @@ DBusConfigurationManager::addAccount(const std::map<std::string, std::string>& d return DRing::addAccount(details); } +auto +DBusConfigurationManager::exportOnRing(const std::string& accountID, const std::string& password) -> decltype(DRing::exportOnRing(accountID, password)) +{ + return DRing::exportOnRing(accountID, password); +} + void DBusConfigurationManager::removeAccount(const std::string& accountID) { diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h index 743d40952dd0ea8cd7e61de9fa2aae52091620be..e6ecec8c6e59c34601a6b23bbb5dd42fcd4bbb73 100644 --- a/bin/dbus/dbusconfigurationmanager.h +++ b/bin/dbus/dbusconfigurationmanager.h @@ -63,6 +63,7 @@ class DBusConfigurationManager : void setAccountActive(const std::string& accountID, const bool& active); std::map<std::string, std::string> getAccountTemplate(const std::string& accountType); std::string addAccount(const std::map<std::string, std::string>& details); + bool exportOnRing(const std::string& accountID, const std::string& password); void removeAccount(const std::string& accoundID); std::vector<std::string> getAccountList(); void sendRegister(const std::string& accoundID, const bool& enable); diff --git a/contrib/src/msgpack/rules.mak b/contrib/src/msgpack/rules.mak index 18ec422bbe36e1be9990b125fd4414393ea84aca..134bb08b0a9c787080200e53e6a77972f59e7238 100644 --- a/contrib/src/msgpack/rules.mak +++ b/contrib/src/msgpack/rules.mak @@ -1,5 +1,5 @@ # MSGPACK -MSGPACK_VERSION := 068041f05eb1b8ab2930a7679dfe89ba7d14cb79 +MSGPACK_VERSION := 1df97bc37b363a340c5ad06c5cbcc53310aaff80 MSGPACK_URL := https://github.com/msgpack/msgpack-c/archive/$(MSGPACK_VERSION).tar.gz PKGS += msgpack diff --git a/contrib/src/opendht/rules.mak b/contrib/src/opendht/rules.mak index 5003d7a0ba1d40c29c9bead489d2e080808c0be8..d67427311a33b23293bfd673f3969a12ee158c43 100644 --- a/contrib/src/opendht/rules.mak +++ b/contrib/src/opendht/rules.mak @@ -1,5 +1,5 @@ # OPENDHT -OPENDHT_VERSION := 0.6.3 +OPENDHT_VERSION := aaed64039a40e7d1bc7a8907c53284506484aefb OPENDHT_URL := https://github.com/savoirfairelinux/opendht/archive/$(OPENDHT_VERSION).tar.gz PKGS += opendht diff --git a/src/Makefile.am b/src/Makefile.am index 87a4716060e2d6e08b6bb8badea9a59124e5b400..6db024c779d56c628070764ac0137661b3b5f36a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -130,7 +130,9 @@ libring_la_SOURCES = \ ring_api.cpp \ rational.h \ smartools.cpp \ - smartools.h + smartools.h \ + base64.h \ + base64.cpp if HAVE_WIN32 libring_la_SOURCES += \ diff --git a/src/account.cpp b/src/account.cpp index 556b9ba50c0a5a94b434cd85b2d959944d71db2f..794a88e7774aee4b769c8a4e37a783dc52083997 100644 --- a/src/account.cpp +++ b/src/account.cpp @@ -213,7 +213,6 @@ Account::serialize(YAML::Emitter& out) out << YAML::Key << RINGTONE_PATH_KEY << YAML::Value << ringtonePath_; out << YAML::Key << HAS_CUSTOM_USER_AGENT_KEY << YAML::Value << hasCustomUserAgent_; out << YAML::Key << USER_AGENT_KEY << YAML::Value << userAgent_; - out << YAML::Key << USERNAME_KEY << YAML::Value << username_; out << YAML::Key << DISPLAY_NAME_KEY << YAML::Value << displayName_; out << YAML::Key << HOSTNAME_KEY << YAML::Value << hostname_; out << YAML::Key << UPNP_ENABLED_KEY << YAML::Value << upnpEnabled_; @@ -226,7 +225,6 @@ Account::unserialize(const YAML::Node& node) parseValue(node, ALIAS_KEY, alias_); parseValue(node, ACCOUNT_ENABLE_KEY, enabled_); - parseValue(node, USERNAME_KEY, username_); parseValue(node, ACCOUNT_AUTOANSWER_KEY, autoAnswerEnabled_); parseValue(node, ACCOUNT_ACTIVE_CALL_LIMIT_KEY, activeCallLimit_); //parseValue(node, PASSWORD_KEY, password_); @@ -257,7 +255,6 @@ Account::setAccountDetails(const std::map<std::string, std::string> &details) parseString(details, Conf::CONFIG_ACCOUNT_ALIAS, alias_); parseString(details, Conf::CONFIG_ACCOUNT_DISPLAYNAME, displayName_); parseBool(details, Conf::CONFIG_ACCOUNT_ENABLE, enabled_); - parseString(details, Conf::CONFIG_ACCOUNT_USERNAME, username_); parseString(details, Conf::CONFIG_ACCOUNT_HOSTNAME, hostname_); parseString(details, Conf::CONFIG_ACCOUNT_MAILBOX, mailBox_); parseString(details, Conf::CONFIG_ACCOUNT_USERAGENT, userAgent_); @@ -366,6 +363,8 @@ Account::mapStateNumberToString(RegistrationState state) CASE_STATE(ERROR_SERVICE_UNAVAILABLE); CASE_STATE(ERROR_EXIST_STUN); CASE_STATE(ERROR_NOT_ACCEPTABLE); + CASE_STATE(ERROR_NEED_MIGRATION); + CASE_STATE(INITIALIZING); default: return DRing::Account::States::ERROR_GENERIC; } diff --git a/src/account.h b/src/account.h index 1b56003a90932a434979d1c93d27982a1de3de5a..b925752828d21dd22e63a576ee521b66fb6edee1 100644 --- a/src/account.h +++ b/src/account.h @@ -134,6 +134,8 @@ class Account : public Serializable, public std::enable_shared_from_this<Account */ virtual void doUnregister(std::function<void(bool)> cb = std::function<void(bool)>()) = 0; + RegistrationState getRegistrationState() const { return registrationState_; } + /** * Create a new outgoing call. * diff --git a/src/archiver.cpp b/src/archiver.cpp index 8f520728eeff551e666b3ba416ca2687c5873e51..ac00041386f87a2360b19e0420e299e1b5b87f98 100644 --- a/src/archiver.cpp +++ b/src/archiver.cpp @@ -20,8 +20,6 @@ #include "archiver.h" -#include <json/json.h> - #include "client/ring_signal.h" #include "account_const.h" #include "configurationmanager_interface.h" @@ -31,27 +29,67 @@ #include "logger.h" #include <opendht/crypto.h> +#include <json/json.h> +#include <zlib.h> -#include <fstream> #include <sys/stat.h> +#include <fstream> namespace ring { +namespace archiver { -Archiver& -Archiver::instance() -{ - // Meyers singleton - static Archiver instance_; - return instance_; +std::map<std::string, std::string> +jsonValueToAccount(Json::Value& value, const std::string& accountId) { + auto idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId; + fileutils::check_dir(idPath_.c_str(), 0700); + auto detailsMap = DRing::getAccountTemplate(value[DRing::Account::ConfProperties::TYPE].asString()); + + for( Json::ValueIterator itr = value.begin() ; itr != value.end() ; itr++ ) { + if (itr->asString().empty()) + continue; + if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0) { + std::string fileContent(itr->asString()); + fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "ca.key", {fileContent.begin(), fileContent.end()}, 0600); + + } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) { + std::string fileContent(itr->asString()); + fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.key", {fileContent.begin(), fileContent.end()}, 0600); + + } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0) { + std::string fileContent(itr->asString()); + fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.crt", {fileContent.begin(), fileContent.end()}, 0600); + } else + detailsMap[itr.key().asString()] = itr->asString(); + } + + return detailsMap; } -Archiver::Archiver() -{ +Json::Value +accountToJsonValue(std::map<std::string, std::string> details) { + Json::Value root; + std::map<std::string, std::string>::iterator iter; + for (iter = details.begin(); iter != details.end(); ++iter) { + if (iter->first.compare(DRing::Account::ConfProperties::Ringtone::PATH) == 0) { + // Ringtone path is not exportable + } else if (iter->first.compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0 || + iter->first.compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0 || + iter->first.compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) { + // replace paths by the files content + std::ifstream ifs(iter->second); + std::string fileContent((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); + root[iter->first] = fileContent; + + } else + root[iter->first] = iter->second; + } + + return root; } int -Archiver::exportAccounts(std::vector<std::string> accountIDs, +exportAccounts(std::vector<std::string> accountIDs, std::string filepath, std::string password) { @@ -109,31 +147,8 @@ Archiver::exportAccounts(std::vector<std::string> accountIDs, return 0; } -Json::Value -Archiver::accountToJsonValue(std::map<std::string, std::string> details) { - Json::Value root; - std::map<std::string, std::string>::iterator iter; - for (iter = details.begin(); iter != details.end(); ++iter) { - - if (iter->first.compare(DRing::Account::ConfProperties::Ringtone::PATH) == 0) { - // Ringtone path is not exportable - } else if (iter->first.compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0 || - iter->first.compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0 || - iter->first.compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) { - // replace paths by the files content - std::ifstream ifs(iter->second); - std::string fileContent((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); - root[iter->first] = fileContent; - - } else - root[iter->first] = iter->second; - } - - return root; -} - int -Archiver::importAccounts(std::string archivePath, std::string password) +importAccounts(std::string archivePath, std::string password) { if (archivePath.empty()) { RING_ERR("Missing arguments"); @@ -191,35 +206,8 @@ Archiver::importAccounts(std::string archivePath, std::string password) return 0; } -std::map<std::string, std::string> -Archiver::jsonValueToAccount(Json::Value& value, const std::string& accountId) { - auto idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId; - fileutils::check_dir(idPath_.c_str(), 0700); - auto detailsMap = DRing::getAccountTemplate(value[DRing::Account::ConfProperties::TYPE].asString()); - - for( Json::ValueIterator itr = value.begin() ; itr != value.end() ; itr++ ) { - if (itr->asString().empty()) - continue; - if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0) { - std::string fileContent(itr->asString()); - fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "ca.key", {fileContent.begin(), fileContent.end()}, 0600); - - } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) { - std::string fileContent(itr->asString()); - fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.key", {fileContent.begin(), fileContent.end()}, 0600); - - } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0) { - std::string fileContent(itr->asString()); - fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.crt", {fileContent.begin(), fileContent.end()}, 0600); - } else - detailsMap[itr.key().asString()] = itr->asString(); - } - - return detailsMap; -} - std::vector<uint8_t> -Archiver::compress(const std::string& str, int compressionlevel) +compress(const std::string& str) { auto destSize = compressBound(str.size()); std::vector<uint8_t> outbuffer(destSize); @@ -229,20 +217,20 @@ Archiver::compress(const std::string& str, int compressionlevel) if (ret != Z_OK) { std::ostringstream oss; oss << "Exception during zlib compression: (" << ret << ") "; - throw(std::runtime_error(oss.str())); + throw std::runtime_error(oss.str()); } return outbuffer; } std::vector<uint8_t> -Archiver::decompress(const std::vector<uint8_t>& str) +decompress(const std::vector<uint8_t>& str) { z_stream zs; // z_stream is zlib's control structure memset(&zs, 0, sizeof(zs)); if (inflateInit(&zs) != Z_OK) - throw(std::runtime_error("inflateInit failed while decompressing.")); + throw std::runtime_error("inflateInit failed while decompressing."); zs.next_in = (Bytef*)str.data(); zs.avail_in = str.size(); @@ -278,4 +266,4 @@ Archiver::decompress(const std::vector<uint8_t>& str) return out; } -} // namespace ring +}} // namespace ring::archiver diff --git a/src/archiver.h b/src/archiver.h index 6513e37cc5833614db402456bb7e1c09172054b8..3ef500f5608d32856786c5a86f7dd232f4ee0720 100644 --- a/src/archiver.h +++ b/src/archiver.h @@ -25,59 +25,45 @@ #include <string> #include <vector> #include <map> -#include <zlib.h> - -namespace Json { -class Value; -}; namespace ring { /** * Archiver is used to generate/read encrypted archives */ -class Archiver { -public: - static Archiver& instance(); - - Archiver(); - - /** - * Create a protected archive containing a list of accounts - * @param accountIDs The accounts to exports - * @param filepath The filepath where to put the resulting archive - * @param password The mandatory password to set on the archive - * @returns 0 for OK, error code otherwise - */ - int exportAccounts(std::vector<std::string> accountIDs, - std::string filepath, - std::string password); +namespace archiver { - /** - * Read a protected archive and add accounts found in it - * Warning: this function must be called from a registered pjsip thread - * @param archivePath The path to the archive file - * @param password The password to read the archive - * @returns 0 for OK, error code otherwise - */ - int importAccounts(std::string archivePath, std::string password); +/** + * Create a protected archive containing a list of accounts + * @param accountIDs The accounts to exports + * @param filepath The filepath where to put the resulting archive + * @param password The mandatory password to set on the archive + * @returns 0 for OK, error code otherwise + */ +int exportAccounts(std::vector<std::string> accountIDs, + std::string filepath, + std::string password); - /** - * Compress a STL string using zlib with given compression level and return - * the binary data. - */ - std::vector<uint8_t> compress(const std::string& str, int compressionlevel = Z_BEST_COMPRESSION); +/** + * Read a protected archive and add accounts found in it + * Warning: this function must be called from a registered pjsip thread + * @param archivePath The path to the archive file + * @param password The password to read the archive + * @returns 0 for OK, error code otherwise + */ +int importAccounts(std::string archivePath, std::string password); - /** - * Decompress an STL string using zlib and return the original data. - */ - std::vector<uint8_t> decompress(const std::vector<uint8_t>& dat); +/** + * Compress a STL string using zlib with given compression level and return + * the binary data. + */ +std::vector<uint8_t> compress(const std::string& str); -private: - NON_COPYABLE(Archiver); +/** + * Decompress an STL string using zlib and return the original data. + */ +std::vector<uint8_t> decompress(const std::vector<uint8_t>& dat); - static Json::Value accountToJsonValue(std::map<std::string, std::string> details); - static std::map<std::string, std::string> jsonValueToAccount(Json::Value& value, const std::string& accountId); -}; +} } // namespace ring diff --git a/src/sip/base64.c b/src/base64.cpp similarity index 81% rename from src/sip/base64.c rename to src/base64.cpp index 707352ad1d789f79b41d7d542db622dec299ba16..d14812afd3f41e64717eb2a443c680e4ce8b4b43 100644 --- a/src/sip/base64.c +++ b/src/base64.cpp @@ -16,6 +16,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "base64.h" + #include <stdint.h> #include <stdlib.h> @@ -109,3 +111,38 @@ uint8_t *ring_base64_decode(const char *input, size_t input_length, return output; } + +namespace ring { +namespace base64 { + +std::string +encode(const std::vector<uint8_t>::const_iterator begin, + const std::vector<uint8_t>::const_iterator end) +{ + size_t output_length = 4 * ((std::distance(begin, end) + 2) / 3); + std::string out; + out.resize(output_length); + ring_base64_encode(&(*begin), std::distance(begin, end), + &(*out.begin()), &output_length); + out.resize(output_length); + return out; +} + +std::string +encode(const std::vector<uint8_t>& dat) +{ + return encode(dat.cbegin(), dat.cend()); +} + +std::vector<uint8_t> +decode(const std::string& str) +{ + size_t output_length = str.length() / 4 * 3 + 2; + std::vector<uint8_t> output; + output.resize(output_length); + ring_base64_decode(str.data(), str.size(), output.data(), &output_length); + output.resize(output_length); + return output; +} + +}} // namespace ring::base64 diff --git a/src/sip/base64.h b/src/base64.h similarity index 63% rename from src/sip/base64.h rename to src/base64.h index 2e2069114e509621a3214e160c72887ed2d5c492..80b53c3521cc34f8cb96111a2544646046ebf3c7 100644 --- a/src/sip/base64.h +++ b/src/base64.h @@ -16,14 +16,10 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef H_BASE64 -#define H_BASE64 +#pragma once -#ifdef __cplusplus -extern "C" { -#endif - -#include "stdint.h" +#include <stdint.h> +#include <stddef.h> /** * Encode a buffer in base64. @@ -51,46 +47,14 @@ char *ring_base64_encode(const uint8_t *input, size_t input_length, uint8_t *ring_base64_decode(const char *input, size_t input_length, uint8_t *output, size_t *output_length); -#ifdef __cplusplus -} -#endif - #include <string> #include <vector> namespace ring { namespace base64 { -std::string -encode(const std::vector<uint8_t>::const_iterator begin, - const std::vector<uint8_t>::const_iterator end) -{ - size_t output_length = 4 * ((std::distance(begin, end) + 2) / 3); - std::string out; - out.resize(output_length); - ring_base64_encode(&(*begin), std::distance(begin, end), - &(*out.begin()), &output_length); - out.resize(output_length); - return out; -} - -std::string -encode(const std::vector<uint8_t>& dat) -{ - return encode(dat.cbegin(), dat.cend()); -} - -std::vector<uint8_t> -decode(const std::string& str) -{ - size_t output_length = str.length() / 4 * 3 + 2; - std::vector<uint8_t> output; - output.resize(output_length); - ring_base64_decode(str.data(), str.size(), output.data(), &output_length); - output.resize(output_length); - return output; -} +std::string encode(const std::vector<uint8_t>::const_iterator begin, const std::vector<uint8_t>::const_iterator end); +std::string encode(const std::vector<uint8_t>& dat); +std::vector<uint8_t> decode(const std::string& str); }} // namespace ring::base64 - -#endif // H_BASE64 diff --git a/src/call.cpp b/src/call.cpp index a17fc9fb1cc834e1c9f4395a32826f602a86898a..f028a31fc75c94ed2cd21ff1a12e87ab900a87f8 100644 --- a/src/call.cpp +++ b/src/call.cpp @@ -158,10 +158,24 @@ Call::setState(CallState call_state, ConnectionState cnx_state, signed code) connectionState_ = cnx_state; auto new_client_state = getStateStr(); + if (call_state == CallState::OVER) { + RING_DBG("[call:%s] %lu subcalls", id_.c_str(), subcalls.size()); + if (not subcalls.empty()) { + auto subs = std::move(subcalls); + for (auto c : subs) + c->hangup(0); + } + } + + for (auto& l : stateChangedListeners_) + l(callState_, connectionState_, code); + if (old_client_state != new_client_state) { - RING_DBG("[call:%s] emit client call state change %s, code %d", - id_.c_str(), new_client_state.c_str(), code); - emitSignal<DRing::CallSignal::StateChange>(id_, new_client_state, code); + if (not quiet) { + RING_DBG("[call:%s] emit client call state change %s, code %d", + id_.c_str(), new_client_state.c_str(), code); + emitSignal<DRing::CallSignal::StateChange>(id_, new_client_state, code); + } } return true; @@ -354,4 +368,87 @@ Call::peerHungup() aborted ? ECONNABORTED : ECONNREFUSED); } +void +Call::addSubCall(const std::shared_ptr<Call>& call) +{ + if (connectionState_ == ConnectionState::CONNECTED + || callState_ == CallState::ACTIVE + || callState_ == CallState::OVER) { + call->removeCall(); + } else { + std::lock_guard<std::recursive_mutex> lk (callMutex_); + if (not subcalls.emplace(call).second) + return; + call->quiet = true; + std::weak_ptr<Call> wthis = shared_from_this(); + std::weak_ptr<Call> wcall = call; + call->addStateListener([wcall,wthis](Call::CallState new_state, Call::ConnectionState new_cstate, int code) { + if (auto call = wcall.lock()) { + if (auto sthis = wthis.lock()) { + auto& this_ = *sthis; + auto sit = this_.subcalls.find(call); + if (sit == this_.subcalls.end()) + return; + RING_WARN("[call %s] DeviceCall call %s state changed %d %d", this_.getCallId().c_str(), call->getCallId().c_str(), new_state, new_cstate); + if (new_state == CallState::OVER) { + std::lock_guard<std::recursive_mutex> lk (this_.callMutex_); + this_.subcalls.erase(call); + } else if (new_state == CallState::ACTIVE && this_.callState_ == CallState::INACTIVE) { + this_.setState(new_state); + } + if ((unsigned)this_.connectionState_ < (unsigned)new_cstate && (unsigned)new_cstate < (unsigned)ConnectionState::RINGING) { + this_.setState(new_cstate); + } else if (new_cstate == ConnectionState::DISCONNECTED && new_state == CallState::ACTIVE) { + std::lock_guard<std::recursive_mutex> lk (this_.callMutex_); + RING_WARN("[call %s] peer hangup", this_.getCallId().c_str()); + auto subcalls = std::move(this_.subcalls); + for (auto& sub : subcalls) { + if (sub != call) + try { + sub->hangup(0); + } catch(const std::exception& e) { + RING_WARN("[call %s] error hanging up: %s", this_.getCallId().c_str()); + } + } + this_.peerHungup(); + } + if (new_state == CallState::ACTIVE && new_cstate == ConnectionState::CONNECTED) { + std::lock_guard<std::recursive_mutex> lk (this_.callMutex_); + RING_WARN("[call %s] peer answer", this_.getCallId().c_str()); + auto subcalls = std::move(this_.subcalls); + for (auto& sub : subcalls) { + if (sub != call) + sub->hangup(0); + } + this_.merge(call); + } + RING_WARN("[call %s] Remaining %d subcalls", this_.getCallId().c_str(), this_.subcalls.size()); + } else { + RING_WARN("DeviceCall IGNORED call %s state changed %d %d", call->getCallId().c_str(), new_state, new_cstate); + } + } + }); + setState(ConnectionState::TRYING); + } +} + +void +Call::merge(std::shared_ptr<Call> scall) +{ + RING_WARN("[call %s] merge to -> [call %s]", scall->getCallId().c_str(), getCallId().c_str()); + auto& call = *scall; + std::lock(callMutex_, call.callMutex_); + std::lock_guard<std::recursive_mutex> lk1 (callMutex_, std::adopt_lock); + std::lock_guard<std::recursive_mutex> lk2 (call.callMutex_, std::adopt_lock); + iceTransport_ = std::move(call.iceTransport_); + peerDisplayName_ = std::move(call.peerDisplayName_); + localAddr_ = call.localAddr_; + localAudioPort_ = call.localAudioPort_; + localVideoPort_ = call.localVideoPort_; + setState(call.getState()); + setState(call.getConnectionState()); + scall->removeCall(); +} + + } // namespace ring diff --git a/src/call.h b/src/call.h index 5eb618fcf38a84b0da3b204875d1dc75025ef04d..f8f428ff864a550b14744da56695d76be8fbc377 100644 --- a/src/call.h +++ b/src/call.h @@ -21,8 +21,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef __CALL_H__ -#define __CALL_H__ +#pragma once #ifdef HAVE_CONFIG_H #include "config.h" @@ -40,6 +39,7 @@ #include <memory> #include <vector> #include <condition_variable> +#include <set> namespace ring { @@ -330,9 +330,18 @@ class Call : public Recordable, public std::enable_shared_from_this<Call> { } virtual void restartMediaSender() = 0; - virtual void restartMediaReceiver() = 0; + using StateListener = std::function<void(CallState, int)>; + + template<class T> + void addStateListener(T&& list) { + stateChangedListeners_.emplace_back(std::forward<T>(list)); + } + void addSubCall(const std::shared_ptr<Call>& call); + + virtual void merge(std::shared_ptr<Call> scall); + protected: /** * Constructor of a call @@ -345,13 +354,18 @@ class Call : public Recordable, public std::enable_shared_from_this<Call> { bool isAudioMuted_{false}; bool isVideoMuted_{false}; + bool quiet {false}; + std::set<std::shared_ptr<Call>> subcalls {}; private: + bool validStateTransition(CallState newState); /** Protect every attribute that can be changed by two threads */ mutable std::recursive_mutex callMutex_ {}; + std::vector<std::function<void(CallState, ConnectionState, int)>> stateChangedListeners_ {}; + // Informations about call socket / audio /** My IP address */ @@ -394,5 +408,3 @@ class Call : public Recordable, public std::enable_shared_from_this<Call> { }; } // namespace ring - -#endif // __CALL_H__ diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp index 44d4a2bd771883866aae1ae4b17f84d5d97727e9..4a9adc1359a9e5c37f4cf817103f56a0739d7c8f 100644 --- a/src/client/configurationmanager.cpp +++ b/src/client/configurationmanager.cpp @@ -276,6 +276,16 @@ getMessageStatus(uint64_t id) return ring::Manager::instance().getMessageStatus(id); } +bool +exportOnRing(const std::string& accountID, const std::string& password) +{ + if (const auto account = ring::Manager::instance().getAccount<ring::RingAccount>(accountID)) { + account->addDevice(password); + return true; + } + return false; +} + /* contact requests */ std::map<std::string, std::string> getTrustRequests(const std::string& accountId) @@ -314,13 +324,13 @@ sendTrustRequest(const std::string& accountId, const std::string& to, const std: int exportAccounts(std::vector<std::string> accountIDs, std::string filepath, std::string password) { - return ring::Archiver::instance().exportAccounts(accountIDs, filepath, password); + return ring::archiver::exportAccounts(accountIDs, filepath, password); } int importAccounts(std::string archivePath, std::string password) { - return ring::Archiver::instance().importAccounts(archivePath, password); + return ring::archiver::importAccounts(archivePath, password); } ///This function is used as a base for new accounts for clients that support it diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp index 0d15295b38ad5b043014d21c09e93cfeb6b83be8..57833b76259030cca1c6762b6a3cc90fe0db8db2 100644 --- a/src/client/ring_signal.cpp +++ b/src/client/ring_signal.cpp @@ -63,6 +63,7 @@ getSignalHandlers() exported_callback<DRing::ConfigurationSignal::IncomingAccountMessage>(), exported_callback<DRing::ConfigurationSignal::AccountMessageStatusChanged>(), exported_callback<DRing::ConfigurationSignal::IncomingTrustRequest>(), + exported_callback<DRing::ConfigurationSignal::ExportOnRingEnded>(), 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 b169f4e7e58ac921aa9a7e343717bff808a29047..5ac7dfd56dd781a6913eee88667b491cb38fa8fd 100644 --- a/src/dring/account_const.h +++ b/src/dring/account_const.h @@ -53,7 +53,10 @@ constexpr static const char ERROR_CONF_STUN [] = "ERROR_CONF_STUN"; constexpr static const char ERROR_EXIST_STUN [] = "ERROR_EXIST_STUN"; constexpr static const char ERROR_SERVICE_UNAVAILABLE [] = "ERROR_SERVICE_UNAVAILABLE"; constexpr static const char ERROR_NOT_ACCEPTABLE [] = "ERROR_NOT_ACCEPTABLE"; +constexpr static const char ERROR_MISSING_ID [] = "ERROR_MISSING_ID"; +constexpr static const char ERROR_NEED_MIGRATION [] = "ERROR_NEED_MIGRATION"; constexpr static const char REQUEST_TIMEOUT [] = "Request Timeout"; +constexpr static const char INITIALIZING [] = "INITIALIZING"; } //namespace DRing::Account @@ -125,7 +128,10 @@ constexpr static const char HAS_CUSTOM_USER_AGENT [] = "Account.hasCustomUserA constexpr static const char ALLOW_CERT_FROM_HISTORY [] = "Account.allowCertFromHistory"; constexpr static const char ALLOW_CERT_FROM_CONTACT [] = "Account.allowCertFromContact"; constexpr static const char ALLOW_CERT_FROM_TRUSTED [] = "Account.allowCertFromTrusted"; - +constexpr static const char ARCHIVE_PASSWORD [] = "Account.archivePassword"; +constexpr static const char ARCHIVE_PATH [] = "Account.archivePath"; +constexpr static const char ARCHIVE_PIN [] = "Account.archivePIN"; +constexpr static const char RING_DEVICE_ID [] = "Account.deviceID"; namespace Audio { @@ -216,6 +222,13 @@ constexpr static const char ALLOW_FROM_TRUSTED [] = "DHT.AllowFromTrusted"; } //namespace DRing::Account::DHT +namespace ETH { + +constexpr static const char KEY_FILE [] = "ETH.keyFile"; +constexpr static const char ACCOUNT [] = "ETH.account"; + +} //namespace DRing::Account::ETH + namespace CodecInfo { constexpr static const char NAME [] = "CodecInfo.name"; diff --git a/src/dring/configurationmanager_interface.h b/src/dring/configurationmanager_interface.h index 46efa792d04a6d0305806070de86d13911cd5da5..c2c67225188060a590bd1ceae19bb0c0ee61f0a5 100644 --- a/src/dring/configurationmanager_interface.h +++ b/src/dring/configurationmanager_interface.h @@ -6,6 +6,7 @@ * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> * Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@savoirfairelinux.com> * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * 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 @@ -22,8 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef DRING_CONFIGURATIONMANAGERI_H -#define DRING_CONFIGURATIONMANAGERI_H +#pragma once #include <vector> #include <map> @@ -45,6 +45,8 @@ std::map<std::string, std::string> testAccountICEInitialization(const std::strin void setAccountActive(const std::string& accountID, bool active); std::map<std::string, std::string> getAccountTemplate(const std::string& accountType); std::string addAccount(const std::map<std::string, std::string>& details); +bool exportOnRing(const std::string& accountID, const std::string& password); + void removeAccount(const std::string& accountID); void setAccountEnabled(const std::string& accountID, bool enable); std::vector<std::string> getAccountList(); @@ -53,6 +55,7 @@ void registerAllAccounts(void); uint64_t sendAccountTextMessage(const std::string& accountID, const std::string& to, const std::map<std::string, std::string>& payloads); int getMessageStatus(uint64_t id); + std::map<std::string, std::string> getTlsDefaultSettings(); std::vector<unsigned> getCodecList(); @@ -208,6 +211,10 @@ struct ConfigurationSignal { constexpr static const char* name = "IncomingTrustRequest"; using cb_type = void(const std::string& /*account_id*/, const std::string& /*from*/, const std::vector<uint8_t>& payload, time_t received); }; + struct ExportOnRingEnded { + constexpr static const char* name = "ExportOnRingEnded"; + using cb_type = void(const std::string& /*account_id*/, int state, const std::string& pin); + }; struct CertificatePinned { constexpr static const char* name = "CertificatePinned"; using cb_type = void(const std::string& /*certId*/); @@ -245,5 +252,3 @@ struct ConfigurationSignal { }; } // namespace DRing - -#endif // DRING_CONFIGURATIONMANAGERI_H diff --git a/src/fileutils.cpp b/src/fileutils.cpp index 707df11a545d949bab671b1974fd805812e9247a..c2c5788a223e5a2f03bd1c0ec8bd04e00d173f47 100644 --- a/src/fileutils.cpp +++ b/src/fileutils.cpp @@ -209,6 +209,11 @@ expand_path(const std::string &path) #endif } +bool isFile (const std::string& path) { + struct stat s; + return (stat (path.c_str(), &s) == 0) and not (s.st_mode & S_IFDIR); +} + bool isDirectory(const std::string& path) { struct stat s; diff --git a/src/fileutils.h b/src/fileutils.h index 4bc3e52f56aa85c0eea86c543105c216a81815ed..1992a125afbb939d01a9368b39fac042be531f13 100644 --- a/src/fileutils.h +++ b/src/fileutils.h @@ -66,6 +66,7 @@ namespace ring { namespace fileutils { bool recursive_mkdir(const std::string& path, mode_t mode=0755); + bool isFile(const std::string& path); bool isDirectory(const std::string& path); bool isSymLink(const std::string& path); diff --git a/src/manager.cpp b/src/manager.cpp index e3d0da2d10aa3bbe1b70753188ed1f0a5a005850..c70f87e504dbefca5381e92d087043f5d8f963bc 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -342,6 +342,11 @@ Manager::finish() noexcept hangupCall(call->getCallId()); callFactory.clear(); + for (const auto &account : getAllAccounts<RingAccount>()) { + if (account->getRegistrationState() == RegistrationState::INITIALIZING) + removeAccount(account->getAccountID()); + } + saveConfig(); // Disconnect accounts, close link stacks and free allocated ressources diff --git a/src/registration_states.h b/src/registration_states.h index 1f633a7513c67a8aa8a78dbcc0e9b045a63add90..5b7bb423c2aa9de25a463335696234799632e340 100644 --- a/src/registration_states.h +++ b/src/registration_states.h @@ -39,7 +39,9 @@ enum class RegistrationState { ERROR_HOST, ERROR_SERVICE_UNAVAILABLE, ERROR_EXIST_STUN, - ERROR_NOT_ACCEPTABLE + ERROR_NOT_ACCEPTABLE, + ERROR_NEED_MIGRATION, + INITIALIZING }; } // namespace ring diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp index c83d75eb54f97ebf282737c25be5f29a794a73f1..5f99d2dfcde96825e1e2a03775b020063dd00e94 100644 --- a/src/ringdht/ringaccount.cpp +++ b/src/ringdht/ringaccount.cpp @@ -54,12 +54,17 @@ #include "fileutils.h" #include "string_utils.h" #include "array_size.h" +#include "archiver.h" #include "config/yamlparser.h" - #include "security/certstore.h" +#include "libdevcrypto/Common.h" +#include "base64.h" #include <yaml-cpp/yaml.h> +#include <json/json.h> + +#include <unistd.h> #include <algorithm> #include <array> @@ -79,10 +84,11 @@ static constexpr int ICE_COMP_SIP_TRANSPORT {0}; static constexpr int ICE_INIT_TIMEOUT {10}; static constexpr auto ICE_NEGOTIATION_TIMEOUT = std::chrono::seconds(60); static constexpr auto TLS_TIMEOUT = std::chrono::seconds(30); +const constexpr auto EXPORT_KEY_RENEWAL_TIME = std::chrono::minutes(20); static constexpr const char * const RING_URI_PREFIX = "ring:"; -constexpr const char * const RingAccount::ACCOUNT_TYPE; +constexpr const char* const RingAccount::ACCOUNT_TYPE; /* constexpr */ const std::pair<uint16_t, uint16_t> RingAccount::DHT_PORT_RANGE {4000, 8888}; static std::uniform_int_distribution<dht::Value::Id> udist; @@ -109,6 +115,14 @@ parseRingUri(const std::string& toUrl) return toUri; } + +static constexpr const char* +dhtStatusStr(dht::NodeStatus status) { + return status == dht::NodeStatus::Connected ? "connected" : ( + status == dht::NodeStatus::Connecting ? "connecting" : + "disconnected"); +} + /** * Local ICE Transport factory helper * @@ -139,12 +153,11 @@ RingAccount::createIceTransport(const Args&... args) } RingAccount::RingAccount(const std::string& accountID, bool /* presenceEnabled */) - : SIPAccountBase(accountID), via_addr_() -{ - cachePath_ = fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID(); - dataPath_ = cachePath_ + DIR_SEPARATOR_STR "values"; - idPath_ = fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID(); -} + : SIPAccountBase(accountID), via_addr_(), + cachePath_(fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID()), + dataPath_(cachePath_ + DIR_SEPARATOR_STR "values"), + idPath_(fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID()) +{} RingAccount::~RingAccount() { @@ -173,9 +186,11 @@ RingAccount::newIncomingCall(const std::string& from) if (not call) { RING_WARN("newIncomingCall: discarding deleted call"); call_it = pendingSipCalls_.erase(call_it); - } else if (call->getPeerNumber() == from) { - pendingSipCalls_.erase(call_it); + } else if (call->getPeerNumber() == from || (call_it->from_cert and + call_it->from_cert->issuer and + call_it->from_cert->issuer->getId().toString() == from)) { RING_DBG("newIncomingCall: found matching call for %s", from.c_str()); + pendingSipCalls_.erase(call_it); return call; } else { ++call_it; @@ -199,87 +214,121 @@ RingAccount::newOutgoingCall(const std::string& toUrl) call->setIPToIP(true); call->setSecure(isTlsEnabled()); + auto shared_this = std::static_pointer_cast<RingAccount>(shared_from_this()); + // TODO: for now, we automatically trust all explicitly called peers setCertificateStatus(toUri, tls::TrustStore::PermissionStatus::ALLOWED); - auto shared_this = std::static_pointer_cast<RingAccount>(shared_from_this()); + const auto toH = dht::InfoHash(toUri); + + call->setState(Call::ConnectionState::TRYING); std::weak_ptr<SIPCall> weak_call = call; - manager.addTask([shared_this, weak_call, toUri] { - auto call = weak_call.lock(); - if (not call) - return false; - // Create an ICE transport for SIP channel - std::shared_ptr<IceTransport> ice {}; + auto treatedDevices_ = std::make_shared<std::set<dht::InfoHash>>(); - try { - ice = shared_this->createIceTransport(("sip:" + call->getCallId()).c_str(), - ICE_COMPONENTS, true, shared_this->getIceOptions()); - } catch (std::runtime_error& e) { - RING_ERR("%s", e.what()); - call->onFailure(); - return false; - } + // Find listening Ring devices for this account + shared_this->dht_.get<DeviceAnnouncement>(toH, [=](DeviceAnnouncement&& dev) { + if (dev.from != toH) + return true; + if (not treatedDevices_->emplace(dev.dev).second) + return true; + RING_WARN("Found device to place call %s", dev.dev.toString().c_str()); + + runOnMainThread([=](){ + if (auto call = weak_call.lock()) { + RING_WARN("[call %s] Found device %s", call->getCallId().c_str(), dev.dev.toString().c_str()); + + auto& manager = Manager::instance(); + auto dev_call = manager.callFactory.newCall<SIPCall, RingAccount>(*this, manager.getNewCallID(), + Call::CallType::OUTGOING); + std::weak_ptr<SIPCall> weak_dev_call = dev_call; + dev_call->setIPToIP(true); + dev_call->setSecure(isTlsEnabled()); + auto ice = createIceTransport(("sip:" + dev_call->getCallId()).c_str(), + ICE_COMPONENTS, true, getIceOptions()); + if (not ice) { + RING_WARN("Can't create ICE"); + dev_call->removeCall(); + return; + } - auto iceInitTimeout = std::chrono::steady_clock::now() + std::chrono::seconds {ICE_INIT_TIMEOUT}; + call->addSubCall(dev_call); - /* First step: wait for an initialized ICE transport for SIP channel */ - if (ice->isFailed() or std::chrono::steady_clock::now() >= iceInitTimeout) { - RING_DBG("ice init failed (or timeout)"); - call->onFailure(); - return false; - } + auto iceInitTimeout = std::chrono::steady_clock::now() + std::chrono::seconds {ICE_INIT_TIMEOUT}; + + manager.addTask([shared_this, weak_dev_call, ice, iceInitTimeout, toUri, dev] { + auto call = weak_dev_call.lock(); + + if (not call) + return false; - if (not ice->isInitialized()) - return true; // process task again! - - /* Next step: sent the ICE data to peer through DHT */ - const dht::Value::Id callvid = udist(shared_this->rand_); - const dht::Value::Id vid = udist(shared_this->rand_); - const auto toH = dht::InfoHash(toUri); - const auto callkey = dht::InfoHash::get("callto:" + toUri); - dht::Value val { dht::IceCandidates(callvid, ice->getLocalAttributesAndCandidates()) }; - val.id = vid; - - call->setState(Call::ConnectionState::TRYING); - shared_this->dht_.putEncrypted( - callkey, toH, - std::move(val), - [=](bool ok) { // Put complete callback - if (!ok) { - RING_WARN("Can't put ICE descriptor on DHT"); - if (auto call = weak_call.lock()) + if (ice->isFailed() or std::chrono::steady_clock::now() >= iceInitTimeout) { + RING_DBG("ice init failed (or timeout)"); call->onFailure(); - } else - RING_DBG("Successfully put ICE descriptor on DHT"); - } - ); + return false; + } - auto listenKey = shared_this->dht_.listen<dht::IceCandidates>( - callkey, - [=] (dht::IceCandidates&& msg) { - if (msg.id != callvid or msg.from != toH) - return true; - RING_WARN("ICE request replied from DHT peer %s\n%s", toH.toString().c_str(), - std::string(msg.ice_data.cbegin(), msg.ice_data.cend()).c_str()); - if (auto call = weak_call.lock()) - call->setState(Call::ConnectionState::PROGRESSING); - if (!ice->start(msg.ice_data)) { - call->onFailure(); - return true; - } - return false; - } - ); + if (not ice->isInitialized()) + return true; // process task again! + + RING_WARN("ICE initialised"); + + // Next step: sent the ICE data to peer through DHT + const dht::Value::Id callvid = udist(shared_this->rand_); + const dht::Value::Id vid = udist(shared_this->rand_); + const auto callkey = dht::InfoHash::get("callto:" + dev.dev.toString()); + dht::Value val { dht::IceCandidates(callvid, ice->getLocalAttributesAndCandidates()) }; + val.id = vid; + + //RING_WARN("ICE initialised"); + shared_this->dht_.putEncrypted( + callkey, dev.dev, + std::move(val), + [=](bool ok) { // Put complete callback + if (!ok) { + RING_WARN("Can't put ICE descriptor on DHT"); + if (auto call = weak_dev_call.lock()) + call->onFailure(); + } else + RING_DBG("Successfully put ICE descriptor on DHT"); + } + ); - shared_this->pendingCalls_.emplace_back(PendingCall{ - std::chrono::steady_clock::now(), - ice, weak_call, - std::move(listenKey), - callkey, toH - }); + auto listenKey = shared_this->dht_.listen<dht::IceCandidates>( + callkey, + [=] (dht::IceCandidates&& msg) { + if (msg.id != callvid or msg.from != dev.dev) + return true; + RING_WARN("ICE request replied from DHT peer %s\n%s", dev.dev.toString().c_str(), + std::string(msg.ice_data.cbegin(), msg.ice_data.cend()).c_str()); + if (auto call = weak_dev_call.lock()) + call->setState(Call::ConnectionState::PROGRESSING); + if (!ice->start(msg.ice_data)) { + call->onFailure(); + return true; + } + return false; + } + ); - return false; + shared_this->pendingCalls_.emplace_back(PendingCall{ + std::chrono::steady_clock::now(), + ice, weak_dev_call, + std::move(listenKey), + callkey, dev.dev + }); + return false; + }); + } + }); + return true; + }, [=](bool ok){ + RING_WARN("newOutgoingCall: found %lu devices", treatedDevices_->size()); + if (treatedDevices_->empty()) { + if (auto call = weak_call.lock()) { + call->onFailure(); + } + } }); return call; @@ -409,6 +458,7 @@ RingAccount::SIPStartCall(const std::shared_ptr<SIPCall>& call, IpAddr target) return false; } + RING_ERR("Sending SIP invite"); if (pjsip_inv_send_msg(call->inv.get(), tdata) != PJ_SUCCESS) { RING_ERR("Unable to send invite message for this call"); return false; @@ -421,6 +471,9 @@ RingAccount::SIPStartCall(const std::shared_ptr<SIPCall>& call, IpAddr target) void RingAccount::serialize(YAML::Emitter &out) { + if (registrationState_ == RegistrationState::INITIALIZING) + return; + out << YAML::BeginMap; SIPAccountBase::serialize(out); out << YAML::Key << Conf::DHT_PORT_KEY << YAML::Value << dhtPort_; @@ -429,6 +482,11 @@ 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_; + 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; SIPAccountBase::serializeTls(out); @@ -442,39 +500,174 @@ void RingAccount::unserialize(const YAML::Node &node) using yaml_utils::parseValue; SIPAccountBase::unserialize(node); - parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_HISTORY, allowPeersFromHistory_); 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) { + RING_WARN("can't read archive path: %s", e.what()); + } + + try { + parseValue(node, Conf::RING_ACCOUNT_RECEIPT, receipt_); + auto receipt_sig = node[Conf::RING_ACCOUNT_RECEIPT_SIG].as<YAML::Binary>(); + receiptSignature_ = {receipt_sig.data(), receipt_sig.data()+receipt_sig.size()}; + } catch (const std::exception& e) { + RING_WARN("can't read receipt: %s", e.what()); + } + if (not dhtPort_) dhtPort_ = getRandomEvenPort(DHT_PORT_RANGE); dhtPortUsed_ = dhtPort_; parseValue(node, Conf::DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_); - checkIdentityPath(); + loadAccount(); } void -RingAccount::checkIdentityPath() +RingAccount::createRingDevice(const dht::crypto::Identity& id) { - if (not tlsPrivateKeyFile_.empty() and not tlsCertificateFile_.empty()) { - loadIdentity(); - return; + RING_WARN("createRingDevice"); + auto dev_id = dht::crypto::generateIdentity("Ring device", id); + if (!dev_id.first || !dev_id.second) { + throw VoipLinkException("Can't generate identity for this account."); + } + idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID(); + fileutils::check_dir(idPath_.c_str(), 0700); + + // save the chain including CA + saveIdentity(dev_id, idPath_ + DIR_SEPARATOR_STR "dht"); + tlsCertificateFile_ = idPath_ + DIR_SEPARATOR_STR "dht.crt"; + tlsPrivateKeyFile_ = idPath_ + DIR_SEPARATOR_STR "dht.key"; + tlsPassword_ = {}; + identity_ = dev_id; + ringDeviceId_ = dev_id.first->getPublicKey().getId().toString(); + + receipt_ = makeReceipt(id); + RING_WARN("createRingDevice with %s", id.first->getPublicKey().getId().toString().c_str()); + receiptSignature_ = id.first->sign({receipt_.begin(), receipt_.end()}); +} + +void +RingAccount::initRingDevice(const ArchiveContent& a) +{ + RING_WARN("initRingDevice"); + SIPAccountBase::setAccountDetails(a.config); + parseInt(a.config, Conf::CONFIG_DHT_PORT, dhtPort_); + parseBool(a.config, Conf::CONFIG_DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_); + parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_HISTORY, allowPeersFromHistory_); + parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_CONTACT, allowPeersFromContact_); + parseBool(a.config, DRing::Account::ConfProperties::ALLOW_CERT_FROM_TRUSTED, allowPeersFromTrusted_); + ringAccountId_ = a.id.second->getId().toString(); + username_ = RING_URI_PREFIX+ringAccountId_; + ethAccount_ = dev::KeyPair(dev::Secret(a.eth_key)).address().hex(); + createRingDevice(a.id); +} + +std::string +RingAccount::makeReceipt(const dht::crypto::Identity& id) +{ + RING_WARN("making receipt"); + DeviceAnnouncement announcement; + announcement.dev = identity_.second->getId(); + dht::Value ann_val {announcement}; + ann_val.sign(*id.first); + + std::ostringstream is; + is << "{\"id\":\"" << id.second->getId() + << "\",\"dev\":\"" << identity_.second->getId() + << "\",\"eth\":\"" << ethAccount_ + << "\",\"announce\":\"" << base64::encode(ann_val.getPacked()) << "\"}"; + + announce_ = std::make_shared<dht::Value>(std::move(ann_val)); + return is.str(); +} + +bool +RingAccount::hasSignedReceipt() +{ + if (receipt_.empty() or receiptSignature_.empty()) + return false; + + if (not identity_.first or not identity_.second) { + RING_WARN("hasSignedReceipt() no identity"); + return false; + } + + auto pk = identity_.second->issuer->getPublicKey(); + RING_WARN("hasSignedReceipt() with %s", pk.getId().toString().c_str()); + if (!pk.checkSignature({receipt_.begin(), receipt_.end()}, receiptSignature_)) { + RING_WARN("hasSignedReceipt() signature check failed"); + return false; + } + + Json::Value root; + Json::Reader reader; + if (!reader.parse(receipt_, root)) + return false; + + auto dev_id = root["dev"].asString(); + if (dev_id != identity_.second->getId().toString()) { + RING_WARN("hasSignedReceipt() dev_id not matching"); + return false; + } + auto id = root["id"].asString(); + if (id != pk.getId().toString()) { + RING_WARN("hasSignedReceipt() id not matching"); + return false; + } + + dht::Value announce_val; + try { + auto announce = base64::decode(root["announce"].asString()); + msgpack::object_handle announce_msg = msgpack::unpack((const char*)announce.data(), announce.size()); + //dht::Value announce_val (announce_msg.get()); + announce_val.msgpack_unpack(announce_msg.get()); + if (not announce_val.checkSignature()) { + RING_WARN("hasSignedReceipt() announce signature check failed"); + return false; + } + DeviceAnnouncement da; + da.unpackValue(announce_val); + if (da.from.toString() != id or da.dev.toString() != dev_id) { + RING_WARN("hasSignedReceipt() announce not matching"); + return false; + } + } catch (const std::exception& e) { + RING_WARN("hasSignedReceipt(): can't read announce: %s", e.what()); + return false; } - const auto idPath = fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID(); - tlsPrivateKeyFile_ = idPath + DIR_SEPARATOR_STR "dht.key"; - tlsCertificateFile_ = idPath + DIR_SEPARATOR_STR "dht.crt"; - loadIdentity(); + ringAccountId_ = id; + 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; + } + + RING_WARN("hasSignedReceipt() -> true"); + return true; } dht::crypto::Identity RingAccount::loadIdentity() { + RING_WARN("loadIdentity() %s %s", tlsCertificateFile_.c_str(), tlsPrivateKeyFile_.c_str()); dht::crypto::Certificate dht_cert; dht::crypto::PrivateKey dht_key; - try { #if TARGET_OS_IPHONE const auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID() + DIR_SEPARATOR_STR; @@ -484,42 +677,225 @@ RingAccount::loadIdentity() dht_cert = dht::crypto::Certificate(fileutils::loadFile(tlsCertificateFile_)); dht_key = dht::crypto::PrivateKey(fileutils::loadFile(tlsPrivateKeyFile_), tlsPassword_); #endif + auto crt_id = dht_cert.getId(); + if (crt_id != dht_key.getPublicKey().getId()) + return {}; + + identity_ = { + std::make_shared<dht::crypto::PrivateKey>(std::move(dht_key)), + std::make_shared<dht::crypto::Certificate>(std::move(dht_cert)) + }; } catch (const std::exception& e) { RING_ERR("Error loading identity: %s", e.what()); - auto ca = dht::crypto::generateIdentity("Ring CA"); - if (!ca.first || !ca.second) { - throw VoipLinkException("Can't generate CA for this account."); - } - auto id = dht::crypto::generateIdentity("Ring", ca); - if (!id.first || !id.second) { - throw VoipLinkException("Can't generate identity for this account."); + } + + return identity_; +} + +RingAccount::ArchiveContent +RingAccount::readArchive(const std::string& pwd) const +{ + RING_WARN("readArchive()"); + + // Read file + std::vector<uint8_t> file = fileutils::loadFile(archivePath_); + + // Decrypt + file = dht::crypto::aesDecrypt(file, pwd); + + // Load + return loadArchive(file); +} + + +RingAccount::ArchiveContent +RingAccount::loadArchive(const std::vector<uint8_t>& dat) +{ + ArchiveContent c; + RING_WARN("loadArchive()"); + + std::vector<uint8_t> file; + + // Decompress + try { + file = archiver::decompress(dat); + } catch (const std::exception& ex) { + RING_ERR("Decompression failed: %s", ex.what()); + throw std::runtime_error("failed to read file."); + } + + // Decode string + std::string decoded {file.begin(), file.end()}; + Json::Value value; + Json::Reader reader; + if (!reader.parse(decoded.c_str(),value)) { + RING_ERR("Failed to parse %s", reader.getFormattedErrorMessages().c_str()); + throw std::runtime_error("failed to parse JSON."); + } + + // Import content + try { + c.config = DRing::getAccountTemplate(ACCOUNT_TYPE); + for (Json::ValueIterator itr = value.begin() ; itr != value.end() ; itr++) { + if (itr->asString().empty()) + continue; + if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0) { + } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) { + } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0) { + } else if (itr.key().asString().compare(Conf::RING_CA_KEY) == 0) { + c.ca_key = {base64::decode(itr->asString())}; + } else if (itr.key().asString().compare(Conf::RING_ACCOUNT_KEY) == 0) { + c.id.first = std::make_shared<dht::crypto::PrivateKey>(base64::decode(itr->asString())); + } else if (itr.key().asString().compare(Conf::RING_ACCOUNT_CERT) == 0) { + c.id.second = std::make_shared<dht::crypto::Certificate>(base64::decode(itr->asString())); + } else if (itr.key().asString().compare(Conf::ETH_KEY) == 0) { + c.eth_key = base64::decode(itr->asString()); + } else + c.config[itr.key().asString()] = itr->asString(); } - idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID(); - fileutils::check_dir(idPath_.c_str(), 0700); + } catch (const std::exception& ex) { + RING_ERR("Can't parse JSON: %s", ex.what()); + } - fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "ca.key", ca.first->serialize(), 0600); + return c; +} - // save the chain including CA - saveIdentity(id, idPath_ + DIR_SEPARATOR_STR "dht"); -#if TARGET_OS_IPHONE - tlsCertificateFile_ = "dht.crt"; - tlsPrivateKeyFile_ = "dht.key"; -#else - tlsCertificateFile_ = idPath_ + DIR_SEPARATOR_STR "dht.crt"; - tlsPrivateKeyFile_ = idPath_ + DIR_SEPARATOR_STR "dht.key"; -#endif - tlsPassword_ = {}; - username_ = RING_URI_PREFIX+id.second->getId().toString(); - return id; +std::vector<uint8_t> +RingAccount::makeArchive(const ArchiveContent& archive) const +{ + RING_WARN("makeArchive()"); + + Json::Value root; + + auto details = getAccountDetails(); + for (auto it : details) { + if (it.first.compare(DRing::Account::ConfProperties::Ringtone::PATH) == 0) { + // Ringtone path is not exportable + } else if (it.first.compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0 || + it.first.compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0 || + it.first.compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) { + // replace paths by the files content + if (not it.second.empty()) { + try { + root[it.first] = base64::encode(fileutils::loadFile(it.second)); + } catch (...) {} + } + } else + root[it.first] = it.second; } - username_ = RING_URI_PREFIX+dht_cert.getId().toString(); - return { - std::make_shared<dht::crypto::PrivateKey>(std::move(dht_key)), - std::make_shared<dht::crypto::Certificate>(std::move(dht_cert)) - }; + root[Conf::RING_CA_KEY] = base64::encode(archive.ca_key.serialize()); + root[Conf::RING_ACCOUNT_KEY] = base64::encode(archive.id.first->serialize()); + root[Conf::RING_ACCOUNT_CERT] = base64::encode(archive.id.second->getPacked()); + root[Conf::ETH_KEY] = base64::encode(archive.eth_key); + + Json::FastWriter fastWriter; + std::string output = fastWriter.write(root); + + // Compress + return archiver::compress(output); +} + +void +RingAccount::saveArchive(const ArchiveContent& archive_content, const std::string& pwd) +{ + std::vector<uint8_t> archive; + try { + archive = makeArchive(archive_content); + } catch (const std::runtime_error& ex) { + RING_ERR("Can't export archive: %s", ex.what()); + return; + } + + // Encrypt using provided password + auto encrypted = dht::crypto::aesEncrypt(archive, pwd); + + // Write + try { + if (archivePath_.empty()) + archivePath_ = idPath_ + DIR_SEPARATOR_STR "export.gz"; + fileutils::saveFile(archivePath_, encrypted); + } catch (const std::runtime_error& ex) { + RING_ERR("Export failed: %s", ex.what()); + return; + } +} + +std::pair<std::vector<uint8_t>, dht::InfoHash> +RingAccount::computeKeys(const std::string& password, const std::string& pin, bool previous) +{ + // Compute time seed + auto now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()); + auto tseed = now.count() / std::chrono::duration_cast<std::chrono::seconds>(EXPORT_KEY_RENEWAL_TIME).count(); + if (previous) + tseed--; + std::stringstream ss; + ss << std::hex << tseed; + auto tseed_str = ss.str(); + + // Generate key for archive encryption, using PIN as the salt + std::vector<uint8_t> salt_key; + salt_key.reserve(pin.size() + tseed_str.size()); + salt_key.insert(salt_key.end(), pin.begin(), pin.end()); + salt_key.insert(salt_key.end(), tseed_str.begin(), tseed_str.end()); + auto key = dht::crypto::stretchKey(password, salt_key, 256/8); + + // Generate public storage location as SHA1(key). + auto loc = dht::InfoHash::get(key); + + return {key, loc}; +} + +void +RingAccount::addDevice(const std::string& password) +{ + auto this_ = std::static_pointer_cast<RingAccount>(shared_from_this()); + ThreadPool::instance().run([this_,password]() { + std::vector<uint8_t> key; + dht::InfoHash loc; + std::string pin_str; + ArchiveContent a; + try { + RING_DBG("Exporting Ring account %s", this_->getAccountID().c_str()); + + a = this_->readArchive(password); + + // Generate random 32bits PIN + std::uniform_int_distribution<uint32_t> dis; + auto pin = dis(this_->rand_); + // Manipulate PIN as hex + std::stringstream ss; + ss << std::hex << pin; + pin_str = ss.str(); + std::transform(pin_str.begin(), pin_str.end(), pin_str.begin(), ::toupper); + + std::tie(key, loc) = computeKeys(password, pin_str); + } catch (const std::exception& e) { + RING_ERR("Can't add device: %s", e.what()); + emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 1, ""); + return; + } + try { + auto archive = this_->makeArchive(a); + auto encrypted = dht::crypto::aesEncrypt(archive, key); + if (not this_->dht_.isRunning()) + throw std::runtime_error("DHT is not running.."); + this_->dht_.put(loc, encrypted, [this_,pin_str](bool ok) { + RING_WARN("Done publishing account archive: %s", ok ? "success" : "failure"); + if (ok) + emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 0, pin_str); + else + emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 2, ""); + }); + RING_WARN("Adding new device with PIN: %s at %s (size %zu)", pin_str.c_str(), loc.toString().c_str(), encrypted.size()); + } catch (const std::exception& e) { + RING_ERR("Can't add device: %s", e.what()); + emitSignal<DRing::ConfigurationSignal::ExportOnRingEnded>(this_->getAccountID(), 2, ""); + return; + } + }); } void @@ -531,6 +907,200 @@ RingAccount::saveIdentity(const dht::crypto::Identity id, const std::string& pat fileutils::saveFile(path + ".crt", id.second->getPacked(), 0600); } +void +RingAccount::loadAccountFromDHT(const std::string& archive_password, const std::string& archive_pin) +{ + setRegistrationState(RegistrationState::INITIALIZING); + + // launch dedicated dht instance + if (dht_.isRunning()) { + RING_ERR("DHT already running (stopping it first)."); + dht_.join(); + } + dht_.setOnStatusChanged([this](dht::NodeStatus s4, dht::NodeStatus s6) { + RING_WARN("Dht status : IPv4 %s; IPv6 %s", dhtStatusStr(s4), dhtStatusStr(s6)); + }); + dht_.run((in_port_t)dhtPortUsed_, {}, true); + dht_.bootstrap(loadNodes()); + auto bootstrap = loadBootstrap(); + if (not bootstrap.empty()) + dht_.bootstrap(bootstrap); + + std::weak_ptr<RingAccount> w = std::static_pointer_cast<RingAccount>(shared_from_this()); + auto state_old = std::make_shared<std::pair<bool, bool>>(false, true); + auto state_new = std::make_shared<std::pair<bool, bool>>(false, true); + auto found = std::make_shared<bool>(false); + + auto archiveFound = [w,found,archive_password](const ArchiveContent& a) { + *found = true; + if (auto this_ = w.lock()) { + this_->initRingDevice(a); + this_->saveArchive(a, archive_password); + this_->registrationState_ = RegistrationState::UNREGISTERED; + Manager::instance().saveConfig(); + this_->doRegister(); + } + }; + auto searchEnded = [w,found,state_old,state_new](){ + if (*found) + return; + if (state_old->first && state_new->first) { + bool network_error = !state_old->second && !state_new->second; + RING_WARN("Failure looking for archive on DHT: %s", network_error ? "network error" : "not found"); + if (auto this_ = w.lock()) { + this_->setRegistrationState(network_error ? RegistrationState::ERROR_NETWORK : RegistrationState::ERROR_GENERIC); + runOnMainThread([=]() { + Manager::instance().removeAccount(this_->getAccountID()); + }); + } + } + }; + + auto search = [w,found,archive_password,archive_pin,archiveFound,searchEnded](bool previous, std::shared_ptr<std::pair<bool, bool>>& state) { + std::vector<uint8_t> key; + dht::InfoHash loc; + + // compute archive location and decryption keys + try { + std::tie(key, loc) = computeKeys(archive_password, archive_pin, previous); + RING_DBG("Trying to load account from DHT with %s at %s", archive_pin.c_str(), loc.toString().c_str()); + if (auto this_ = w.lock()) { + this_->dht_.get(loc, [w,key,found,archive_password,archiveFound](std::shared_ptr<dht::Value> val) { + std::vector<uint8_t> decrypted; + try { + decrypted = dht::crypto::aesDecrypt(val->data, key); + } catch (const std::exception& ex) { + return true; + } + RING_DBG("Found archive on the DHT"); + runOnMainThread([=]() { + try { + archiveFound(loadArchive(decrypted)); + } catch (const std::exception& e) { + RING_WARN("Error reading archive: %s", e.what()); + if (auto this_ = w.lock()) { + this_->setRegistrationState(RegistrationState::ERROR_GENERIC); + Manager::instance().removeAccount(this_->getAccountID()); + } + } + }); + return not *found; + }, [=](bool ok) { + RING_DBG("DHT archive search ended at %s", loc.toString().c_str()); + state->first = true; + state->second = ok; + searchEnded(); + }); + } + } catch (const std::exception& e) { + RING_ERR("Error computing keys: %s", e.what()); + state->first = true; + state->second = true; + searchEnded(); + return; + } + }; + + ThreadPool::instance().run(std::bind(search, true, state_old)); + ThreadPool::instance().run(std::bind(search, false, state_new)); +} + +void +RingAccount::createAccount(const std::string& archive_password) +{ + RING_WARN("Creating new Ring account"); + setRegistrationState(RegistrationState::INITIALIZING); + auto sthis = std::static_pointer_cast<RingAccount>(shared_from_this()); + ThreadPool::instance().run([sthis,archive_password](){ + ArchiveContent a; + auto& this_ = *sthis; + + RING_WARN("Generating ETH key"); + auto future_keypair = ThreadPool::instance().get<dev::KeyPair>(std::bind(&dev::KeyPair::create)); + + try { + if (this_.identity_.first and this_.identity_.second) { + RING_WARN("Converting certificate from old ring account"); + a.id = this_.identity_; + try { + a.ca_key = fileutils::loadFile(this_.idPath_ + DIR_SEPARATOR_STR "ca.key"); + } catch (...) {} + } else { + auto ca = dht::crypto::generateIdentity("Ring CA"); + if (!ca.first || !ca.second) { + throw VoipLinkException("Can't generate CA for this account."); + } + a.id = dht::crypto::generateIdentity("Ring", ca, 4096, true); + if (!a.id.first || !a.id.second) { + throw VoipLinkException("Can't generate identity for this account."); + } + RING_WARN("New account: CA: %s, RingID: %s", ca.second->getId().toString().c_str(), a.id.second->getId().toString().c_str()); + a.ca_key = std::move(*ca.first); + } + this_.ringAccountId_ = a.id.second->getId().toString(); + this_.createRingDevice(a.id); + this_.username_ = RING_URI_PREFIX+this_.ringAccountId_; + auto keypair = future_keypair.get(); + this_.ethAccount_ = keypair.address().hex(); + a.eth_key = keypair.secret().makeInsecure().asBytes(); + this_.saveArchive(a, archive_password); + } catch (...) { + this_.setRegistrationState(RegistrationState::ERROR_GENERIC); + runOnMainThread([sthis]() { + Manager::instance().removeAccount(sthis->getAccountID()); + }); + } + this_.registrationState_ = RegistrationState::UNREGISTERED; + Manager::instance().saveConfig(); + this_.doRegister(); + }); +} + +void +RingAccount::loadAccount(const std::string& archive_password, const std::string& archive_pin) +{ + if (registrationState_ == RegistrationState::INITIALIZING) + return; + + RING_WARN("RingAccount::loadAccount"); + try { + loadIdentity(); + + if (hasSignedReceipt()) { + if (archivePath_.empty() or not fileutils::isFile(archivePath_)) + RING_WARN("Account archive not found, won't be able to add new devices."); + // normal account loading path + return; + } + + if (archivePath_.empty() or not fileutils::isFile(archivePath_)) { + // no receipt or archive, creating new account + if (archive_password.empty()) { + RING_WARN("Password needed to create archive"); + setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION); + } else { + if (archive_pin.empty()) { + createAccount(archive_password); + } else { + loadAccountFromDHT(archive_password, archive_pin); + } + } + } else { + if (archive_password.empty()) { + RING_WARN("Password needed to read archive"); + setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION); + } else { + RING_WARN("Archive present but no valid receipt: creating new device"); + initRingDevice(readArchive(archive_password)); + Manager::instance().saveConfig(); + } + } + } catch (const std::exception& e) { + RING_WARN("Error loading account: %s", e.what()); + setRegistrationState(RegistrationState::ERROR_GENERIC); + } +} + void RingAccount::setAccountDetails(const std::map<std::string, std::string> &details) { @@ -545,7 +1115,15 @@ RingAccount::setAccountDetails(const std::map<std::string, std::string> &details if (not dhtPort_) dhtPort_ = getRandomEvenPort(DHT_PORT_RANGE); dhtPortUsed_ = dhtPort_; - checkIdentityPath(); + + std::string archive_password; + std::string archive_pin; + parseString(details, DRing::Account::ConfProperties::ARCHIVE_PASSWORD, archive_password); + parseString(details, DRing::Account::ConfProperties::ARCHIVE_PIN, archive_pin); + std::transform(archive_pin.begin(), archive_pin.end(), archive_pin.begin(), ::toupper); + parseString(details, DRing::Account::ConfProperties::ARCHIVE_PATH, archivePath_); + + loadAccount(archive_password, archive_pin); } std::map<std::string, std::string> @@ -554,6 +1132,7 @@ RingAccount::getAccountDetails() const std::map<std::string, std::string> a = SIPAccountBase::getAccountDetails(); a.emplace(Conf::CONFIG_DHT_PORT, ring::to_string(dhtPort_)); a.emplace(Conf::CONFIG_DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_ ? TRUE_STR : FALSE_STR); + a.emplace(DRing::Account::ConfProperties::RING_DEVICE_ID, ringDeviceId_); /* these settings cannot be changed (read only), but clients should still be * able to read what they are */ @@ -573,6 +1152,9 @@ RingAccount::getAccountDetails() const /* GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT is defined as -1 */ 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_); + return a; } @@ -638,7 +1220,7 @@ RingAccount::handlePendingCallList() pj_status_t check_peer_certificate(dht::InfoHash from, unsigned status, const gnutls_datum_t* cert_list, - unsigned cert_num) + unsigned cert_num, std::shared_ptr<dht::crypto::Certificate>& cert_out) { if (cert_num == 0) { RING_ERR("[peer:%s] No certificate", from.toString().c_str()); @@ -655,8 +1237,12 @@ check_peer_certificate(dht::InfoHash from, unsigned status, const gnutls_datum_t return PJ_SSL_CERT_EUNTRUSTED; } - std::vector<uint8_t> crt_blob(cert_list[0].data, cert_list[0].data + cert_list[0].size); - dht::crypto::Certificate crt(crt_blob); + // Assumes the chain has already been checked by GnuTLS. + std::vector<std::pair<uint8_t*, uint8_t*>> crt_data; + crt_data.reserve(cert_num); + for (unsigned i=0; i<cert_num; i++) + crt_data.emplace_back(cert_list[i].data, cert_list[i].data + cert_list[i].size); + dht::crypto::Certificate crt(crt_data); const auto tls_id = crt.getId(); if (crt.getUID() != tls_id.toString()) { @@ -671,6 +1257,9 @@ check_peer_certificate(dht::InfoHash from, unsigned status, const gnutls_datum_t } RING_DBG("[peer:%s] Certificate verified", from.toString().c_str()); + + cert_out = std::make_shared<dht::crypto::Certificate>(std::move(crt)); + return PJ_SUCCESS; } @@ -701,18 +1290,38 @@ RingAccount::handlePendingCall(PendingCall& pc, bool incoming) // Securize a SIP transport with TLS (on top of ICE tranport) and assign the call with it auto remote_h = pc.from; - auto id(loadIdentity()); + if (not identity_.first or not identity_.second) + throw std::runtime_error("No identity configured for this account."); + std::weak_ptr<RingAccount> w = std::static_pointer_cast<RingAccount>(shared_from_this()); tls::TlsParams tlsParams { .ca_list = "", - .cert = id.second, - .cert_key = id.first, + .cert = identity_.second, + .cert_key = identity_.first, .dh_params = dhParams_, .timeout = std::chrono::duration_cast<decltype(tls::TlsParams::timeout)>(TLS_TIMEOUT), - .cert_check = [remote_h](unsigned status, const gnutls_datum_t* cert_list, - unsigned cert_num) -> pj_status_t { + .cert_check = [w,call,remote_h,incoming](unsigned status, const gnutls_datum_t* cert_list, unsigned cert_num) -> pj_status_t { try { - return check_peer_certificate(remote_h, status, cert_list, cert_num); + if (auto sthis = w.lock()) { + auto& this_ = *sthis; + std::shared_ptr<dht::crypto::Certificate> peer_cert; + auto ret = check_peer_certificate(remote_h, status, cert_list, cert_num, peer_cert); + if (ret == PJ_SUCCESS and peer_cert) { + std::lock_guard<std::mutex> lock(this_.callsMutex_); + for (auto& pscall : this_.pendingSipCalls_) { + if (auto pcall = pscall.call.lock()) { + if (pcall == call and not pscall.from_cert) { + RING_DBG("[call:%s] got peer certificate from TLS negotiation", call->getCallId().c_str()); + tls::CertificateStore::instance().pinCertificate(peer_cert); + pscall.from_cert = peer_cert; + break; + } + } + } + } + return ret; + } else + return PJ_SSL_CERT_EUNTRUSTED; } catch (const std::exception& e) { RING_ERR("[peer:%s] TLS certificate check exception: %s", remote_h.toString().c_str(), e.what()); @@ -720,9 +1329,8 @@ RingAccount::handlePendingCall(PendingCall& pc, bool incoming) } } }; - auto tr = link_->sipTransportBroker->getTlsIceTransport(pc.ice_sp, ICE_COMP_SIP_TRANSPORT, - tlsParams); - call->setTransport(tr); + call->setTransport(link_->sipTransportBroker->getTlsIceTransport(pc.ice_sp, ICE_COMP_SIP_TRANSPORT, + tlsParams)); // Notify of fully available connection between peers RING_DBG("[call:%s] SIP communication established", call->getCallId().c_str()); @@ -754,6 +1362,7 @@ RingAccount::mapPortUPnP() */ uint16_t port_used; std::lock_guard<std::mutex> lock(upnp_mtx); + upnp_->removeMappings(); added = upnp_->addAnyMapping(dhtPort_, ring::upnp::PortType::UDP, false, &port_used); if (added) { if (port_used != dhtPort_) @@ -778,6 +1387,13 @@ RingAccount::doRegister() return; } + // invalid state transitions: + // INITIALIZING: generating/loading certificates, can't register + // NEED_MIGRATION: old Ring account detected, user needs to migrate + if (registrationState_ == RegistrationState::INITIALIZING + || registrationState_ == RegistrationState::ERROR_NEED_MIGRATION) + return; + if (not dhParams_.valid()) { generateDhParams(); } @@ -798,24 +1414,50 @@ RingAccount::doRegister() } -static constexpr const char* -dhtStatusStr(dht::NodeStatus status) { - return status == dht::NodeStatus::Connected ? "connected" : ( - status == dht::NodeStatus::Connecting ? "connecting" : - "disconnected"); + +std::vector<std::pair<sockaddr_storage, socklen_t>> +RingAccount::loadBootstrap() const +{ + std::vector<std::pair<sockaddr_storage, socklen_t>> bootstrap; + if (!hostname_.empty()) { + std::stringstream ss(hostname_); + std::string node_addr; + while (std::getline(ss, node_addr, ';')) { + auto ips = ip_utils::getAddrList(node_addr); + if (ips.empty()) { + IpAddr resolved(node_addr); + if (resolved) { + if (resolved.getPort() == 0) + resolved.setPort(DHT_DEFAULT_PORT); + bootstrap.emplace_back(resolved, resolved.getLength()); + } + } else { + for (auto& ip : ips) { + if (ip.getPort() == 0) + ip.setPort(DHT_DEFAULT_PORT); + bootstrap.emplace_back(ip, ip.getLength()); + } + } + } + for (auto ip : bootstrap) + RING_DBG("Bootstrap node: %s", IpAddr(ip.first).toString(true).c_str()); + } + return bootstrap; } void RingAccount::doRegister_() { try { + if (not identity_.first or not identity_.second) + throw std::runtime_error("No identity configured for this account."); + loadTreatedCalls(); loadTreatedMessages(); if (dht_.isRunning()) { RING_ERR("DHT already running (stopping it first)."); dht_.join(); } - auto identity = loadIdentity(); dht_.setOnStatusChanged([this](dht::NodeStatus s4, dht::NodeStatus s6) { RING_WARN("Dht status : IPv4 %s; IPv6 %s", dhtStatusStr(s4), dhtStatusStr(s6)); @@ -837,7 +1479,7 @@ RingAccount::doRegister_() setRegistrationState(state); }); - dht_.run((in_port_t)dhtPortUsed_, identity, false); + dht_.run((in_port_t)dhtPortUsed_, identity_, false); dht_.setLocalCertificateStore([](const dht::InfoHash& pk_id) { auto& store = tls::CertificateStore::instance(); @@ -853,7 +1495,7 @@ RingAccount::doRegister_() dht_.setLoggers( [](char const* m, va_list args){ vlogger(LOG_ERR, m, args); }, [](char const* m, va_list args){ vlogger(LOG_WARNING, m, args); }, - [](char const* m, va_list args){ vlogger(LOG_DEBUG, m, args); } + [](char const* m, va_list args){ /*vlogger(LOG_DEBUG, m, args);*/ } ); #endif @@ -863,36 +1505,21 @@ RingAccount::doRegister_() setRegistrationState(RegistrationState::TRYING); dht_.bootstrap(loadNodes()); - if (!hostname_.empty()) { - std::stringstream ss(hostname_); - std::vector<std::pair<sockaddr_storage, socklen_t>> bootstrap; - std::string node_addr; - while (std::getline(ss, node_addr, ';')) { - auto ips = ip_utils::getAddrList(node_addr); - if (ips.empty()) { - IpAddr resolved(node_addr); - if (resolved) { - if (resolved.getPort() == 0) - resolved.setPort(DHT_DEFAULT_PORT); - bootstrap.emplace_back(resolved, resolved.getLength()); - } - } else { - for (auto& ip : ips) { - if (ip.getPort() == 0) - ip.setPort(DHT_DEFAULT_PORT); - bootstrap.emplace_back(ip, ip.getLength()); - } - } - } - for (auto ip : bootstrap) - RING_DBG("Bootstrap node: %s", IpAddr(ip.first).toString(true).c_str()); + auto bootstrap = loadBootstrap(); + if (not bootstrap.empty()) dht_.bootstrap(bootstrap); + + // Put device annoucement + if (announce_) { + RING_DBG("Announcing device at %s: %s", dht::InfoHash(ringAccountId_).toString().c_str(), announce_->toString().c_str()); + dht_.put(dht::InfoHash(ringAccountId_), announce_, dht::DoneCallback{}, {}, true); } // Listen for incoming calls + auto ringDeviceId = identity_.first->getPublicKey().getId().toString(); auto shared = std::static_pointer_cast<RingAccount>(shared_from_this()); - callKey_ = dht::InfoHash::get("callto:"+dht_.getId().toString()); - RING_DBG("Listening on callto:%s : %s", dht_.getId().toString().c_str(), callKey_.toString().c_str()); + callKey_ = dht::InfoHash::get("callto:"+ringDeviceId); + RING_DBG("Listening on callto:%s : %s", ringDeviceId.c_str(), callKey_.toString().c_str()); dht_.listen<dht::IceCandidates>( callKey_, [shared] (dht::IceCandidates&& msg) { @@ -901,6 +1528,8 @@ RingAccount::doRegister_() if (msg.from == this_.dht_.getId()) return true; + RING_WARN("ICE candidate from %s.", msg.from.toString().c_str()); + // quick check in case we already explicilty banned this public key auto trustStatus = this_.trust_.getCertificateStatus(msg.from.toString()); if (trustStatus == tls::TrustStore::PermissionStatus::BANNED) { @@ -913,40 +1542,40 @@ RingAccount::doRegister_() if (!res.second) return true; - if (not this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::PermissionStatus::ALLOWED) { - this_.findCertificate( - msg.from, - [shared, msg](const std::shared_ptr<dht::crypto::Certificate> cert) mutable { - if (!cert or cert->getId() != msg.from) { - RING_WARN("Can't find certificate of %s for incoming call.", - msg.from.toString().c_str()); - return; - } - - tls::CertificateStore::instance().pinCertificate(cert); + RING_WARN("findCertificate"); + this_.findCertificate( msg.from, + [shared, msg, trustStatus](const std::shared_ptr<dht::crypto::Certificate> cert) mutable { + RING_WARN("findCertificate: found %p", cert.get()); + auto& this_ = *shared; + if (not this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::PermissionStatus::ALLOWED) { + if (!cert or cert->getId() != msg.from) { + RING_WARN("Can't find certificate of %s for incoming call.", + msg.from.toString().c_str()); + return; + } - auto& this_ = *shared; - if (!this_.trust_.isAllowed(*cert)) { - RING_WARN("Discarding incoming DHT call from untrusted peer %s.", - msg.from.toString().c_str()); - return; - } + tls::CertificateStore::instance().pinCertificate(cert); - runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg)); }); + auto& this_ = *shared; + if (!this_.trust_.isAllowed(*cert)) { + RING_WARN("Discarding incoming DHT call from untrusted peer %s.", + msg.from.toString().c_str()); + return; } - ); - return true; - } - else if (this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::PermissionStatus::BANNED) { - this_.findCertificate(msg.from.toString().c_str()); - } + + runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg), cert); }); + } else if (this_.dhtPublicInCalls_ and trustStatus != tls::TrustStore::PermissionStatus::BANNED) { + //this_.findCertificate(msg.from.toString().c_str()); + runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg), cert); }); + } + }); // public incoming calls allowed or we explicitly authorised this public key - runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg)); }); + //runOnMainThread([=]() mutable { shared->incomingCall(std::move(msg), {}); }); return true; } ); - auto inboxKey = dht::InfoHash::get("inbox:"+dht_.getId().toString()); + auto inboxKey = dht::InfoHash::get("inbox:"+ringAccountId_); dht_.listen<dht::TrustRequest>( inboxKey, [shared](dht::TrustRequest&& v) { @@ -1008,7 +1637,7 @@ RingAccount::doRegister_() } void -RingAccount::incomingCall(dht::IceCandidates&& msg) +RingAccount::incomingCall(dht::IceCandidates&& msg, std::shared_ptr<dht::crypto::Certificate> from_cert) { auto from = msg.from.toString(); RING_WARN("ICE incoming from DHT peer %s\n%s", from.c_str(), @@ -1048,7 +1677,8 @@ RingAccount::incomingCall(dht::IceCandidates&& msg) .call = weak_call, .listen_key = {}, .call_key = {}, - .from = msg.from + .from = msg.from, + .from_cert = from_cert }); } } @@ -1056,6 +1686,7 @@ RingAccount::incomingCall(dht::IceCandidates&& msg) void RingAccount::doUnregister(std::function<void(bool)> released_cb) { + RING_WARN("doUnregister"); { std::lock_guard<std::mutex> lock(callsMutex_); pendingCalls_.clear(); @@ -1078,19 +1709,16 @@ RingAccount::doUnregister(std::function<void(bool)> released_cb) void RingAccount::connectivityChanged() { + RING_WARN("connectivityChanged"); if (not isUsable()) { // nothing to do return; } auto shared = std::static_pointer_cast<RingAccount>(shared_from_this()); - doUnregister([shared](bool /* transport_free */) { - if (shared->isUsable()) - shared->doRegister(); - }); + dht_.connectivityChanged(); } - bool RingAccount::findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>)> cb) { @@ -1147,7 +1775,7 @@ loadIdList(const std::string& path) std::set<dht::Value::Id> ids; std::ifstream file(path); if (!file.is_open()) { - RING_WARN("Could not load %s", path.c_str()); + RING_DBG("Could not load %s", path.c_str()); return ids; } std::string line; @@ -1235,7 +1863,7 @@ RingAccount::loadNodes() const { std::ifstream file(nodesPath); if (!file.is_open()) { - RING_ERR("Could not load nodes from %s", nodesPath.c_str()); + RING_DBG("Could not load nodes from %s", nodesPath.c_str()); return nodes; } std::string line; @@ -1279,12 +1907,12 @@ RingAccount::loadDhParams(const std::string path) // writeTime throw exception if file doesn't exist auto duration = system_clock::now() - fileutils::writeTime(path); if (duration >= std::chrono::hours(24 * 3)) // file is valid only 3 days - throw std::runtime_error("too old file"); + throw std::runtime_error("file too old"); RING_DBG("Loading DhParams from file '%s'", path.c_str()); return {fileutils::loadFile(path)}; } catch (const std::exception& e) { - RING_WARN("Failed to load DhParams file '%s': %s", path.c_str(), e.what()); + RING_DBG("Failed to load DhParams file '%s': %s", path.c_str(), e.what()); if (auto params = tls::DhParams::generate()) { try { fileutils::saveFile(path, params.serialize(), 0600); @@ -1294,6 +1922,7 @@ RingAccount::loadDhParams(const std::string path) } return params; } + RING_ERR("Can't generate DH params."); return {}; } } @@ -1309,8 +1938,7 @@ RingAccount::generateDhParams() MatchRank RingAccount::matches(const std::string &userName, const std::string &server) const { - auto dhtId = dht_.getId().toString(); - if (userName == dhtId || server == dhtId) { + if (userName == ringAccountId_ || server == ringAccountId_ || userName == ringDeviceId_) { RING_DBG("Matching account id in request with username %s", userName.c_str()); return MatchRank::FULL; } else { @@ -1321,42 +1949,42 @@ RingAccount::matches(const std::string &userName, const std::string &server) con std::string RingAccount::getFromUri() const { - const std::string uri = "<sip:" + dht_.getId().toString() + "@ring.dht>"; + const std::string uri = "<sip:" + ringAccountId_ + "@ring.dht>"; if (not displayName_.empty()) return "\"" + displayName_ + "\" " + uri; + RING_DBG("getFromUri %s", uri.c_str()); return uri; } std::string RingAccount::getToUri(const std::string& to) const { + RING_DBG("getToUri %s", to.c_str()); return "<sips:" + to + ";transport=tls>"; } pj_str_t RingAccount::getContactHeader(pjsip_transport* t) { - auto ringid = dht_.getId().toString(); - if (!t) { + if (t) { + // FIXME: be sure that given transport is from SipIceTransport + auto tlsTr = reinterpret_cast<tls::SipsIceTransport::TransportData*>(t)->self; + auto address = tlsTr->getLocalAddress().toString(true); + contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE, + "%s%s<sips:%s%s%s;transport=tls>", + displayName_.c_str(), + (displayName_.empty() ? "" : " "), + identity_.second->getId().toString().c_str(), + (address.empty() ? "" : "@"), + address.c_str()); + } else { RING_ERR("getContactHeader: no SIP transport provided"); contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE, "%s%s<sips:%s@ring.dht>", displayName_.c_str(), (displayName_.empty() ? "" : " "), - ringid.c_str()); - return contact_; - } - - // FIXME: be sure that given transport is from SipIceTransport - auto tlsTr = reinterpret_cast<tls::SipsIceTransport::TransportData*>(t)->self; - auto address = tlsTr->getLocalAddress(); - contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE, - "%s%s<sips:%s%s%s;transport=tls>", - displayName_.c_str(), - (displayName_.empty() ? "" : " "), - ringid.c_str(), - (ringid.empty() ? "" : "@"), - address.toString(true).c_str()); + identity_.second->getId().toString().c_str()); + } return contact_; } diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h index 4c0e819169a3f6245fe6a3bcabd7a951f482d6b6..ca20167e105f7c939c20bcd5b28b9740f5f4bec9 100644 --- a/src/ringdht/ringaccount.h +++ b/src/ringdht/ringaccount.h @@ -52,17 +52,33 @@ class Node; class Emitter; } + +namespace dev +{ + template <unsigned N> class FixedHash; + using h160 = FixedHash<20>; + using Address = h160; +} + namespace ring { namespace Conf { -const char *const DHT_PORT_KEY = "dhtPort"; -const char *const DHT_VALUES_PATH_KEY = "dhtValuesPath"; -const char *const DHT_CONTACTS = "dhtContacts"; -const char *const DHT_PUBLIC_PROFILE = "dhtPublicProfile"; -const char *const DHT_PUBLIC_IN_CALLS = "dhtPublicInCalls"; -const char *const DHT_ALLOW_PEERS_FROM_HISTORY = "allowPeersFromHistory"; -const char *const DHT_ALLOW_PEERS_FROM_CONTACT = "allowPeersFromContact"; -const char *const DHT_ALLOW_PEERS_FROM_TRUSTED = "allowPeersFromTrusted"; +constexpr const char* const DHT_PORT_KEY = "dhtPort"; +constexpr const char* const DHT_VALUES_PATH_KEY = "dhtValuesPath"; +constexpr const char* const DHT_CONTACTS = "dhtContacts"; +constexpr const char* const DHT_PUBLIC_PROFILE = "dhtPublicProfile"; +constexpr const char* const DHT_PUBLIC_IN_CALLS = "dhtPublicInCalls"; +constexpr const char* const DHT_ALLOW_PEERS_FROM_HISTORY = "allowPeersFromHistory"; +constexpr const char* const DHT_ALLOW_PEERS_FROM_CONTACT = "allowPeersFromContact"; +constexpr const char* const DHT_ALLOW_PEERS_FROM_TRUSTED = "allowPeersFromTrusted"; +constexpr const char* const ETH_KEY = "ethKey"; +constexpr const char* const ETH_PATH = "ethPath"; +constexpr const char* const ETH_ACCOUNT = "ethAccount"; +constexpr const char* const RING_CA_KEY = "ringCaKey"; +constexpr const char* const RING_ACCOUNT_KEY = "ringAccountKey"; +constexpr const char* const RING_ACCOUNT_CERT = "ringAccountCert"; +constexpr const char* const RING_ACCOUNT_RECEIPT = "ringAccountReceipt"; +constexpr const char* const RING_ACCOUNT_RECEIPT_SIG = "ringAccountReceiptSignature"; } class IceTransport; @@ -126,7 +142,7 @@ class RingAccount : public SIPAccountBase { /** * Disconnect from the DHT. */ - void doUnregister(std::function<void(bool)> cb = std::function<void(bool)>()) override; + void doUnregister(std::function<void(bool)> cb = {}) override; /** * @return pj_str_t "From" uri based on account information. @@ -254,6 +270,8 @@ class RingAccount : public SIPAccountBase { void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload); virtual void sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads, uint64_t id) override; + void addDevice(const std::string& password); + void connectivityChanged() override; public: // overloaded methods @@ -262,8 +280,64 @@ class RingAccount : public SIPAccountBase { private: NON_COPYABLE(RingAccount); + struct PendingCall { + std::chrono::steady_clock::time_point start; + std::shared_ptr<IceTransport> ice_sp; + std::weak_ptr<SIPCall> call; + std::future<size_t> listen_key; + dht::InfoHash call_key; + dht::InfoHash from; + std::shared_ptr<dht::crypto::Certificate> from_cert; + }; + + struct PendingMessage { + dht::InfoHash to; + std::chrono::steady_clock::time_point received; + }; + + struct TrustRequest { + dht::InfoHash from; + std::chrono::system_clock::time_point received; + std::vector<uint8_t> payload; + }; + + /** + * Crypto material contained in the archive, + * not persisted in the account configuration + */ + struct ArchiveContent { + /** Account main private key and certificate chain */ + dht::crypto::Identity id; + + /** Generated CA key (for self-signed certificates) */ + dht::crypto::PrivateKey ca_key; + + /** Ethereum private key */ + std::vector<uint8_t> eth_key; + + /** Account configuration */ + std::map<std::string, std::string> config; + }; + + /** + * Device announcement stored on DHT. + */ + struct DeviceAnnouncement : public dht::SignedValue<DeviceAnnouncement> { + private: + using BaseClass = dht::SignedValue<DeviceAnnouncement>; + public: + static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA; + dht::InfoHash dev; + MSGPACK_DEFINE_MAP(dev); + }; + + /** + * Compute archive encryption key and DHT storage location from password and PIN. + */ + static std::pair<std::vector<uint8_t>, dht::InfoHash> computeKeys(const std::string& password, const std::string& pin, bool previous=false); + void doRegister_(); - void incomingCall(dht::IceCandidates&& msg); + void incomingCall(dht::IceCandidates&& msg, std::shared_ptr<dht::crypto::Certificate> from); const dht::ValueType USER_PROFILE_TYPE = {9, "User profile", std::chrono::hours(24 * 7)}; @@ -293,18 +367,10 @@ class RingAccount : public SIPAccountBase { void igdChanged(); dht::DhtRunner dht_ {}; + dht::crypto::Identity identity_ {}; dht::InfoHash callKey_; - struct PendingCall { - std::chrono::steady_clock::time_point start; - std::shared_ptr<IceTransport> ice_sp; - std::weak_ptr<SIPCall> call; - std::future<size_t> listen_key; - dht::InfoHash call_key; - dht::InfoHash from; - }; - void handlePendingCallList(); bool handlePendingCall(PendingCall& pc, bool incoming); @@ -320,33 +386,46 @@ class RingAccount : public SIPAccountBase { std::set<dht::Value::Id> treatedCalls_ {}; mutable std::mutex callsMutex_ {}; - struct PendingMessage { - dht::InfoHash to; - std::chrono::steady_clock::time_point received; - }; - std::map<dht::Value::Id, PendingMessage> sentMessages_ {}; std::set<dht::Value::Id> treatedMessages_ {}; + std::string ringAccountId_ {}; + std::string ringDeviceId_ {}; std::string idPath_ {}; std::string cachePath_ {}; std::string dataPath_ {}; + std::string ethPath_ {}; + std::string ethAccount_ {}; - struct TrustRequest { - dht::InfoHash from; - std::chrono::system_clock::time_point received; - std::vector<uint8_t> payload; - }; + std::string archivePath_ {}; + + std::string receipt_ {}; + std::vector<uint8_t> receiptSignature_ {}; + dht::Value announceVal_; std::vector<TrustRequest> trustRequests_; tls::TrustStore trust_; - /** - * Validate the values for privkeyPath_ and certPath_. - * If one of these fields is empty, reset them to the default values. - */ - void checkIdentityPath(); + std::shared_ptr<dht::Value> announce_; + + void loadAccount(const std::string& archive_password = {}, const std::string& archive_pin = {}); + void loadAccountFromDHT(const std::string& archive_password, const std::string& archive_pin); + + bool hasCertificate() const; + bool hasPrivateKey() const; + bool hasSignedReceipt(); + + std::string makeReceipt(const dht::crypto::Identity& id); + void createRingDevice(const dht::crypto::Identity& id); + void initRingDevice(const ArchiveContent& a); + + void createAccount(const std::string& archive_password); + std::vector<uint8_t> makeArchive(const ArchiveContent& content) const; + void saveArchive(const ArchiveContent& content, const std::string& pwd); + ArchiveContent readArchive(const std::string& pwd) const; + static ArchiveContent loadArchive(const std::vector<uint8_t>& data); + std::vector<std::pair<sockaddr_storage, socklen_t>> loadBootstrap() const; void saveIdentity(const dht::crypto::Identity id, const std::string& path) const; void saveNodes(const std::vector<dht::NodeExport>&) const; diff --git a/src/security/certstore.cpp b/src/security/certstore.cpp index 1e6bcb83d73e3f0c9d617dfdba72d6a2eb9a8d39..2f70c7b5d8aa768314ddf98ab108204fcb1c2177 100644 --- a/src/security/certstore.cpp +++ b/src/security/certstore.cpp @@ -131,8 +131,12 @@ CertificateStore::findIssuer(std::shared_ptr<crypto::Certificate> crt) const { std::shared_ptr<crypto::Certificate> ret {}; auto n = crt->getIssuerUID(); - if (not n.empty()) - ret = findCertificateByUID(n); + if (not n.empty()) { + if (crt->issuer and crt->issuer->getUID() == n) + ret = crt->issuer; + else + ret = findCertificateByUID(n); + } if (not ret) { n = crt->getIssuerName(); if (not n.empty()) @@ -471,9 +475,23 @@ TrustStore::getCertificatesByStatus(TrustStore::PermissionStatus status) bool TrustStore::isAllowed(const crypto::Certificate& crt) { - if (getCertificateStatus(crt.getId().toString()) == PermissionStatus::ALLOWED) + // Match by certificate pinning (device) + auto status = getCertificateStatus(crt.getId().toString()); + if (status == PermissionStatus::ALLOWED) return true; + else if (status == PermissionStatus::BANNED) + return false; + + // Match by certificate pinning (Ring account) + if (crt.issuer) { + status = getCertificateStatus(crt.issuer->getId().toString()); + if (status == PermissionStatus::ALLOWED) + return true; + else if (status == PermissionStatus::BANNED) + return false; + } + // Match by certificate chain updateKnownCerts(); return matchTrustStore(getChain(crt), allowed_); } diff --git a/src/security/tls_session.cpp b/src/security/tls_session.cpp index 09641e8e9bbe020f3c82dc8479a34e9010511580..730030b219e5c1ffa52286334922515e700bf3f3 100644 --- a/src/security/tls_session.cpp +++ b/src/security/tls_session.cpp @@ -279,8 +279,15 @@ TlsSession::initCredentials() // Load user-given identity (key and passwd) if (params_.cert) { - ret = gnutls_certificate_set_x509_key(*xcred_, ¶ms_.cert->cert, 1, - params_.cert_key->x509_key); + std::vector<gnutls_x509_crt_t> certs; + certs.reserve(3); + auto crt = params_.cert; + while (crt) { + certs.emplace_back(crt->cert); + crt = crt->issuer; + } + + ret = gnutls_certificate_set_x509_key(*xcred_, certs.data(), certs.size(), params_.cert_key->x509_key); if (ret < 0) throw std::runtime_error("can't load certificate: " + std::string(gnutls_strerror(ret))); diff --git a/src/sip/Makefile.am b/src/sip/Makefile.am index 6b791bae6d71710f8d4721c5fad3c299bde1e2f6..71f7b272b32ed48d248475fe878323ab160b4679 100644 --- a/src/sip/Makefile.am +++ b/src/sip/Makefile.am @@ -17,9 +17,7 @@ libsiplink_la_SOURCES = \ sipvoiplink.h \ siptransport.h \ sip_utils.cpp \ - sip_utils.h \ - base64.h \ - base64.c + sip_utils.h libsiplink_la_SOURCES+=sippresence.cpp \ sippresence.h \ diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp index cd1d18b463c6fbe6d61dcb3d119e3d061b26a068..544f3f201061a492150d943ec724ff89a3bdb35e 100644 --- a/src/sip/sipaccount.cpp +++ b/src/sip/sipaccount.cpp @@ -407,6 +407,8 @@ void SIPAccount::serialize(YAML::Emitter &out) out << YAML::Key << Conf::PORT_KEY << YAML::Value << localPort_; + out << YAML::Key << USERNAME_KEY << YAML::Value << username_; + // each credential is a map, and we can have multiple credentials out << YAML::Key << Conf::CRED_KEY << YAML::Value << getCredentials(); out << YAML::Key << Conf::KEEP_ALIVE_ENABLED << YAML::Value << keepAliveEnabled_; @@ -471,6 +473,8 @@ validate(std::string &member, const std::string ¶m, const T& valid) void SIPAccount::unserialize(const YAML::Node &node) { SIPAccountBase::unserialize(node); + parseValue(node, USERNAME_KEY, username_); + if (not publishedSameasLocal_) usePublishedAddressPortInVIA(); @@ -478,9 +482,17 @@ void SIPAccount::unserialize(const YAML::Node &node) parseValue(node, Conf::PORT_KEY, port); localPort_ = port; - if (not isIP2IP()) parseValue(node, Preferences::REGISTRATION_EXPIRE_KEY, registrationExpire_); - - if (not isIP2IP()) parseValue(node, Conf::KEEP_ALIVE_ENABLED, keepAliveEnabled_); + if (not isIP2IP()) { + parseValue(node, Preferences::REGISTRATION_EXPIRE_KEY, registrationExpire_); + parseValue(node, Conf::KEEP_ALIVE_ENABLED, keepAliveEnabled_); + parseValue(node, Conf::SERVICE_ROUTE_KEY, serviceRoute_); + const auto& credsNode = node[Conf::CRED_KEY]; + setCredentials(parseVectorMap(credsNode, { + Conf::CONFIG_ACCOUNT_REALM, + Conf::CONFIG_ACCOUNT_USERNAME, + Conf::CONFIG_ACCOUNT_PASSWORD + })); + } bool presEnabled = false; parseValue(node, PRESENCE_MODULE_ENABLED_KEY, presEnabled); @@ -494,8 +506,6 @@ void SIPAccount::unserialize(const YAML::Node &node) presence_->support(PRESENCE_FUNCTION_SUBSCRIBE, subscribeSupported); } - if (not isIP2IP()) parseValue(node, Conf::SERVICE_ROUTE_KEY, serviceRoute_); - // Init stun server name with default server name stunServerName_ = pj_str((char*) stunServer_.data()); @@ -535,6 +545,7 @@ void SIPAccount::unserialize(const YAML::Node &node) void SIPAccount::setAccountDetails(const std::map<std::string, std::string> &details) { SIPAccountBase::setAccountDetails(details); + parseString(details, Conf::CONFIG_ACCOUNT_USERNAME, username_); parseInt(details, Conf::CONFIG_LOCAL_PORT, localPort_); diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp index f234be540b5420d940e2c327e3f8b14e88b3d34a..767e91e3343a1f6176b1c397e8d133d59120f13d 100644 --- a/src/sip/sipcall.cpp +++ b/src/sip/sipcall.cpp @@ -112,7 +112,7 @@ SIPCall::SIPCall(SIPAccountBase& account, const std::string& id, Call::CallType , avformatrtp_(new AudioRtpSession(id)) #ifdef RING_VIDEO // The ID is used to associate video streams to calls - , videortp_(id, getVideoSettings()) + , videortp_(new video::VideoRtpSession(id, getVideoSettings())) , videoInput_(Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice()) #endif , sdp_(new Sdp(id)) @@ -326,7 +326,7 @@ SIPCall::answer() auto& account = getSIPAccount(); if (not inv) - throw VoipLinkException("No invite session for this call"); + throw VoipLinkException("[call:" + getCallId() + "] answer: no invite session for this call"); if (!inv->neg) { RING_WARN("[call:%s] Negotiator is NULL, we've received an INVITE without an SDP", @@ -372,7 +372,7 @@ SIPCall::hangup(int reason) if (not inv or not inv->dlg) { removeCall(); - throw VoipLinkException("No invite session for this call"); + throw VoipLinkException("[call:" + getCallId() + "] hangup: no invite session for this call"); } pjsip_route_hdr *route = inv->dlg->route_set.next; @@ -653,10 +653,11 @@ SIPCall::peerHungup() // Stop all RTP streams stopAllMedia(); - if (not inv) - throw VoipLinkException("No invite session for this call"); + if (inv) + terminateSipSession(PJSIP_SC_NOT_FOUND); + else + RING_ERR("[call:%s] peerHungup: no invite session for this call", getCallId().c_str()); - terminateSipSession(PJSIP_SC_NOT_FOUND); Call::peerHungup(); } @@ -668,16 +669,20 @@ SIPCall::carryingDTMFdigits(char code) void SIPCall::sendTextMessage(const std::map<std::string, std::string>& messages, - const std::string& /* from */) + const std::string& from) { - if (not inv) - throw VoipLinkException("No invite session for this call"); - //TODO: for now we ignore the "from" (the previous implementation for sending this info was // buggy and verbose), another way to send the original message sender will be implemented // in the future - - im::sendSipMessage(inv.get(), messages); + if (inv) + im::sendSipMessage(inv.get(), messages); + else { + if (not subcalls.empty()) { + for (auto& c : subcalls) + c->sendTextMessage(messages, from); + } else + throw VoipLinkException("[call:" + getCallId() + "] sendTextMessage: no invite session for this call"); + } } void @@ -781,8 +786,8 @@ bool SIPCall::useVideoCodec(const AccountVideoCodecInfo* codec) const { #ifdef RING_VIDEO - if (videortp_.isSending()) - return videortp_.useCodec(codec); + if (videortp_->isSending()) + return videortp_->useCodec(codec); #endif return false; } @@ -790,6 +795,7 @@ SIPCall::useVideoCodec(const AccountVideoCodecInfo* codec) const void SIPCall::startAllMedia() { + RING_WARN("[call:%s] startAllMedia()", getCallId().c_str()); if (isSecure() && not transport_->isSecure()) { RING_ERR("[call:%s] Can't perform secure call over insecure SIP transport", getCallId().c_str()); @@ -815,7 +821,7 @@ SIPCall::startAllMedia() RtpSession* rtp = local.type == MEDIA_AUDIO ? static_cast<RtpSession*>(avformatrtp_.get()) #ifdef RING_VIDEO - : static_cast<RtpSession*>(&videortp_); + : static_cast<RtpSession*>(videortp_.get()); #else : nullptr; #endif @@ -844,7 +850,7 @@ SIPCall::startAllMedia() #ifdef RING_VIDEO if (local.type == MEDIA_VIDEO) - videortp_.switchInput(videoInput_); + videortp_->switchInput(videoInput_); #endif rtp->updateMedia(remote, local); @@ -873,7 +879,7 @@ SIPCall::startAllMedia() } } - if (peerHolding_ != peer_holding) { + if (not quiet and peerHolding_ != peer_holding) { peerHolding_ = peer_holding; emitSignal<DRing::CallSignal::PeerHold>(getCallId(), peerHolding_); } @@ -885,7 +891,7 @@ SIPCall::restartMediaSender() RING_DBG("[call:%s] restarting TX media streams", getCallId().c_str()); avformatrtp_->restartSender(); #ifdef RING_VIDEO - videortp_.restartSender(); + videortp_->restartSender(); #endif } @@ -895,7 +901,7 @@ SIPCall::restartMediaReceiver() RING_DBG("[call:%s] restarting RX media streams", getCallId().c_str()); avformatrtp_->restartReceiver(); #ifdef RING_VIDEO - videortp_.restartReceiver(); + videortp_->restartReceiver(); #endif } @@ -905,7 +911,7 @@ SIPCall::stopAllMedia() RING_DBG("[call:%s] stopping all medias", getCallId().c_str()); avformatrtp_->stop(); #ifdef RING_VIDEO - videortp_.stop(); + videortp_->stop(); #endif } @@ -919,47 +925,61 @@ SIPCall::muteMedia(const std::string& mediaType, bool mute) isVideoMuted_ = mute; videoInput_ = isVideoMuted_ ? "" : Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice(); DRing::switchInput(getCallId(), videoInput_); - emitSignal<DRing::CallSignal::VideoMuted>(getCallId(), isVideoMuted_); + if (not quiet) + emitSignal<DRing::CallSignal::VideoMuted>(getCallId(), isVideoMuted_); #endif } else if (mediaType.compare(DRing::Media::Details::MEDIA_TYPE_AUDIO) == 0) { if (mute == isAudioMuted_) return; RING_WARN("[call:%s] audio muting %s", getCallId().c_str(), bool_to_str(mute)); isAudioMuted_ = mute; avformatrtp_->setMuted(isAudioMuted_); - emitSignal<DRing::CallSignal::AudioMuted>(getCallId(), isAudioMuted_); + if (not quiet) + emitSignal<DRing::CallSignal::AudioMuted>(getCallId(), isAudioMuted_); } } void SIPCall::onMediaUpdate() { + RING_WARN("[call:%s] onMediaUpdate", getCallId().c_str()); stopAllMedia(); openPortsUPnP(); - if (startIce()) { - auto this_ = std::static_pointer_cast<SIPCall>(shared_from_this()); - auto ice = iceTransport_; - auto iceTimeout = std::chrono::steady_clock::now() + std::chrono::seconds(10); - Manager::instance().addTask([=] { - if (ice != this_->iceTransport_) { - RING_WARN("[call:%s] ICE transport replaced", getCallId().c_str()); + if (not quiet) + waitForIceAndStartMedia(); + } else { + RING_WARN("[call:%s] ICE not used for media", getCallId().c_str()); + startAllMedia(); + } +} + +void +SIPCall::waitForIceAndStartMedia() +{ + auto ice = iceTransport_; + auto iceTimeout = std::chrono::steady_clock::now() + std::chrono::seconds(10); + if (not wthis_) + wthis_ = std::make_shared<std::weak_ptr<SIPCall>>(std::static_pointer_cast<SIPCall>(shared_from_this())); + auto wthis = wthis_; + Manager::instance().addTask([wthis,ice,iceTimeout] { + if (auto sthis = wthis->lock()) { + auto& this_ = *sthis; + if (ice != this_.iceTransport_) { + RING_WARN("[call:%s] ICE transport replaced", this_.getCallId().c_str()); return false; } /* First step: wait for an ICE transport for SIP channel */ - if (this_->iceTransport_->isFailed() or std::chrono::steady_clock::now() >= iceTimeout) { - RING_DBG("[call:%s] ICE init failed (or timeout)", getCallId().c_str()); - this_->onFailure(ETIMEDOUT); + if (this_.iceTransport_->isFailed() or std::chrono::steady_clock::now() >= iceTimeout) { + RING_DBG("[call:%s] ICE init failed (or timeout)", this_.getCallId().c_str()); + this_.onFailure(ETIMEDOUT); return false; } - if (not this_->iceTransport_->isRunning()) + if (not this_.iceTransport_->isRunning()) return true; - startAllMedia(); - return false; - }); - } else { - RING_WARN("[call:%s] ICE not used for media", getCallId().c_str()); - startAllMedia(); - } + this_.startAllMedia(); + } + return false; + }); } void @@ -1086,4 +1106,27 @@ SIPCall::initIceTransport(bool master, unsigned channel_num) return result; } +void +SIPCall::merge(std::shared_ptr<SIPCall> scall) +{ + Call::merge(scall); + RING_WARN("SIPCall::merge %s -> %s", scall->getCallId().c_str(), getCallId().c_str()); + inv = std::move(scall->inv); + inv->mod_data[getSIPVoIPLink()->getModId()] = this; + if (not wthis_) + wthis_ = std::make_shared<std::weak_ptr<SIPCall>>(std::static_pointer_cast<SIPCall>(shared_from_this())); + if (not scall->wthis_) + scall->wthis_ = wthis_; + else + *scall->wthis_ = *wthis_; + setTransport(scall->transport_); + sdp_ = std::move(scall->sdp_); + peerHolding_ = scall->peerHolding_; + upnp_ = std::move(scall->upnp_); + std::copy_n(scall->contactBuffer_, PJSIP_MAX_URL_SIZE, contactBuffer_); + pj_strcpy(&contactHeader_, &scall->contactHeader_); + if (iceTransport_->isStarted()) + waitForIceAndStartMedia(); +} + } // namespace ring diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h index 2448dba06e3351aa7e1343adc38943b59de012b7..3319d9777113246d76a08df51c29cdd83dda4df4 100644 --- a/src/sip/sipcall.h +++ b/src/sip/sipcall.h @@ -103,7 +103,7 @@ class SIPCall : public Call * Returns a pointer to the VideoRtp object */ video::VideoRtpSession& getVideoRtp () { - return videortp_; + return *videortp_; } #endif @@ -212,9 +212,17 @@ class SIPCall : public Call bool initIceTransport(bool master, unsigned channel_num=4) override; void terminateSipSession(int status); + + virtual void merge(std::shared_ptr<Call> scall) { + merge(std::dynamic_pointer_cast<SIPCall>(scall)); + } + virtual void merge(std::shared_ptr<SIPCall> scall); + private: NON_COPYABLE(SIPCall); + void waitForIceAndStartMedia(); + void stopAllMedia(); /** @@ -234,7 +242,7 @@ class SIPCall : public Call /** * Video Rtp Session factory */ - video::VideoRtpSession videortp_; + std::unique_ptr<video::VideoRtpSession> videortp_; std::string videoInput_; #endif @@ -258,6 +266,8 @@ class SIPCall : public Call pj_str_t contactHeader_ {contactBuffer_, 0}; std::unique_ptr<ring::upnp::Controller> upnp_; + + std::shared_ptr<std::weak_ptr<SIPCall>> wthis_; }; } // namespace ring