sipaccount.cpp 71.3 KB
Newer Older
yanmorin's avatar
 
yanmorin committed
1
/*
Sébastien Blin's avatar
Sébastien Blin committed
2
 *  Copyright (C) 2004-2019 Savoir-faire Linux Inc.
Guillaume Roguez's avatar
Guillaume Roguez committed
3
4
5
 *
 *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
 *  Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com>
Guillaume Roguez's avatar
Guillaume Roguez committed
6
 *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
Guillaume Roguez's avatar
Guillaume Roguez committed
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *
 *  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.
 */
22

23
#include "sipaccount.h"
24

25
26
27
28
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

29
#include "compiler_intrinsics.h"
Tristan Matthews's avatar
Tristan Matthews committed
30

31
#include "sdp.h"
32
#include "sipvoiplink.h"
33
#include "sipcall.h"
34
#include "sip_utils.h"
35
#include "array_size.h"
36

Guillaume Roguez's avatar
Guillaume Roguez committed
37
38
#include "call_factory.h"

39
40
#include "sippresence.h"

41
42
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Tristan Matthews's avatar
Tristan Matthews committed
43
#include <yaml-cpp/yaml.h>
44
#pragma GCC diagnostic pop
Tristan Matthews's avatar
Tristan Matthews committed
45

46
#include "account_schema.h"
Tristan Matthews's avatar
Tristan Matthews committed
47
#include "config/yamlparser.h"
48
49
#include "logger.h"
#include "manager.h"
50
#include "client/ring_signal.h"
51
#include "dring/account_const.h"
52

Adrien Béraud's avatar
Adrien Béraud committed
53
#ifdef ENABLE_VIDEO
54
#include "libav_utils.h"
55
56
#endif

Eloi Bail's avatar
Eloi Bail committed
57
58
#include "system_codec_container.h"

59
60
61
62
#include "upnp/upnp_control.h"
#include "ip_utils.h"
#include "string_utils.h"

63
64
#include "im/instant_messaging.h"

Guillaume Roguez's avatar
Guillaume Roguez committed
65
66
#include <opendht/crypto.h>

67
#include <unistd.h>
Edric Milaret's avatar
Edric Milaret committed
68

69
#include <algorithm>
70
71
#include <array>
#include <memory>
72
73
#include <sstream>
#include <cstdlib>
Guillaume Roguez's avatar
Guillaume Roguez committed
74
#include <thread>
75

Edric Milaret's avatar
Edric Milaret committed
76
77
78
79
80
81
#ifdef _WIN32
#include <lmcons.h>
#else
#include <pwd.h>
#endif

Adrien Béraud's avatar
Adrien Béraud committed
82
namespace jami {
Guillaume Roguez's avatar
Guillaume Roguez committed
83
84
85

using yaml_utils::parseValue;
using yaml_utils::parseVectorMap;
86
using sip_utils::CONST_PJ_STR;
Guillaume Roguez's avatar
Guillaume Roguez committed
87

88
89
90
91
static constexpr int MIN_REGISTRATION_TIME = 60;  // seconds
static constexpr unsigned DEFAULT_REGISTRATION_TIME = 3600;  // seconds
static constexpr unsigned REGISTRATION_FIRST_RETRY_INTERVAL = 60; // seconds
static constexpr unsigned REGISTRATION_RETRY_INTERVAL = 300; // seconds
92
static const char *const VALID_TLS_PROTOS[] = {"Default", "TLSv1.2", "TLSv1.1", "TLSv1"};
Adrien Béraud's avatar
Adrien Béraud committed
93

94
constexpr const char * const SIPAccount::ACCOUNT_TYPE;
95

96
97
98
99
static void
registration_cb(pjsip_regc_cbparam *param)
{
    if (!param) {
Adrien Béraud's avatar
Adrien Béraud committed
100
        JAMI_ERR("registration callback parameter is null");
101
102
103
104
105
        return;
    }

    auto account = static_cast<SIPAccount *>(param->token);
    if (!account) {
Adrien Béraud's avatar
Adrien Béraud committed
106
        JAMI_ERR("account doesn't exist in registration callback");
107
108
109
        return;
    }

110
    account->onRegister(param);
111
112
}

113
SIPAccount::SIPAccount(const std::string& accountID, bool presenceEnabled)
114
    : SIPAccountBase(accountID)
115
    , auto_rereg_()
116
    , credentials_()
117
    , regc_(nullptr)
118
    , bRegister_(false)
119
    , registrationExpire_(MIN_REGISTRATION_TIME)
120
    , serviceRoute_()
121
    , cred_()
122
    , tlsSetting_()
123
    , ciphers_(100)
124
    , tlsMethod_("TLSv1")
125
    , tlsCiphers_()
126
    , tlsServerName_("")
127
    , tlsVerifyServer_(false)
128
129
130
    , tlsVerifyClient_(true)
    , tlsRequireClientCertificate_(true)
    , tlsNegotiationTimeoutSec_("2")
131
    , registrationStateDetailed_()
132
    , keepAliveEnabled_(false)
133
    , keepAliveTimer_()
134
    , keepAliveTimerActive_(false)
135
    , receivedParameter_("")
136
    , rPort_(-1)
137
    , via_addr_()
138
139
    , contactBuffer_()
    , contact_{contactBuffer_, 0}
140
141
    , contactRewriteMethod_(2)
    , allowViaRewrite_(true)
142
    , allowContactRewrite_(1)
143
    , contactOverwritten_(false)
144
    , via_tp_(nullptr)
145
    , presence_(presenceEnabled ? new SIPPresence(this) : nullptr)
146
{
147
148
149
    via_addr_.host.ptr = 0;
    via_addr_.host.slen = 0;
    via_addr_.port = 0;
150
}
yanmorin's avatar
 
yanmorin committed
151

152
153
SIPAccount::~SIPAccount()
{
154
155
    // ensure that no registration callbacks survive past this point
    destroyRegistrationInfo();
156
    setTransport();
157

158
159
160
    delete presence_;
}

Guillaume Roguez's avatar
Guillaume Roguez committed
161
std::shared_ptr<SIPCall>
162
SIPAccount::newIncomingCall(const std::string& from UNUSED, const std::map<std::string, std::string>& details)
Guillaume Roguez's avatar
Guillaume Roguez committed
163
{
164
    auto& manager = Manager::instance();
165
166
167
168
    return manager.callFactory.newCall<SIPCall, SIPAccount>(*this,
                                                            manager.getNewCallID(),
                                                            Call::CallType::INCOMING,
                                                            details);
Guillaume Roguez's avatar
Guillaume Roguez committed
169
170
171
172
}

template <>
std::shared_ptr<SIPCall>
173
174
SIPAccount::newOutgoingCall(const std::string& toUrl,
                            const std::map<std::string, std::string>& volatileCallDetails)
175
176
177
178
{
    std::string to;
    int family;

Adrien Béraud's avatar
Adrien Béraud committed
179
    JAMI_DBG() << *this << "Calling SIP peer " << toUrl;
180

181
    auto& manager = Manager::instance();
182
183
184
    auto call = manager.callFactory.newCall<SIPCall, SIPAccount>(*this, manager.getNewCallID(),
                                                                 Call::CallType::OUTGOING,
                                                                 volatileCallDetails);
185
    call->setSecure(isTlsEnabled());
186

187
188
189
190
191
192
193
194
    if (isIP2IP()) {
        bool ipv6 = false;
#if HAVE_IPV6
        ipv6 = IpAddr::isIpv6(toUrl);
#endif
        to = ipv6 ? IpAddr(toUrl).toString(false, true) : toUrl;
        family = ipv6 ? pj_AF_INET6() : pj_AF_INET();

195
        // TODO: resolve remote host using SIPVoIPLink::resolveSrvName
196
197
        std::shared_ptr<SipTransport> t = isTlsEnabled() ?
            link_->sipTransportBroker->getTlsTransport(tlsListener_, IpAddr(sip_utils::getHostFromUri(to))) :
Adrien Béraud's avatar
Adrien Béraud committed
198
199
200
201
            transport_;
        setTransport(t);
        call->setTransport(t);

Adrien Béraud's avatar
Adrien Béraud committed
202
        JAMI_DBG("New %s IP to IP call to %s", ipv6?"IPv6":"IPv4", to.c_str());
203
204
205
    }
    else {
        to = toUrl;
Adrien Béraud's avatar
Adrien Béraud committed
206
        call->setTransport(transport_);
207
        // FIXME : for now, use the same address family as the SIP transport
208
209
        family = pjsip_transport_type_get_af(getTransportType());

Adrien Béraud's avatar
Adrien Béraud committed
210
        JAMI_DBG("UserAgent: New registered account call to %s", toUrl.c_str());
211
212
    }

213
    auto toUri = getToUri(to);
214
    call->initIceMediaTransport(true);
215
216
    call->setIPToIP(isIP2IP());
    call->setPeerNumber(toUri);
Philippe Gorley's avatar
Philippe Gorley committed
217
    call->setPeerUri(toUri);
218
219
220

    const auto localAddress = ip_utils::getInterfaceAddr(getLocalInterface(), family);

Stepan Salenikovich's avatar
Stepan Salenikovich committed
221
    IpAddr addrSdp;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
222
    if (getUPnPActive()) {
Stepan Salenikovich's avatar
Stepan Salenikovich committed
223
224
225
226
227
228
229
        /* use UPnP addr, or published addr if its set */
        addrSdp = getPublishedSameasLocal() ?
            getUPnPIpAddress() : getPublishedIpAddress();
    } else {
        addrSdp = isStunEnabled() or (not getPublishedSameasLocal()) ?
            getPublishedIpAddress() : localAddress;
    }
230

231
232
233
    /* fallback on local address */
    if (not addrSdp) addrSdp = localAddress;

234
    // Building the local SDP offer
235
    auto& sdp = call->getSDP();
236
237

    if (getPublishedSameasLocal())
238
        sdp.setPublishedIP(addrSdp);
239
    else
240
        sdp.setPublishedIP(getPublishedAddress());
241

Guillaume Roguez's avatar
Guillaume Roguez committed
242
    const bool created = sdp.createOffer(
243
        getActiveAccountCodecInfoList(MEDIA_AUDIO),
244
        getActiveAccountCodecInfoList(videoEnabled_ and not call->isAudioOnly() ? MEDIA_VIDEO : MEDIA_NONE),
Guillaume Roguez's avatar
Guillaume Roguez committed
245
246
        getSrtpKeyExchange()
    );
247

248
249
    if (created) {
        std::weak_ptr<SIPCall> weak_call = call;
250
        manager.scheduler().run([this, weak_call] {
251
252
            if (auto call = weak_call.lock()) {
                if (not SIPStartCall(call)) {
Adrien Béraud's avatar
Adrien Béraud committed
253
                    JAMI_ERR("Could not send outgoing INVITE request for new call");
254
255
                    call->onFailure();
                }
256
257
258
259
            }
            return false;
        });
    } else {
260
        throw VoipLinkException("Could not send outgoing INVITE request for new call");
261
    }
262
263
264
265

    return call;
}

266
267
268
269
void
SIPAccount::onTransportStateChanged(pjsip_transport_state state, const pjsip_transport_state_info *info)
{
    pj_status_t currentStatus = transportStatus_;
Adrien Béraud's avatar
Adrien Béraud committed
270
    JAMI_DBG("Transport state changed to %s for account %s !", SipTransport::stateToStr(state), accountID_.c_str());
271
272
273
    if (!SipTransport::isAlive(transport_, state)) {
        if (info) {
            transportStatus_ = info->status;
274
            transportError_  = sip_utils::sip_strerror(info->status);
Adrien Béraud's avatar
Adrien Béraud committed
275
            JAMI_ERR("Transport disconnected: %s", transportError_.c_str());
276
277
278
279
280
281
        }
        else {
            // This is already the generic error used by pjsip.
            transportStatus_ = PJSIP_SC_SERVICE_UNAVAILABLE;
            transportError_  = "";
        }
282
        setRegistrationState(RegistrationState::ERROR_GENERIC, PJSIP_SC_TSX_TRANSPORT_ERROR);
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
        setTransport();
    }
    else {
        // The status can be '0', this is the same as OK
        transportStatus_ = info && info->status ? info->status : PJSIP_SC_OK;
        transportError_  = "";
    }

    // Notify the client of the new transport state
    if (currentStatus != transportStatus_)
        emitSignal<DRing::ConfigurationSignal::VolatileDetailsChanged>(accountID_, getVolatileAccountDetails());
}

void
SIPAccount::setTransport(const std::shared_ptr<SipTransport>& t)
{
    if (t == transport_)
        return;
    if (transport_) {
Adrien Béraud's avatar
Adrien Béraud committed
302
        JAMI_DBG("Removing transport from account");
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
        if (regc_)
            pjsip_regc_release_transport(regc_);
        transport_->removeStateListener(reinterpret_cast<uintptr_t>(this));
    }

    transport_ = t;

    if (transport_)
        transport_->addStateListener(reinterpret_cast<uintptr_t>(this), std::bind(&SIPAccount::onTransportStateChanged, this, std::placeholders::_1, std::placeholders::_2));
}

pjsip_tpselector
SIPAccount::getTransportSelector() {
    if (!transport_)
        return SIPVoIPLink::getTransportSelector(nullptr);
    return SIPVoIPLink::getTransportSelector(transport_->get());
}

Guillaume Roguez's avatar
Guillaume Roguez committed
321
std::shared_ptr<Call>
322
SIPAccount::newOutgoingCall(const std::string& toUrl, const std::map<std::string, std::string>& volatileCallDetails)
Guillaume Roguez's avatar
Guillaume Roguez committed
323
{
324
    return newOutgoingCall<SIPCall>(toUrl, volatileCallDetails);
Guillaume Roguez's avatar
Guillaume Roguez committed
325
326
}

327
328
329
bool
SIPAccount::SIPStartCall(std::shared_ptr<SIPCall>& call)
{
330
    // Add Ice headers to local SDP if ice transport exist
331
332
    call->setupLocalSDPFromIce();

333
334
335
336
337
338
339
340
    std::string toUri(call->getPeerNumber()); // expecting a fully well formed sip uri

    pj_str_t pjTo = pj_str((char*) toUri.c_str());

    // Create the from header
    std::string from(getFromUri());
    pj_str_t pjFrom = pj_str((char*) from.c_str());

341
342
    auto transport = call->getTransport();
    if (!transport) {
Adrien Béraud's avatar
Adrien Béraud committed
343
        JAMI_ERR("Unable to start call without transport");
344
345
        return false;
    }
346

347
    pj_str_t pjContact = getContactHeader(transport->get());
Adrien Béraud's avatar
Adrien Béraud committed
348
    JAMI_DBG("contact header: %.*s / %s -> %s",
349
             (int)pjContact.slen, pjContact.ptr, from.c_str(), toUri.c_str());
350

351
352
353
354
    auto local_sdp = call->getSDP().getLocalSdpSession();
    pjsip_dialog* dialog {nullptr};
    pjsip_inv_session* inv {nullptr};
    if (!CreateClientDialogAndInvite(&pjFrom, &pjContact, &pjTo, nullptr, local_sdp, &dialog, &inv))
355
        return false;
356

357
    inv->mod_data[link_->getModId()] = call.get();
358
359
    call->inv.reset(inv);

360
361
362
363
364
365
    updateDialogViaSentBy(dialog);

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

    if (hasCredentials() and pjsip_auth_clt_set_credentials(&dialog->auth_sess, getCredentialCount(), getCredInfo()) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
366
        JAMI_ERR("Could not initialize credentials for invite session authentication");
367
368
369
370
371
        return false;
    }

    pjsip_tx_data *tdata;

372
    if (pjsip_inv_invite(call->inv.get(), &tdata) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
373
        JAMI_ERR("Could not initialize invite messager for this call");
374
375
376
        return false;
    }

377
    const pjsip_tpselector tp_sel = link_->getTransportSelector(transport->get());
378
    if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
379
        JAMI_ERR("Unable to associate transport for invite session dialog");
380
381
382
        return false;
    }

383
    if (pjsip_inv_send_msg(call->inv.get(), tdata) != PJ_SUCCESS) {
Adrien Béraud's avatar
Adrien Béraud committed
384
        JAMI_ERR("Unable to send invite message for this call");
385
386
387
        return false;
    }

Guillaume Roguez's avatar
Guillaume Roguez committed
388
    call->setState(Call::CallState::ACTIVE, Call::ConnectionState::PROGRESSING);
389
390
391
392

    return true;
}

393
void SIPAccount::serialize(YAML::Emitter &out) const
Julien Bonjean's avatar
Julien Bonjean committed
394
{
395
396
    std::lock_guard<std::mutex> lock(configurationMutex_);

Tristan Matthews's avatar
Tristan Matthews committed
397
    out << YAML::BeginMap;
398
399
    SIPAccountBase::serialize(out);

400
401
    out << YAML::Key << Conf::PORT_KEY << YAML::Value << localPort_;

402
403
    out << YAML::Key << USERNAME_KEY << YAML::Value << username_;

Tristan Matthews's avatar
Tristan Matthews committed
404
    // each credential is a map, and we can have multiple credentials
405
    out << YAML::Key << Conf::CRED_KEY << YAML::Value << getCredentials();
Guillaume Roguez's avatar
Guillaume Roguez committed
406
    out << YAML::Key << Conf::KEEP_ALIVE_ENABLED << YAML::Value << keepAliveEnabled_;
Julien Bonjean's avatar
Julien Bonjean committed
407

Tristan Matthews's avatar
Tristan Matthews committed
408
    out << YAML::Key << PRESENCE_MODULE_ENABLED_KEY << YAML::Value << (presence_ and presence_->isEnabled());
Guillaume Roguez's avatar
Guillaume Roguez committed
409
410
    out << YAML::Key << Conf::PRESENCE_PUBLISH_SUPPORTED_KEY << YAML::Value << (presence_ and presence_->isSupported(PRESENCE_FUNCTION_PUBLISH));
    out << YAML::Key << Conf::PRESENCE_SUBSCRIBE_SUPPORTED_KEY << YAML::Value << (presence_ and presence_->isSupported(PRESENCE_FUNCTION_SUBSCRIBE));
411

Tristan Matthews's avatar
Tristan Matthews committed
412
    out << YAML::Key << Preferences::REGISTRATION_EXPIRE_KEY << YAML::Value << registrationExpire_;
Guillaume Roguez's avatar
Guillaume Roguez committed
413
    out << YAML::Key << Conf::SERVICE_ROUTE_KEY << YAML::Value << serviceRoute_;
Tristan Matthews's avatar
Tristan Matthews committed
414
415

    // tls submap
Guillaume Roguez's avatar
Guillaume Roguez committed
416
    out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap;
Adrien Béraud's avatar
Adrien Béraud committed
417
    SIPAccountBase::serializeTls(out);
Guillaume Roguez's avatar
Guillaume Roguez committed
418
    out << YAML::Key << Conf::TLS_ENABLE_KEY << YAML::Value << tlsEnable_;
419
    out << YAML::Key << Conf::TLS_PORT_KEY << YAML::Value << tlsListenerPort_;
Guillaume Roguez's avatar
Guillaume Roguez committed
420
421
422
423
424
425
426
    out << YAML::Key << Conf::VERIFY_CLIENT_KEY << YAML::Value << tlsVerifyClient_;
    out << YAML::Key << Conf::VERIFY_SERVER_KEY << YAML::Value << tlsVerifyServer_;
    out << YAML::Key << Conf::REQUIRE_CERTIF_KEY << YAML::Value << tlsRequireClientCertificate_;
    out << YAML::Key << Conf::TIMEOUT_KEY << YAML::Value << tlsNegotiationTimeoutSec_;
    out << YAML::Key << Conf::CIPHERS_KEY << YAML::Value << tlsCiphers_;
    out << YAML::Key << Conf::METHOD_KEY << YAML::Value << tlsMethod_;
    out << YAML::Key << Conf::SERVER_KEY << YAML::Value << tlsServerName_;
Adrien Béraud's avatar
Adrien Béraud committed
427
428
429
    out << YAML::EndMap;

    // srtp submap
Guillaume Roguez's avatar
Guillaume Roguez committed
430
    out << YAML::Key << Conf::SRTP_KEY << YAML::Value << YAML::BeginMap;
431
    out << YAML::Key << Conf::KEY_EXCHANGE_KEY << YAML::Value << sip_utils::getKeyExchangeName(srtpKeyExchange_);
Guillaume Roguez's avatar
Guillaume Roguez committed
432
    out << YAML::Key << Conf::RTP_FALLBACK_KEY << YAML::Value << srtpFallback_;
Tristan Matthews's avatar
Tristan Matthews committed
433
434
435
    out << YAML::EndMap;

    out << YAML::EndMap;
436
}
437

438
439
440
441
442
443
444
void SIPAccount::usePublishedAddressPortInVIA()
{
    via_addr_.host.ptr = (char *) publishedIpAddress_.c_str();
    via_addr_.host.slen = publishedIpAddress_.size();
    via_addr_.port = publishedPort_;
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
445
446
void SIPAccount::useUPnPAddressPortInVIA()
{
447
448
449
    upnpIpAddr_ = getUPnPIpAddress().toString();
    via_addr_.host.ptr = (char *) upnpIpAddr_.c_str();
    via_addr_.host.slen = upnpIpAddr_.size();
Stepan Salenikovich's avatar
Stepan Salenikovich committed
450
451
452
    via_addr_.port = publishedPortUsed_;
}

453
template <typename T>
454
455
static void
validate(std::string &member, const std::string &param, const T& valid)
456
457
458
459
460
461
{
    const auto begin = std::begin(valid);
    const auto end = std::end(valid);
    if (find(begin, end, param) != end)
        member = param;
    else
Adrien Béraud's avatar
Adrien Béraud committed
462
        JAMI_ERR("Invalid parameter \"%s\"", param.c_str());
463
464
}

Tristan Matthews's avatar
Tristan Matthews committed
465
void SIPAccount::unserialize(const YAML::Node &node)
466
{
467
468
    std::lock_guard<std::mutex> lock(configurationMutex_);

469
    SIPAccountBase::unserialize(node);
470
471
    parseValue(node, USERNAME_KEY, username_);

472
473
474
    if (not publishedSameasLocal_)
        usePublishedAddressPortInVIA();

475
476
477
478
    int port = sip_utils::DEFAULT_SIP_PORT;
    parseValue(node, Conf::PORT_KEY, port);
    localPort_ = port;

479
480
481
482
483
484
485
486
487
488
489
    if (not isIP2IP()) {
        parseValue(node, Preferences::REGISTRATION_EXPIRE_KEY, registrationExpire_);
        parseValue(node, Conf::KEEP_ALIVE_ENABLED, keepAliveEnabled_);
        parseValue(node, Conf::SERVICE_ROUTE_KEY, serviceRoute_);
        const auto& credsNode = node[Conf::CRED_KEY];
        setCredentials(parseVectorMap(credsNode, {
            Conf::CONFIG_ACCOUNT_REALM,
            Conf::CONFIG_ACCOUNT_USERNAME,
            Conf::CONFIG_ACCOUNT_PASSWORD
        }));
    }
490

Tristan Matthews's avatar
Tristan Matthews committed
491
    bool presEnabled = false;
492
    parseValue(node, PRESENCE_MODULE_ENABLED_KEY, presEnabled);
Tristan Matthews's avatar
Tristan Matthews committed
493
494
    enablePresence(presEnabled);
    bool publishSupported = false;
Guillaume Roguez's avatar
Guillaume Roguez committed
495
    parseValue(node, Conf::PRESENCE_PUBLISH_SUPPORTED_KEY, publishSupported);
Tristan Matthews's avatar
Tristan Matthews committed
496
    bool subscribeSupported = false;
Guillaume Roguez's avatar
Guillaume Roguez committed
497
    parseValue(node, Conf::PRESENCE_SUBSCRIBE_SUPPORTED_KEY, subscribeSupported);
Tristan Matthews's avatar
Tristan Matthews committed
498
499
500
501
    if (presence_) {
        presence_->support(PRESENCE_FUNCTION_PUBLISH, publishSupported);
        presence_->support(PRESENCE_FUNCTION_SUBSCRIBE, subscribeSupported);
    }
502

503
    // Init stun server name with default server name
504
    stunServerName_ = pj_str((char*) stunServer_.data());
Julien Bonjean's avatar
Julien Bonjean committed
505

Guillaume Roguez's avatar
Guillaume Roguez committed
506
    const auto &credsNode = node[Conf::CRED_KEY];
507
508
509
510
511
    setCredentials(parseVectorMap(credsNode, {
        Conf::CONFIG_ACCOUNT_REALM,
        Conf::CONFIG_ACCOUNT_USERNAME,
        Conf::CONFIG_ACCOUNT_PASSWORD
    }));
Julien Bonjean's avatar
Julien Bonjean committed
512
513

    // get tls submap
Guillaume Roguez's avatar
Guillaume Roguez committed
514
    const auto &tlsMap = node[Conf::TLS_KEY];
515
516
517
518
    parseValue(tlsMap, Conf::CERTIFICATE_KEY, tlsCertificateFile_);
    parseValue(tlsMap, Conf::CALIST_KEY, tlsCaListFile_);
    parseValue(tlsMap, Conf::TLS_PASSWORD_KEY, tlsPassword_);
    parseValue(tlsMap, Conf::PRIVATE_KEY_KEY, tlsPrivateKeyFile_);
Guillaume Roguez's avatar
Guillaume Roguez committed
519
    parseValue(tlsMap, Conf::TLS_ENABLE_KEY, tlsEnable_);
520
    parseValue(tlsMap, Conf::TLS_PORT_KEY, tlsListenerPort_);
Guillaume Roguez's avatar
Guillaume Roguez committed
521
    parseValue(tlsMap, Conf::CIPHERS_KEY, tlsCiphers_);
Tristan Matthews's avatar
Tristan Matthews committed
522
523

    std::string tmpMethod(tlsMethod_);
Guillaume Roguez's avatar
Guillaume Roguez committed
524
    parseValue(tlsMap, Conf::METHOD_KEY, tmpMethod);
525
    validate(tlsMethod_, tmpMethod, VALID_TLS_PROTOS);
Tristan Matthews's avatar
Tristan Matthews committed
526

Guillaume Roguez's avatar
Guillaume Roguez committed
527
528
529
530
    parseValue(tlsMap, Conf::SERVER_KEY, tlsServerName_);
    parseValue(tlsMap, Conf::REQUIRE_CERTIF_KEY, tlsRequireClientCertificate_);
    parseValue(tlsMap, Conf::VERIFY_CLIENT_KEY, tlsVerifyClient_);
    parseValue(tlsMap, Conf::VERIFY_SERVER_KEY, tlsVerifyServer_);
Tristan Matthews's avatar
Tristan Matthews committed
531
    // FIXME
Guillaume Roguez's avatar
Guillaume Roguez committed
532
    parseValue(tlsMap, Conf::TIMEOUT_KEY, tlsNegotiationTimeoutSec_);
Tristan Matthews's avatar
Tristan Matthews committed
533

Adrien Béraud's avatar
Adrien Béraud committed
534
    // get srtp submap
Guillaume Roguez's avatar
Guillaume Roguez committed
535
    const auto &srtpMap = node[Conf::SRTP_KEY];
Adrien Béraud's avatar
Adrien Béraud committed
536
    std::string tmpKey;
Guillaume Roguez's avatar
Guillaume Roguez committed
537
    parseValue(srtpMap, Conf::KEY_EXCHANGE_KEY, tmpKey);
538
    srtpKeyExchange_ = sip_utils::getKeyExchangeProtocol(tmpKey.c_str());
Guillaume Roguez's avatar
Guillaume Roguez committed
539
    parseValue(srtpMap, Conf::RTP_FALLBACK_KEY, srtpFallback_);
540
541
}

542
void SIPAccount::setAccountDetails(const std::map<std::string, std::string> &details)
543
{
544
545
    std::lock_guard<std::mutex> lock(configurationMutex_);

Tristan Matthews's avatar
Tristan Matthews committed
546
    SIPAccountBase::setAccountDetails(details);
547
    parseString(details, Conf::CONFIG_ACCOUNT_USERNAME, username_);
Julien Bonjean's avatar
Julien Bonjean committed
548

549
550
    parseInt(details, Conf::CONFIG_LOCAL_PORT, localPort_);

551
552
553
554
555
556
    // TLS
    parseString(details, Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile_);
    parseString(details, Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile_);
    parseString(details, Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile_);
    parseString(details, Conf::CONFIG_TLS_PASSWORD, tlsPassword_);

Julien Bonjean's avatar
Julien Bonjean committed
557
    // SIP specific account settings
Guillaume Roguez's avatar
Guillaume Roguez committed
558
    parseString(details, Conf::CONFIG_ACCOUNT_ROUTESET, serviceRoute_);
559

560
561
562
    if (not publishedSameasLocal_)
        usePublishedAddressPortInVIA();

Guillaume Roguez's avatar
Guillaume Roguez committed
563
    parseInt(details, Conf::CONFIG_ACCOUNT_REGISTRATION_EXPIRE, registrationExpire_);
564
565

    if (registrationExpire_ < MIN_REGISTRATION_TIME)
566
        registrationExpire_ = MIN_REGISTRATION_TIME;
567

Guillaume Roguez's avatar
Guillaume Roguez committed
568
    parseBool(details, Conf::CONFIG_KEEP_ALIVE_ENABLED, keepAliveEnabled_);
569
    bool presenceEnabled = false;
Guillaume Roguez's avatar
Guillaume Roguez committed
570
    parseBool(details, Conf::CONFIG_PRESENCE_ENABLED, presenceEnabled);
571
    enablePresence(presenceEnabled);
572
573

    // TLS settings
Guillaume Roguez's avatar
Guillaume Roguez committed
574
    parseBool(details, Conf::CONFIG_TLS_ENABLE, tlsEnable_);
575
    parseInt(details, Conf::CONFIG_TLS_LISTENER_PORT, tlsListenerPort_);
Guillaume Roguez's avatar
Guillaume Roguez committed
576
    auto iter = details.find(Conf::CONFIG_TLS_METHOD);
577
    if (iter != details.end())
578
        validate(tlsMethod_, iter->second, VALID_TLS_PROTOS);
Guillaume Roguez's avatar
Guillaume Roguez committed
579
580
581
582
583
584
585
586
587
588
    parseString(details, Conf::CONFIG_TLS_CIPHERS, tlsCiphers_);
    parseString(details, Conf::CONFIG_TLS_SERVER_NAME, tlsServerName_);
    parseBool(details, Conf::CONFIG_TLS_VERIFY_SERVER, tlsVerifyServer_);
    parseBool(details, Conf::CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient_);
    parseBool(details, Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate_);
    parseString(details, Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeoutSec_);
    parseBool(details, Conf::CONFIG_TLS_VERIFY_SERVER, tlsVerifyServer_);
    parseBool(details, Conf::CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient_);
    parseBool(details, Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate_);
    parseString(details, Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeoutSec_);
Adrien Béraud's avatar
Adrien Béraud committed
589
590

    // srtp settings
Guillaume Roguez's avatar
Guillaume Roguez committed
591
592
    parseBool(details, Conf::CONFIG_SRTP_RTP_FALLBACK, srtpFallback_);
    iter = details.find(Conf::CONFIG_SRTP_KEY_EXCHANGE);
Adrien Béraud's avatar
Adrien Béraud committed
593
    if (iter != details.end())
594
        srtpKeyExchange_ = sip_utils::getKeyExchangeProtocol(iter->second.c_str());
595

596
    if (credentials_.empty()) { // credentials not set, construct 1 entry
Adrien Béraud's avatar
Adrien Béraud committed
597
        JAMI_WARN("No credentials set, inferring them...");
598
599
        std::vector<std::map<std::string, std::string> > v;
        std::map<std::string, std::string> map;
Guillaume Roguez's avatar
Guillaume Roguez committed
600
601
602
        map[Conf::CONFIG_ACCOUNT_USERNAME] = username_;
        parseString(details, Conf::CONFIG_ACCOUNT_PASSWORD, map[Conf::CONFIG_ACCOUNT_PASSWORD]);
        map[Conf::CONFIG_ACCOUNT_REALM] = "*";
603
604
605
        v.push_back(map);
        setCredentials(v);
    }
606
607
}

608
609
std::map<std::string, std::string>
SIPAccount::getAccountDetails() const
610
{
611
612
    std::lock_guard<std::mutex> lock(configurationMutex_);

613
    auto a = SIPAccountBase::getAccountDetails();
Julien Bonjean's avatar
Julien Bonjean committed
614

615
    std::string password {};
616
    if (hasCredentials()) {
617
618
619
620
621
        for (const auto &cred : credentials_)
            if (cred.username == username_) {
                password = cred.password;
                break;
            }
622
    }
623
    a.emplace(Conf::CONFIG_ACCOUNT_PASSWORD,                std::move(password));
624

625
    a.emplace(Conf::CONFIG_LOCAL_PORT,                      std::to_string(localPort_));
626
    a.emplace(Conf::CONFIG_ACCOUNT_ROUTESET,                serviceRoute_);
627
    a.emplace(Conf::CONFIG_ACCOUNT_REGISTRATION_EXPIRE,     std::to_string(registrationExpire_));
628
629
630
631
632
633
634
635
636
637
638
639
    a.emplace(Conf::CONFIG_KEEP_ALIVE_ENABLED,              keepAliveEnabled_ ? TRUE_STR : FALSE_STR);

    a.emplace(Conf::CONFIG_PRESENCE_ENABLED,                presence_ and presence_->isEnabled()? TRUE_STR : FALSE_STR);
    a.emplace(Conf::CONFIG_PRESENCE_PUBLISH_SUPPORTED,      presence_ and presence_->isSupported(PRESENCE_FUNCTION_PUBLISH)? TRUE_STR : FALSE_STR);
    a.emplace(Conf::CONFIG_PRESENCE_SUBSCRIBE_SUPPORTED,    presence_ and presence_->isSupported(PRESENCE_FUNCTION_SUBSCRIBE)? TRUE_STR : FALSE_STR);

    auto tlsSettings(getTlsSettings());
    a.insert(tlsSettings.begin(), tlsSettings.end());

    a.emplace(Conf::CONFIG_SRTP_KEY_EXCHANGE,               sip_utils::getKeyExchangeName(srtpKeyExchange_));
    a.emplace(Conf::CONFIG_SRTP_ENABLE,                     isSrtpEnabled() ? TRUE_STR : FALSE_STR);
    a.emplace(Conf::CONFIG_SRTP_RTP_FALLBACK,               srtpFallback_ ? TRUE_STR : FALSE_STR);
Guillaume Roguez's avatar
Guillaume Roguez committed
640

Julien Bonjean's avatar
Julien Bonjean committed
641
    return a;
642
643
}

644
645
std::map<std::string, std::string>
SIPAccount::getVolatileAccountDetails() const
646
{
647
    auto a = SIPAccountBase::getVolatileAccountDetails();
648
    a.emplace(Conf::CONFIG_ACCOUNT_REGISTRATION_STATE_CODE, std::to_string(registrationStateDetailed_.first));
649
    a.emplace(Conf::CONFIG_ACCOUNT_REGISTRATION_STATE_DESC, registrationStateDetailed_.second);
650
    a.emplace(DRing::Account::VolatileProperties::InstantMessaging::OFF_CALL, TRUE_STR);
651
652

    if (presence_) {
653
654
        a.emplace(Conf::CONFIG_PRESENCE_STATUS,     presence_->isOnline() ? TRUE_STR : FALSE_STR);
        a.emplace(Conf::CONFIG_PRESENCE_NOTE,       presence_->getNote());
655
656
    }

657
658
659
660
    if (transport_ and transport_->isSecure() and transport_->isConnected()) {
        const auto& tlsInfos = transport_->getTlsInfos();
        auto cipher = pj_ssl_cipher_name(tlsInfos.cipher);
        if (tlsInfos.cipher and not cipher)
Adrien Béraud's avatar
Adrien Béraud committed
661
            JAMI_WARN("Unknown cipher: %d", tlsInfos.cipher);
662
663
664
665
666
667
668
669
670
671
        a.emplace(DRing::TlsTransport::TLS_CIPHER,         cipher ? cipher : "");
        a.emplace(DRing::TlsTransport::TLS_PEER_CERT,      tlsInfos.peerCert->toString());
        auto ca = tlsInfos.peerCert->issuer;
        unsigned n = 0;
        while (ca) {
            std::ostringstream name_str;
            name_str << DRing::TlsTransport::TLS_PEER_CA_ << n++;
            a.emplace(name_str.str(),                      ca->toString());
            ca = ca->issuer;
        }
672
        a.emplace(DRing::TlsTransport::TLS_PEER_CA_NUM,    std::to_string(n));
673
    }
674
675
676
677

    return a;
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
678
679
bool SIPAccount::mapPortUPnP()
{
680
681
682
    // return true if not using UPnP
    bool added = true;

Stepan Salenikovich's avatar
Stepan Salenikovich committed
683
    if (getUPnPActive()) {
Stepan Salenikovich's avatar
Stepan Salenikovich committed
684
685
686
687
688
689
690
691
        /* create port mapping from published port to local port to the local IP
         * note that since different accounts can use the same port,
         * it may already be open, thats OK
         *
         * if the desired port is taken by another client, then it will try to map
         * a different port, if succesfull, then we have to use that port for SIP
         */
        uint16_t port_used;
Adrien Béraud's avatar
Adrien Béraud committed
692
        bool added = upnp_->addAnyMapping(publishedPort_, localPort_, jami::upnp::PortType::UDP, false, false, &port_used);
693
        if (added) {
Stepan Salenikovich's avatar
Stepan Salenikovich committed
694
            if (port_used != publishedPort_)
Adrien Béraud's avatar
Adrien Béraud committed
695
                JAMI_DBG("UPnP could not map published port %u for SIP, using %u instead", publishedPort_, port_used);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
696
697
698
            publishedPortUsed_ = port_used;
        }
    }
699

700
701
702
    upnp_->setIGDListener([w=weak()] {
        if (auto acc = w.lock())
            acc->doRegister();
703
704
705
    });

    return added;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
706
707
}

708
void SIPAccount::doRegister()
Julien Bonjean's avatar
Julien Bonjean committed
709
{
710
    std::unique_lock<std::mutex> lock(configurationMutex_);
711
    if (not isUsable()) {
Adrien Béraud's avatar
Adrien Béraud committed
712
        JAMI_WARN("Account must be enabled and active to register, ignoring");
Adrien Béraud's avatar
Adrien Béraud committed
713
714
715
        return;
    }

Adrien Béraud's avatar
Adrien Béraud committed
716
    JAMI_DBG("doRegister %s", hostname_.c_str());
717

Stepan Salenikovich's avatar
Stepan Salenikovich committed
718
    /* if UPnP is enabled, then wait for IGD to complete registration */
719
    if (upnp_) {
Adrien Béraud's avatar
Adrien Béraud committed
720
        JAMI_DBG("UPnP: waiting for IGD to register SIP account");
721
        lock.unlock();
Stepan Salenikovich's avatar
Stepan Salenikovich committed
722
        setRegistrationState(RegistrationState::TRYING);
723
724
725
726
        std::thread{ [w=weak()] {
            if (auto acc = w.lock()) {
                sip_utils::register_thread();
                if (not acc->mapPortUPnP())
Adrien Béraud's avatar
Adrien Béraud committed
727
                    JAMI_WARN("UPnP: Could not successfully map SIP port with UPnP, continuing with account registration anyways.");
728
729
                acc->doRegister1_();
            }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
730
        }}.detach();
731
732
    } else {
        lock.unlock();
Stepan Salenikovich's avatar
Stepan Salenikovich committed
733
        doRegister1_();
734
    }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
735
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
736

Stepan Salenikovich's avatar
Stepan Salenikovich committed
737
738
void SIPAccount::doRegister1_()
{
739
740
741
742
743
744
    {
        std::lock_guard<std::mutex> lock(configurationMutex_);
        if (isIP2IP()) {
            doRegister2_();
            return;
        }
745
    }
746

747
748
749
    link_->resolveSrvName(
        hostname_,
        tlsEnable_ ? PJSIP_TRANSPORT_TLS : PJSIP_TRANSPORT_UDP,
750
751
        [w = weak()](std::vector<IpAddr> host_ips) {
            if (auto acc = w.lock()) {
752
                std::lock_guard<std::mutex> lock(acc->configurationMutex_);
753
                if (host_ips.empty()) {
Adrien Béraud's avatar
Adrien Béraud committed
754
                    JAMI_ERR("Can't resolve hostname for registration.");
755
756
757
758
759
                    acc->setRegistrationState(RegistrationState::ERROR_GENERIC, PJSIP_SC_NOT_FOUND);
                    return;
                }
                acc->hostIp_ = host_ips[0];
                acc->doRegister2_();
760
761
762
763
            }
        }
    );
}
764

Stepan Salenikovich's avatar
Stepan Salenikovich committed
765
void SIPAccount::doRegister2_()
766
767
{
    bool ipv6 = false;
768
    if (isIP2IP()) {
Adrien Béraud's avatar
Adrien Béraud committed
769
        JAMI_DBG("doRegister isIP2IP.");
770
771
772
773
#if HAVE_IPV6
        ipv6 = ip_utils::getInterfaceAddr(interface_).isIpv6();
#endif
    } else if (!hostIp_) {
774
        setRegistrationState(RegistrationState::ERROR_GENERIC, PJSIP_SC_NOT_FOUND);
Adrien Béraud's avatar
Adrien Béraud committed
775
        JAMI_ERR("Hostname not resolved.");
776
777
778
779
780
        return;
    }
#if HAVE_IPV6
    else
        ipv6 = hostIp_.isIpv6();
781
782
#endif

783
    // Init TLS settings if the user wants to use TLS
784
    if (tlsEnable_) {
Adrien Béraud's avatar
Adrien Béraud committed
785
        JAMI_DBG("TLS is enabled for account %s", accountID_.c_str());
786
787
788

        // Dropping current calls already using the transport is currently required
        // with TLS.
789
        freeAccount();
790

791
        transportType_ = ipv6 ? PJSIP_TRANSPORT_TLS6 : PJSIP_TRANSPORT_TLS;
Emmanuel Milou's avatar
Emmanuel Milou committed
792
        initTlsConfiguration();
Adrien Béraud's avatar
Adrien Béraud committed
793
794

        if (!tlsListener_) {
795
            tlsListener_ = link_->sipTransportBroker->getTlsListener(
Adrien Béraud's avatar
Adrien Béraud committed
796
797
798
799
                SipTransportDescr {getTransportType(), getTlsListenerPort(), getLocalInterface()},
                getTlsSetting());
            if (!tlsListener_) {
                setRegistrationState(RegistrationState::ERROR_GENERIC);
Adrien Béraud's avatar
Adrien Béraud committed
800
                JAMI_ERR("Error creating TLS listener.");
Adrien Béraud's avatar
Adrien Béraud committed
801
802
803
                return;
            }
        }
804
    } else {
Adrien Béraud's avatar
Adrien Béraud committed
805
        tlsListener_.reset();
806
        transportType_ = ipv6 ? PJSIP_TRANSPORT_UDP6 : PJSIP_TRANSPORT_UDP;
807
    }
808

809
    // Init STUN settings for this account if the user selected it
Guillaume Roguez's avatar
Guillaume Roguez committed
810
    if (stunEnabled_)
811
        initStunConfiguration();
Guillaume Roguez's avatar
Guillaume Roguez committed
812
    else
813
        stunServerName_ = pj_str((char*) stunServer_.c_str());
814

815
816
    // In our definition of the ip2ip profile (aka Direct IP Calls),
    // no registration should be performed
Adrien Béraud's avatar
Adrien Béraud committed
817
818
819
    if (isIP2IP()) {
        // If we use Tls for IP2IP, transports will be created on connection.
        if (!tlsEnable_)
820
            setTransport(link_->sipTransportBroker->getUdpTransport(
Adrien Béraud's avatar
Adrien Béraud committed
821
822
                SipTransportDescr { getTransportType(), getLocalPort(), getLocalInterface() }
            ));
823
        setRegistrationState(RegistrationState::REGISTERED);
824
        return;
Adrien Béraud's avatar
Adrien Béraud committed
825
    }
826

827
    try {
Adrien Béraud's avatar
Adrien Béraud committed
828
        JAMI_WARN("Creating transport");
Adrien Béraud's avatar
Adrien Béraud committed
829