jamiaccount.cpp 134 KB
Newer Older
Adrien Béraud's avatar
Adrien Béraud committed
1
/*
2
 *  Copyright (C) 2004-2020 Savoir-faire Linux Inc.
Adrien Béraud's avatar
Adrien Béraud committed
3 4
 *
 *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
5
 *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
6
 *  Author: Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
Nicolas Jager's avatar
Nicolas Jager committed
7
 *  Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>
8
 *  Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com>
Adrien Béraud's avatar
Adrien Béraud committed
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 *
 *  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.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

29
#include "jamiaccount.h"
30 31 32

#include "logger.h"

33
#include "accountarchive.h"
34
#include "jami_contact.h"
35
#include "configkeys.h"
36
#include "contact_list.h"
37 38
#include "archive_account_manager.h"
#include "server_account_manager.h"
39
#include "jamidht/channeled_transport.h"
40
#include "multiplexed_socket.h"
Adrien Béraud's avatar
Adrien Béraud committed
41

Adrien Béraud's avatar
Adrien Béraud committed
42 43 44
#include "sip/sdp.h"
#include "sip/sipvoiplink.h"
#include "sip/sipcall.h"
45
#include "sip/siptransport.h"
46
#include "sip/sip_utils.h"
Adrien Béraud's avatar
Adrien Béraud committed
47

48
#include "sips_transport_ice.h"
49 50
#include "ice_transport.h"

51
#include "p2p.h"
52
#include "connectionmanager.h"
53

54
#include "client/ring_signal.h"
55
#include "dring/call_const.h"
56
#include "dring/account_const.h"
Adrien Béraud's avatar
Adrien Béraud committed
57

58 59 60
#include "upnp/upnp_control.h"
#include "system_codec_container.h"

Adrien Béraud's avatar
Adrien Béraud committed
61 62
#include "account_schema.h"
#include "manager.h"
63
#include "utf8_utils.h"
Adrien Béraud's avatar
Adrien Béraud committed
64

Adrien Béraud's avatar
Adrien Béraud committed
65
#ifdef ENABLE_VIDEO
66
#include "libav_utils.h"
Adrien Béraud's avatar
Adrien Béraud committed
67 68
#endif
#include "fileutils.h"
69 70
#include "string_utils.h"
#include "array_size.h"
71
#include "archiver.h"
72
#include "data_transfer.h"
Adrien Béraud's avatar
Adrien Béraud committed
73 74

#include "config/yamlparser.h"
75
#include "security/certstore.h"
76 77
#include "libdevcrypto/Common.h"
#include "base64.h"
78
#include "im/instant_messaging.h"
79

Adrien Béraud's avatar
Adrien Béraud committed
80
#include <opendht/thread_pool.h>
81
#include <opendht/peer_discovery.h>
82 83
#include <opendht/http.h>

84
#include <yaml-cpp/yaml.h>
85 86 87
#include <json/json.h>

#include <unistd.h>
Stepan Salenikovich's avatar
Stepan Salenikovich committed
88

Adrien Béraud's avatar
Adrien Béraud committed
89 90
#include <algorithm>
#include <array>
91
#include <cctype>
92
#include <cinttypes>
93
#include <cstdarg>
94 95 96 97
#include <initializer_list>
#include <memory>
#include <regex>
#include <sstream>
98
#include <string>
99
#include <system_error>
Adrien Béraud's avatar
Adrien Béraud committed
100

101 102
using namespace std::placeholders;

Adrien Béraud's avatar
Adrien Béraud committed
103
namespace jami {
Guillaume Roguez's avatar
Guillaume Roguez committed
104

105 106
constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID");

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
struct PendingConfirmation {
    std::mutex lock;
    bool replied {false};
    std::map<dht::InfoHash, std::future<size_t>> listenTokens {};
};

// Used to pass infos to a pjsip callback (pjsip_endpt_send_request)
struct TextMessageCtx {
    std::weak_ptr<JamiAccount> acc;
    std::string to;
    std::string deviceId;
    uint64_t id;
    bool retryOnTimeout;
    std::shared_ptr<PendingConfirmation> confirmation;
};

123 124 125 126 127 128
struct VCardMessageCtx {
    std::shared_ptr<std::atomic_int> success;
    int total;
    std::string path;
};

Nicolas Jager's avatar
Nicolas Jager committed
129 130 131 132 133 134 135 136 137 138 139 140 141
namespace Migration {

enum class State { // Contains all the Migration states
    SUCCESS,
    INVALID
};

std::string
mapStateNumberToString(const State migrationState)
{
#define CASE_STATE(X) case Migration::State::X: \
                           return #X

142 143 144 145 146
    switch (migrationState) {
        CASE_STATE(INVALID);
        CASE_STATE(SUCCESS);
    }
    return {};
Nicolas Jager's avatar
Nicolas Jager committed
147 148 149 150 151 152 153 154 155
}

void
setState (const std::string& accountID,
          const State migrationState)
{
    emitSignal<DRing::ConfigurationSignal::MigrationEnded>(accountID,
        mapStateNumberToString(migrationState));
}
156

Adrien Béraud's avatar
Adrien Béraud committed
157
} // namespace jami::Migration
Nicolas Jager's avatar
Nicolas Jager committed
158

159
struct JamiAccount::BuddyInfo
160 161 162 163
{
    /* the buddy id */
    dht::InfoHash id;

164 165
    /* number of devices connected on the DHT */
    uint32_t devices_cnt {};
166

167
    /* The disposable object to update buddy info */
168
    std::future<size_t> listenToken;
169 170 171 172

    BuddyInfo(dht::InfoHash id) : id(id) {}
};

173
struct JamiAccount::PendingCall
174 175 176
{
    std::chrono::steady_clock::time_point start;
    std::shared_ptr<IceTransport> ice_sp;
177
    std::shared_ptr<IceTransport> ice_tcp_sp;
178 179 180 181
    std::weak_ptr<SIPCall> call;
    std::future<size_t> listen_key;
    dht::InfoHash call_key;
    dht::InfoHash from;
182
    dht::InfoHash from_account;
183 184 185
    std::shared_ptr<dht::crypto::Certificate> from_cert;
};

186
struct JamiAccount::PendingMessage
187
{
188
    std::set<dht::InfoHash> to;
189 190
};

191 192 193 194 195 196 197 198 199 200 201 202 203
struct AccountPeerInfo
{
    dht::InfoHash accountId;
    std::string displayName;
    MSGPACK_DEFINE(accountId, displayName)
};

struct JamiAccount::DiscoveredPeer
{
    std::string displayName;
    std::shared_ptr<Task> cleanupTask;
};

204 205
static constexpr int ICE_COMPONENTS {1};
static constexpr int ICE_COMP_SIP_TRANSPORT {0};
206 207
static constexpr auto ICE_NEGOTIATION_TIMEOUT = std::chrono::seconds(60);
static constexpr auto TLS_TIMEOUT = std::chrono::seconds(30);
208
const constexpr auto EXPORT_KEY_RENEWAL_TIME = std::chrono::minutes(20);
209

210
static constexpr const char * const RING_URI_PREFIX = "ring:";
211
static constexpr const char * const JAMI_URI_PREFIX = "jami:";
212
static constexpr const char * DEFAULT_TURN_SERVER = "turn.jami.net";
213 214 215
static constexpr const char * DEFAULT_TURN_USERNAME = "ring";
static constexpr const char * DEFAULT_TURN_PWD = "ring";
static constexpr const char * DEFAULT_TURN_REALM = "ring";
216
static const auto PROXY_REGEX = std::regex("(https?://)?([\\w\\.]+)(:(\\d+)|:\\[(.+)-(.+)\\])?");
217
static const std::string PEER_DISCOVERY_JAMI_SERVICE = "jami";
218
const constexpr auto PEER_DISCOVERY_EXPIRATION = std::chrono::minutes(1);
219

220 221
constexpr const char* const JamiAccount::ACCOUNT_TYPE;
/* constexpr */ const std::pair<uint16_t, uint16_t> JamiAccount::DHT_PORT_RANGE {4000, 8888};
Adrien Béraud's avatar
Adrien Béraud committed
222

223
using ValueIdDist = std::uniform_int_distribution<dht::Value::Id>;
224

225
static const std::string
226
stripPrefix(const std::string& toUrl)
227
{
228
    auto dhtf = toUrl.find(RING_URI_PREFIX);
229 230 231
    if (dhtf != std::string::npos) {
        dhtf = dhtf+5;
    } else {
232 233 234 235 236 237 238
        dhtf = toUrl.find(JAMI_URI_PREFIX);
        if (dhtf != std::string::npos) {
            dhtf = dhtf+5;
        } else {
            dhtf = toUrl.find("sips:");
            dhtf = (dhtf == std::string::npos) ? 0 : dhtf+5;
        }
239 240 241
    }
    while (dhtf < toUrl.length() && toUrl[dhtf] == '/')
        dhtf++;
242 243
    return toUrl.substr(dhtf);
}
244

245
static const std::string
246
parseJamiUri(const std::string& toUrl)
247 248 249
{
    auto sufix = stripPrefix(toUrl);
    if (sufix.length() < 40)
250
        throw std::invalid_argument("id must be a Jami infohash");
251

252
    const std::string toUri = sufix.substr(0, 40);
253
    if (std::find_if_not(toUri.cbegin(), toUri.cend(), ::isxdigit) != toUri.cend())
254
        throw std::invalid_argument("id must be a Jami infohash");
255 256 257
    return toUri;
}

258 259 260 261 262 263 264
static constexpr const char*
dhtStatusStr(dht::NodeStatus status) {
    return status == dht::NodeStatus::Connected  ? "connected"  : (
           status == dht::NodeStatus::Connecting ? "connecting" :
                                                   "disconnected");
}

265 266 267
/**
 * Local ICE Transport factory helper
 *
268
 * JamiAccount must use this helper than direct IceTranportFactory API
269 270 271
 */
template <class... Args>
std::shared_ptr<IceTransport>
272
JamiAccount::createIceTransport(const Args&... args)
273 274
{
    auto ice = Manager::instance().getIceTransportFactory().createTransport(args...);
275
    if (!ice)
276 277 278 279 280
        throw std::runtime_error("ICE transport creation failed");

    return ice;
}

281
JamiAccount::JamiAccount(const std::string& accountID, bool /* presenceEnabled */)
Guillaume Roguez's avatar
Guillaume Roguez committed
282
    : SIPAccountBase(accountID)
283
    , dht_(new dht::DhtRunner)
Guillaume Roguez's avatar
Guillaume Roguez committed
284 285 286
    , idPath_(fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID())
    , cachePath_(fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID())
    , dataPath_(cachePath_ + DIR_SEPARATOR_STR "values")
287 288
    , dhtPeerConnector_ {}
    , connectionManager_ {}
289 290 291 292 293 294 295
{
    // Force the SFL turn server if none provided yet
    turnServer_ = DEFAULT_TURN_SERVER;
    turnServerUserName_ = DEFAULT_TURN_USERNAME;
    turnServerPwd_ = DEFAULT_TURN_PWD;
    turnServerRealm_ = DEFAULT_TURN_REALM;
    turnEnabled_ = true;
296

297 298 299
    proxyListUrl_ = DHT_DEFAULT_PROXY_LIST_URL;
    proxyServer_ = DHT_DEFAULT_PROXY;

300 301 302
    try {
        std::istringstream is(fileutils::loadCacheTextFile(cachePath_ + DIR_SEPARATOR_STR "dhtproxy", std::chrono::hours(24 * 7)));
        std::getline(is, proxyServerCached_);
303 304
    } catch (const std::exception& e) {
        JAMI_DBG("Can't load proxy URL from cache: %s", e.what());
305
    }
306 307

    setActiveCodecs({});
308
}
Adrien Béraud's avatar
Adrien Béraud committed
309

310
JamiAccount::~JamiAccount()
Adrien Béraud's avatar
Adrien Béraud committed
311
{
312
    shutdownConnections();
313 314 315 316
    if(peerDiscovery_){
        peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE);
        peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE);
    }
317 318
    if (auto dht = dht_)
        dht->join();
Adrien Béraud's avatar
Adrien Béraud committed
319 320
}

321 322 323 324 325
void
JamiAccount::shutdownConnections()
{
    connectionManager_.reset();
    dhtPeerConnector_.reset();
326 327 328
    std::lock_guard<std::mutex> lk(sipConnectionsMtx_);
    sipConnections_.clear();
    pendingSipConnections_.clear();
329 330
}

331
void
332
JamiAccount::flush()
333 334 335 336 337 338
{
    // Class base method
    SIPAccountBase::flush();

    fileutils::removeAll(dataPath_);
    fileutils::removeAll(cachePath_);
339
    fileutils::removeAll(idPath_, true);
340 341
}

Adrien Béraud's avatar
Adrien Béraud committed
342
std::shared_ptr<SIPCall>
343
JamiAccount::newIncomingCall(const std::string& from, const std::map<std::string, std::string>& details, const std::shared_ptr<SipTransport>& sipTr)
Adrien Béraud's avatar
Adrien Béraud committed
344
{
345
    std::lock_guard<std::mutex> lock(callsMutex_);
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370

    if (sipTr) {
        std::unique_lock<std::mutex> lk(sipConnectionsMtx_);
        auto sipConnIt = sipConnections_.find(from);
        if (sipConnIt != sipConnections_.end() && !sipConnIt->second.empty()) {
            for (auto dit = sipConnIt->second.rbegin(); dit != sipConnIt->second.rend(); ++dit) {
                for (auto it = dit->second.rbegin(); it != dit->second.rend(); ++it) {
                    // Search linked Sip Transport
                    if (it->transport != sipTr) continue;

                    auto call = Manager::instance().callFactory.newCall<SIPCall, JamiAccount>(*this, Manager::instance().getNewCallID(), Call::CallType::INCOMING);
                    if (!call) return {};

                    std::weak_ptr<SIPCall> wcall = call;
                    call->setPeerUri(RING_URI_PREFIX + from);
                    call->setPeerNumber(from);

                    call->updateDetails(details);
                    return call;
                }
            }
        }
        lk.unlock();
    }

371 372
    auto call_it = pendingSipCalls_.begin();
    while (call_it != pendingSipCalls_.end()) {
373 374
        auto call = call_it->call.lock();
        if (not call) {
Adrien Béraud's avatar
Adrien Béraud committed
375
            JAMI_WARN("newIncomingCall: discarding deleted call");
376
            call_it = pendingSipCalls_.erase(call_it);
377 378 379
        } else if (call->getPeerNumber() == from || (call_it->from_cert and
                                                     call_it->from_cert->issuer and
                                                     call_it->from_cert->issuer->getId().toString() == from)) {
Adrien Béraud's avatar
Adrien Béraud committed
380
            JAMI_DBG("newIncomingCall: found matching call for %s", from.c_str());
381
            pendingSipCalls_.erase(call_it);
382
            call->updateDetails(details);
383 384 385
            return call;
        } else {
            ++call_it;
386 387
        }
    }
Adrien Béraud's avatar
Adrien Béraud committed
388
    JAMI_ERR("newIncomingCall: can't find matching call for %s", from.c_str());
389
    return nullptr;
Adrien Béraud's avatar
Adrien Béraud committed
390 391 392 393
}

template <>
std::shared_ptr<SIPCall>
394
JamiAccount::newOutgoingCall(const std::string& toUrl,
395
                             const std::map<std::string, std::string>& volatileCallDetails)
Adrien Béraud's avatar
Adrien Béraud committed
396
{
397
    auto suffix = stripPrefix(toUrl);
Adrien Béraud's avatar
Adrien Béraud committed
398
    JAMI_DBG() << *this << "Calling DHT peer " << suffix;
399
    auto& manager = Manager::instance();
400
    auto call = manager.callFactory.newCall<SIPCall, JamiAccount>(*this, manager.getNewCallID(),
401 402
                                                                  Call::CallType::OUTGOING,
                                                                  volatileCallDetails);
403

404
    call->setIPToIP(true);
Adrien Béraud's avatar
Adrien Béraud committed
405
    call->setSecure(isTlsEnabled());
406 407

    try {
408
        const std::string toUri = parseJamiUri(suffix);
409 410 411
        startOutgoingCall(call, toUri);
    } catch (...) {
#if HAVE_RINGNS
Adrien Béraud's avatar
Adrien Béraud committed
412
        NameDirectory::lookupUri(suffix, nameServer_, [wthis_=weak(), call](const std::string& result,
413 414
                                                                   NameDirectory::Response response) {
            // we may run inside an unknown thread, but following code must be called in main thread
Adrien Béraud's avatar
Adrien Béraud committed
415
            runOnMainThread([wthis_, result, response, call]() {
416 417 418 419
                if (response != NameDirectory::Response::found) {
                    call->onFailure(EINVAL);
                    return;
                }
420 421
                if (auto sthis = wthis_.lock()) {
                    try {
422
                        const std::string toUri = parseJamiUri(result);
423 424 425 426 427 428 429 430 431 432 433 434 435
                        sthis->startOutgoingCall(call, toUri);
                    } catch (...) {
                        call->onFailure(ENOENT);
                    }
                } else {
                    call->onFailure();
                }
            });
        });
#else
        call->onFailure(ENOENT);
#endif
    }
436

437 438 439
    return call;
}

440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
void
initICE(const std::vector<uint8_t> &msg, const std::shared_ptr<IceTransport> &ice,
        const std::shared_ptr<IceTransport> &ice_tcp, bool &udp_failed, bool &tcp_failed)
{
    auto sdp_list = IceTransport::parseSDPList(msg);
    for (const auto &sdp : sdp_list) {
        if (sdp.candidates.size() > 0) {
            if (sdp.candidates[0].find("TCP") != std::string::npos) {
                // It is a SDP for the TCP component
                tcp_failed = (ice_tcp && !ice_tcp->start(sdp));
            } else {
                // For UDP
                udp_failed = (ice && !ice->start(sdp));
            }
        }
    }

    // During the ICE reply we can start the ICE negotiation
    if (tcp_failed && ice_tcp) {
        ice_tcp->stop();
        JAMI_WARN("ICE over TCP not started, will only use UDP");
    }
}

464
void
465
JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri)
466
{
467 468 469 470
    if (not accountManager_) {
        call->onFailure(ENETDOWN);
        return;
    }
471
    // TODO: for now, we automatically trust all explicitly called peers
472
    setCertificateStatus(toUri, tls::TrustStore::PermissionStatus::ALLOWED);
473

474
    call->setPeerNumber(toUri + "@ring.dht");
Philippe Gorley's avatar
Philippe Gorley committed
475
    call->setPeerUri(RING_URI_PREFIX + toUri);
476
    call->setState(Call::ConnectionState::TRYING);
477
    std::weak_ptr<SIPCall> wCall = call;
478

479
#if HAVE_RINGNS
480
    accountManager_->lookupAddress(toUri, [wCall](const std::string& result, const NameDirectory::Response& response){
481
        if (response == NameDirectory::Response::found)
Philippe Gorley's avatar
Philippe Gorley committed
482
            if (auto call = wCall.lock()) {
483
                call->setPeerRegistredName(result);
Philippe Gorley's avatar
Philippe Gorley committed
484 485
                call->setPeerUri(RING_URI_PREFIX + result);
            }
486 487 488
    });
#endif

489
    dht::InfoHash peer_account(toUri);
490
    auto sendDhtRequest = [this, wCall, toUri, peer_account](const std::string& deviceId) {
491 492
        auto call = wCall.lock();
        if (not call) return;
493
        JAMI_DBG("[call %s] calling device %s", call->getCallId().c_str(), deviceId.c_str());
494

495
        auto& manager = Manager::instance();
496
        auto dev_call = manager.callFactory.newCall<SIPCall, JamiAccount>(*this, manager.getNewCallID(),
497 498
                                                                          Call::CallType::OUTGOING,
                                                                          call->getDetails());
499 500 501

        auto callId = dev_call->getCallId();
        auto onNegoDone = [callId, w=weak()](bool) {
502 503 504 505
            runOnMainThread([callId, w]() {
                if (auto shared = w.lock())
                    shared->checkPendingCall(callId);
            });
506 507
        };

508
        std::weak_ptr<SIPCall> weak_dev_call = dev_call;
509 510
        auto iceOptions = getIceOptions();
        iceOptions.onNegoDone = onNegoDone;
511
        dev_call->setIPToIP(true);
512 513
        dev_call->setSecure(isTlsEnabled());
        auto ice = createIceTransport(("sip:" + dev_call->getCallId()).c_str(),
514
                                             ICE_COMPONENTS, true, iceOptions);
515
        if (not ice) {
516
            JAMI_WARN("[call %s] Can't create ICE", call->getCallId().c_str());
517 518 519
            dev_call->removeCall();
            return;
        }
520

521 522
        iceOptions.tcpEnable = true;
        auto ice_tcp = createIceTransport(("sip:" + dev_call->getCallId()).c_str(), ICE_COMPONENTS, true, iceOptions);
523 524 525
        if (not ice_tcp) {
            JAMI_WARN("Can't create ICE over TCP, will only use UDP");
        }
526
        call->addSubCall(*dev_call);
527

528
        manager.addTask([w=weak(), weak_dev_call, ice, ice_tcp, deviceId, toUri, peer_account] {
529
            auto sthis = w.lock();
530 531
            if (not sthis) {
                dht::ThreadPool::io().run([ice=std::move(ice), ice_tcp=std::move(ice_tcp)](){});
532
                return false;
533
            }
534
            auto call = weak_dev_call.lock();
535

536
            // call aborted?
537 538
            if (not call) {
                dht::ThreadPool::io().run([ice=std::move(ice), ice_tcp=std::move(ice_tcp)](){});
539
                return false;
540
            }
541

542
            if (ice->isFailed()) {
Adrien Béraud's avatar
Adrien Béraud committed
543
                JAMI_ERR("[call:%s] ice init failed", call->getCallId().c_str());
544
                call->onFailure(EIO);
545
                dht::ThreadPool::io().run([ice=std::move(ice), ice_tcp=std::move(ice_tcp)](){});
546 547
                return false;
            }
548

549 550 551 552
            if (ice_tcp && ice_tcp->isFailed()) {
                JAMI_WARN("[call:%s] ice tcp init failed, will only use UDP", call->getCallId().c_str());
            }

553 554 555
            // Loop until ICE transport is initialized.
            // Note: we suppose that ICE init routine has a an internal timeout (bounded in time)
            // and we let upper layers decide when the call shall be aborded (our first check upper).
556
            if ((not ice->isInitialized()) || (ice_tcp && !ice_tcp->isInitialized()))
557 558 559
                return true;

            sthis->registerDhtAddress(*ice);
560
            if (ice_tcp) sthis->registerDhtAddress(*ice_tcp);
561
            // Next step: sent the ICE data to peer through DHT
562
            const dht::Value::Id callvid  = ValueIdDist()(sthis->rand);
563
            const auto callkey = dht::InfoHash::get("callto:" + deviceId);
564 565 566 567 568 569
            auto blob = ice->packIceMsg();
            if (ice_tcp)  {
                auto ice_tcp_msg = ice_tcp->packIceMsg(2);
                blob.insert(blob.end(), ice_tcp_msg.begin(), ice_tcp_msg.end());
            }
            dht::Value val { dht::IceCandidates(callvid,  blob) };
570

571
            dht::InfoHash dev(deviceId);
572
            sthis->dht_->putEncrypted(
573 574
                callkey, dev,
                std::move(val),
575
                [weak_dev_call](bool ok) { // Put complete callback
576
                    if (!ok) {
Adrien Béraud's avatar
Adrien Béraud committed
577
                        JAMI_WARN("Can't put ICE descriptor on DHT");
578 579 580
                        if (auto call = weak_dev_call.lock())
                            call->onFailure();
                    } else
Adrien Béraud's avatar
Adrien Béraud committed
581
                        JAMI_DBG("Successfully put ICE descriptor on DHT");
582 583 584
                }
            );

585
            auto listenKey = sthis->dht_->listen<dht::IceCandidates>(
586
                callkey,
587 588
                [weak_dev_call, ice, ice_tcp, callvid, deviceId] (dht::IceCandidates&& msg) {
                    if (msg.id != callvid or msg.from.toString() != deviceId)
589
                        return true;
Sébastien Blin's avatar
Sébastien Blin committed
590 591 592
                    auto call = weak_dev_call.lock();
                    if (!call)
                        return false;
593 594 595 596 597
                    // remove unprintable characters
                    auto iceData = std::string(msg.ice_data.cbegin(), msg.ice_data.cend());
                    iceData.erase(std::remove_if(iceData.begin(), iceData.end(),
                                                 [](unsigned char c){ return !std::isprint(c) && !std::isspace(c); }
                                                ), iceData.end());
Sébastien Blin's avatar
Sébastien Blin committed
598 599
                    JAMI_WARN("ICE request for call %s replied from DHT peer %s\nData: %s", call->getCallId().c_str(), deviceId.c_str(), iceData.c_str());
                    call->setState(Call::ConnectionState::PROGRESSING);
600

Sébastien Blin's avatar
Sébastien Blin committed
601 602 603 604 605
                    auto udp_failed = true, tcp_failed = true;
                    initICE(msg.ice_data, ice, ice_tcp, udp_failed, tcp_failed);
                    if (udp_failed && tcp_failed) {
                        call->onFailure();
                        return true;
606
                    }
607 608 609 610
                    return false;
                }
            );

611
            std::lock_guard<std::mutex> lock(sthis->callsMutex_);
612
            sthis->pendingCalls_.emplace(call->getCallId(), PendingCall {
613
                std::chrono::steady_clock::now(),
614
                std::move(ice), std::move(ice_tcp), weak_dev_call,
615
                std::move(listenKey),
616 617 618
                callkey,
                dev,
                peer_account,
619
                tls::CertificateStore::instance().getCertificate(toUri)
620
            });
621 622 623 624 625

            Manager::instance().scheduleTask([w, callId=call->getCallId()]() {
                if (auto shared = w.lock())
                    shared->checkPendingCall(callId);
            }, std::chrono::steady_clock::now() + ICE_NEGOTIATION_TIMEOUT);
626
            return false;
627
        });
628 629 630 631 632
    };

    // Call connected devices
    std::set<std::string> devices;
    std::unique_lock<std::mutex> lk(sipConnectionsMtx_);
633
    auto& sipConns = sipConnections_[toUri];
634 635 636 637 638 639 640 641 642
    // NOTE: dummyCall is a call used to avoid to mark the call as failed if the
    // cached connection is failing with ICE (close event still not detected).
    auto& manager = Manager::instance();
    auto dummyCall = manager.callFactory.newCall<SIPCall, JamiAccount>(*this, manager.getNewCallID(),
                                                                            Call::CallType::OUTGOING,
                                                                            call->getDetails());
    dummyCall->setIPToIP(true);
    dummyCall->setSecure(isTlsEnabled());
    call->addSubCall(*dummyCall);
643
    for (auto deviceConnIt = sipConns.begin(); deviceConnIt != sipConns.end(); ++deviceConnIt) {
644 645 646 647 648 649 650 651 652 653
        if (deviceConnIt->second.empty()) continue;
        auto& it = deviceConnIt->second.back();

        auto transport = it.transport;
        if (!it.channel->underlyingICE()) {
            JAMI_WARN("A SIP transport exists without Channel, this is a bug. Please report");
            continue;
        }
        if (!transport) continue;

Sébastien Blin's avatar
Sébastien Blin committed
654 655
        JAMI_WARN("[call %s] A channeled socket is detected with this peer.", call->getCallId().c_str());

656 657 658 659 660 661 662
        auto dev_call = manager.callFactory.newCall<SIPCall, JamiAccount>(*this, manager.getNewCallID(),
                                                                          Call::CallType::OUTGOING,
                                                                          call->getDetails());
        dev_call->setIPToIP(true);
        dev_call->setSecure(isTlsEnabled());
        dev_call->setTransport(transport);
        call->addSubCall(*dev_call);
663 664 665 666 667
        // Set the call in PROGRESSING State because the ICE session
        // is already ready. Note that this line should be after
        // addSubcall() to change the state of the main call
        // and avoid to get an active call in a TRYING state.
        dev_call->setState(Call::ConnectionState::PROGRESSING);
668 669

        auto remoted_address = it.channel->underlyingICE()->getRemoteAddress(ICE_COMP_SIP_TRANSPORT);
670 671 672 673 674 675 676 677 678 679
        try {
            onConnectedOutgoingCall(*dev_call, toUri, remoted_address);
        } catch (const VoipLinkException&) {
            // In this case, the main scenario is that SIPStartCall failed because
            // the ICE is dead and the TLS session didn't send any packet on that dead
            // link (connectivity change, killed by the os, etc)
            // Here, we don't need to do anything, the TLS will fail and will delete
            // the cached transport
            continue;
        }
680 681 682 683 684 685 686 687
        devices.emplace(deviceConnIt->first);

        call->setOnNeedFallback([sendDhtRequest, deviceId = deviceConnIt->first]() {
            sendDhtRequest(deviceId);
        });
    }

    // Find listening devices for this account
Sébastien Blin's avatar
Sébastien Blin committed
688
    accountManager_->forEachDevice(peer_account, [this, toUri, devices, sendDhtRequest, callId=call->getCallId()](const dht::InfoHash& dev)
689 690 691 692
    {
        // Test if already sent via a SIP transport
        if (devices.find(dev.toString()) != devices.end()) return;

Sébastien Blin's avatar
Sébastien Blin committed
693
        JAMI_WARN("[call %s] No channeled socket with this peer. Send request + DHT request", callId.c_str());
694 695 696 697
        // Else, ask for a channel (for future calls/text messages) and send a DHT message
        requestSIPConnection(toUri, dev.toString());

        sendDhtRequest(dev.toString());
698 699 700
    }, [wCall, dummyCall] (bool ok) {
        // Mark the temp call as failed to stop the main call if necessary
        if (dummyCall) dummyCall->onFailure(static_cast<int>(std::errc::no_such_device_or_address));
701
        if (not ok) {
702
            if (auto call = wCall.lock()) {
Adrien Béraud's avatar
Adrien Béraud committed
703
                JAMI_WARN("[call:%s] no devices found", call->getCallId().c_str());
704 705
                call->onFailure(static_cast<int>(std::errc::no_such_device_or_address));
            }
706
        }
707
    });
708

Adrien Béraud's avatar
Adrien Béraud committed
709 710 711
}

void
712
JamiAccount::onConnectedOutgoingCall(SIPCall& call, const std::string& to_id, IpAddr target)
Adrien Béraud's avatar
Adrien Béraud committed
713
{
Adrien Béraud's avatar
Adrien Béraud committed
714
    JAMI_DBG("[call:%s] outgoing call connected to %s", call.getCallId().c_str(), to_id.c_str());
715 716 717

    call.initIceMediaTransport(true);
    call.setIPToIP(true);
718
    call.setPeerNumber(to_id);
Adrien Béraud's avatar
Adrien Béraud committed
719

720
    const auto localAddress = ip_utils::getInterfaceAddr(getLocalInterface(), target.getFamily());
Adrien Béraud's avatar
Adrien Béraud committed
721

Stepan Salenikovich's avatar
Stepan Salenikovich committed
722
    IpAddr addrSdp;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
723
    if (getUPnPActive()) {
Stepan Salenikovich's avatar
Stepan Salenikovich committed
724 725 726 727 728 729 730
        /* use UPnP addr, or published addr if its set */
        addrSdp = getPublishedSameasLocal() ?
            getUPnPIpAddress() : getPublishedIpAddress();
    } else {
        addrSdp = isStunEnabled() or (not getPublishedSameasLocal()) ?
            getPublishedIpAddress() : localAddress;
    }
Adrien Béraud's avatar
Adrien Béraud committed
731

732 733 734
    /* fallback on local address */
    if (not addrSdp) addrSdp = localAddress;

Adrien Béraud's avatar
Adrien Béraud committed
735 736 737
    // 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
Adrien Béraud's avatar
Adrien Béraud committed
738
    if (!getSystemCodecContainer()->searchCodecByName("PCMA", jami::MEDIA_AUDIO))
Adrien Béraud's avatar
Adrien Béraud committed
739 740 741
        throw VoipLinkException("Could not instantiate codec for early media");

    // Building the local SDP offer
742
    auto& sdp = call.getSDP();
Adrien Béraud's avatar
Adrien Béraud committed
743

Stepan Salenikovich's avatar
Stepan Salenikovich committed
744
    sdp.setPublishedIP(addrSdp);
Guillaume Roguez's avatar
Guillaume Roguez committed
745
    const bool created = sdp.createOffer(
746
                            getActiveAccountCodecInfoList(MEDIA_AUDIO),
747 748
                            getActiveAccountCodecInfoList(videoEnabled_ and not call.isAudioOnly() ? MEDIA_VIDEO
                                                                                                   : MEDIA_NONE),
Guillaume Roguez's avatar
Guillaume Roguez committed
749 750
                            getSrtpKeyExchange()
                         );
Adrien Béraud's avatar
Adrien Béraud committed
751

752
    if (not created or not SIPStartCall(call, target))
Adrien Béraud's avatar
Adrien Béraud committed
753 754 755 756
        throw VoipLinkException("Could not send outgoing INVITE request for new call");
}

std::shared_ptr<Call>
757
JamiAccount::newOutgoingCall(const std::string& toUrl, const std::map<std::string, std::string>& volatileCallDetails)
Adrien Béraud's avatar
Adrien Béraud committed
758
{
759
    return newOutgoingCall<SIPCall>(toUrl, volatileCallDetails);
Adrien Béraud's avatar
Adrien Béraud committed
760 761 762
}

bool
763
JamiAccount::SIPStartCall(SIPCall& call, IpAddr target)
Adrien Béraud's avatar
Adrien Béraud committed
764
{
765
    call.setupLocalSDPFromIce();
766
    std::string toUri(getToUri(call.getPeerNumber()+"@"+target.toString(true).c_str())); // expecting a fully well formed sip uri
Adrien Béraud's avatar
Adrien Béraud committed
767 768 769 770 771 772 773

    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());

774 775 776
    std::string targetStr = getToUri(target.toString(true)/*+";transport=ICE"*/);
    pj_str_t pjTarget = pj_str((char*) targetStr.c_str());

777 778
    pj_str_t pjContact;
    {
779
        auto transport = call.getTransport();
780 781
        pjContact = getContactHeader(transport ? transport->get() : nullptr);
    }
Adrien Béraud's avatar
Adrien Béraud committed
782

Adrien Béraud's avatar
Adrien Béraud committed
783
    JAMI_DBG("contact header: %.*s / %s -> %s / %.*s",
784 785
             (int)pjContact.slen, pjContact.ptr, from.c_str(), toUri.c_str(),
             (int)pjTarget.slen, pjTarget.ptr);
Adrien Béraud's avatar
Adrien Béraud committed
786

787
    auto local_sdp = call.getSDP().getLocalSdpSession();
788 789 790
    pjsip_dialog* dialog {nullptr};
    pjsip_inv_session* inv {nullptr};
    if (!CreateClientDialogAndInvite(&pjFrom, &pjContact, &pjTo, &pjTarget, local_sdp, &dialog, &inv))
Adrien Béraud's avatar
Adrien Béraud committed
791 792
        return false;

793
    inv->mod_data[link_.getModId()] = &call;
794
    call.inv.reset(inv);
Adrien Béraud's avatar
Adrien Béraud committed
795 796 797 798 799 800 801 802 803

/*
    updateDialogViaSentBy(dialog);
    if (hasServiceRoute())
        pjsip_dlg_set_route_set(dialog, sip_utils::createRouteSet(getServiceRoute(), call->inv->pool));
*/

    pjsip_tx_data *tdata;

804
    if (pjsip_inv_invite(call.inv.get(), &tdata) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
805
        JAMI_ERR("Could not initialize invite messager for this call");
Adrien Béraud's avatar
Adrien Béraud committed
806 807 808
        return false;
    }

809 810
    pjsip_tpselector tp_sel;
    tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
811 812 813 814
    if (!call.getTransport()) {
        JAMI_ERR("Could not get transport for this call");
        return false;
    }
815
    tp_sel.u.transport = call.getTransport()->get();
Adrien Béraud's avatar
Adrien Béraud committed
816
    if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
817
        JAMI_ERR("Unable to associate transport for invite session dialog");
Adrien Béraud's avatar
Adrien Béraud committed
818 819 820
        return false;
    }

Adrien Béraud's avatar
Adrien Béraud committed
821
    JAMI_DBG("[call:%s] Sending SIP invite", call.getCallId().c_str());
822
    if (pjsip_inv_send_msg(call.inv.get(), tdata) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
823
        JAMI_ERR("Unable to send invite message for this call");
Adrien Béraud's avatar
Adrien Béraud committed
824 825 826
        return false;
    }

827
    call.setState(Call::CallState::ACTIVE, Call::ConnectionState::PROGRESSING);
Adrien Béraud's avatar
Adrien Béraud committed
828 829 830 831

    return true;
}

832 833 834 835 836 837 838 839
void JamiAccount::saveConfig() const
{
    try {
        YAML::Emitter accountOut;
        serialize(accountOut);
        auto accountConfig = getPath() + DIR_SEPARATOR_STR + "config.yml";

        std::lock_guard<std::mutex> lock(fileutils::getFileLock(accountConfig));
840
        std::ofstream fout = fileutils::ofstream(accountConfig);
841 842 843 844 845 846 847
        fout << accountOut.c_str();
        JAMI_DBG("Exported account to %s", accountConfig.c_str());
    } catch (const std::exception& e) {
        JAMI_ERR("Error exporting account: %s", e.what());
    }
}

848
void JamiAccount::serialize(YAML::Emitter &out) const
Adrien Béraud's avatar
Adrien Béraud committed
849
{
850 851
    std::lock_guard<std::mutex> lock(configurationMutex_);

852 853 854
    if (r