diff --git a/daemon/src/Makefile.am b/daemon/src/Makefile.am index 1b71b13fb4628c96f68264fa6ddaa8e15104da0b..0a87e45cbb713365dc93ab24daeaa821427daf58 100644 --- a/daemon/src/Makefile.am +++ b/daemon/src/Makefile.am @@ -20,6 +20,12 @@ IAX_LIBA=./iax/libiaxlink.la IAX_LIB=-liax endif +if USE_DHT +DHT_SUBDIR=dht +DHT_CXXFLAG=-DUSE_DHT +DHT_LIBA=./dht/libdht.la +endif + if USE_NETWORKMANAGER NETWORKMANAGER=-DUSE_NETWORKMANAGER endif @@ -29,11 +35,7 @@ TLS_LIB = @GNUTLS_LIBS@ TLS_CFLAGS = @GNUTLS_CFLAGS@ endif -SUBDIRS = client audio config hooks history sip $(IAX_SUBDIR) $(INSTANT_MESSAGING_SUBDIR) $(SFL_VIDEO_SUBDIR) - -if USE_DHT -SUBDIRS += dht -endif +SUBDIRS = client audio config hooks history sip $(IAX_SUBDIR) $(DHT_SUBDIR) $(INSTANT_MESSAGING_SUBDIR) $(SFL_VIDEO_SUBDIR) # libsflphone @@ -46,6 +48,7 @@ libsflphone_la_LIBADD = \ ./config/libconfig.la \ ./hooks/libhooks.la \ ./history/libhistory.la \ + $(DHT_LIBA) \ $(IAX_LIBA) \ $(IM_LIBA) \ $(SFL_VIDEO_LIBS) diff --git a/daemon/src/account_factory.cpp b/daemon/src/account_factory.cpp index 73bf24cba682c2bfcca4c0a3fff88f0024fff171..53dedb4759cb8291ad8812f0e907e0341fc4b3e4 100644 --- a/daemon/src/account_factory.cpp +++ b/daemon/src/account_factory.cpp @@ -38,6 +38,9 @@ #if HAVE_IAX #include "iax/iaxaccount.h" #endif +#if HAVE_DHT +#include "dht/dhtaccount.h" +#endif #include "sip/sipvoiplink.h" // for SIPVoIPLink::loadIP2IPSettings @@ -55,6 +58,11 @@ AccountFactory::AccountFactory() generators_.insert(std::make_pair(IAXAccount::ACCOUNT_TYPE, iaxfunc)); DEBUG("registered %s account", IAXAccount::ACCOUNT_TYPE); #endif +#if HAVE_DHT + auto dhtfunc = [](const std::string& id){ return std::make_shared<DHTAccount>(id, false); }; + generators_.insert(std::make_pair(DHTAccount::ACCOUNT_TYPE, dhtfunc)); + DEBUG("registered %s account", DHTAccount::ACCOUNT_TYPE); +#endif } std::shared_ptr<Account> diff --git a/daemon/src/dht/Makefile.am b/daemon/src/dht/Makefile.am index 8def4e2fc28c720e83045eb059bc5b734dfcf745..b748335c8e0e3aa45975db983daa6b3e3316f93d 100644 --- a/daemon/src/dht/Makefile.am +++ b/daemon/src/dht/Makefile.am @@ -1 +1,17 @@ +include $(top_srcdir)/globals.mak + +if USE_DHT + +noinst_LTLIBRARIES = libdht.la +libdht_la_CXXFLAGS = @CXXFLAGS@ + SUBDIRS = dhtcpp + +libdht_la_LIBADD = \ + dhtcpp/libdhtcpp.la + +libdht_la_SOURCES = \ + dhtaccount.cpp \ + dhtaccount.h + +endif diff --git a/daemon/src/dht/dhtaccount.cpp b/daemon/src/dht/dhtaccount.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5ecdc79c0f8f3c7ead83a523ba7f9b9fb3579e61 --- /dev/null +++ b/daemon/src/dht/dhtaccount.cpp @@ -0,0 +1,668 @@ +/* + * Copyright (C) 2014 Savoir-Faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "dhtaccount.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sip/sdp.h" +#include "sip/sipvoiplink.h" +#include "sip/sipcall.h" +#include "sip/sip_utils.h" + +#include "dhtcpp/securedht.h" + +#include "array_size.h" + +#include "call_factory.h" + +#ifdef SFL_PRESENCE +//#include "sippresence.h" +#include "client/configurationmanager.h" +#endif + +#include "account_schema.h" +#include "logger.h" +#include "manager.h" + +#ifdef SFL_VIDEO +#include "video/libav_utils.h" +#endif +#include "fileutils.h" + +#include "config/yamlparser.h" +#include <yaml-cpp/yaml.h> + +#include <unistd.h> +#include <pwd.h> + +#include <algorithm> +#include <array> +#include <memory> +#include <sstream> +#include <cstdlib> + +constexpr const char * const DHTAccount::ACCOUNT_TYPE; + +DHTAccount::DHTAccount(const std::string& accountID, bool /* presenceEnabled */) + : SIPAccountBase(accountID) +{ + fileutils::check_dir(fileutils::get_cache_dir().c_str()); + nodePath_ = fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID(); + fileutils::check_dir(nodePath_.c_str()); + WARN("node cache path: %s", nodePath_.c_str()); + if (privkeyPath_.empty()) { + fileutils::check_dir(fileutils::get_data_dir().c_str()); + const auto idPath = fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID(); + fileutils::check_dir(idPath.c_str()); + privkeyPath_ = idPath+DIR_SEPARATOR_STR+"id_rsa"; + WARN("privkeyPath : %s", privkeyPath_.c_str()); + } + if (certPath_.empty()) { + certPath_ = privkeyPath_+".pub"; + WARN("certPath : %s", certPath_.c_str()); + } + int rc = gnutls_global_init(); + if (rc != GNUTLS_E_SUCCESS) { + ERROR("Error initializing GnuTLS : %s", gnutls_strerror(rc)); + throw VoipLinkException("Can't initialize GnuTLS."); + } +} + +DHTAccount::~DHTAccount() +{ + setTransport(); + dht_.join(); + gnutls_global_deinit(); +} + +std::shared_ptr<SIPCall> +DHTAccount::newIncomingCall(const std::string& id) +{ + return Manager::instance().callFactory.newCall<SIPCall, DHTAccount>(*this, id, Call::INCOMING); +} + +template <> +std::shared_ptr<SIPCall> +DHTAccount::newOutgoingCall(const std::string& id, const std::string& toUrl) +{ + auto call = Manager::instance().callFactory.newCall<SIPCall, DHTAccount>(*this, id, Call::OUTGOING); + auto dhtf = toUrl.find("dht:"); + dhtf = (dhtf == std::string::npos) ? 0 : dhtf+4; + const std::string toUri = toUrl.substr(dhtf, 40); + DEBUG("Calling DHT peer %s", toUri.c_str()); + call->setIPToIP(true); + + auto resolved = std::make_shared<bool>(false); + dht_.get(dht::InfoHash(toUri), + [this,call,toUri,resolved](const std::vector<std::shared_ptr<dht::Value>>& values) { + if (*resolved) + return false; + for (const auto& v : values) { + DEBUG("Resolved value : %s", v->toString().c_str()); + IpAddr peer; + try { + peer = IpAddr{ dht::ServiceAnnouncement(v->data).getPeerAddr() }; + } catch (const std::exception& e) { + ERROR("Exception while reading value : %s", e.what()); + continue; + } + *resolved = true; + std::string toip = getToUri(toUri+"@"+peer.toString(true, true)); + DEBUG("Got DHT peer IP: %s", toip.c_str()); + createOutgoingCall(call, toUri, toip, peer); + return false; + } + return true; + }, + [call,resolved] (bool ok){ + if (not *resolved) { + call->setConnectionState(Call::DISCONNECTED); + call->setState(Call::ERROR); + } + }, + dht::Value::TypeFilter(dht::ServiceAnnouncement::TYPE)); + return call; +} + +void +DHTAccount::createOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& to, const std::string& toUri, const IpAddr& peer) +{ + WARN("DHTAccount::createOutgoingCall to: %s toUri: %s tlsListener: %d", to.c_str(), toUri.c_str(), tlsListener_?1:0); + std::shared_ptr<SipTransport> t = link_->sipTransport->getTlsTransport(tlsListener_, getToUri(peer.toString(true, true))); + setTransport(t); + call->setTransport(t); + call->setIPToIP(true); + call->setPeerNumber(toUri); + call->initRecFilename(to); + + const auto localAddress = ip_utils::getInterfaceAddr(getLocalInterface(), peer.getFamily()); + call->setCallMediaLocal(localAddress); + + // May use the published address as well + const auto addrSdp = isStunEnabled() or (not getPublishedSameasLocal()) ? + getPublishedIpAddress() : localAddress; + + // Initialize the session using ULAW as default codec in case of early media + // The session should be ready to receive media once the first INVITE is sent, before + // the session initialization is completed + sfl::AudioCodec* ac = Manager::instance().audioCodecFactory.instantiateCodec(PAYLOAD_CODEC_ULAW); + if (!ac) + throw VoipLinkException("Could not instantiate codec for early media"); + + std::vector<sfl::AudioCodec *> audioCodecs; + audioCodecs.push_back(ac); + + try { + call->getAudioRtp().initConfig(); + call->getAudioRtp().initSession(); + + if (isStunEnabled()) + call->updateSDPFromSTUN(); + + call->getAudioRtp().initLocalCryptoInfo(); + call->getAudioRtp().start(audioCodecs); + } catch (...) { + throw VoipLinkException("Could not start rtp session for early media"); + } + + // Building the local SDP offer + auto& localSDP = call->getLocalSDP(); + + if (getPublishedSameasLocal()) + localSDP.setPublishedIP(addrSdp); + else + localSDP.setPublishedIP(getPublishedAddress()); + + const bool created = localSDP.createOffer(getActiveAudioCodecs(), getActiveVideoCodecs()); + + if (not created or not SIPStartCall(call)) + throw VoipLinkException("Could not send outgoing INVITE request for new call"); +} + +std::shared_ptr<Call> +DHTAccount::newOutgoingCall(const std::string& id, const std::string& toUrl) +{ + return newOutgoingCall<SIPCall>(id, toUrl); +} + +bool +DHTAccount::SIPStartCall(const std::shared_ptr<SIPCall>& call) +{ + std::string toUri(call->getPeerNumber()); // expecting a fully well formed sip uri + + pj_str_t pjTo = pj_str((char*) toUri.c_str()); + + // Create the from header + std::string from(getFromUri()); + pj_str_t pjFrom = pj_str((char*) from.c_str()); + + pj_str_t pjContact(getContactHeader()); + + const std::string debugContactHeader(pj_strbuf(&pjContact), pj_strlen(&pjContact)); + DEBUG("contact header: %s / %s -> %s", + debugContactHeader.c_str(), from.c_str(), toUri.c_str()); + + pjsip_dialog *dialog = NULL; + + if (pjsip_dlg_create_uac(pjsip_ua_instance(), &pjFrom, &pjContact, &pjTo, NULL, &dialog) != PJ_SUCCESS) { + ERROR("Unable to create SIP dialogs for user agent client when " + "calling %s", toUri.c_str()); + return false; + } + + pj_str_t subj_hdr_name = CONST_PJ_STR("Subject"); + pjsip_hdr* subj_hdr = (pjsip_hdr*) pjsip_parse_hdr(dialog->pool, &subj_hdr_name, (char *) "Phone call", 10, NULL); + + pj_list_push_back(&dialog->inv_hdr, subj_hdr); + + pjsip_inv_session* inv = nullptr; + if (pjsip_inv_create_uac(dialog, call->getLocalSDP().getLocalSdpSession(), 0, &inv) != PJ_SUCCESS) { + ERROR("Unable to create invite session for user agent client"); + return false; + } + + if (!inv) { + ERROR("Call invite is not initialized"); + return PJ_FALSE; + } + + call->inv.reset(inv); + +/* + updateDialogViaSentBy(dialog); + if (hasServiceRoute()) + pjsip_dlg_set_route_set(dialog, sip_utils::createRouteSet(getServiceRoute(), call->inv->pool)); +*/ + call->inv->mod_data[link_->getModId()] = (void*)call.get(); + + pjsip_tx_data *tdata; + + if (pjsip_inv_invite(call->inv.get(), &tdata) != PJ_SUCCESS) { + ERROR("Could not initialize invite messager for this call"); + return false; + } + + const pjsip_tpselector tp_sel = getTransportSelector(); + if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) { + ERROR("Unable to associate transport for invite session dialog"); + return false; + } + + if (pjsip_inv_send_msg(call->inv.get(), tdata) != PJ_SUCCESS) { + call->inv.reset(); + ERROR("Unable to send invite message for this call"); + return false; + } + + call->setConnectionState(Call::PROGRESSING); + call->setState(Call::ACTIVE); + + return true; +} + +void DHTAccount::serialize(YAML::Emitter &out) +{ + using namespace Conf; + + out << YAML::BeginMap; + SIPAccountBase::serialize(out); + out << YAML::Key << DHT_PRIVKEY_PATH_KEY << YAML::Value << privkeyPath_; + out << YAML::Key << DHT_CERT_PATH_KEY << YAML::Value << certPath_; + + // tls submap + out << YAML::Key << TLS_KEY << YAML::Value << YAML::BeginMap; + SIPAccountBase::serializeTls(out); + out << YAML::EndMap; + + out << YAML::EndMap; +} + +void DHTAccount::unserialize(const YAML::Node &node) +{ + using namespace yaml_utils; + SIPAccountBase::unserialize(node); + parseValue(node, Conf::DHT_PRIVKEY_PATH_KEY, privkeyPath_); + parseValue(node, Conf::DHT_CERT_PATH_KEY, certPath_); +} + +dht::crypto::Identity +DHTAccount::loadIdentity() const +{ + std::vector<char> buffer; + std::vector<char> buffer_crt; + try { + { + std::ifstream file(privkeyPath_, std::ios::binary); + if (!file) + throw std::runtime_error("Can't read private key file."); + file.seekg(0, std::ios::end); + std::streamsize size = file.tellg(); + if (size > std::numeric_limits<unsigned>::max()) + throw std::runtime_error("Can't read private key file."); + buffer.resize(size); + file.seekg(0, std::ios::beg); + if (!file.read(buffer.data(), size)) + throw std::runtime_error("Can't load private key."); + } + { + std::ifstream file(certPath_, std::ios::binary); + if (!file) + throw std::runtime_error("Can't read certificate file."); + file.seekg(0, std::ios::end); + std::streamsize size = file.tellg(); + if (size > std::numeric_limits<unsigned>::max()) + throw std::runtime_error("Can't read certificate file."); + buffer_crt.resize(size); + file.seekg(0, std::ios::beg); + if (!file.read(buffer_crt.data(), size)) + throw std::runtime_error("Can't load certificate."); + } + } + catch (const std::exception& e) { + ERROR("Error loading identity: %s", e.what()); + auto id = dht::crypto::generateIdentity(); + if (!id.first || !id.second) { + throw VoipLinkException("Can't generate identity for this account."); + } + saveIdentity(id); + return id; + } + + gnutls_datum_t dt {reinterpret_cast<uint8_t*>(buffer.data()), static_cast<unsigned>(buffer.size())}; + gnutls_x509_privkey_t x509_key; + gnutls_x509_privkey_init(&x509_key); + int err = gnutls_x509_privkey_import(x509_key, &dt, GNUTLS_X509_FMT_PEM); + if (err != GNUTLS_E_SUCCESS) { + ERROR("Could not read PEM key - %s", gnutls_strerror(err)); + err = gnutls_x509_privkey_import(x509_key, &dt, GNUTLS_X509_FMT_DER); + } + if (err != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(x509_key); + ERROR("Could not read key - %s", gnutls_strerror(err)); + return {}; + } + + gnutls_x509_crt_t certificate; + gnutls_x509_crt_init(&certificate); + + gnutls_datum_t crt_dt {reinterpret_cast<uint8_t*>(buffer_crt.data()), static_cast<unsigned>(buffer_crt.size())}; + err = gnutls_x509_crt_import(certificate, &crt_dt, GNUTLS_X509_FMT_PEM); + if (err != GNUTLS_E_SUCCESS) { + ERROR("Could not read PEM certificate - %s", gnutls_strerror(err)); + err = gnutls_x509_crt_import(certificate, &crt_dt, GNUTLS_X509_FMT_DER); + } + if (err != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(x509_key); + gnutls_x509_crt_deinit(certificate); + ERROR("Could not read key - %s", gnutls_strerror(err)); + return {}; + } + + return {std::make_shared<dht::crypto::PrivateKey>(x509_key), std::make_shared<dht::crypto::Certificate>(certificate)}; +} + +void +DHTAccount::saveIdentity(const dht::crypto::Identity id) const +{ + if (id.first) { + auto buffer = id.first->serialize(); + std::ofstream file(privkeyPath_, std::ios::trunc | std::ios::binary); + if (!file.is_open()) { + ERROR("Could not write key to %s", privkeyPath_.c_str()); + return; + } + file.write((char*)buffer.data(), buffer.size()); + } + + if (id.second) { + auto buffer = id.second->getPacked(); + std::ofstream file(certPath_, std::ios::trunc | std::ios::binary); + if (!file.is_open()) { + ERROR("Could not write key to %s", certPath_.c_str()); + return; + } + file.write((char*)buffer.data(), buffer.size()); + } +} + +void DHTAccount::setAccountDetails(const std::map<std::string, std::string> &details) +{ + SIPAccountBase::setAccountDetails(details); +} + +std::map<std::string, std::string> DHTAccount::getAccountDetails() const +{ + std::map<std::string, std::string> a = SIPAccountBase::getAccountDetails(); + return a; +} + +void DHTAccount::doRegister() +{ + if (not isEnabled()) { + WARN("Account must be enabled to register, ignoring"); + return; + } + + DEBUG("doRegister"); + try { + if (dht_.isRunning()) { + ERROR("DHT already running"); + } + dht_.run(getLocalPort()+5, loadIdentity(), [=](dht::Dht::Status s4, dht::Dht::Status s6) { + WARN("Dht status : %d %d", (int)s4, (int)s6); + auto status = std::max(s4, s6); + switch(status) { + case dht::Dht::Status::Connecting: + case dht::Dht::Status::Connected: + setRegistrationState(status == dht::Dht::Status::Connected ? RegistrationState::REGISTERED : RegistrationState::TRYING); + if (!tlsListener_) { + initTlsConfiguration(); + tlsListener_ = link_->sipTransport->getTlsListener( + SipTransportDescr {getTransportType(), getTlsListenerPort(), getLocalInterface()}, + getTlsSetting()); + if (!tlsListener_) { + setRegistrationState(RegistrationState::ERROR_GENERIC); + ERROR("Error creating TLS listener."); + return; + } + } + break; + case dht::Dht::Status::Disconnected: + default: + setRegistrationState(status == dht::Dht::Status::Disconnected ? RegistrationState::UNREGISTERED : RegistrationState::ERROR_GENERIC); + tlsListener_.reset(); + break; + } + }); + dht_.put(dht_.getId(), dht::Value{dht::ServiceAnnouncement::TYPE.id, dht::ServiceAnnouncement(getTlsListenerPort())}, [](bool ok) { + DEBUG("Peer announce callback ! %d", ok); + }); + + username_ = dht_.getId().toString(); + + Manager::instance().registerEventHandler((uintptr_t)this, std::bind(&dht::DhtRunner::loop, &dht_)); + setRegistrationState(RegistrationState::TRYING); + + dht_.bootstrap(loadNodes()); + if (!hostname_.empty()) { + std::stringstream ss(hostname_); + std::vector<sockaddr_storage> bootstrap; + std::string node_addr; + while (std::getline(ss, node_addr, ';')) { + auto ips = ip_utils::getAddrList(node_addr); + bootstrap.insert(bootstrap.end(), ips.begin(), ips.end()); + } + for (auto ip : bootstrap) + DEBUG("Bootstrap node: %s", IpAddr(ip).toString(true).c_str()); + dht_.bootstrap(bootstrap); + } + } + catch (const std::exception& e) { + ERROR("Error registering DHT account: %s", e.what()); + setRegistrationState(RegistrationState::ERROR_GENERIC); + } +} + + +void DHTAccount::doUnregister(std::function<void(bool)> released_cb) +{ + Manager::instance().unregisterEventHandler((uintptr_t)this); + saveNodes(dht_.getNodes()); + dht_.join(); + tlsListener_.reset(); + setRegistrationState(RegistrationState::UNREGISTERED); + if (released_cb) + released_cb(false); +} + +void DHTAccount::saveNodes(const std::vector<dht::Dht::NodeExport>& nodes) const +{ + if (nodes.empty()) + return; + std::string nodesPath = nodePath_+DIR_SEPARATOR_STR "nodes"; + { + std::ofstream file(nodesPath, std::ios::trunc); + if (!file.is_open()) { + ERROR("Could not save nodes to %s", nodesPath.c_str()); + return; + } + for (auto& n : nodes) + file << n.id << " " << IpAddr(n.ss).toString(true) << "\n"; + } +} + +std::vector<dht::Dht::NodeExport> +DHTAccount::loadNodes() const +{ + std::vector<dht::Dht::NodeExport> nodes; + std::string nodesPath = nodePath_+DIR_SEPARATOR_STR "nodes"; + { + std::ifstream file(nodesPath); + if (!file.is_open()) { + ERROR("Could not load nodes from %s", nodesPath.c_str()); + return nodes; + } + std::string line; + while (std::getline(file, line)) + { + std::istringstream iss(line); + std::string id, ipstr; + if (!(iss >> id >> ipstr)) { break; } + IpAddr ip {ipstr}; + dht::Dht::NodeExport e {{id}, ip, ip.getLength()}; + nodes.push_back(e); + } + } +} + +void DHTAccount::initTlsConfiguration() +{ + ciphers_ = {PJ_TLS_RSA_WITH_AES_256_CBC_SHA256}; + + // TLS listener is unique and should be only modified through IP2IP_PROFILE + pjsip_tls_setting_default(&tlsSetting_); + + pj_cstr(&tlsSetting_.ca_list_file, ""); + pj_cstr(&tlsSetting_.cert_file, certPath_.c_str()); + pj_cstr(&tlsSetting_.privkey_file, privkeyPath_.c_str()); + pj_cstr(&tlsSetting_.password, ""); + tlsSetting_.method = PJSIP_TLSV1_METHOD; + tlsSetting_.ciphers_num = ciphers_.size(); + tlsSetting_.ciphers = &ciphers_.front(); + tlsSetting_.verify_server = false; + tlsSetting_.verify_client = false; + tlsSetting_.require_client_cert = false; + tlsSetting_.timeout.sec = 2; + tlsSetting_.qos_type = PJ_QOS_TYPE_BEST_EFFORT; + tlsSetting_.qos_ignore_error = PJ_TRUE; +} + +void DHTAccount::loadConfig() +{ + WARN("DHTAccount::loadConfig()"); + initTlsConfiguration(); + transportType_ = PJSIP_TRANSPORT_TLS; +} + +bool DHTAccount::userMatch(const std::string& username) const +{ + return !username.empty() and username == dht_.getId().toString(); +} + +MatchRank +DHTAccount::matches(const std::string &userName, const std::string &server) const +{ + if (userMatch(userName)) { + DEBUG("Matching account id in request with username %s", userName.c_str()); + return MatchRank::FULL; + } else { + return MatchRank::NONE; + } +} + +std::string DHTAccount::getFromUri() const +{ + std::string username(username_); + std::string transport {pjsip_transport_get_type_name(transportType_)}; + return username + "<dht:" + dht_.getId().toString() + "@dht.invalid;transport=" + transport + ">"; +} + +std::string DHTAccount::getToUri(const std::string& to) const +{ + const std::string transport {pjsip_transport_get_type_name(transportType_)}; + return "<sips:" + to + ";transport=" + transport + ">"; +} + +pj_str_t +DHTAccount::getContactHeader() +{ + if (transport_ == nullptr) + ERROR("Transport not created yet"); + + // The transport type must be specified, in our case START_OTHER refers to stun transport + pjsip_transport_type_e transportType = transportType_; + + if (transportType == PJSIP_TRANSPORT_START_OTHER) + transportType = PJSIP_TRANSPORT_UDP; + + // Else we determine this infor based on transport information + std::string address = "dht.invalid"; + pj_uint16_t port = getTlsListenerPort(); + + link_->sipTransport->findLocalAddressFromTransport(transport_->get(), transportType, hostname_, address, port); + +#if HAVE_IPV6 + /* Enclose IPv6 address in square brackets */ + if (IpAddr::isIpv6(address)) { + address = IpAddr(address).toString(false, true); + } +#endif + + WARN("getContactHeader %s@%s:%d", username_.c_str(), address.c_str(), port); + contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE, + "<sips:%s%s%s:%d;transport=%s>", + username_.c_str(), + (username_.empty() ? "" : "@"), + address.c_str(), + port, + pjsip_transport_get_type_name(transportType)); + return contact_; +} + +#ifdef SFL_PRESENCE +/** + * Enable the presence module + */ +void +DHTAccount::enablePresence(const bool& /* enabled */) +{ +} + +/** + * Set the presence (PUBLISH/SUBSCRIBE) support flags + * and process the change. + */ +void +DHTAccount::supportPresence(int /* function */, bool /* enabled*/) +{ +} +#endif + +/* +void DHTAccount::updateDialogViaSentBy(pjsip_dialog *dlg) +{ + if (allowViaRewrite_ && via_addr_.host.slen > 0) + pjsip_dlg_set_via_sent_by(dlg, &via_addr_, via_tp_); +} +*/ \ No newline at end of file diff --git a/daemon/src/dht/dhtaccount.h b/daemon/src/dht/dhtaccount.h new file mode 100644 index 0000000000000000000000000000000000000000..65d0601ac05be59afe34da2a683310aeaa781035 --- /dev/null +++ b/daemon/src/dht/dhtaccount.h @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2014 Savoir-Faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef DHTACCOUNT_H +#define DHTACCOUNT_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sip/siptransport.h" +#include "sip/sipaccountbase.h" +#include "noncopyable.h" +#include "ip_utils.h" +#include "sfl_types.h" // enable_if_base_of +#include "dhtcpp/dhtrunner.h" + +#include <pjsip/sip_transport_tls.h> +#include <pjsip/sip_types.h> +#include <pjsip-ua/sip_regc.h> + +#include <vector> +#include <map> + +namespace Conf { + const char *const DHT_PRIVKEY_PATH_KEY = "dhtPrivkeyPath"; + const char *const DHT_CERT_PATH_KEY = "dhtPubkeyPath"; +} + +namespace YAML { + class Node; + class Emitter; +} + +/** + * @file sipaccount.h + * @brief A SIP Account specify SIP specific functions and object = SIPCall/SIPVoIPLink) + */ +class DHTAccount : public SIPAccountBase { + public: + constexpr static const char * const ACCOUNT_TYPE = "DHT"; + + const char* getAccountType() const { + return ACCOUNT_TYPE; + } + + /** + * Constructor + * @param accountID The account identifier + */ + DHTAccount(const std::string& accountID, bool presenceEnabled); + + ~DHTAccount(); + + /** + * Serialize internal state of this account for configuration + * @param YamlEmitter the configuration engine which generate the configuration file + */ + virtual void serialize(YAML::Emitter &out); + + /** + * Populate the internal state for this account based on info stored in the configuration file + * @param The configuration node for this account + */ + virtual void unserialize(const YAML::Node &node); + + /** + * Return an map containing the internal state of this account. Client application can use this method to manage + * account info. + * @return A map containing the account information. + */ + virtual std::map<std::string, std::string> getAccountDetails() const; + + /** + * Actually useless, since config loading is done in init() + */ + void loadConfig(); + + /** + * Connect to the DHT. + */ + void doRegister(); + + /** + * Disconnect from the DHT. + */ + void doUnregister(std::function<void(bool)> cb = std::function<void(bool)>()); + + /** + * @return pj_str_t "From" uri based on account information. + * From RFC3261: "The To header field first and foremost specifies the desired + * logical" recipient of the request, or the address-of-record of the + * user or resource that is the target of this request. [...] As such, it is + * very important that the From URI not contain IP addresses or the FQDN + * of the host on which the UA is running, since these are not logical + * names." + */ + std::string getFromUri() const; + + /** + * This method adds the correct scheme, hostname and append + * the ;transport= parameter at the end of the uri, in accordance with RFC3261. + * It is expected that "port" is present in the internal hostname_. + * + * @return pj_str_t "To" uri based on @param username + * @param username A string formatted as : "username" + */ + std::string getToUri(const std::string& username) const; + + /** + * In the current version of SFLPhone, "srv" uri is obtained in the preformated + * way: hostname:port. This method adds the correct scheme and append + * the ;transport= parameter at the end of the uri, in accordance with RFC3261. + * + * @return pj_str_t "server" uri based on @param hostPort + * @param hostPort A string formatted as : "hostname:port" + */ + std::string getServerUri() const { return ""; }; + + /** + * Get the contact header for + * @return pj_str_t The contact header based on account information + */ + pj_str_t getContactHeader(); + + void setReceivedParameter(const std::string &received) { + receivedParameter_ = received; + via_addr_.host.ptr = (char *) receivedParameter_.c_str(); + via_addr_.host.slen = receivedParameter_.size(); + } + + std::string getReceivedParameter() const { + return receivedParameter_; + } + + pjsip_host_port * + getViaAddr() { + return &via_addr_; + } + + int getRPort() const { + if (rPort_ == -1) + return localPort_; + else + return rPort_; + } + + void setRPort(int rPort) { + rPort_ = rPort; + via_addr_.port = rPort; + } + + /* Returns true if the username and/or hostname match this account */ + MatchRank matches(const std::string &username, const std::string &hostname) const; + +#ifdef SFL_PRESENCE + /** + * Presence management + */ + //SIPPresence * getPresence() const; + + /** + * Activate the module. + * @param function Publish or subscribe to enable + * @param enable Flag + */ + void enablePresence(const bool& enable); + /** + * Activate the publish/subscribe. + * @param enable Flag + */ + void supportPresence(int function, bool enable); +#endif + + /** + * Implementation of Account::newOutgoingCall() + * Note: keep declaration before newOutgoingCall template. + */ + std::shared_ptr<Call> newOutgoingCall(const std::string& id, const std::string& toUrl); + + /** + * Create outgoing SIPCall. + * @param[in] id The ID of the call + * @param[in] toUrl The address to call + * @return std::shared_ptr<T> A shared pointer on the created call. + * The type of this instance is given in template argument. + * This type can be any base class of SIPCall class (included). + */ + template <class T=SIPCall> + std::shared_ptr<enable_if_base_of<T, SIPCall> > + newOutgoingCall(const std::string& id, const std::string& toUrl); + + /** + * Create incoming SIPCall. + * @param[in] id The ID of the call + * @return std::shared_ptr<T> A shared pointer on the created call. + * The type of this instance is given in template argument. + * This type can be any base class of SIPCall class (included). + */ + virtual std::shared_ptr<SIPCall> + newIncomingCall(const std::string& id); + + virtual bool isTlsEnabled() const { + return true; + } + + virtual bool getSrtpEnabled() const { + return true; + } + + virtual std::string getSrtpKeyExchange() const { + return "sdes"; + } + + virtual bool getSrtpFallback() const { + return false; + } + + private: + void createOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& to, const std::string& toUrl, const IpAddr& peer); + + /** + * Set the internal state for this account, mainly used to manage account details from the client application. + * @param The map containing the account information. + */ + virtual void setAccountDetails(const std::map<std::string, std::string> &details); + + NON_COPYABLE(DHTAccount); + + /** + * Start a SIP Call + * @param call The current call + * @return true if all is correct + */ + bool SIPStartCall(const std::shared_ptr<SIPCall>& call); + + bool userMatch(const std::string &username) const; + + /** + * @return pjsip_tls_setting structure, filled from the configuration + * file, that can be used directly by PJSIP to initialize + * TLS transport. + */ + pjsip_tls_setting * getTlsSetting() { + return &tlsSetting_; + } + + dht::DhtRunner dht_ {}; + + std::string nodePath_ {}; + std::string privkeyPath_ {}; + std::string certPath_ {}; + + /** + * If identityPath_ is a valid private key file (PEM or DER), + * load and returns it. Otherwise, generate one. + * Check if the given path contains a valid private key. + * @return the key if a valid private key exists there, nullptr otherwise. + */ + dht::crypto::Identity loadIdentity() const; + void saveIdentity(const dht::crypto::Identity id) const; + void saveNodes(const std::vector<dht::Dht::NodeExport>& nodes) const; + std::vector<dht::Dht::NodeExport> loadNodes() const; + + /** + * Initializes tls settings from configuration file. + */ + void initTlsConfiguration(); + + /** + * PJSIP aborts if the string length of our cipher list is too + * great, so this function forces our cipher list to fit this constraint. + */ + void trimCiphers(); + + /** + * The TLS settings, used only if tls is chosen as a sip transport. + */ + pjsip_tls_setting tlsSetting_ {}; + + /** + * Allocate a vector to be used by pjsip to store the supported ciphers on this system. + */ + CipherArray ciphers_ {}; + + /** + * Optional: "received" parameter from VIA header + */ + std::string receivedParameter_ {}; + + /** + * Optional: "rport" parameter from VIA header + */ + int rPort_ {-1}; + + /** + * Optional: via_addr construct from received parameters + */ + pjsip_host_port via_addr_ {}; + + char contactBuffer_[PJSIP_MAX_URL_SIZE] {}; + pj_str_t contact_ {contactBuffer_, 0}; + pjsip_transport *via_tp_ {nullptr}; + +}; + +#endif diff --git a/daemon/src/managerimpl.cpp b/daemon/src/managerimpl.cpp index 9599a73ec8d0c0d4899aaa7ed350b8ca9e42308d..9296aa49c4dfe682121c6d231db95df2cde9039d 100644 --- a/daemon/src/managerimpl.cpp +++ b/daemon/src/managerimpl.cpp @@ -45,6 +45,9 @@ #include "fileutils.h" #include "map_utils.h" #include "account.h" +#if HAVE_DHT +#include "dht/dhtaccount.h" +#endif #include "call_factory.h" @@ -2757,6 +2760,15 @@ ManagerImpl::newOutgoingCall(const std::string& id, std::shared_ptr<Account> account = Manager::instance().getIP2IPAccount(); std::string finalToUrl = toUrl; +#if HAVE_DHT + if (toUrl.find("dht:") != std::string::npos) { + WARN("DHT call detected"); + auto dhtAcc = getAllAccounts<DHTAccount>(); + if (not dhtAcc.empty()) + return dhtAcc.front()->newOutgoingCall(id, finalToUrl); + } +#endif + // FIXME: have a generic version to remove sip dependency sip_utils::stripSipUriPrefix(finalToUrl); diff --git a/daemon/src/sip/sipaccount.cpp b/daemon/src/sip/sipaccount.cpp index 4f46cca231bc746f39076755d9b4cc2bea9400c3..b4baf150a7400d26d075de2a15bdb8b14dfa0762 100644 --- a/daemon/src/sip/sipaccount.cpp +++ b/daemon/src/sip/sipaccount.cpp @@ -75,6 +75,8 @@ static const int MIN_REGISTRATION_TIME = 60; static const int DEFAULT_REGISTRATION_TIME = 3600; static const char *const VALID_TLS_METHODS[] = {"Default", "TLSv1", "SSLv3", "SSLv23"}; +static const char *const VALID_SRTP_KEY_EXCHANGES[] = {"", "sdes", "zrtp"}; + constexpr const char * const SIPAccount::ACCOUNT_TYPE; #if HAVE_TLS @@ -407,19 +409,26 @@ void SIPAccount::serialize(YAML::Emitter &out) // tls submap out << YAML::Key << TLS_KEY << YAML::Value << YAML::BeginMap; + SIPAccountBase::serializeTls(out); + out << YAML::Key << TLS_ENABLE_KEY << YAML::Value << tlsEnable_; + out << YAML::Key << VERIFY_CLIENT_KEY << YAML::Value << tlsVerifyClient_; + out << YAML::Key << VERIFY_SERVER_KEY << YAML::Value << tlsVerifyServer_; + out << YAML::Key << REQUIRE_CERTIF_KEY << YAML::Value << tlsRequireClientCertificate_; + out << YAML::Key << TIMEOUT_KEY << YAML::Value << tlsNegotiationTimeoutSec_; out << YAML::Key << CALIST_KEY << YAML::Value << tlsCaListFile_; out << YAML::Key << CERTIFICATE_KEY << YAML::Value << tlsCertificateFile_; out << YAML::Key << CIPHERS_KEY << YAML::Value << tlsCiphers_; - out << YAML::Key << TLS_ENABLE_KEY << YAML::Value << tlsEnable_; out << YAML::Key << METHOD_KEY << YAML::Value << tlsMethod_; out << YAML::Key << TLS_PASSWORD_KEY << YAML::Value << tlsPassword_; out << YAML::Key << PRIVATE_KEY_KEY << YAML::Value << tlsPrivateKeyFile_; - out << YAML::Key << REQUIRE_CERTIF_KEY << YAML::Value << tlsRequireClientCertificate_; out << YAML::Key << SERVER_KEY << YAML::Value << tlsServerName_; - out << YAML::Key << TIMEOUT_KEY << YAML::Value << tlsNegotiationTimeoutSec_; - out << YAML::Key << TLS_PORT_KEY << YAML::Value << tlsListenerPort_; - out << YAML::Key << VERIFY_CLIENT_KEY << YAML::Value << tlsVerifyClient_; - out << YAML::Key << VERIFY_SERVER_KEY << YAML::Value << tlsVerifyServer_; + out << YAML::EndMap; + + // srtp submap + out << YAML::Key << SRTP_KEY << YAML::Value << YAML::BeginMap; + out << YAML::Key << SRTP_ENABLE_KEY << YAML::Value << srtpEnabled_; + out << YAML::Key << KEY_EXCHANGE_KEY << YAML::Value << srtpKeyExchange_; + out << YAML::Key << RTP_FALLBACK_KEY << YAML::Value << srtpFallback_; out << YAML::EndMap; // zrtp submap @@ -505,7 +514,6 @@ void SIPAccount::unserialize(const YAML::Node &node) const auto &tlsMap = node[TLS_KEY]; parseValue(tlsMap, TLS_ENABLE_KEY, tlsEnable_); - parseValue(tlsMap, TLS_PORT_KEY, tlsListenerPort_); parseValue(tlsMap, CERTIFICATE_KEY, tlsCertificateFile_); parseValue(tlsMap, CALIST_KEY, tlsCaListFile_); parseValue(tlsMap, CIPHERS_KEY, tlsCiphers_); @@ -516,13 +524,21 @@ void SIPAccount::unserialize(const YAML::Node &node) parseValue(tlsMap, TLS_PASSWORD_KEY, tlsPassword_); parseValue(tlsMap, PRIVATE_KEY_KEY, tlsPrivateKeyFile_); - parseValue(tlsMap, REQUIRE_CERTIF_KEY, tlsRequireClientCertificate_); parseValue(tlsMap, SERVER_KEY, tlsServerName_); + parseValue(tlsMap, REQUIRE_CERTIF_KEY, tlsRequireClientCertificate_); parseValue(tlsMap, VERIFY_CLIENT_KEY, tlsVerifyClient_); parseValue(tlsMap, VERIFY_SERVER_KEY, tlsVerifyServer_); // FIXME parseValue(tlsMap, TIMEOUT_KEY, tlsNegotiationTimeoutSec_); + // get srtp submap + const auto &srtpMap = node[SRTP_KEY]; + parseValue(srtpMap, SRTP_ENABLE_KEY, srtpEnabled_); + + std::string tmpKey; + parseValue(srtpMap, KEY_EXCHANGE_KEY, tmpKey); + validate(srtpKeyExchange_, tmpKey, VALID_SRTP_KEY_EXCHANGES); + parseValue(srtpMap, RTP_FALLBACK_KEY, srtpFallback_); } template <typename T> @@ -584,6 +600,17 @@ void SIPAccount::setAccountDetails(const std::map<std::string, std::string> &det parseBool(details, CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient_); parseBool(details, CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate_); parseString(details, CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeoutSec_); + parseBool(details, CONFIG_TLS_VERIFY_SERVER, tlsVerifyServer_); + parseBool(details, CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient_); + parseBool(details, CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate_); + parseString(details, CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeoutSec_); + + // srtp settings + parseBool(details, CONFIG_SRTP_ENABLE, srtpEnabled_); + parseBool(details, CONFIG_SRTP_RTP_FALLBACK, srtpFallback_); + iter = details.find(CONFIG_SRTP_KEY_EXCHANGE); + if (iter != details.end()) + validate(srtpKeyExchange_, iter->second, VALID_SRTP_KEY_EXCHANGES); if (credentials_.empty()) { // credentials not set, construct 1 entry WARN("No credentials set, inferring them..."); @@ -664,15 +691,7 @@ std::map<std::string, std::string> SIPAccount::getAccountDetails() const a[CONFIG_STUN_SERVER] = stunServer_; a[CONFIG_KEEP_ALIVE_ENABLED] = keepAliveEnabled_ ? TRUE_STR : FALSE_STR; - a[CONFIG_ZRTP_DISPLAY_SAS] = zrtpDisplaySas_ ? TRUE_STR : FALSE_STR; - a[CONFIG_ZRTP_DISPLAY_SAS_ONCE] = zrtpDisplaySasOnce_ ? TRUE_STR : FALSE_STR; - a[CONFIG_ZRTP_HELLO_HASH] = zrtpHelloHash_ ? TRUE_STR : FALSE_STR; - a[CONFIG_ZRTP_NOT_SUPP_WARNING] = zrtpNotSuppWarning_ ? TRUE_STR : FALSE_STR; - // TLS listener is unique and parameters are modified through IP2IP_PROFILE - std::stringstream tlslistenerport; - tlslistenerport << tlsListenerPort_; - a[CONFIG_TLS_LISTENER_PORT] = tlslistenerport.str(); a[CONFIG_TLS_ENABLE] = tlsEnable_ ? TRUE_STR : FALSE_STR; a[CONFIG_TLS_CA_LIST_FILE] = tlsCaListFile_; a[CONFIG_TLS_CERTIFICATE_FILE] = tlsCertificateFile_; @@ -686,6 +705,15 @@ std::map<std::string, std::string> SIPAccount::getAccountDetails() const a[CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE] = tlsRequireClientCertificate_ ? TRUE_STR : FALSE_STR; a[CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC] = tlsNegotiationTimeoutSec_; + a[CONFIG_SRTP_KEY_EXCHANGE] = srtpKeyExchange_; + a[CONFIG_SRTP_ENABLE] = srtpEnabled_ ? TRUE_STR : FALSE_STR; + a[CONFIG_SRTP_RTP_FALLBACK] = srtpFallback_ ? TRUE_STR : FALSE_STR; + + a[CONFIG_ZRTP_DISPLAY_SAS] = zrtpDisplaySas_ ? TRUE_STR : FALSE_STR; + a[CONFIG_ZRTP_DISPLAY_SAS_ONCE] = zrtpDisplaySasOnce_ ? TRUE_STR : FALSE_STR; + a[CONFIG_ZRTP_HELLO_HASH] = zrtpHelloHash_ ? TRUE_STR : FALSE_STR; + a[CONFIG_ZRTP_NOT_SUPP_WARNING] = zrtpNotSuppWarning_ ? TRUE_STR : FALSE_STR; + return a; } diff --git a/daemon/src/sip/sipaccount.h b/daemon/src/sip/sipaccount.h index 32650ca23901459282c2dca5ecf5ec3d9c88b085..b2eeb414e37fb188ca052cba8c2596144486e6ff 100644 --- a/daemon/src/sip/sipaccount.h +++ b/daemon/src/sip/sipaccount.h @@ -55,6 +55,19 @@ typedef std::vector<pj_ssl_cipher> CipherArray; namespace Conf { const char *const KEEP_ALIVE_ENABLED = "keepAlive"; + + // TODO: write an object to store credential which implement serializable + const char *const SRTP_KEY = "srtp"; + const char *const SRTP_ENABLE_KEY = "enable"; + const char *const KEY_EXCHANGE_KEY = "keyExchange"; + const char *const RTP_FALLBACK_KEY = "rtpFallback"; + + // TODO: wirte an object to store zrtp params wich implement serializable + const char *const ZRTP_KEY = "zrtp"; + const char *const DISPLAY_SAS_KEY = "displaySas"; + const char *const DISPLAY_SAS_ONCE_KEY = "displaySasOnce"; + const char *const HELLO_HASH_ENABLED_KEY = "helloHashEnabled"; + const char *const NOT_SUPP_WARNING_KEY = "notSuppWarning"; } namespace YAML { @@ -309,14 +322,6 @@ class SIPAccount : public SIPAccountBase { return stunPort_; } - /** - * @return bool Tells if current transport for that - * account is set to TLS. - */ - bool isTlsEnabled() const { - return tlsEnable_; - } - /** * @return bool Tells if current transport for that * account is set to OTHER. @@ -369,6 +374,21 @@ class SIPAccount : public SIPAccountBase { bool hasServiceRoute() const { return not serviceRoute_.empty(); } + virtual bool isTlsEnabled() const { + return tlsEnable_; + } + + virtual bool getSrtpEnabled() const { + return srtpEnabled_; + } + + virtual std::string getSrtpKeyExchange() const { + return srtpKeyExchange_; + } + + virtual bool getSrtpFallback() const { + return srtpFallback_; + } bool getZrtpHelloHash() const { return zrtpHelloHash_; @@ -605,11 +625,7 @@ class SIPAccount : public SIPAccountBase { */ pj_uint16_t stunPort_ {PJ_STUN_PORT}; - - - /** - * Certificate autority file - */ + bool tlsEnable_ {false}; std::string tlsCaListFile_; std::string tlsCertificateFile_; std::string tlsPrivateKeyFile_; @@ -622,6 +638,25 @@ class SIPAccount : public SIPAccountBase { bool tlsRequireClientCertificate_; std::string tlsNegotiationTimeoutSec_; + /** + * Determine if SRTP is enabled for this account, SRTP and ZRTP are mutually exclusive + * This only determine if the media channel is secured. One could only enable TLS + * with no secured media channel. + */ + bool srtpEnabled_ {false}; + + /** + * Specifies the type of key exchange usd for SRTP (sdes/zrtp) + */ + std::string srtpKeyExchange_ {""}; + + /** + * Determine if the softphone should fallback on non secured media channel if SRTP negotiation fails. + * Make sure other SIP endpoints share the same behavior since it could result in encrypted data to be + * played through the audio device. + */ + bool srtpFallback_ {}; + /** * Determine if the SAS sould be displayed on client side. SAS is a 4-charcter string * that end users should verbaly validate to ensure the channel is secured. Used especially diff --git a/daemon/src/sip/sipaccountbase.cpp b/daemon/src/sip/sipaccountbase.cpp index df82054e1392cb1a2ed124016c36aaace86c3ec3..ed763f537165ff2f8411db38f2a580498c7b8969 100644 --- a/daemon/src/sip/sipaccountbase.cpp +++ b/daemon/src/sip/sipaccountbase.cpp @@ -44,8 +44,6 @@ bool SIPAccountBase::portsInUse_[HALF_MAX_PORT]; -static const char *const VALID_SRTP_KEY_EXCHANGES[] = {"", "sdes", "zrtp"}; - SIPAccountBase::SIPAccountBase(const std::string& accountID) : Account(accountID), link_(getSIPVoIPLink()) {} @@ -108,19 +106,17 @@ void SIPAccountBase::serialize(YAML::Emitter &out) out << YAML::Key << PUBLISH_PORT_KEY << YAML::Value << publishedPort_; out << YAML::Key << SAME_AS_LOCAL_KEY << YAML::Value << publishedSameasLocal_; - // srtp submap - out << YAML::Key << SRTP_KEY << YAML::Value << YAML::BeginMap; - out << YAML::Key << SRTP_ENABLE_KEY << YAML::Value << srtpEnabled_; - out << YAML::Key << KEY_EXCHANGE_KEY << YAML::Value << srtpKeyExchange_; - out << YAML::Key << RTP_FALLBACK_KEY << YAML::Value << srtpFallback_; - out << YAML::EndMap; - out << YAML::Key << VIDEO_CODECS_KEY << YAML::Value << videoCodecList_; out << YAML::Key << VIDEO_ENABLED_KEY << YAML::Value << videoEnabled_; out << YAML::Key << VIDEO_PORT_MAX_KEY << YAML::Value << videoPortRange_.second; out << YAML::Key << VIDEO_PORT_MIN_KEY << YAML::Value << videoPortRange_.first; } +void SIPAccountBase::serializeTls(YAML::Emitter &out) +{ + using namespace Conf; + out << YAML::Key << TLS_PORT_KEY << YAML::Value << tlsListenerPort_; +} void SIPAccountBase::unserialize(const YAML::Node &node) { @@ -153,14 +149,9 @@ void SIPAccountBase::unserialize(const YAML::Node &node) parseValue(node, DTMF_TYPE_KEY, dtmfType_); - // get srtp submap - const auto &srtpMap = node[SRTP_KEY]; - parseValue(srtpMap, SRTP_ENABLE_KEY, srtpEnabled_); - - std::string tmpKey; - parseValue(srtpMap, KEY_EXCHANGE_KEY, tmpKey); - validate(srtpKeyExchange_, tmpKey, VALID_SRTP_KEY_EXCHANGES); - parseValue(srtpMap, RTP_FALLBACK_KEY, srtpFallback_); + // get tls submap + const auto &tlsMap = node[TLS_KEY]; + parseValue(tlsMap, TLS_PORT_KEY, tlsListenerPort_); unserializeRange(node, AUDIO_PORT_MIN_KEY, AUDIO_PORT_MAX_KEY, audioPortRange_); unserializeRange(node, VIDEO_PORT_MIN_KEY, VIDEO_PORT_MAX_KEY, videoPortRange_); @@ -195,12 +186,8 @@ void SIPAccountBase::setAccountDetails(const std::map<std::string, std::string> updateRange(tmpMin, tmpMax, videoPortRange_); #endif - // srtp settings - parseBool(details, CONFIG_SRTP_ENABLE, srtpEnabled_); - parseBool(details, CONFIG_SRTP_RTP_FALLBACK, srtpFallback_); - auto iter = details.find(CONFIG_SRTP_KEY_EXCHANGE); - if (iter != details.end()) - validate(srtpKeyExchange_, iter->second, VALID_SRTP_KEY_EXCHANGES); + // TLS + parseInt(details, CONFIG_TLS_LISTENER_PORT, tlsListenerPort_); } std::map<std::string, std::string> @@ -231,9 +218,9 @@ SIPAccountBase::getAccountDetails() const publishedport << publishedPort_; a[CONFIG_PUBLISHED_PORT] = publishedport.str(); - a[CONFIG_SRTP_KEY_EXCHANGE] = srtpKeyExchange_; - a[CONFIG_SRTP_ENABLE] = srtpEnabled_ ? TRUE_STR : FALSE_STR; - a[CONFIG_SRTP_RTP_FALLBACK] = srtpFallback_ ? TRUE_STR : FALSE_STR; + std::stringstream tlslistenerport; + tlslistenerport << tlsListenerPort_; + a[CONFIG_TLS_LISTENER_PORT] = tlslistenerport.str(); return a; } diff --git a/daemon/src/sip/sipaccountbase.h b/daemon/src/sip/sipaccountbase.h index 50993be61ad1b94402a9d834dd6214a2ac3d33c6..c279a57f1d22966c0c390ccbd4423a9cd1bc69b9 100644 --- a/daemon/src/sip/sipaccountbase.h +++ b/daemon/src/sip/sipaccountbase.h @@ -66,19 +66,6 @@ namespace Conf { const char *const PRESENCE_STATUS_KEY = "presenceStatus"; const char *const PRESENCE_NOTE_KEY = "presenceNote"; - // TODO: write an object to store credential which implement serializable - const char *const SRTP_KEY = "srtp"; - const char *const SRTP_ENABLE_KEY = "enable"; - const char *const KEY_EXCHANGE_KEY = "keyExchange"; - const char *const RTP_FALLBACK_KEY = "rtpFallback"; - - // TODO: wirte an object to store zrtp params wich implement serializable - const char *const ZRTP_KEY = "zrtp"; - const char *const DISPLAY_SAS_KEY = "displaySas"; - const char *const DISPLAY_SAS_ONCE_KEY = "displaySasOnce"; - const char *const HELLO_HASH_ENABLED_KEY = "helloHashEnabled"; - const char *const NOT_SUPP_WARNING_KEY = "notSuppWarning"; - // TODO: write an object to store tls params which implement serializable const char *const TLS_KEY = "tls"; const char *const TLS_PORT_KEY = "tlsPort"; @@ -152,8 +139,12 @@ public: return dtmfType_; } - bool isTlsEnabled() const { - return tlsEnable_; + /** + * Determine if TLS is enabled for this account. TLS provides a secured channel for + * SIP signalization. It is independant than the media encription provided by SRTP or ZRTP. + */ + virtual bool isTlsEnabled() const { + return false; } virtual pjsip_tls_setting * getTlsSetting() { @@ -238,17 +229,13 @@ public: publishedPort_ = port; } - bool getSrtpEnabled() const { - return srtpEnabled_; + virtual bool getSrtpEnabled() const { + return false; } - std::string getSrtpKeyExchange() const { - return srtpKeyExchange_; - } + virtual std::string getSrtpKeyExchange() const = 0; - bool getSrtpFallback() const { - return srtpFallback_; - } + virtual bool getSrtpFallback() const = 0; /** * Get the contact header for @@ -287,6 +274,7 @@ public: protected: virtual void serialize(YAML::Emitter &out); + virtual void serializeTls(YAML::Emitter &out); virtual void unserialize(const YAML::Node &node); virtual void setAccountDetails(const std::map<std::string, std::string> &details); @@ -360,31 +348,6 @@ protected: */ std::string dtmfType_ {OVERRTP_STR}; - /** - * Determine if TLS is enabled for this account. TLS provides a secured channel for - * SIP signalization. It is independant than the media encription provided by SRTP or ZRTP. - */ - bool tlsEnable_ {false}; - - /** - * Determine if SRTP is enabled for this account, SRTP and ZRTP are mutually exclusive - * This only determine if the media channel is secured. One could only enable TLS - * with no secured media channel. - */ - bool srtpEnabled_ {false}; - - /** - * Specifies the type of key exchange usd for SRTP (sdes/zrtp) - */ - std::string srtpKeyExchange_ {""}; - - /** - * Determine if the softphone should fallback on non secured media channel if SRTP negotiation fails. - * Make sure other SIP endpoints share the same behavior since it could result in encrypted data to be - * played through the audio device. - */ - bool srtpFallback_ {}; - pj_status_t transportStatus_ {PJSIP_SC_TRYING}; std::string transportError_ {}; diff --git a/daemon/src/sip/sipvoiplink.cpp b/daemon/src/sip/sipvoiplink.cpp index 2e1e39284a9842c83b59d0b557aa3d1edfdabf11..d40792131d4a5e4f65ad27950e919f4b73036eef 100644 --- a/daemon/src/sip/sipvoiplink.cpp +++ b/daemon/src/sip/sipvoiplink.cpp @@ -43,6 +43,8 @@ #include "sipaccount.h" #include "sip_utils.h" +#include "dht/dhtaccount.h" + #include "call_factory.h" #include "manager.h" @@ -231,13 +233,13 @@ transaction_request_cb(pjsip_rx_data *rdata) const std::string remote_user(sip_from_uri->user.ptr, sip_from_uri->user.slen); const std::string remote_hostname(sip_from_uri->host.ptr, sip_from_uri->host.slen); - auto sipaccount(getSIPVoIPLink()->guessAccount(toUsername, viaHostname, remote_hostname)); - if (!sipaccount) { + auto account(getSIPVoIPLink()->guessAccount(toUsername, viaHostname, remote_hostname)); + if (!account) { ERROR("NULL account"); return PJ_FALSE; } - const auto& account_id = sipaccount->getAccountID(); + const auto& account_id = account->getAccountID(); std::string displayName(sip_utils::parseDisplayName(rdata->msg_info.msg_buf)); pjsip_msg_body *body = rdata->msg_info.msg->body; @@ -271,7 +273,7 @@ transaction_request_cb(pjsip_rx_data *rdata) if (!body || pjmedia_sdp_parse(rdata->tp_info.pool, (char*) body->data, body->len, &r_sdp) != PJ_SUCCESS) r_sdp = NULL; - if (sipaccount->getActiveAudioCodecs().empty()) { + if (account->getActiveAudioCodecs().empty()) { try_respond_stateless(endpt_, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL, NULL, NULL); return PJ_FALSE; @@ -287,19 +289,19 @@ transaction_request_cb(pjsip_rx_data *rdata) Manager::instance().hookPreference.runHook(rdata->msg_info.msg); - auto call = sipaccount->newIncomingCall(Manager::instance().getNewCallID()); + auto call = account->newIncomingCall(Manager::instance().getNewCallID()); // FIXME : for now, use the same address family as the SIP transport - auto family = pjsip_transport_type_get_af(sipaccount->getTransportType()); - IpAddr addrToUse = ip_utils::getInterfaceAddr(sipaccount->getLocalInterface(), family); + auto family = pjsip_transport_type_get_af(account->getTransportType()); + IpAddr addrToUse = ip_utils::getInterfaceAddr(account->getLocalInterface(), family); // May use the published address as well - IpAddr addrSdp = sipaccount->isStunEnabled() or (not sipaccount->getPublishedSameasLocal()) - ? sipaccount->getPublishedIpAddress() : addrToUse; + IpAddr addrSdp = account->isStunEnabled() or (not account->getPublishedSameasLocal()) + ? account->getPublishedIpAddress() : addrToUse; char tmp[PJSIP_MAX_URL_SIZE]; size_t length = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_from_uri, tmp, PJSIP_MAX_URL_SIZE); - std::string peerNumber(tmp, std::min(length, sizeof tmp)); + std::string peerNumber(tmp, length); sip_utils::stripSipUriPrefix(peerNumber); if (not remote_user.empty() and not remote_hostname.empty()) @@ -310,7 +312,7 @@ transaction_request_cb(pjsip_rx_data *rdata) auto transport = getSIPVoIPLink()->sipTransport->findTransport(rdata->tp_info.transport); if (!transport) { - transport = sipaccount->getTransport(); + transport = account->getTransport(); if (!transport) { ERROR("No suitable transport to answer this call."); return PJ_FALSE; @@ -335,7 +337,7 @@ transaction_request_cb(pjsip_rx_data *rdata) return PJ_FALSE; } - if (sipaccount->isStunEnabled()) + if (account->isStunEnabled()) call->updateSDPFromSTUN(); if (body and body->len > 0 and call->getAudioRtp().isSdesEnabled()) { @@ -370,7 +372,7 @@ transaction_request_cb(pjsip_rx_data *rdata) } } - call->getLocalSDP().receiveOffer(r_sdp, sipaccount->getActiveAudioCodecs(), sipaccount->getActiveVideoCodecs()); + call->getLocalSDP().receiveOffer(r_sdp, account->getActiveAudioCodecs(), account->getActiveVideoCodecs()); sfl::AudioCodec* ac = Manager::instance().audioCodecFactory.instantiateCodec(PAYLOAD_CODEC_ULAW); @@ -468,7 +470,7 @@ transaction_request_cb(pjsip_rx_data *rdata) } // contactStr must stay in scope as long as tdata - const pj_str_t contactStr(sipaccount->getContactHeader()); + const pj_str_t contactStr(account->getContactHeader()); sip_utils::addContactHeader(&contactStr, tdata); if (pjsip_inv_send_msg(call->inv.get(), tdata) != PJ_SUCCESS) { @@ -636,18 +638,33 @@ SIPVoIPLink::guessAccount(const std::string& userName, auto result = std::static_pointer_cast<SIPAccountBase>(Manager::instance().getIP2IPAccount()); // default result MatchRank best = MatchRank::NONE; - for (const auto& sipaccount : Manager::instance().getAllAccounts<SIPAccount>()) { - if (!sipaccount) + // DHT accounts + for (const auto& account : Manager::instance().getAllAccounts<DHTAccount>()) { + if (!account) continue; + const MatchRank match(account->matches(userName, server)); - const MatchRank match(sipaccount->matches(userName, server, endpt_, pool_)); + // return right away if this is a full match + if (match == MatchRank::FULL) { + return account; + } else if (match > best) { + best = match; + result = account; + } + } + + // SIP accounts + for (const auto& account : Manager::instance().getAllAccounts<SIPAccount>()) { + if (!account) + continue; + const MatchRank match(account->matches(userName, server, endpt_, pool_)); // return right away if this is a full match if (match == MatchRank::FULL) { - return std::static_pointer_cast<SIPAccountBase>(sipaccount); + return account; } else if (match > best) { best = match; - result = sipaccount; + result = account; } } @@ -820,7 +837,7 @@ invite_session_state_changed_cb(pjsip_inv_session *inv, pjsip_event *ev) // After we sent or received a ACK - The connection is established call->onAnswered(); } else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { - std::string accId(call->getAccountId()); + //std::string accId(call->getAccountId()); switch (inv->cause) { // The call terminates normally - BYE / CANCEL