jamiaccount.cpp 149 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");

Sébastien Blin's avatar
Sébastien Blin committed
107
108
struct PendingConfirmation
{
109
110
111
112
113
114
    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)
Sébastien Blin's avatar
Sébastien Blin committed
115
116
struct TextMessageCtx
{
117
118
119
120
121
122
123
124
    std::weak_ptr<JamiAccount> acc;
    std::string to;
    std::string deviceId;
    uint64_t id;
    bool retryOnTimeout;
    std::shared_ptr<PendingConfirmation> confirmation;
};

Sébastien Blin's avatar
Sébastien Blin committed
125
126
struct VCardMessageCtx
{
127
128
129
130
131
    std::shared_ptr<std::atomic_int> success;
    int total;
    std::string path;
};

Nicolas Jager's avatar
Nicolas Jager committed
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)
{
Sébastien Blin's avatar
Sébastien Blin committed
142
143
144
#define CASE_STATE(X) \
    case Migration::State::X: \
        return #X
Nicolas Jager's avatar
Nicolas Jager committed
145

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

void
Sébastien Blin's avatar
Sébastien Blin committed
154
setState(const std::string& accountID, const State migrationState)
Nicolas Jager's avatar
Nicolas Jager committed
155
156
{
    emitSignal<DRing::ConfigurationSignal::MigrationEnded>(accountID,
Sébastien Blin's avatar
Sébastien Blin committed
157
                                                           mapStateNumberToString(migrationState));
Nicolas Jager's avatar
Nicolas Jager committed
158
}
159

Sébastien Blin's avatar
Sébastien Blin committed
160
} // namespace Migration
Nicolas Jager's avatar
Nicolas Jager committed
161

162
struct JamiAccount::BuddyInfo
163
164
165
166
{
    /* the buddy id */
    dht::InfoHash id;

167
168
    /* number of devices connected on the DHT */
    uint32_t devices_cnt {};
169

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

Sébastien Blin's avatar
Sébastien Blin committed
173
174
175
    BuddyInfo(dht::InfoHash id)
        : id(id)
    {}
176
177
};

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

191
struct JamiAccount::PendingMessage
192
{
193
    std::set<dht::InfoHash> to;
194
195
};

196
197
198
199
200
201
202
203
204
205
206
207
208
struct AccountPeerInfo
{
    dht::InfoHash accountId;
    std::string displayName;
    MSGPACK_DEFINE(accountId, displayName)
};

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

209
210
static constexpr int ICE_COMPONENTS {1};
static constexpr int ICE_COMP_SIP_TRANSPORT {0};
211
static constexpr auto ICE_NEGOTIATION_TIMEOUT = std::chrono::seconds(60);
212
213
static constexpr auto TLS_TIMEOUT = std::chrono::seconds(30);
const constexpr auto EXPORT_KEY_RENEWAL_TIME = std::chrono::minutes(20);
Sébastien Blin's avatar
Sébastien Blin committed
214
215
216

static constexpr const char* const RING_URI_PREFIX = "ring:";
static constexpr const char* const JAMI_URI_PREFIX = "jami:";
217
static constexpr const char* DEFAULT_TURN_SERVER = "turn.jami.net";
Sébastien Blin's avatar
Sébastien Blin committed
218
static constexpr const char* DEFAULT_TURN_USERNAME = "ring";
219
220
static constexpr const char* DEFAULT_TURN_PWD = "ring";
static constexpr const char* DEFAULT_TURN_REALM = "ring";
221
static const auto PROXY_REGEX = std::regex("(https?://)?([\\w\\.]+)(:(\\d+)|:\\[(.+)-(.+)\\])?");
222
static const std::string PEER_DISCOVERY_JAMI_SERVICE = "jami";
223
const constexpr auto PEER_DISCOVERY_EXPIRATION = std::chrono::minutes(1);
224

225
226
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
227

228
using ValueIdDist = std::uniform_int_distribution<dht::Value::Id>;
229

230
static const std::string
231
stripPrefix(const std::string& toUrl)
232
{
233
    auto dhtf = toUrl.find(RING_URI_PREFIX);
234
    if (dhtf != std::string::npos) {
Sébastien Blin's avatar
Sébastien Blin committed
235
        dhtf = dhtf + 5;
236
    } else {
237
238
        dhtf = toUrl.find(JAMI_URI_PREFIX);
        if (dhtf != std::string::npos) {
Sébastien Blin's avatar
Sébastien Blin committed
239
            dhtf = dhtf + 5;
240
241
        } else {
            dhtf = toUrl.find("sips:");
Sébastien Blin's avatar
Sébastien Blin committed
242
            dhtf = (dhtf == std::string::npos) ? 0 : dhtf + 5;
243
        }
244
245
246
    }
    while (dhtf < toUrl.length() && toUrl[dhtf] == '/')
        dhtf++;
247
248
    return toUrl.substr(dhtf);
}
249

250
static const std::string
251
parseJamiUri(const std::string& toUrl)
252
253
254
{
    auto sufix = stripPrefix(toUrl);
    if (sufix.length() < 40)
255
        throw std::invalid_argument("id must be a Jami infohash");
256

257
    const std::string toUri = sufix.substr(0, 40);
258
    if (std::find_if_not(toUri.cbegin(), toUri.cend(), ::isxdigit) != toUri.cend())
259
        throw std::invalid_argument("id must be a Jami infohash");
260
261
262
    return toUri;
}

263
static constexpr const char*
Sébastien Blin's avatar
Sébastien Blin committed
264
265
266
267
268
dhtStatusStr(dht::NodeStatus status)
{
    return status == dht::NodeStatus::Connected
               ? "connected"
               : (status == dht::NodeStatus::Connecting ? "connecting" : "disconnected");
269
270
}

271
272
273
/**
 * Local ICE Transport factory helper
 *
274
 * JamiAccount must use this helper than direct IceTranportFactory API
275
 */
Sébastien Blin's avatar
Sébastien Blin committed
276
template<class... Args>
277
std::shared_ptr<IceTransport>
278
JamiAccount::createIceTransport(const Args&... args)
279
280
{
    auto ice = Manager::instance().getIceTransportFactory().createTransport(args...);
281
    if (!ice)
282
283
284
285
286
        throw std::runtime_error("ICE transport creation failed");

    return ice;
}

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

303
    proxyListUrl_ = DHT_DEFAULT_PROXY_LIST_URL;
304
    proxyServer_ = DHT_DEFAULT_PROXY;
305

306
    try {
Sébastien Blin's avatar
Sébastien Blin committed
307
308
        std::istringstream is(fileutils::loadCacheTextFile(cachePath_ + DIR_SEPARATOR_STR "dhtproxy",
                                                           std::chrono::hours(24 * 7)));
309
        std::getline(is, proxyServerCached_);
310
311
    } catch (const std::exception& e) {
        JAMI_DBG("Can't load proxy URL from cache: %s", e.what());
312
    }
313
314

    setActiveCodecs({});
315
}
Adrien Béraud's avatar
Adrien Béraud committed
316

317
JamiAccount::~JamiAccount()
Adrien Béraud's avatar
Adrien Béraud committed
318
{
319
    shutdownConnections();
Sébastien Blin's avatar
Sébastien Blin committed
320
    if (peerDiscovery_) {
321
322
323
        peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE);
        peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE);
    }
324
325
    if (auto dht = dht_)
        dht->join();
Adrien Béraud's avatar
Adrien Béraud committed
326
327
}

328
329
330
331
332
void
JamiAccount::shutdownConnections()
{
    connectionManager_.reset();
    dhtPeerConnector_.reset();
333
334
335
    std::lock_guard<std::mutex> lk(sipConnectionsMtx_);
    sipConnections_.clear();
    pendingSipConnections_.clear();
336
337
}

338
void
339
JamiAccount::flush()
340
341
342
343
344
345
{
    // Class base method
    SIPAccountBase::flush();

    fileutils::removeAll(dataPath_);
    fileutils::removeAll(cachePath_);
346
    fileutils::removeAll(idPath_, true);
347
348
}

Adrien Béraud's avatar
Adrien Béraud committed
349
std::shared_ptr<SIPCall>
Sébastien Blin's avatar
Sébastien Blin committed
350
351
352
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
353
{
354
    std::lock_guard<std::mutex> lock(callsMutex_);
355
356
357
358
359
360
361
362

    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
Sébastien Blin's avatar
Sébastien Blin committed
363
364
                    if (it->transport != sipTr)
                        continue;
365

Sébastien Blin's avatar
Sébastien Blin committed
366
367
368
369
                    auto call = Manager::instance().callFactory.newCall<SIPCall, JamiAccount>(
                        *this, Manager::instance().getNewCallID(), Call::CallType::INCOMING);
                    if (!call)
                        return {};
370
371
372
373
374
375
376
377
378
379
380
381
382

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

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

383
384
    auto call_it = pendingSipCalls_.begin();
    while (call_it != pendingSipCalls_.end()) {
385
386
        auto call = call_it->call.lock();
        if (not call) {
Adrien Béraud's avatar
Adrien Béraud committed
387
            JAMI_WARN("newIncomingCall: discarding deleted call");
388
            call_it = pendingSipCalls_.erase(call_it);
Sébastien Blin's avatar
Sébastien Blin committed
389
390
391
        } 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
392
            JAMI_DBG("newIncomingCall: found matching call for %s", from.c_str());
393
            pendingSipCalls_.erase(call_it);
394
            call->updateDetails(details);
395
396
397
            return call;
        } else {
            ++call_it;
398
399
        }
    }
Adrien Béraud's avatar
Adrien Béraud committed
400
    JAMI_ERR("newIncomingCall: can't find matching call for %s", from.c_str());
401
    return nullptr;
Adrien Béraud's avatar
Adrien Béraud committed
402
403
}

Sébastien Blin's avatar
Sébastien Blin committed
404
template<>
Adrien Béraud's avatar
Adrien Béraud committed
405
std::shared_ptr<SIPCall>
406
JamiAccount::newOutgoingCall(const std::string& toUrl,
407
                             const std::map<std::string, std::string>& volatileCallDetails)
Adrien Béraud's avatar
Adrien Béraud committed
408
{
409
    auto suffix = stripPrefix(toUrl);
Adrien Béraud's avatar
Adrien Béraud committed
410
    JAMI_DBG() << *this << "Calling DHT peer " << suffix;
411
    auto& manager = Manager::instance();
412
    auto call = manager.callFactory.newCall<SIPCall, JamiAccount>(*this,
Sébastien Blin's avatar
Sébastien Blin committed
413
                                                                  manager.getNewCallID(),
414
415
                                                                  Call::CallType::OUTGOING,
                                                                  volatileCallDetails);
416

417
    call->setIPToIP(true);
Adrien Béraud's avatar
Adrien Béraud committed
418
    call->setSecure(isTlsEnabled());
419
420

    try {
421
        const std::string toUri = parseJamiUri(suffix);
422
423
424
        startOutgoingCall(call, toUri);
    } catch (...) {
#if HAVE_RINGNS
Sébastien Blin's avatar
Sébastien Blin committed
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
        NameDirectory::lookupUri(suffix,
                                 nameServer_,
                                 [wthis_ = weak(), call](const std::string& result,
                                                         NameDirectory::Response response) {
                                     // we may run inside an unknown thread, but following code must
                                     // be called in main thread
                                     runOnMainThread([wthis_, result, response, call]() {
                                         if (response != NameDirectory::Response::found) {
                                             call->onFailure(EINVAL);
                                             return;
                                         }
                                         if (auto sthis = wthis_.lock()) {
                                             try {
                                                 const std::string toUri = parseJamiUri(result);
                                                 sthis->startOutgoingCall(call, toUri);
                                             } catch (...) {
                                                 call->onFailure(ENOENT);
                                             }
                                         } else {
                                             call->onFailure();
                                         }
                                     });
                                 });
448
449
450
451
#else
        call->onFailure(ENOENT);
#endif
    }
452

453
454
455
    return call;
}

456
void
Sébastien Blin's avatar
Sébastien Blin committed
457
458
459
460
461
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)
462
463
{
    auto sdp_list = IceTransport::parseSDPList(msg);
Sébastien Blin's avatar
Sébastien Blin committed
464
    for (const auto& sdp : sdp_list) {
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
        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");
    }
}

483
void
484
JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri)
485
{
486
    if (not accountManager_ or not dht_) {
487
488
489
        call->onFailure(ENETDOWN);
        return;
    }
490
    // TODO: for now, we automatically trust all explicitly called peers
491
    setCertificateStatus(toUri, tls::TrustStore::PermissionStatus::ALLOWED);
492

493
    call->setPeerNumber(toUri + "@ring.dht");
Philippe Gorley's avatar
Philippe Gorley committed
494
    call->setPeerUri(RING_URI_PREFIX + toUri);
495
    call->setState(Call::ConnectionState::TRYING);
496
    std::weak_ptr<SIPCall> wCall = call;
497

498
#if HAVE_RINGNS
Sébastien Blin's avatar
Sébastien Blin committed
499
500
501
502
503
504
505
506
507
    accountManager_->lookupAddress(toUri,
                                   [wCall](const std::string& result,
                                           const NameDirectory::Response& response) {
                                       if (response == NameDirectory::Response::found)
                                           if (auto call = wCall.lock()) {
                                               call->setPeerRegistredName(result);
                                               call->setPeerUri(RING_URI_PREFIX + result);
                                           }
                                   });
508
509
#endif

510
    dht::InfoHash peer_account(toUri);
511
    auto sendDhtRequest = [this, wCall, toUri, peer_account](const std::string& deviceId) {
512
        auto call = wCall.lock();
Sébastien Blin's avatar
Sébastien Blin committed
513
514
        if (not call)
            return;
515
        JAMI_DBG("[call %s] calling device %s", call->getCallId().c_str(), deviceId.c_str());
516

517
        auto& manager = Manager::instance();
Sébastien Blin's avatar
Sébastien Blin committed
518
519
        auto dev_call = manager.callFactory.newCall<SIPCall, JamiAccount>(*this,
                                                                          manager.getNewCallID(),
520
521
                                                                          Call::CallType::OUTGOING,
                                                                          call->getDetails());
522

523
        auto callId = dev_call->getCallId();
Sébastien Blin's avatar
Sébastien Blin committed
524
        auto onNegoDone = [callId, w = weak()](bool) {
525
526
527
528
            runOnMainThread([callId, w]() {
                if (auto shared = w.lock())
                    shared->checkPendingCall(callId);
            });
529
530
        };

531
        std::weak_ptr<SIPCall> weak_dev_call = dev_call;
532
533
        auto iceOptions = getIceOptions();
        iceOptions.onNegoDone = onNegoDone;
534
        dev_call->setIPToIP(true);
535
536
        dev_call->setSecure(isTlsEnabled());
        auto ice = createIceTransport(("sip:" + dev_call->getCallId()).c_str(),
Sébastien Blin's avatar
Sébastien Blin committed
537
538
539
                                      ICE_COMPONENTS,
                                      true,
                                      iceOptions);
540
        if (not ice) {
541
            JAMI_WARN("[call %s] Can't create ICE", call->getCallId().c_str());
542
543
544
            dev_call->removeCall();
            return;
        }
545

546
        iceOptions.tcpEnable = true;
547
        auto ice_tcp = createIceTransport(("sip:" + dev_call->getCallId()).c_str(),
Sébastien Blin's avatar
Sébastien Blin committed
548
549
550
                                          ICE_COMPONENTS,
                                          true,
                                          iceOptions);
551
552
553
        if (not ice_tcp) {
            JAMI_WARN("Can't create ICE over TCP, will only use UDP");
        }
554
        call->addSubCall(*dev_call);
555

Sébastien Blin's avatar
Sébastien Blin committed
556
        manager.addTask([w = weak(), weak_dev_call, ice, ice_tcp, deviceId, toUri, peer_account] {
557
            auto sthis = w.lock();
558
            if (not sthis) {
Sébastien Blin's avatar
Sébastien Blin committed
559
                dht::ThreadPool::io().run([ice = std::move(ice), ice_tcp = std::move(ice_tcp)]() {});
560
                return false;
561
            }
562
            auto call = weak_dev_call.lock();
563

564
            // call aborted?
565
            if (not call) {
Sébastien Blin's avatar
Sébastien Blin committed
566
                dht::ThreadPool::io().run([ice = std::move(ice), ice_tcp = std::move(ice_tcp)]() {});
567
                return false;
568
            }
569

570
            if (ice->isFailed()) {
Adrien Béraud's avatar
Adrien Béraud committed
571
                JAMI_ERR("[call:%s] ice init failed", call->getCallId().c_str());
572
                call->onFailure(EIO);
Sébastien Blin's avatar
Sébastien Blin committed
573
                dht::ThreadPool::io().run([ice = std::move(ice), ice_tcp = std::move(ice_tcp)]() {});
574
575
                return false;
            }
576

577
            if (ice_tcp && ice_tcp->isFailed()) {
Sébastien Blin's avatar
Sébastien Blin committed
578
579
                JAMI_WARN("[call:%s] ice tcp init failed, will only use UDP",
                          call->getCallId().c_str());
580
581
            }

582
583
584
            // 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).
585
            if ((not ice->isInitialized()) || (ice_tcp && !ice_tcp->isInitialized()))
586
587
588
                return true;

            sthis->registerDhtAddress(*ice);
Sébastien Blin's avatar
Sébastien Blin committed
589
590
            if (ice_tcp)
                sthis->registerDhtAddress(*ice_tcp);
591
            // Next step: sent the ICE data to peer through DHT
Sébastien Blin's avatar
Sébastien Blin committed
592
            const dht::Value::Id callvid = ValueIdDist()(sthis->rand);
593
594
            const auto callkey = dht::InfoHash::get("callto:" + deviceId);
            auto blob = ice->packIceMsg();
Sébastien Blin's avatar
Sébastien Blin committed
595
            if (ice_tcp) {
596
597
598
                auto ice_tcp_msg = ice_tcp->packIceMsg(2);
                blob.insert(blob.end(), ice_tcp_msg.begin(), ice_tcp_msg.end());
            }
Sébastien Blin's avatar
Sébastien Blin committed
599
            dht::Value val {dht::IceCandidates(callvid, blob)};
600

601
            dht::InfoHash dev(deviceId);
Sébastien Blin's avatar
Sébastien Blin committed
602
603
604
605
606
607
608
609
610
611
612
            sthis->dht_->putEncrypted(callkey,
                                      dev,
                                      std::move(val),
                                      [weak_dev_call](bool ok) { // Put complete callback
                                          if (!ok) {
                                              JAMI_WARN("Can't put ICE descriptor on DHT");
                                              if (auto call = weak_dev_call.lock())
                                                  call->onFailure();
                                          } else
                                              JAMI_DBG("Successfully put ICE descriptor on DHT");
                                      });
613

614
            auto listenKey = sthis->dht_->listen<dht::IceCandidates>(
Sébastien Blin's avatar
Sébastien Blin committed
615
                callkey, [weak_dev_call, ice, ice_tcp, callvid, deviceId](dht::IceCandidates&& msg) {
616
                    if (msg.id != callvid or msg.from.toString() != deviceId)
617
                        return true;
Sébastien Blin's avatar
Sébastien Blin committed
618
619
620
                    auto call = weak_dev_call.lock();
                    if (!call)
                        return false;
621
622
                    // remove unprintable characters
                    auto iceData = std::string(msg.ice_data.cbegin(), msg.ice_data.cend());
Sébastien Blin's avatar
Sébastien Blin committed
623
624
625
626
627
628
629
630
631
632
                    iceData.erase(std::remove_if(iceData.begin(),
                                                 iceData.end(),
                                                 [](unsigned char c) {
                                                     return !std::isprint(c) && !std::isspace(c);
                                                 }),
                                  iceData.end());
                    JAMI_WARN("ICE request for call %s replied from DHT peer %s\nData: %s",
                              call->getCallId().c_str(),
                              deviceId.c_str(),
                              iceData.c_str());
Sébastien Blin's avatar
Sébastien Blin committed
633
                    call->setState(Call::ConnectionState::PROGRESSING);
634

Sébastien Blin's avatar
Sébastien Blin committed
635
636
637
638
639
                    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;
640
                    }
641
                    return false;
Sébastien Blin's avatar
Sébastien Blin committed
642
                });
643

644
            std::lock_guard<std::mutex> lock(sthis->callsMutex_);
Sébastien Blin's avatar
Sébastien Blin committed
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
            sthis->pendingCalls_
                .emplace(call->getCallId(),
                         PendingCall {std::chrono::steady_clock::now(),
                                      std::move(ice),
                                      std::move(ice_tcp),
                                      weak_dev_call,
                                      std::move(listenKey),
                                      callkey,
                                      dev,
                                      peer_account,
                                      tls::CertificateStore::instance().getCertificate(toUri)});

            Manager::instance().scheduleTask(
                [w, callId = call->getCallId()]() {
                    if (auto shared = w.lock())
                        shared->checkPendingCall(callId);
                },
                std::chrono::steady_clock::now() + ICE_NEGOTIATION_TIMEOUT);
663
            return false;
664
        });
665
666
667
668
669
    };

    // Call connected devices
    std::set<std::string> devices;
    std::unique_lock<std::mutex> lk(sipConnectionsMtx_);
670
    auto& sipConns = sipConnections_[toUri];
671
672
    // 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).
673
    auto& manager = Manager::instance();
Sébastien Blin's avatar
Sébastien Blin committed
674
675
676
677
    auto dummyCall = manager.callFactory.newCall<SIPCall, JamiAccount>(*this,
                                                                       manager.getNewCallID(),
                                                                       Call::CallType::OUTGOING,
                                                                       call->getDetails());
678
679
680
    dummyCall->setIPToIP(true);
    dummyCall->setSecure(isTlsEnabled());
    call->addSubCall(*dummyCall);
681
    for (auto deviceConnIt = sipConns.begin(); deviceConnIt != sipConns.end(); ++deviceConnIt) {
Sébastien Blin's avatar
Sébastien Blin committed
682
683
        if (deviceConnIt->second.empty())
            continue;
684
685
686
687
688
689
690
        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;
        }
Sébastien Blin's avatar
Sébastien Blin committed
691
692
        if (!transport)
            continue;
693

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

Sébastien Blin's avatar
Sébastien Blin committed
697
698
        auto dev_call = manager.callFactory.newCall<SIPCall, JamiAccount>(*this,
                                                                          manager.getNewCallID(),
699
700
701
702
703
704
                                                                          Call::CallType::OUTGOING,
                                                                          call->getDetails());
        dev_call->setIPToIP(true);
        dev_call->setSecure(isTlsEnabled());
        dev_call->setTransport(transport);
        call->addSubCall(*dev_call);
705
706
707
708
709
        // 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);
710
711

        auto remoted_address = it.channel->underlyingICE()->getRemoteAddress(ICE_COMP_SIP_TRANSPORT);
712
713
714
715
716
717
718
719
720
721
        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;
        }
722
723
        devices.emplace(deviceConnIt->first);

Sébastien Blin's avatar
Sébastien Blin committed
724
725
        call->setOnNeedFallback(
            [sendDhtRequest, deviceId = deviceConnIt->first]() { sendDhtRequest(deviceId); });
726
727
728
    }

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

Sébastien Blin's avatar
Sébastien Blin committed
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
            JAMI_WARN("[call %s] No channeled socket with this peer. Send request + DHT request",
                      callId.c_str());
            // Else, ask for a channel (for future calls/text messages) and send a DHT message
            requestSIPConnection(toUri, dev.toString());

            sendDhtRequest(dev.toString());
        },
        [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));
            if (not ok) {
                if (auto call = wCall.lock()) {
                    JAMI_WARN("[call:%s] no devices found", call->getCallId().c_str());
                    call->onFailure(static_cast<int>(std::errc::no_such_device_or_address));
                }
            }
        });
Adrien Béraud's avatar
Adrien Béraud committed
755
756
757
}

void
758
JamiAccount::onConnectedOutgoingCall(SIPCall& call, const std::string& to_id, IpAddr target)
Adrien Béraud's avatar
Adrien Béraud committed
759
{
Adrien Béraud's avatar
Adrien Béraud committed
760
    JAMI_DBG("[call:%s] outgoing call connected to %s", call.getCallId().c_str(), to_id.c_str());
761
762
763

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

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

Stepan Salenikovich's avatar
Stepan Salenikovich committed
768
    IpAddr addrSdp;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
769
    if (getUPnPActive()) {
Stepan Salenikovich's avatar
Stepan Salenikovich committed
770
        /* use UPnP addr, or published addr if its set */
Sébastien Blin's avatar
Sébastien Blin committed
771
        addrSdp = getPublishedSameasLocal() ? getUPnPIpAddress() : getPublishedIpAddress();
Stepan Salenikovich's avatar
Stepan Salenikovich committed
772
    } else {
Sébastien Blin's avatar
Sébastien Blin committed
773
774
        addrSdp = isStunEnabled() or (not getPublishedSameasLocal()) ? getPublishedIpAddress()
                                                                     : localAddress;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
775
    }
Adrien Béraud's avatar
Adrien Béraud committed
776

777
    /* fallback on local address */
Sébastien Blin's avatar
Sébastien Blin committed
778
779
    if (not addrSdp)
        addrSdp = localAddress;
780

Adrien Béraud's avatar
Adrien Béraud committed
781
782
783
    // 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
784
    if (!getSystemCodecContainer()->searchCodecByName("PCMA", jami::MEDIA_AUDIO))
Adrien Béraud's avatar
Adrien Béraud committed
785
786
787
        throw VoipLinkException("Could not instantiate codec for early media");

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

Stepan Salenikovich's avatar
Stepan Salenikovich committed
790
    sdp.setPublishedIP(addrSdp);
Sébastien Blin's avatar
Sébastien Blin committed
791
792
793
794
795
    const bool created = sdp.createOffer(getActiveAccountCodecInfoList(MEDIA_AUDIO),
                                         getActiveAccountCodecInfoList(
                                             videoEnabled_ and not call.isAudioOnly() ? MEDIA_VIDEO
                                                                                      : MEDIA_NONE),
                                         getSrtpKeyExchange());
Adrien Béraud's avatar
Adrien Béraud committed
796

797
    if (not created or not SIPStartCall(call, target))
Adrien Béraud's avatar
Adrien Béraud committed
798
799
800
801
        throw VoipLinkException("Could not send outgoing INVITE request for new call");
}

std::shared_ptr<Call>
Sébastien Blin's avatar
Sébastien Blin committed
802
803
JamiAccount::newOutgoingCall(const std::string& toUrl,
                             const std::map<std::string, std::string>& volatileCallDetails)
Adrien Béraud's avatar
Adrien Béraud committed
804
{
805
    return newOutgoingCall<SIPCall>(toUrl, volatileCallDetails);
Adrien Béraud's avatar
Adrien Béraud committed
806
807
808
}

bool
809
JamiAccount::SIPStartCall(SIPCall& call, IpAddr target)
Adrien Béraud's avatar
Adrien Béraud committed
810
{
811
    call.setupLocalSDPFromIce();
Sébastien Blin's avatar
Sébastien Blin committed
812
813
814
    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
815
816
817
818
819
820
821

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

Sébastien Blin's avatar
Sébastien Blin committed
822
    std::string targetStr = getToUri(target.toString(true) /*+";transport=ICE"*/);
823
    pj_str_t pjTarget = pj_str((char*) targetStr.c_str());
824

825
826
    pj_str_t pjContact;
    {
827
        auto transport = call.getTransport();
828
        pjContact = getContactHeader(transport ? transport->get() : nullptr);
829
    }
Adrien Béraud's avatar
Adrien Béraud committed
830

Adrien Béraud's avatar
Adrien Béraud committed
831
    JAMI_DBG("contact header: %.*s / %s -> %s / %.*s",
Sébastien Blin's avatar
Sébastien Blin committed
832
833
834
835
836
837
             (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
838

839
    auto local_sdp = call.getSDP().getLocalSdpSession();
840
841
842
    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
843
844
        return false;

845
    inv->mod_data[link_.getModId()] = &call;
846
    call.inv.reset(inv);
Adrien Béraud's avatar
Adrien Béraud committed
847

Sébastien Blin's avatar
Sébastien Blin committed
848
849
850
851
852
853
    /*
        updateDialogViaSentBy(dialog);
        if (hasServiceRoute())
            pjsip_dlg_set_route_set(dialog, sip_utils::createRouteSet(getServiceRoute(),
       call->inv->pool));
    */
Adrien Béraud's avatar
Adrien Béraud committed
854

Sébastien Blin's avatar
Sébastien Blin committed
855
    pjsip_tx_data* tdata;
Adrien Béraud's avatar
Adrien Béraud committed
856

857
    if (pjsip_inv_invite(call.inv.get(), &tdata) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
858
        JAMI_ERR("Could not initialize invite messager for this call");
Adrien Béraud's avatar
Adrien Béraud committed
859
860
861
        return false;
    }

862
863
    pjsip_tpselector tp_sel;
    tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
864
865
866
867
    if (!call.getTransport()) {
        JAMI_ERR("Could not get transport for this call");
        return false;
    }
868
    tp_sel.u.transport = call.getTransport()->get();
Adrien Béraud's avatar
Adrien Béraud committed
869
    if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
870
        JAMI_ERR("Unable to associate transport for invite session dialog");
Adrien Béraud's avatar
Adrien Béraud committed
871
872
873
        return false;
    }

Adrien Béraud's avatar
Adrien Béraud committed
874
    JAMI_DBG("[call:%s] Sending SIP invite", call.getCallId().c_str());
875
    if (pjsip_inv_send_msg(call.inv.get(), tdata) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
876
        JAMI_ERR("Unable to send invite message for this call");
Adrien Béraud's avatar
Adrien Béraud committed
877
878
879
        return false;
    }

880
    call.setState(Call::CallState::ACTIVE, Call::ConnectionState::PROGRESSING);
Adrien Béraud's avatar
Adrien Béraud committed
881
882
883
884

    return true;
}

Sébastien Blin's avatar
Sébastien Blin committed
885
886
void
JamiAccount::saveConfig() const
887
888
889
890
891
892
893
{
    try {
        YAML::Emitter accountOut;
        serialize(accountOut);
        auto accountConfig = getPath() + DIR_SEPARATOR_STR + "config.yml";

        std::lock_guard<std::mutex> lock(fileutils::getFileLock(accountConfig));
894
        std::ofstream fout = fileutils::ofstream(accountConfig);
895
896
897
898
899
900
901
        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());
    }
}

Sébastien Blin's avatar
Sébastien Blin committed
902
903
void
JamiAccount::serialize(YAML::Emitter& out) const
Adrien Béraud's avatar
Adrien Béraud committed
904
{
905
906
    std::lock_guard<std::mutex> lock(configurationMutex_);

907
908
909
    if (registrationState_ == RegistrationState::INITIALIZING)
        return;

Adrien Béraud's avatar
Adrien Béraud committed
910
911
    out << YAML::BeginMap;
    SIPAccountBase::serialize(out);
Guillaume Roguez's avatar
Guillaume Roguez committed
912
    out << YAML::Key << Conf::DHT_PORT_KEY << YAML::Value << dhtPort_;