jamiaccount.cpp 179 KB
Newer Older
Adrien Béraud's avatar
Adrien Béraud committed
1
/*
Sébastien Blin's avatar
Sébastien Blin committed
2
 *  Copyright (C) 2004-2021 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"
41
#include "conversation_channel_handler.h"
Adrien Béraud's avatar
Adrien Béraud committed
42

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

49
50
#include "ice_transport.h"

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

54
#include "client/ring_signal.h"
55
56
#include "jami/call_const.h"
#include "jami/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

65
66
67
68
69
#ifdef ENABLE_PLUGIN
#include "plugin/jamipluginmanager.h"
#include "plugin/chatservicesmanager.h"
#endif

Adrien Béraud's avatar
Adrien Béraud committed
70
#ifdef ENABLE_VIDEO
71
#include "libav_utils.h"
Adrien Béraud's avatar
Adrien Béraud committed
72
73
#endif
#include "fileutils.h"
74
#include "string_utils.h"
75
#include "archiver.h"
76
#include "data_transfer.h"
77
#include "conversation.h"
Adrien Béraud's avatar
Adrien Béraud committed
78
79

#include "config/yamlparser.h"
80
#include "security/certstore.h"
81
82
#include "libdevcrypto/Common.h"
#include "base64.h"
83
#include "vcard.h"
84
#include "im/instant_messaging.h"
85

Adrien Béraud's avatar
Adrien Béraud committed
86
#include <opendht/thread_pool.h>
87
#include <opendht/peer_discovery.h>
88
89
#include <opendht/http.h>

90
#include <yaml-cpp/yaml.h>
91
92

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

Adrien Béraud's avatar
Adrien Béraud committed
94
95
#include <algorithm>
#include <array>
96
#include <cctype>
Adrien Béraud's avatar
Adrien Béraud committed
97
#include <charconv>
98
99
#include <cinttypes>
#include <cstdarg>
100
101
102
103
#include <initializer_list>
#include <memory>
#include <regex>
#include <sstream>
104
#include <string>
105
#include <system_error>
Adrien Béraud's avatar
Adrien Béraud committed
106

107
108
using namespace std::placeholders;

Adrien Béraud's avatar
Adrien Béraud committed
109
namespace jami {
Guillaume Roguez's avatar
Guillaume Roguez committed
110

111
constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID");
112
113
114
115
116
117
118
119
120
121
static constexpr const char MIME_TYPE_IMDN[] {"message/imdn+xml"};
static constexpr const char MIME_TYPE_IM_COMPOSING[] {"application/im-iscomposing+xml"};
static constexpr const char MIME_TYPE_INVITE[] {"application/invite"};
static constexpr const char MIME_TYPE_INVITE_JSON[] {"application/invite+json"};
static constexpr const char MIME_TYPE_GIT[] {"application/im-gitmessage-id"};
static constexpr const char FILE_URI[] {"file://"};
static constexpr const char VCARD_URI[] {"vcard://"};
static constexpr const char SYNC_URI[] {"sync://"};
static constexpr const char DATA_TRANSFER_URI[] {"data-transfer://"};
static constexpr std::chrono::steady_clock::duration COMPOSING_TIMEOUT {std::chrono::seconds(12)};
122

Sébastien Blin's avatar
Sébastien Blin committed
123
124
struct PendingConfirmation
{
125
126
127
128
129
130
    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
131
132
struct TextMessageCtx
{
133
134
    std::weak_ptr<JamiAccount> acc;
    std::string to;
135
    DeviceId deviceId;
136
137
    uint64_t id;
    bool retryOnTimeout;
138
    std::shared_ptr<ChannelSocket> channel;
Sébastien Blin's avatar
Sébastien Blin committed
139
    bool onlyConnected;
140
141
142
    std::shared_ptr<PendingConfirmation> confirmation;
};

Sébastien Blin's avatar
Sébastien Blin committed
143
144
struct VCardMessageCtx
{
145
146
147
148
149
    std::shared_ptr<std::atomic_int> success;
    int total;
    std::string path;
};

Nicolas Jager's avatar
Nicolas Jager committed
150
151
152
153
154
155
156
157
158
159
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
160
161
162
#define CASE_STATE(X) \
    case Migration::State::X: \
        return #X
Nicolas Jager's avatar
Nicolas Jager committed
163

164
165
166
167
168
    switch (migrationState) {
        CASE_STATE(INVALID);
        CASE_STATE(SUCCESS);
    }
    return {};
Nicolas Jager's avatar
Nicolas Jager committed
169
170
171
}

void
Sébastien Blin's avatar
Sébastien Blin committed
172
setState(const std::string& accountID, const State migrationState)
Nicolas Jager's avatar
Nicolas Jager committed
173
174
{
    emitSignal<DRing::ConfigurationSignal::MigrationEnded>(accountID,
Sébastien Blin's avatar
Sébastien Blin committed
175
                                                           mapStateNumberToString(migrationState));
Nicolas Jager's avatar
Nicolas Jager committed
176
}
177

Sébastien Blin's avatar
Sébastien Blin committed
178
} // namespace Migration
Nicolas Jager's avatar
Nicolas Jager committed
179

180
struct JamiAccount::BuddyInfo
181
182
183
184
{
    /* the buddy id */
    dht::InfoHash id;

185
186
    /* number of devices connected on the DHT */
    uint32_t devices_cnt {};
187

188
    /* The disposable object to update buddy info */
189
    std::future<size_t> listenToken;
190

Sébastien Blin's avatar
Sébastien Blin committed
191
192
193
    BuddyInfo(dht::InfoHash id)
        : id(id)
    {}
194
195
};

196
struct JamiAccount::PendingCall
197
198
199
{
    std::chrono::steady_clock::time_point start;
    std::shared_ptr<IceTransport> ice_sp;
200
    std::shared_ptr<IceTransport> ice_tcp_sp;
201
202
203
204
    std::weak_ptr<SIPCall> call;
    std::future<size_t> listen_key;
    dht::InfoHash call_key;
    dht::InfoHash from;
205
    dht::InfoHash from_account;
206
207
208
    std::shared_ptr<dht::crypto::Certificate> from_cert;
};

209
struct JamiAccount::PendingMessage
210
{
211
    std::set<DeviceId> to;
212
213
};

214
215
216
217
218
219
220
221
222
223
224
225
226
struct AccountPeerInfo
{
    dht::InfoHash accountId;
    std::string displayName;
    MSGPACK_DEFINE(accountId, displayName)
};

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

227
static constexpr int ICE_COMP_ID_SIP_TRANSPORT {1};
Sébastien Blin's avatar
Sébastien Blin committed
228
229
230

static constexpr const char* const RING_URI_PREFIX = "ring:";
static constexpr const char* const JAMI_URI_PREFIX = "jami:";
231
static constexpr const char* DEFAULT_TURN_SERVER = "turn.jami.net";
Sébastien Blin's avatar
Sébastien Blin committed
232
static constexpr const char* DEFAULT_TURN_USERNAME = "ring";
233
234
static constexpr const char* DEFAULT_TURN_PWD = "ring";
static constexpr const char* DEFAULT_TURN_REALM = "ring";
235
static const auto PROXY_REGEX = std::regex(
236
    "(https?://)?([\\w\\.\\-_\\~]+)(:(\\d+)|:\\[(.+)-(.+)\\])?");
237
static const std::string PEER_DISCOVERY_JAMI_SERVICE = "jami";
238
const constexpr auto PEER_DISCOVERY_EXPIRATION = std::chrono::minutes(1);
239

240
constexpr const char* const JamiAccount::ACCOUNT_TYPE;
Adrien Béraud's avatar
Adrien Béraud committed
241
constexpr const std::pair<uint16_t, uint16_t> JamiAccount::DHT_PORT_RANGE {4000, 8888};
Adrien Béraud's avatar
Adrien Béraud committed
242

243
using ValueIdDist = std::uniform_int_distribution<dht::Value::Id>;
244

Adrien Béraud's avatar
Adrien Béraud committed
245
246
static std::string_view
stripPrefix(std::string_view toUrl)
247
{
248
    auto dhtf = toUrl.find(RING_URI_PREFIX);
Adrien Béraud's avatar
Adrien Béraud committed
249
    if (dhtf != std::string_view::npos) {
Sébastien Blin's avatar
Sébastien Blin committed
250
        dhtf = dhtf + 5;
251
    } else {
252
        dhtf = toUrl.find(JAMI_URI_PREFIX);
Adrien Béraud's avatar
Adrien Béraud committed
253
        if (dhtf != std::string_view::npos) {
Sébastien Blin's avatar
Sébastien Blin committed
254
            dhtf = dhtf + 5;
255
256
        } else {
            dhtf = toUrl.find("sips:");
Adrien Béraud's avatar
Adrien Béraud committed
257
            dhtf = (dhtf == std::string_view::npos) ? 0 : dhtf + 5;
258
        }
259
260
261
    }
    while (dhtf < toUrl.length() && toUrl[dhtf] == '/')
        dhtf++;
262
263
    return toUrl.substr(dhtf);
}
264

Adrien Béraud's avatar
Adrien Béraud committed
265
266
static std::string_view
parseJamiUri(std::string_view toUrl)
267
268
269
{
    auto sufix = stripPrefix(toUrl);
    if (sufix.length() < 40)
270
        throw std::invalid_argument("id must be a Jami infohash");
271

Adrien Béraud's avatar
Adrien Béraud committed
272
    const std::string_view toUri = sufix.substr(0, 40);
273
    if (std::find_if_not(toUri.cbegin(), toUri.cend(), ::isxdigit) != toUri.cend())
274
        throw std::invalid_argument("id must be a Jami infohash");
275
276
277
    return toUri;
}

278
static constexpr const char*
Sébastien Blin's avatar
Sébastien Blin committed
279
280
281
282
283
dhtStatusStr(dht::NodeStatus status)
{
    return status == dht::NodeStatus::Connected
               ? "connected"
               : (status == dht::NodeStatus::Connecting ? "connecting" : "disconnected");
284
285
}

286
287
288
/**
 * Local ICE Transport factory helper
 *
289
 * JamiAccount must use this helper than direct IceTranportFactory API
290
 */
Sébastien Blin's avatar
Sébastien Blin committed
291
template<class... Args>
292
std::shared_ptr<IceTransport>
293
JamiAccount::createIceTransport(const Args&... args)
294
295
{
    auto ice = Manager::instance().getIceTransportFactory().createTransport(args...);
296
    if (!ice)
297
298
299
300
301
        throw std::runtime_error("ICE transport creation failed");

    return ice;
}

302
JamiAccount::JamiAccount(const std::string& accountID, bool /* presenceEnabled */)
Guillaume Roguez's avatar
Guillaume Roguez committed
303
    : SIPAccountBase(accountID)
304
    , dht_(new dht::DhtRunner)
Sébastien Blin's avatar
Sébastien Blin committed
305
306
    , 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
307
    , dataPath_(cachePath_ + DIR_SEPARATOR_STR "values")
308
309
    , dhtPeerConnector_ {}
    , connectionManager_ {}
310
311
{
    // Force the SFL turn server if none provided yet
312
    turnServer_ = DEFAULT_TURN_SERVER;
313
    turnServerUserName_ = DEFAULT_TURN_USERNAME;
314
315
316
    turnServerPwd_ = DEFAULT_TURN_PWD;
    turnServerRealm_ = DEFAULT_TURN_REALM;
    turnEnabled_ = true;
317

318
    proxyListUrl_ = DHT_DEFAULT_PROXY_LIST_URL;
319
    proxyServer_ = DHT_DEFAULT_PROXY;
320
    nonSwarmTransferManager_ = std::make_shared<TransferManager>(getAccountID(), "");
321

322
    try {
Sébastien Blin's avatar
Sébastien Blin committed
323
324
        std::istringstream is(fileutils::loadCacheTextFile(cachePath_ + DIR_SEPARATOR_STR "dhtproxy",
                                                           std::chrono::hours(24 * 7)));
325
        std::getline(is, proxyServerCached_);
326
    } catch (const std::exception& e) {
327
328
329
        JAMI_DBG("[Account %s] Can't load proxy URL from cache: %s",
                 getAccountID().c_str(),
                 e.what());
330
    }
331
332

    setActiveCodecs({});
333
}
Adrien Béraud's avatar
Adrien Béraud committed
334

Adrien Béraud's avatar
Adrien Béraud committed
335
JamiAccount::~JamiAccount() noexcept
Adrien Béraud's avatar
Adrien Béraud committed
336
{
Sébastien Blin's avatar
Sébastien Blin committed
337
    if (peerDiscovery_) {
338
339
340
        peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE);
        peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE);
    }
341
342
    if (auto dht = dht_)
        dht->join();
Adrien Béraud's avatar
Adrien Béraud committed
343
344
}

345
346
347
void
JamiAccount::shutdownConnections()
{
348
349
    JAMI_DBG("[Account %s] Shutdown connections", getAccountID().c_str());

350
    decltype(gitServers_) gservers;
351
352
    {
        std::lock_guard<std::mutex> lk(gitServersMtx_);
353
        gservers = std::move(gitServers_);
354
    }
355
356
    for (auto& [_id, gs] : gservers)
        gs->stop();
357
358
    {
        std::lock_guard<std::mutex> lk(connManagerMtx_);
359
360
361
        // Just move destruction on another thread.
        dht::ThreadPool::io().run([conMgr = std::make_shared<decltype(connectionManager_)>(
                                       std::move(connectionManager_))] {});
362
363
        connectionManager_.reset();
    }
Sébastien Blin's avatar
Sébastien Blin committed
364
365
366
367
368
    {
        std::unique_lock<std::mutex> lk(syncConnectionsMtx_);
        syncConnections_.clear();
    }
    gitSocketList_.clear();
369
    dhtPeerConnector_.reset();
370
371
    std::lock_guard<std::mutex> lk(sipConnsMtx_);
    sipConns_.clear();
372
373
}

374
void
375
JamiAccount::flush()
376
377
378
379
380
{
    // Class base method
    SIPAccountBase::flush();

    fileutils::removeAll(cachePath_);
381
    fileutils::removeAll(dataPath_);
382
    fileutils::removeAll(idPath_, true);
383
384
}

Adrien Béraud's avatar
Adrien Béraud committed
385
std::shared_ptr<SIPCall>
Sébastien Blin's avatar
Sébastien Blin committed
386
JamiAccount::newIncomingCall(const std::string& from,
387
                             const std::vector<DRing::MediaMap>& mediaList,
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
                             const std::shared_ptr<SipTransport>& sipTransp)
{
    JAMI_DBG("New incoming call from %s with %lu media", from.c_str(), mediaList.size());

    if (sipTransp) {
        std::unique_lock<std::mutex> connLock(sipConnsMtx_);
        for (auto& [key, value] : sipConns_) {
            if (key.first == from) {
                // Search for a matching linked SipTransport in connection list.
                for (auto conIter = value.rbegin(); conIter != value.rend(); conIter++) {
                    if (conIter->transport != sipTransp)
                        continue;

                    auto call = Manager::instance().callFactory.newSipCall(shared(),
                                                                           Call::CallType::INCOMING,
                                                                           mediaList);
                    call->setPeerUri(RING_URI_PREFIX + from);
                    call->setPeerNumber(from);
                    return call;
                }
            }
        }
    }

    JAMI_ERR("newIncomingCall: can't find matching call for %s", from.c_str());
    return nullptr;
}

416
std::shared_ptr<Call>
Adrien Béraud's avatar
Adrien Béraud committed
417
JamiAccount::newOutgoingCall(std::string_view toUrl,
418
                             const std::map<std::string, std::string>& volatileCallDetails)
Adrien Béraud's avatar
Adrien Béraud committed
419
{
420
    auto& manager = Manager::instance();
421
    auto call = manager.callFactory.newSipCall(shared(),
422
423
424
425
426
                                               Call::CallType::OUTGOING,
                                               volatileCallDetails);
    if (not call)
        return {};

427
428
429
430
431
432
433
434
    if (call->isIceEnabled()) {
        call->createIceMediaTransport();
        getIceOptions([=](auto&& opts) {
            call->initIceMediaTransport(true, std::forward<IceTransportOptions>(opts));
        });
    }

    newOutgoingCallHelper(call, toUrl);
435
436
437
438
439

    return call;
}

std::shared_ptr<Call>
440
JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<DRing::MediaMap>& mediaList)
441
442
443
444
445
446
{
    auto suffix = stripPrefix(toUrl);
    JAMI_DBG() << *this << "Calling peer " << suffix;

    auto& manager = Manager::instance();

447
    auto call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
448
449
450
451

    if (not call)
        return {};

452
453
454
455
456
457
458
459
    if (call->isIceEnabled()) {
        call->createIceMediaTransport();
        getIceOptions([=](auto&& opts) {
            call->initIceMediaTransport(true, std::forward<IceTransportOptions>(opts));
        });
    }

    newOutgoingCallHelper(call, toUrl);
460
461
462
463
464
465
466
467
468
469

    return call;
}

void
JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, std::string_view toUri)
{
    auto suffix = stripPrefix(toUri);
    JAMI_DBG() << *this << "Calling DHT peer " << suffix;

470
    try {
471
472
        const std::string uri {parseJamiUri(suffix)};
        startOutgoingCall(call, uri);
473
474
    } catch (...) {
#if HAVE_RINGNS
Sébastien Blin's avatar
Sébastien Blin committed
475
476
477
478
479
480
481
482
483
484
485
486
487
        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 {
Adrien Béraud's avatar
Adrien Béraud committed
488
                                                 const std::string toUri {parseJamiUri(result)};
Sébastien Blin's avatar
Sébastien Blin committed
489
490
491
492
493
494
495
496
497
                                                 sthis->startOutgoingCall(call, toUri);
                                             } catch (...) {
                                                 call->onFailure(ENOENT);
                                             }
                                         } else {
                                             call->onFailure();
                                         }
                                     });
                                 });
498
499
500
501
#else
        call->onFailure(ENOENT);
#endif
    }
502
}
503

504
505
506
std::shared_ptr<SIPCall>
JamiAccount::createSubCall(const std::shared_ptr<SIPCall>& mainCall)
{
507
508
    auto mediaList = MediaAttribute::mediaAttributesToMediaMaps(mainCall->getMediaAttributeList());
    if (not mediaList.empty()) {
509
510
        return Manager::instance().callFactory.newSipCall(shared(),
                                                          Call::CallType::OUTGOING,
511
                                                          mediaList);
512
513
514
515
516
    } else {
        return Manager::instance().callFactory.newSipCall(shared(),
                                                          Call::CallType::OUTGOING,
                                                          mainCall->getDetails());
    }
517
518
}

519
void
Sébastien Blin's avatar
Sébastien Blin committed
520
521
522
523
524
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)
525
526
{
    auto sdp_list = IceTransport::parseSDPList(msg);
Sébastien Blin's avatar
Sébastien Blin committed
527
    for (const auto& sdp : sdp_list) {
528
529
530
        if (sdp.candidates.size() > 0) {
            if (sdp.candidates[0].find("TCP") != std::string::npos) {
                // It is a SDP for the TCP component
531
                tcp_failed = (ice_tcp && !ice_tcp->startIce(sdp));
532
533
            } else {
                // For UDP
534
                udp_failed = (ice && !ice->startIce(sdp));
535
536
537
538
539
540
541
542
543
544
545
            }
        }
    }

    // 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");
    }
}

546
void
547
JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri)
548
{
549
    if (not accountManager_ or not dht_) {
550
551
552
        call->onFailure(ENETDOWN);
        return;
    }
553
    // TODO: for now, we automatically trust all explicitly called peers
554
    setCertificateStatus(toUri, tls::TrustStore::PermissionStatus::ALLOWED);
555

556
    call->setPeerNumber(toUri + "@ring.dht");
557
    call->setPeerUri(JAMI_URI_PREFIX + toUri);
558
    call->setState(Call::ConnectionState::TRYING);
559
    std::weak_ptr<SIPCall> wCall = call;
560

561
#if HAVE_RINGNS
Sébastien Blin's avatar
Sébastien Blin committed
562
563
564
565
566
    accountManager_->lookupAddress(toUri,
                                   [wCall](const std::string& result,
                                           const NameDirectory::Response& response) {
                                       if (response == NameDirectory::Response::found)
                                           if (auto call = wCall.lock()) {
567
                                               call->setPeerRegisteredName(result);
568
                                               call->setPeerUri(JAMI_URI_PREFIX + result);
Sébastien Blin's avatar
Sébastien Blin committed
569
570
                                           }
                                   });
571
572
#endif

573
    dht::InfoHash peer_account(toUri);
574

575
    // Call connected devices
576
    std::set<DeviceId> devices;
577
    std::unique_lock<std::mutex> lkSipConn(sipConnsMtx_);
578
579
    // 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).
580
581
    auto dummyCall = createSubCall(call);

582
    call->addSubCall(*dummyCall);
583
    dummyCall->setIceMedia(call->getIceMedia());
Sébastien Blin's avatar
Sébastien Blin committed
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
    auto sendRequest =
        [this, wCall, toUri, dummyCall = std::move(dummyCall)](const DeviceId& deviceId,
                                                               bool eraseDummy) {
            if (eraseDummy) {
                // 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));
                return;
            }
            auto call = wCall.lock();
            if (not call)
                return;
            auto state = call->getConnectionState();
            if (state != Call::ConnectionState::PROGRESSING
                and state != Call::ConnectionState::TRYING)
                return;
600

Sébastien Blin's avatar
Sébastien Blin committed
601
602
603
604
605
606
607
608
609
610
611
612
            auto dev_call = createSubCall(call);
            dev_call->setIPToIP(true);
            dev_call->setState(Call::ConnectionState::TRYING);
            call->addStateListener(
                [w = weak(), deviceId](Call::CallState, Call::ConnectionState state, int) {
                    if (state != Call::ConnectionState::PROGRESSING
                        and state != Call::ConnectionState::TRYING) {
                        if (auto shared = w.lock())
                            shared->callConnectionClosed(deviceId, true);
                    }
                });
            call->addSubCall(*dev_call);
613
            dev_call->setIceMedia(call->getIceMedia());
Sébastien Blin's avatar
Sébastien Blin committed
614
615
616
617
            {
                std::lock_guard<std::mutex> lk(pendingCallsMutex_);
                pendingCalls_[deviceId].emplace_back(dev_call);
            }
618

Sébastien Blin's avatar
Sébastien Blin committed
619
620
621
622
623
            JAMI_WARN("[call %s] No channeled socket with this peer. Send request",
                      call->getCallId().c_str());
            // Else, ask for a channel (for future calls/text messages)
            requestSIPConnection(toUri, deviceId);
        };
624

625
    std::vector<std::shared_ptr<ChannelSocket>> channels;
Adrien Béraud's avatar
Adrien Béraud committed
626
    for (auto& [key, value] : sipConns_) {
627
        if (key.first != toUri)
Sébastien Blin's avatar
Sébastien Blin committed
628
            continue;
629
630
631
        if (value.empty())
            continue;
        auto& sipConn = value.back();
632

633
        if (!sipConn.channel) {
634
635
636
            JAMI_WARN("A SIP transport exists without Channel, this is a bug. Please report");
            continue;
        }
637

638
639
640
        auto transport = sipConn.transport;
        auto ice = sipConn.channel->underlyingICE();
        if (!transport or !ice)
Sébastien Blin's avatar
Sébastien Blin committed
641
            continue;
642

643
        channels.emplace_back(sipConn.channel);
644

Sébastien Blin's avatar
Sébastien Blin committed
645
646
        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
647

648
649
        auto dev_call = createSubCall(call);

650
651
        dev_call->setTransport(transport);
        call->addSubCall(*dev_call);
652
653
        dev_call->setIceMedia(call->getIceMedia());

654
655
656
657
658
        // 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);
659

660
661
        {
            std::lock_guard<std::mutex> lk(onConnectionClosedMtx_);
662
            onConnectionClosed_[key.second] = sendRequest;
663
664
665
        }

        call->addStateListener(
666
            [w = weak(), deviceId = key.second](Call::CallState, Call::ConnectionState state, int) {
667
668
                if (state != Call::ConnectionState::PROGRESSING
                    and state != Call::ConnectionState::TRYING) {
669
670
                    if (auto shared = w.lock())
                        shared->callConnectionClosed(deviceId, true);
671
672
673
                }
            });

674
        auto remote_address = ice->getRemoteAddress(ICE_COMP_ID_SIP_TRANSPORT);
675
        try {
676
            onConnectedOutgoingCall(dev_call, toUri, remote_address);
677
678
679
680
681
682
683
684
        } 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;
        }
685
        devices.emplace(key.second);
686
687
    }

688
689
690
691
692
693
    lkSipConn.unlock();
    // Note: Send beacon can destroy the socket (if storing last occurence of shared_ptr)
    // causing sipConn to be destroyed. So, do it while sipConns_ not locked.
    for (const auto& channel : channels)
        channel->sendBeacon();

694
    // Find listening devices for this account
Sébastien Blin's avatar
Sébastien Blin committed
695
696
    accountManager_->forEachDevice(
        peer_account,
697
698
        [this, devices = std::move(devices), sendRequest](
            const std::shared_ptr<dht::crypto::PublicKey>& dev) {
Sébastien Blin's avatar
Sébastien Blin committed
699
            // Test if already sent via a SIP transport
700
701
            auto deviceId = dev->getLongId();
            if (devices.find(deviceId) != devices.end())
Sébastien Blin's avatar
Sébastien Blin committed
702
                return;
703
704
            {
                std::lock_guard<std::mutex> lk(onConnectionClosedMtx_);
705
                onConnectionClosed_[deviceId] = sendRequest;
706
            }
707
            sendRequest(deviceId, false);
Sébastien Blin's avatar
Sébastien Blin committed
708
        },
709
        [wCall](bool ok) {
Sébastien Blin's avatar
Sébastien Blin committed
710
711
712
713
714
715
716
            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
717
718
719
}

void
720
721
722
JamiAccount::onConnectedOutgoingCall(const std::shared_ptr<SIPCall>& call,
                                     const std::string& to_id,
                                     IpAddr target)
Adrien Béraud's avatar
Adrien Béraud committed
723
{
724
725
726
    if (!call)
        return;
    JAMI_DBG("[call:%s] outgoing call connected to %s", call->getCallId().c_str(), to_id.c_str());
727

728
    const auto localAddress = ip_utils::getInterfaceAddr(getLocalInterface(), target.getFamily());
729

730
731
    IpAddr addrSdp = getPublishedSameasLocal() ? localAddress
                                               : getPublishedIpAddress(target.getFamily());
732

733
734
735
    // fallback on local address
    if (not addrSdp)
        addrSdp = localAddress;
736

737
738
739
740
741
    // 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
    if (!getSystemCodecContainer()->searchCodecByName("PCMA", jami::MEDIA_AUDIO))
        JAMI_WARN("Could not instantiate codec for early media");
742

743
744
    // Building the local SDP offer
    auto& sdp = call->getSDP();
745

746
    sdp.setPublishedIP(addrSdp);
747

748
    auto mediaAttrList = call->getMediaAttributeList();
749

750
751
752
753
    if (mediaAttrList.empty()) {
        JAMI_ERR("Call [%s] has no media. Abort!", call->getCallId().c_str());
        return;
    }
Adrien Béraud's avatar
Adrien Béraud committed
754

755
756
757
758
    if (not sdp.createOffer(mediaAttrList)) {
        JAMI_ERR("Could not send outgoing INVITE request for new call");
        return;
    }
759

760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
    call->setIPToIP(true);
    call->setPeerNumber(to_id);

    // Note: pj_ice_strans_create can call onComplete in the same thread
    // This means that iceMutex_ in IceTransport can be locked when onInitDone is called
    // So, we need to run the call creation in the main thread
    // Also, we do not directly call SIPStartCall before receiving onInitDone, because
    // there is an inside waitForInitialization that can block the thread.
    // Note: avoid runMainThread as SIPStartCall use transportMutex
    dht::ThreadPool::io().run([w = weak(), call = std::move(call), target] {
        auto account = w.lock();
        if (not account)
            return;

        if (not account->SIPStartCall(*call, target)) {
            JAMI_ERR("Could not send outgoing INVITE request for new call");
        }
Adrien Béraud's avatar
Adrien Béraud committed
777
    });
Adrien Béraud's avatar
Adrien Béraud committed
778
779
780
}

bool
Adrien Béraud's avatar
Adrien Béraud committed
781
JamiAccount::SIPStartCall(SIPCall& call, const IpAddr& target)
Adrien Béraud's avatar
Adrien Béraud committed
782
{
783
784
    JAMI_DBG("Start SIP call [%s]", call.getCallId().c_str());

785
    if (call.getIceMedia())
786
787
        call.addLocalIceAttributes();

788
789
    std::string toUri(getToUri(call.getPeerNumber() + "@"
                               + target.toString(true))); // expecting a fully well formed sip uri
Adrien Béraud's avatar
Adrien Béraud committed
790

Adrien Béraud's avatar
Adrien Béraud committed
791
    pj_str_t pjTo = sip_utils::CONST_PJ_STR(toUri);
Adrien Béraud's avatar
Adrien Béraud committed
792
793
794

    // Create the from header
    std::string from(getFromUri());
Adrien Béraud's avatar
Adrien Béraud committed
795
    pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
Adrien Béraud's avatar
Adrien Béraud committed
796

Adrien Béraud's avatar
Adrien Béraud committed
797
    std::string targetStr = getToUri(target.toString(true));
Adrien Béraud's avatar
Adrien Béraud committed
798
    pj_str_t pjTarget = sip_utils::CONST_PJ_STR(targetStr);
799

800
801
    pj_str_t pjContact;
    {
802
        auto transport = call.getTransport();
803
        pjContact = getContactHeader(transport ? transport->get() : nullptr);
804
    }
Adrien Béraud's avatar
Adrien Béraud committed
805

Adrien Béraud's avatar
Adrien Béraud committed
806
    JAMI_DBG("contact header: %.*s / %s -> %s / %.*s",
Sébastien Blin's avatar
Sébastien Blin committed
807
808
809
810
811
812
             (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
813

814
    auto local_sdp = call.getSDP().getLocalSdpSession();
815
816
817
    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
818
819
        return false;

820
    inv->mod_data[link_.getModId()] = &call;
821
    call.setInviteSession(inv);
Adrien Béraud's avatar
Adrien Béraud committed
822

Sébastien Blin's avatar
Sébastien Blin committed
823
    pjsip_tx_data* tdata;
Adrien Béraud's avatar
Adrien Béraud committed
824

825
    if (pjsip_inv_invite(call.inviteSession_.get(), &tdata) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
826
        JAMI_ERR("Could not initialize invite messager for this call");
Adrien Béraud's avatar
Adrien Béraud committed
827
828
829
        return false;
    }

830
831
    pjsip_tpselector tp_sel;
    tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
832
833
834
835
    if (!call.getTransport()) {
        JAMI_ERR("Could not get transport for this call");
        return false;
    }
836
    tp_sel.u.transport = call.getTransport()->get();
Adrien Béraud's avatar
Adrien Béraud committed
837
    if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
838
        JAMI_ERR("Unable to associate transport for invite session dialog");
Adrien Béraud's avatar
Adrien Béraud committed
839
840
841
        return false;
    }

Adrien Béraud's avatar
Adrien Béraud committed
842
    JAMI_DBG("[call:%s] Sending SIP invite", call.getCallId().c_str());
843
844

    // Add user-agent header
845
    sip_utils::addUserAgentHeader(getUserAgentName(), tdata);
846

847
    if (pjsip_inv_send_msg(call.inviteSession_.get(), tdata) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
848
        JAMI_ERR("Unable to send invite message for this call");
Adrien Béraud's avatar
Adrien Béraud committed
849
850
851
        return false;
    }

852
    call.setState(Call::CallState::ACTIVE, Call::ConnectionState::PROGRESSING);
Adrien Béraud's avatar
Adrien Béraud committed
853
854
855
856

    return true;
}

Sébastien Blin's avatar
Sébastien Blin committed
857
858
void
JamiAccount::saveConfig() const
859
860
861
862
863
864
865
{
    try {
        YAML::Emitter accountOut;
        serialize(accountOut);
        auto accountConfig = getPath() + DIR_SEPARATOR_STR + "config.yml";

        std::lock_guard<std::mutex> lock(fileutils::getFileLock(accountConfig));
866
        std::ofstream fout = fileutils::ofstream(accountConfig);
867
868
869
870
871
872
873
        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
874
875
void
JamiAccount::serialize(YAML::Emitter& out) const
Adrien Béraud's avatar
Adrien Béraud committed
876
{
877
878
    std::lock_guard<std::mutex> lock(configurationMutex_);

879
880
881
    if (registrationState_ == RegistrationState::INITIALIZING)
        return;

Adrien Béraud's avatar
Adrien Béraud committed
882
883
    out << YAML::BeginMap;
    SIPAccountBase::serialize(out);
884
    out << YAML::Key << Conf::DHT_PORT_KEY << YAML::Value << dhtDefaultPort_;
885
    out << YAML::Key << Conf::DHT_PUBLIC_IN_CALLS << YAML::Value << dhtPublicInCalls_;
886
887
    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_HISTORY << YAML::Value << allowPeersFromHistory_;
    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_CONTACT << YAML::Value << allowPeersFromContact_;
888
    out << YAML::Key << Conf::DHT_ALLOW_PEERS_FROM_TRUSTED << YAML::Value << allowPeersFromTrusted_;
Sébastien Blin's avatar
Sébastien Blin committed
889
890
891
892
893
894
    out << YAML::Key << DRing::Account::ConfProperties::DHT_PEER_DISCOVERY << YAML::Value
        << dhtPeerDiscovery_;
    out << YAML::Key << DRing::Account::ConfProperties::ACCOUNT_PEER_DISCOVERY << YAML::Value
        << accountPeerDiscovery_;
    out << YAML::Key << DRing::Account::ConfProperties::ACCOUNT_PUBLISH << YAML::Value
        << accountPublish_;
Adrien Béraud's avatar
Adrien Béraud committed
895

896
897
    out << YAML::Key << Conf::PROXY_ENABLED_KEY << YAML::Value << proxyEnabled_;
    out << YAML::Key << Conf::PROXY_SERVER_KEY << YAML::Value << proxyServer_;
Sébastien Blin's avatar
Sébastien Blin committed
898
899
    out << YAML::Key << DRing::Account::ConfProperties::DHT_PROXY_LIST_URL << YAML::Value
        << proxyListUrl_;
900

901
#if HAVE_RINGNS
Sébastien Blin's avatar
Sébastien Blin committed
902
    out << YAML::Key << DRing::Account::ConfProperties::RingNS::URI << YAML::Value << nameServer_;
903
    if (not registeredName_.empty())
Sébastien Blin's avatar
Sébastien Blin committed
904
905
        out << YAML::Key << DRing::Account::VolatileProperties::REGISTERED_NAME << YAML::Value
            << registeredName_;
906
907
#endif

908
    out << YAML::Key << DRing::Account::ConfProperties::ARCHIVE_PATH << YAML::Value << archivePath_;
Sébastien Blin's avatar
Sébastien Blin committed
909
910
    out << YAML::Key << DRing::Account::ConfProperties::ARCHIVE_HAS_PASSWORD << YAML::Value
        << archiveHasPassword_;
911
    out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT << YAML::Value << receipt_;
912
    if (receiptSignature_.size() > 0)
Sébastien Blin's avatar
Sébastien Blin committed
913
914
        out << YAML::Key << Conf::RING_ACCOUNT_RECEIPT_SIG << YAML::Value
            << YAML::Binary(receiptSignature_.data(), receiptSignature_.size());
915
    out << YAML::Key << DRing::Account::ConfProperties::DEVICE_NAME << YAML::Value << deviceName_;
916
    out << YAML::Key << DRing::Account::ConfProperties::MANAGER_URI << YAML::Value << managerUri_;
Sébastien Blin's avatar
Sébastien Blin committed
917
918
    out << YAML::Key << DRing::Account::ConfProperties::MANAGER_USERNAME << YAML::Value
        << managerUsername_;