Line data Source code
1 : /*
2 : * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #ifdef HAVE_CONFIG_H
19 : #include "config.h"
20 : #endif
21 :
22 : #include "jamiaccount.h"
23 :
24 : #include "logger.h"
25 :
26 : #include "accountarchive.h"
27 : #include "jami_contact.h"
28 : #include "configkeys.h"
29 : #include "contact_list.h"
30 : #include "archive_account_manager.h"
31 : #include "server_account_manager.h"
32 : #include "jamidht/channeled_transport.h"
33 : #include "conversation_channel_handler.h"
34 : #include "sync_channel_handler.h"
35 : #include "message_channel_handler.h"
36 : #include "auth_channel_handler.h"
37 : #include "transfer_channel_handler.h"
38 : #include "swarm/swarm_channel_handler.h"
39 : #include "jami/media_const.h"
40 :
41 : #include "sip/sdp.h"
42 : #include "sip/sipvoiplink.h"
43 : #include "sip/sipcall.h"
44 : #include "sip/siptransport.h"
45 : #include "connectivity/sip_utils.h"
46 :
47 : #include "uri.h"
48 :
49 : #include "client/ring_signal.h"
50 : #include "jami/call_const.h"
51 : #include "jami/account_const.h"
52 :
53 : #include "system_codec_container.h"
54 :
55 : #include "account_schema.h"
56 : #include "manager.h"
57 : #include "connectivity/utf8_utils.h"
58 : #include "connectivity/ip_utils.h"
59 :
60 : #ifdef ENABLE_PLUGIN
61 : #include "plugin/jamipluginmanager.h"
62 : #include "plugin/chatservicesmanager.h"
63 : #endif
64 :
65 : #ifdef ENABLE_VIDEO
66 : #include "libav_utils.h"
67 : #endif
68 : #include "fileutils.h"
69 : #include "string_utils.h"
70 : #include "archiver.h"
71 : #include "data_transfer.h"
72 : #include "json_utils.h"
73 :
74 : #include "libdevcrypto/Common.h"
75 : #include "base64.h"
76 : #include "vcard.h"
77 : #include "im/instant_messaging.h"
78 :
79 : #include <dhtnet/ice_transport.h>
80 : #include <dhtnet/ice_transport_factory.h>
81 : #include <dhtnet/upnp/upnp_control.h>
82 : #include <dhtnet/multiplexed_socket.h>
83 : #include <dhtnet/certstore.h>
84 :
85 : #include <opendht/thread_pool.h>
86 : #include <opendht/peer_discovery.h>
87 : #include <opendht/http.h>
88 :
89 : #include <yaml-cpp/yaml.h>
90 :
91 : #include <unistd.h>
92 :
93 : #include <algorithm>
94 : #include <array>
95 : #include <cctype>
96 : #include <charconv>
97 : #include <cinttypes>
98 : #include <cstdarg>
99 : #include <initializer_list>
100 : #include <memory>
101 : #include <regex>
102 : #include <sstream>
103 : #include <string>
104 : #include <system_error>
105 :
106 : using namespace std::placeholders;
107 :
108 : namespace jami {
109 :
110 : constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID");
111 : static constexpr const char MIME_TYPE_IMDN[] {"message/imdn+xml"};
112 : static constexpr const char MIME_TYPE_PIDF[] {"application/pidf+xml"};
113 : static constexpr const char MIME_TYPE_INVITE_JSON[] {"application/invite+json"};
114 : static constexpr const char DEVICE_ID_PATH[] {"ring_device"};
115 : static constexpr auto TREATED_PATH = "treatedImMessages"sv;
116 :
117 : struct VCardMessageCtx
118 : {
119 : std::shared_ptr<std::atomic_int> success;
120 : int total;
121 : std::string path;
122 : };
123 :
124 : namespace Migration {
125 :
126 : enum class State { // Contains all the Migration states
127 : SUCCESS,
128 : INVALID
129 : };
130 :
131 : std::string
132 4 : mapStateNumberToString(const State migrationState)
133 : {
134 : #define CASE_STATE(X) \
135 : case Migration::State::X: \
136 : return #X
137 :
138 4 : switch (migrationState) {
139 0 : CASE_STATE(INVALID);
140 4 : CASE_STATE(SUCCESS);
141 : }
142 0 : return {};
143 : }
144 :
145 : void
146 4 : setState(const std::string& accountID, const State migrationState)
147 : {
148 4 : emitSignal<libjami::ConfigurationSignal::MigrationEnded>(accountID,
149 8 : mapStateNumberToString(migrationState));
150 4 : }
151 :
152 : } // namespace Migration
153 :
154 : struct JamiAccount::BuddyInfo
155 : {
156 : /* the buddy id */
157 : dht::InfoHash id;
158 :
159 : /* number of devices connected on the DHT */
160 : uint32_t devices_cnt {};
161 :
162 : /* The disposable object to update buddy info */
163 : std::future<size_t> listenToken;
164 :
165 29 : BuddyInfo(dht::InfoHash id)
166 29 : : id(id)
167 29 : {}
168 : };
169 :
170 : struct JamiAccount::PendingCall
171 : {
172 : std::chrono::steady_clock::time_point start;
173 : std::shared_ptr<IceTransport> ice_sp;
174 : std::shared_ptr<IceTransport> ice_tcp_sp;
175 : std::weak_ptr<SIPCall> call;
176 : std::future<size_t> listen_key;
177 : dht::InfoHash call_key;
178 : dht::InfoHash from;
179 : dht::InfoHash from_account;
180 : std::shared_ptr<dht::crypto::Certificate> from_cert;
181 : };
182 :
183 : struct JamiAccount::PendingMessage
184 : {
185 : std::set<DeviceId> to;
186 : };
187 :
188 : struct AccountPeerInfo
189 : {
190 : dht::InfoHash accountId;
191 : std::string displayName;
192 0 : MSGPACK_DEFINE(accountId, displayName)
193 : };
194 :
195 : struct JamiAccount::DiscoveredPeer
196 : {
197 : std::string displayName;
198 : std::shared_ptr<Task> cleanupTask;
199 : };
200 :
201 : static constexpr const char* const RING_URI_PREFIX = "ring:";
202 : static constexpr const char* const JAMI_URI_PREFIX = "jami:";
203 : static const auto PROXY_REGEX = std::regex(
204 : "(https?://)?([\\w\\.\\-_\\~]+)(:(\\d+)|:\\[(.+)-(.+)\\])?");
205 : static const std::string PEER_DISCOVERY_JAMI_SERVICE = "jami";
206 : const constexpr auto PEER_DISCOVERY_EXPIRATION = std::chrono::minutes(1);
207 :
208 : using ValueIdDist = std::uniform_int_distribution<dht::Value::Id>;
209 :
210 : std::string_view
211 42376 : stripPrefix(std::string_view toUrl)
212 : {
213 42376 : auto dhtf = toUrl.find(RING_URI_PREFIX);
214 42376 : if (dhtf != std::string_view::npos) {
215 0 : dhtf = dhtf + 5;
216 : } else {
217 42376 : dhtf = toUrl.find(JAMI_URI_PREFIX);
218 42376 : if (dhtf != std::string_view::npos) {
219 0 : dhtf = dhtf + 5;
220 : } else {
221 42376 : dhtf = toUrl.find("sips:");
222 42376 : dhtf = (dhtf == std::string_view::npos) ? 0 : dhtf + 5;
223 : }
224 : }
225 42376 : while (dhtf < toUrl.length() && toUrl[dhtf] == '/')
226 0 : dhtf++;
227 42376 : return toUrl.substr(dhtf);
228 : }
229 :
230 : std::string_view
231 42376 : parseJamiUri(std::string_view toUrl)
232 : {
233 42376 : auto sufix = stripPrefix(toUrl);
234 42376 : if (sufix.length() < 40)
235 0 : throw std::invalid_argument("ID must be a Jami infohash");
236 :
237 42376 : const std::string_view toUri = sufix.substr(0, 40);
238 42376 : if (std::find_if_not(toUri.cbegin(), toUri.cend(), ::isxdigit) != toUri.cend())
239 0 : throw std::invalid_argument("ID must be a Jami infohash");
240 42376 : return toUri;
241 : }
242 :
243 : static constexpr std::string_view
244 3410 : dhtStatusStr(dht::NodeStatus status)
245 : {
246 : return status == dht::NodeStatus::Connected
247 1724 : ? "connected"sv
248 3410 : : (status == dht::NodeStatus::Connecting ? "connecting"sv : "disconnected"sv);
249 : }
250 :
251 635 : JamiAccount::JamiAccount(const std::string& accountId)
252 : : SIPAccountBase(accountId)
253 635 : , cachePath_(fileutils::get_cache_dir() / accountId)
254 635 : , dataPath_(cachePath_ / "values")
255 635 : , certStore_ {std::make_unique<dhtnet::tls::CertificateStore>(idPath_, Logger::dhtLogger())}
256 635 : , dht_(new dht::DhtRunner)
257 635 : , treatedMessages_(cachePath_ / TREATED_PATH)
258 635 : , connectionManager_ {}
259 3175 : , nonSwarmTransferManager_()
260 635 : {}
261 :
262 1270 : JamiAccount::~JamiAccount() noexcept
263 : {
264 635 : if (dht_)
265 635 : dht_->join();
266 635 : }
267 :
268 : void
269 651 : JamiAccount::shutdownConnections()
270 : {
271 651 : JAMI_DBG("[Account %s] Shutdown connections", getAccountID().c_str());
272 :
273 651 : decltype(gitServers_) gservers;
274 : {
275 651 : std::lock_guard lk(gitServersMtx_);
276 651 : gservers = std::move(gitServers_);
277 651 : }
278 1013 : for (auto& [_id, gs] : gservers)
279 362 : gs->stop();
280 : {
281 651 : std::lock_guard lk(connManagerMtx_);
282 : // Just move destruction on another thread.
283 1302 : dht::ThreadPool::io().run([conMgr = std::make_shared<decltype(connectionManager_)>(
284 651 : std::move(connectionManager_))] {});
285 651 : connectionManager_.reset();
286 651 : channelHandlers_.clear();
287 651 : }
288 651 : if (convModule_) {
289 589 : convModule_->shutdownConnections();
290 : }
291 :
292 651 : std::lock_guard lk(sipConnsMtx_);
293 651 : sipConns_.clear();
294 651 : }
295 :
296 : void
297 630 : JamiAccount::flush()
298 : {
299 : // Class base method
300 630 : SIPAccountBase::flush();
301 :
302 630 : dhtnet::fileutils::removeAll(cachePath_);
303 630 : dhtnet::fileutils::removeAll(dataPath_);
304 630 : dhtnet::fileutils::removeAll(idPath_, true);
305 630 : }
306 :
307 : std::shared_ptr<SIPCall>
308 97 : JamiAccount::newIncomingCall(const std::string& from,
309 : const std::vector<libjami::MediaMap>& mediaList,
310 : const std::shared_ptr<SipTransport>& sipTransp)
311 : {
312 291 : JAMI_DEBUG("New incoming call from {:s} with {:d} media", from, mediaList.size());
313 :
314 97 : if (sipTransp) {
315 97 : auto call = Manager::instance().callFactory.newSipCall(shared(),
316 : Call::CallType::INCOMING,
317 194 : mediaList);
318 97 : call->setPeerUri(JAMI_URI_PREFIX + from);
319 97 : call->setPeerNumber(from);
320 :
321 97 : call->setSipTransport(sipTransp, getContactHeader(sipTransp));
322 :
323 97 : return call;
324 97 : }
325 :
326 0 : JAMI_ERR("newIncomingCall: unable to find matching call for %s", from.c_str());
327 0 : return nullptr;
328 : }
329 :
330 : std::shared_ptr<Call>
331 111 : JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<libjami::MediaMap>& mediaList)
332 : {
333 111 : auto uri = Uri(toUrl);
334 111 : if (uri.scheme() == Uri::Scheme::SWARM || uri.scheme() == Uri::Scheme::RENDEZVOUS) {
335 : // NOTE: In this case newOutgoingCall can act as "unholdConference" and just attach the
336 : // host to the current hosted conference. So, no call will be returned in that case.
337 22 : return newSwarmOutgoingCallHelper(uri, mediaList);
338 : }
339 :
340 89 : auto& manager = Manager::instance();
341 89 : std::shared_ptr<SIPCall> call;
342 :
343 : // SIP allows sending empty invites, this use case is not used with Jami accounts.
344 89 : if (not mediaList.empty()) {
345 33 : call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
346 : } else {
347 56 : JAMI_WARN("Media list is empty, setting a default list");
348 112 : call = manager.callFactory.newSipCall(shared(),
349 : Call::CallType::OUTGOING,
350 112 : MediaAttribute::mediaAttributesToMediaMaps(
351 168 : createDefaultMediaList(isVideoEnabled())));
352 : }
353 :
354 89 : if (not call)
355 0 : return {};
356 :
357 89 : std::shared_lock lkCM(connManagerMtx_);
358 89 : if (!connectionManager_)
359 0 : return {};
360 :
361 89 : connectionManager_->getIceOptions([call, w = weak(), uri = std::move(uri)](auto&& opts) {
362 89 : if (call->isIceEnabled()) {
363 89 : if (not call->createIceMediaTransport(false)
364 178 : or not call->initIceMediaTransport(true,
365 : std::forward<dhtnet::IceTransportOptions>(opts))) {
366 0 : return;
367 : }
368 : }
369 89 : auto shared = w.lock();
370 89 : if (!shared)
371 0 : return;
372 89 : JAMI_DBG() << "New outgoing call with " << uri.toString();
373 89 : call->setPeerNumber(uri.authority());
374 89 : call->setPeerUri(uri.toString());
375 :
376 89 : shared->newOutgoingCallHelper(call, uri);
377 89 : });
378 :
379 89 : return call;
380 111 : }
381 :
382 : void
383 89 : JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const Uri& uri)
384 : {
385 89 : JAMI_DBG() << this << "Calling peer " << uri.authority();
386 : try {
387 89 : startOutgoingCall(call, uri.authority());
388 0 : } catch (...) {
389 : #ifdef ENABLE_NAMESERVER
390 0 : auto suffix = stripPrefix(uri.toString());
391 0 : NameDirectory::lookupUri(suffix,
392 0 : config().nameServer,
393 0 : [wthis_ = weak(), call](const std::string& regName,
394 : const std::string& address,
395 : NameDirectory::Response response) {
396 : // we may run inside an unknown thread, but following code must
397 : // be called in main thread
398 0 : runOnMainThread([wthis_, regName, address, response, call]() {
399 0 : if (response != NameDirectory::Response::found) {
400 0 : call->onFailure(EINVAL);
401 0 : return;
402 : }
403 0 : if (auto sthis = wthis_.lock()) {
404 : try {
405 0 : sthis->startOutgoingCall(call, regName);
406 0 : } catch (...) {
407 0 : call->onFailure(ENOENT);
408 0 : }
409 : } else {
410 0 : call->onFailure();
411 0 : }
412 : });
413 0 : });
414 : #else
415 : call->onFailure(ENOENT);
416 : #endif
417 0 : }
418 89 : }
419 :
420 : std::shared_ptr<SIPCall>
421 22 : JamiAccount::newSwarmOutgoingCallHelper(const Uri& uri,
422 : const std::vector<libjami::MediaMap>& mediaList)
423 : {
424 66 : JAMI_DEBUG("[Account {}] Calling conversation {}", getAccountID(), uri.authority());
425 : return convModule()->call(
426 : uri.authority(),
427 : mediaList,
428 66 : [this, uri](const auto& accountUri, const auto& deviceId, const auto& call) {
429 11 : if (!call)
430 0 : return;
431 11 : std::unique_lock lkSipConn(sipConnsMtx_);
432 12 : for (auto& [key, value] : sipConns_) {
433 1 : if (key.first != accountUri || key.second != deviceId)
434 1 : continue;
435 0 : if (value.empty())
436 0 : continue;
437 0 : auto& sipConn = value.back();
438 :
439 0 : if (!sipConn.channel) {
440 0 : JAMI_WARN(
441 : "A SIP transport exists without Channel, this is a bug. Please report");
442 0 : continue;
443 : }
444 :
445 0 : auto transport = sipConn.transport;
446 0 : if (!transport or !sipConn.channel)
447 0 : continue;
448 0 : call->setState(Call::ConnectionState::PROGRESSING);
449 :
450 0 : auto remoted_address = sipConn.channel->getRemoteAddress();
451 : try {
452 0 : onConnectedOutgoingCall(call, uri.authority(), remoted_address);
453 0 : return;
454 0 : } catch (const VoipLinkException&) {
455 : // In this case, the main scenario is that SIPStartCall failed because
456 : // the ICE is dead and the TLS session didn't send any packet on that dead
457 : // link (connectivity change, killed by the operating system, etc)
458 : // Here, we don't need to do anything, the TLS will fail and will delete
459 : // the cached transport
460 0 : continue;
461 : }
462 : }
463 11 : lkSipConn.unlock();
464 : {
465 11 : std::lock_guard lkP(pendingCallsMutex_);
466 11 : pendingCalls_[deviceId].emplace_back(call);
467 11 : }
468 :
469 : // Else, ask for a channel (for future calls/text messages)
470 11 : auto type = call->hasVideo() ? "videoCall" : "audioCall";
471 33 : JAMI_WARNING("[call {}] No channeled socket with this peer. Send request",
472 : call->getCallId());
473 11 : requestSIPConnection(accountUri, deviceId, type, true, call);
474 33 : });
475 : }
476 :
477 : void
478 10 : JamiAccount::handleIncomingConversationCall(const std::string& callId,
479 : const std::string& destination)
480 : {
481 10 : auto split = jami::split_string(destination, '/');
482 10 : if (split.size() != 4)
483 0 : return;
484 10 : auto conversationId = std::string(split[0]);
485 10 : auto accountUri = std::string(split[1]);
486 10 : auto deviceId = std::string(split[2]);
487 10 : auto confId = std::string(split[3]);
488 :
489 10 : if (getUsername() != accountUri || currentDeviceId() != deviceId)
490 0 : return;
491 :
492 : // Avoid concurrent checks in this part
493 10 : std::lock_guard lk(rdvMtx_);
494 10 : auto isNotHosting = !convModule()->isHosting(conversationId, confId);
495 10 : if (confId == "0") {
496 1 : auto currentCalls = convModule()->getActiveCalls(conversationId);
497 1 : if (!currentCalls.empty()) {
498 0 : confId = currentCalls[0]["id"];
499 0 : isNotHosting = false;
500 : } else {
501 1 : confId = callId;
502 3 : JAMI_DEBUG("No active call to join, create conference");
503 : }
504 1 : }
505 10 : auto preferences = convModule()->getConversationPreferences(conversationId);
506 10 : auto canHost = true;
507 : #if defined(__ANDROID__) || defined(__APPLE__)
508 : // By default, mobile devices SHOULD NOT host conferences.
509 : canHost = false;
510 : #endif
511 10 : auto itPref = preferences.find(ConversationPreferences::HOST_CONFERENCES);
512 10 : if (itPref != preferences.end()) {
513 0 : canHost = itPref->second == TRUE_STR;
514 : }
515 :
516 10 : auto call = getCall(callId);
517 10 : if (!call) {
518 0 : JAMI_ERROR("Call {} not found", callId);
519 0 : return;
520 : }
521 :
522 10 : if (isNotHosting && !canHost) {
523 0 : JAMI_DEBUG("Request for hosting a conference declined");
524 0 : Manager::instance().hangupCall(getAccountID(), callId);
525 0 : return;
526 : }
527 : // Due to the fact that in a conference, the host is not the one who
528 : // provides the initial sdp offer, the following block of code is responsible
529 : // for handling the medialist that the host will form his response with.
530 : // We always want the hosts response to be the same length as that of the
531 : // peer who is asking to join (providing the offer). A priori though the peer
532 : // doesn't know what active media streams the host will have so we deal with the
533 : // possible cases here.
534 10 : std::shared_ptr<Conference> conf;
535 10 : std::vector<libjami::MediaMap> currentMediaList;
536 10 : if (!isNotHosting) {
537 7 : conf = getConference(confId);
538 7 : if (!conf) {
539 0 : JAMI_ERROR("Conference {} not found", confId);
540 0 : return;
541 : }
542 7 : auto hostMedias = conf->currentMediaList();
543 7 : auto sipCall = std::dynamic_pointer_cast<SIPCall>(call);
544 7 : if (hostMedias.empty()) {
545 0 : currentMediaList = MediaAttribute::mediaAttributesToMediaMaps(
546 0 : createDefaultMediaList(call->hasVideo(), true));
547 7 : } else if (hostMedias.size() < sipCall->getRtpSessionList().size()) {
548 : // First case: host has less media streams than the other person is joining
549 : // with. We need to add video media to the host before accepting the offer
550 : // This can happen if we host an audio call and someone joins with video
551 0 : currentMediaList = hostMedias;
552 0 : currentMediaList.push_back({{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
553 : libjami::Media::MediaAttributeValue::VIDEO},
554 : {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
555 : {libjami::Media::MediaAttributeKey::MUTED, TRUE_STR},
556 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
557 : {libjami::Media::MediaAttributeKey::LABEL, "video_0"}});
558 : } else {
559 : // The second case is that the host has the same or more media
560 : // streams than the person joining. In this case we match all their
561 : // medias to form our offer. They will then potentially join the call without seeing
562 : // seeing all of our medias. For now we deal with this by calling a
563 : // requestmediachange once they've joined.
564 14 : for (const auto& m : conf->currentMediaList()) {
565 : // We only expect to have 1 audio stream, add it.
566 26 : if (m.at(libjami::Media::MediaAttributeKey::MEDIA_TYPE)
567 13 : == libjami::Media::MediaAttributeValue::AUDIO) {
568 7 : currentMediaList.emplace_back(m);
569 6 : } else if (call->hasVideo()
570 18 : && m.at(libjami::Media::MediaAttributeKey::MEDIA_TYPE)
571 6 : == libjami::Media::MediaAttributeValue::VIDEO) {
572 : // Add the first video media and break the loop
573 : // other video medias will be requested when joined.
574 6 : currentMediaList.emplace_back(m);
575 6 : break;
576 : }
577 7 : }
578 : }
579 7 : }
580 10 : Manager::instance().acceptCall(*call, currentMediaList);
581 :
582 10 : if (isNotHosting) {
583 9 : JAMI_DEBUG("Creating conference for swarm {} with ID {}", conversationId, confId);
584 : // Create conference and host it.
585 3 : convModule()->hostConference(conversationId, confId, callId);
586 : } else {
587 21 : JAMI_DEBUG("Adding participant {} for swarm {} with ID {}", callId, conversationId, confId);
588 7 : Manager::instance().addAudio(*call);
589 7 : conf->addSubCall(callId);
590 14 : emitSignal<libjami::CallSignal::ConferenceChanged>(getAccountID(),
591 7 : conf->getConfId(),
592 : conf->getStateStr());
593 : }
594 10 : }
595 :
596 : std::shared_ptr<SIPCall>
597 178 : JamiAccount::createSubCall(const std::shared_ptr<SIPCall>& mainCall)
598 : {
599 178 : auto mediaList = MediaAttribute::mediaAttributesToMediaMaps(mainCall->getMediaAttributeList());
600 356 : return Manager::instance().callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
601 178 : }
602 :
603 : void
604 89 : JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri)
605 : {
606 89 : if (not accountManager_ or not dht_) {
607 0 : call->onFailure(ENETDOWN);
608 0 : return;
609 : }
610 :
611 : // TODO: for now, we automatically trust all explicitly called peers
612 89 : setCertificateStatus(toUri, dhtnet::tls::TrustStore::PermissionStatus::ALLOWED);
613 :
614 89 : call->setState(Call::ConnectionState::TRYING);
615 89 : std::weak_ptr<SIPCall> wCall = call;
616 :
617 : #ifdef ENABLE_NAMESERVER
618 89 : accountManager_->lookupAddress(toUri,
619 89 : [wCall](const std::string& regName,
620 : const std::string& address,
621 : const NameDirectory::Response& response) {
622 89 : if (response == NameDirectory::Response::found)
623 1 : if (auto call = wCall.lock()) {
624 1 : call->setPeerRegisteredName(regName);
625 1 : }
626 89 : });
627 : #endif
628 :
629 89 : dht::InfoHash peer_account(toUri);
630 :
631 : // Call connected devices
632 89 : std::set<DeviceId> devices;
633 89 : std::unique_lock lkSipConn(sipConnsMtx_);
634 : // NOTE: dummyCall is a call used to avoid to mark the call as failed if the
635 : // cached connection is failing with ICE (close event still not detected).
636 89 : auto dummyCall = createSubCall(call);
637 :
638 89 : call->addSubCall(*dummyCall);
639 89 : dummyCall->setIceMedia(call->getIceMedia());
640 : auto sendRequest =
641 89 : [this, wCall, toUri, dummyCall = std::move(dummyCall)](const DeviceId& deviceId,
642 425 : bool eraseDummy) {
643 174 : if (eraseDummy) {
644 : // Mark the temp call as failed to stop the main call if necessary
645 89 : if (dummyCall)
646 89 : dummyCall->onFailure(static_cast<int>(std::errc::no_such_device_or_address));
647 89 : return;
648 : }
649 85 : auto call = wCall.lock();
650 85 : if (not call)
651 0 : return;
652 85 : auto state = call->getConnectionState();
653 85 : if (state != Call::ConnectionState::PROGRESSING
654 85 : and state != Call::ConnectionState::TRYING)
655 0 : return;
656 :
657 85 : auto dev_call = createSubCall(call);
658 85 : dev_call->setPeerNumber(call->getPeerNumber());
659 85 : dev_call->setState(Call::ConnectionState::TRYING);
660 85 : call->addStateListener(
661 251 : [w = weak(), deviceId](Call::CallState, Call::ConnectionState state, int) {
662 251 : if (state != Call::ConnectionState::PROGRESSING
663 85 : and state != Call::ConnectionState::TRYING) {
664 85 : if (auto shared = w.lock())
665 85 : shared->callConnectionClosed(deviceId, true);
666 85 : return false;
667 : }
668 166 : return true;
669 : });
670 85 : call->addSubCall(*dev_call);
671 85 : dev_call->setIceMedia(call->getIceMedia());
672 : {
673 85 : std::lock_guard lk(pendingCallsMutex_);
674 85 : pendingCalls_[deviceId].emplace_back(dev_call);
675 85 : }
676 :
677 255 : JAMI_WARNING("[call {}] No channeled socket with this peer. Send request",
678 : call->getCallId());
679 : // Else, ask for a channel (for future calls/text messages)
680 85 : auto type = call->hasVideo() ? "videoCall" : "audioCall";
681 85 : requestSIPConnection(toUri, deviceId, type, true, dev_call);
682 263 : };
683 :
684 89 : std::vector<std::shared_ptr<dhtnet::ChannelSocket>> channels;
685 141 : for (auto& [key, value] : sipConns_) {
686 52 : if (key.first != toUri)
687 48 : continue;
688 4 : if (value.empty())
689 0 : continue;
690 4 : auto& sipConn = value.back();
691 :
692 4 : if (!sipConn.channel) {
693 0 : JAMI_WARNING("A SIP transport exists without Channel, this is a bug. Please report");
694 0 : continue;
695 0 : }
696 :
697 4 : auto transport = sipConn.transport;
698 4 : auto remote_address = sipConn.channel->getRemoteAddress();
699 4 : if (!transport or !remote_address)
700 0 : continue;
701 :
702 4 : channels.emplace_back(sipConn.channel);
703 :
704 12 : JAMI_WARNING("[call {}] A channeled socket is detected with this peer.", call->getCallId());
705 :
706 4 : auto dev_call = createSubCall(call);
707 4 : dev_call->setPeerNumber(call->getPeerNumber());
708 4 : dev_call->setSipTransport(transport, getContactHeader(transport));
709 4 : call->addSubCall(*dev_call);
710 4 : dev_call->setIceMedia(call->getIceMedia());
711 :
712 : // Set the call in PROGRESSING State because the ICE session
713 : // is already ready. Note that this line should be after
714 : // addSubcall() to change the state of the main call
715 : // and avoid to get an active call in a TRYING state.
716 4 : dev_call->setState(Call::ConnectionState::PROGRESSING);
717 :
718 : {
719 4 : std::lock_guard lk(onConnectionClosedMtx_);
720 4 : onConnectionClosed_[key.second] = sendRequest;
721 4 : }
722 :
723 4 : call->addStateListener(
724 12 : [w = weak(), deviceId = key.second](Call::CallState, Call::ConnectionState state, int) {
725 12 : if (state != Call::ConnectionState::PROGRESSING
726 4 : and state != Call::ConnectionState::TRYING) {
727 4 : if (auto shared = w.lock())
728 4 : shared->callConnectionClosed(deviceId, true);
729 4 : return false;
730 : }
731 8 : return true;
732 : });
733 :
734 : try {
735 4 : onConnectedOutgoingCall(dev_call, toUri, remote_address);
736 0 : } catch (const VoipLinkException&) {
737 : // In this case, the main scenario is that SIPStartCall failed because
738 : // the ICE is dead and the TLS session didn't send any packet on that dead
739 : // link (connectivity change, killed by the os, etc)
740 : // Here, we don't need to do anything, the TLS will fail and will delete
741 : // the cached transport
742 0 : continue;
743 0 : }
744 4 : devices.emplace(key.second);
745 4 : }
746 :
747 89 : lkSipConn.unlock();
748 : // Note: Send beacon can destroy the socket (if storing last occurence of shared_ptr)
749 : // causing sipConn to be destroyed. So, do it while sipConns_ not locked.
750 93 : for (const auto& channel : channels)
751 4 : channel->sendBeacon();
752 :
753 : // Find listening devices for this account
754 178 : accountManager_->forEachDevice(
755 : peer_account,
756 89 : [this, devices = std::move(devices), sendRequest](
757 170 : const std::shared_ptr<dht::crypto::PublicKey>& dev) {
758 : // Test if already sent via a SIP transport
759 89 : auto deviceId = dev->getLongId();
760 89 : if (devices.find(deviceId) != devices.end())
761 4 : return;
762 : {
763 85 : std::lock_guard lk(onConnectionClosedMtx_);
764 85 : onConnectionClosed_[deviceId] = sendRequest;
765 85 : }
766 85 : sendRequest(deviceId, false);
767 : },
768 89 : [wCall](bool ok) {
769 89 : if (not ok) {
770 1 : if (auto call = wCall.lock()) {
771 3 : JAMI_WARNING("[call:{}] No devices found", call->getCallId());
772 : // Note: if a P2P connection exists, the call will be at least in CONNECTING
773 1 : if (call->getConnectionState() == Call::ConnectionState::TRYING)
774 1 : call->onFailure(static_cast<int>(std::errc::no_such_device_or_address));
775 1 : }
776 : }
777 89 : });
778 89 : }
779 :
780 : void
781 97 : JamiAccount::onConnectedOutgoingCall(const std::shared_ptr<SIPCall>& call,
782 : const std::string& to_id,
783 : dhtnet::IpAddr target)
784 : {
785 97 : if (!call)
786 0 : return;
787 291 : JAMI_LOG("[call:{}] Outgoing call connected to {}", call->getCallId(), to_id);
788 :
789 97 : const auto localAddress = dhtnet::ip_utils::getInterfaceAddr(getLocalInterface(),
790 97 : target.getFamily());
791 :
792 97 : dhtnet::IpAddr addrSdp = getPublishedSameasLocal()
793 : ? localAddress
794 97 : : connectionManager_->getPublishedIpAddress(target.getFamily());
795 :
796 : // fallback on local address
797 97 : if (not addrSdp)
798 0 : addrSdp = localAddress;
799 :
800 : // Building the local SDP offer
801 97 : auto& sdp = call->getSDP();
802 :
803 97 : sdp.setPublishedIP(addrSdp);
804 :
805 97 : auto mediaAttrList = call->getMediaAttributeList();
806 97 : if (mediaAttrList.empty()) {
807 0 : JAMI_ERROR("[call:{}] No media. Abort!", call->getCallId());
808 0 : return;
809 : }
810 :
811 97 : if (not sdp.createOffer(mediaAttrList)) {
812 0 : JAMI_ERROR("[call:{}] Unable to send outgoing INVITE request for new call",
813 : call->getCallId());
814 0 : return;
815 : }
816 :
817 : // Note: pj_ice_strans_create can call onComplete in the same thread
818 : // This means that iceMutex_ in IceTransport can be locked when onInitDone is called
819 : // So, we need to run the call creation in the main thread
820 : // Also, we do not directly call SIPStartCall before receiving onInitDone, because
821 : // there is an inside waitForInitialization that can block the thread.
822 : // Note: avoid runMainThread as SIPStartCall use transportMutex
823 97 : dht::ThreadPool::io().run([w = weak(), call = std::move(call), target] {
824 97 : auto account = w.lock();
825 97 : if (not account)
826 0 : return;
827 :
828 97 : if (not account->SIPStartCall(*call, target)) {
829 0 : JAMI_ERROR("[call:{}] Unable to send outgoing INVITE request for new call",
830 : call->getCallId());
831 : }
832 97 : });
833 97 : }
834 :
835 : bool
836 97 : JamiAccount::SIPStartCall(SIPCall& call, const dhtnet::IpAddr& target)
837 : {
838 291 : JAMI_LOG("[call:{}] Start SIP call", call.getCallId());
839 :
840 97 : if (call.isIceEnabled())
841 97 : call.addLocalIceAttributes();
842 :
843 194 : std::string toUri(getToUri(call.getPeerNumber() + "@"
844 291 : + target.toString(true))); // expecting a fully well formed sip uri
845 :
846 97 : pj_str_t pjTo = sip_utils::CONST_PJ_STR(toUri);
847 :
848 : // Create the from header
849 97 : std::string from(getFromUri());
850 97 : pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
851 :
852 97 : std::string targetStr = getToUri(target.toString(true));
853 97 : pj_str_t pjTarget = sip_utils::CONST_PJ_STR(targetStr);
854 :
855 97 : auto contact = call.getContactHeader();
856 97 : auto pjContact = sip_utils::CONST_PJ_STR(contact);
857 :
858 291 : JAMI_LOG("[call:{}] Contact header: {} / {} -> {} / {}",
859 : call.getCallId(),
860 : contact,
861 : from,
862 : toUri,
863 : targetStr);
864 :
865 97 : auto local_sdp = call.getSDP().getLocalSdpSession();
866 97 : pjsip_dialog* dialog {nullptr};
867 97 : pjsip_inv_session* inv {nullptr};
868 97 : if (!CreateClientDialogAndInvite(&pjFrom, &pjContact, &pjTo, &pjTarget, local_sdp, &dialog, &inv))
869 0 : return false;
870 :
871 97 : inv->mod_data[link_.getModId()] = &call;
872 97 : call.setInviteSession(inv);
873 :
874 : pjsip_tx_data* tdata;
875 :
876 97 : if (pjsip_inv_invite(call.inviteSession_.get(), &tdata) != PJ_SUCCESS) {
877 0 : JAMI_ERROR("[call:{}] Unable to initialize invite", call.getCallId());
878 0 : return false;
879 : }
880 :
881 : pjsip_tpselector tp_sel;
882 97 : tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
883 97 : if (!call.getTransport()) {
884 0 : JAMI_ERROR("[call:{}] Unable to get transport", call.getCallId());
885 0 : return false;
886 : }
887 97 : tp_sel.u.transport = call.getTransport()->get();
888 97 : if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
889 0 : JAMI_ERROR("[call:{}] Unable to associate transport for invite session dialog",
890 : call.getCallId());
891 0 : return false;
892 : }
893 :
894 291 : JAMI_LOG("[call:{}] Sending SIP invite", call.getCallId());
895 :
896 : // Add user-agent header
897 97 : sip_utils::addUserAgentHeader(getUserAgentName(), tdata);
898 :
899 97 : if (pjsip_inv_send_msg(call.inviteSession_.get(), tdata) != PJ_SUCCESS) {
900 0 : JAMI_ERROR("[call:{}] Unable to send invite message", call.getCallId());
901 0 : return false;
902 : }
903 :
904 97 : call.setState(Call::CallState::ACTIVE, Call::ConnectionState::PROGRESSING);
905 97 : return true;
906 97 : }
907 :
908 : void
909 2064 : JamiAccount::saveConfig() const
910 : {
911 : try {
912 2064 : YAML::Emitter accountOut;
913 2064 : config().serialize(accountOut);
914 2064 : auto accountConfig = config().path / "config.yml";
915 2064 : std::lock_guard lock(dhtnet::fileutils::getFileLock(accountConfig));
916 2064 : std::ofstream fout(accountConfig);
917 2064 : fout.write(accountOut.c_str(), accountOut.size());
918 6192 : JAMI_LOG("Saved account config to {}", accountConfig);
919 2064 : } catch (const std::exception& e) {
920 0 : JAMI_ERROR("Error saving account config: {}", e.what());
921 0 : }
922 2064 : }
923 :
924 : void
925 647 : JamiAccount::loadConfig()
926 : {
927 647 : SIPAccountBase::loadConfig();
928 647 : registeredName_ = config().registeredName;
929 647 : if (accountManager_)
930 17 : accountManager_->setAccountDeviceName(config().deviceName);
931 647 : if (connectionManager_) {
932 13 : if (auto c = connectionManager_->getConfig()) {
933 : // Update connectionManager's config
934 13 : c->upnpEnabled = config().upnpEnabled;
935 13 : c->turnEnabled = config().turnEnabled;
936 13 : c->turnServer = config().turnServer;
937 13 : c->turnServerUserName = config().turnServerUserName;
938 13 : c->turnServerPwd = config().turnServerPwd;
939 13 : c->turnServerRealm = config().turnServerRealm;
940 13 : }
941 : }
942 647 : if (config().proxyEnabled) {
943 : try {
944 0 : auto str = fileutils::loadCacheTextFile(cachePath_ / "dhtproxy",
945 0 : std::chrono::hours(24 * 14));
946 0 : Json::Value root;
947 0 : if (json::parse(str, root)) {
948 0 : proxyServerCached_ = root[getProxyConfigKey()].asString();
949 : }
950 0 : } catch (const std::exception& e) {
951 0 : JAMI_LOG("[Account {}] Unable to load proxy URL from cache: {}",
952 : getAccountID(),
953 : e.what());
954 0 : proxyServerCached_.clear();
955 0 : }
956 : } else {
957 647 : proxyServerCached_.clear();
958 647 : std::error_code ec;
959 647 : std::filesystem::remove(cachePath_ / "dhtproxy", ec);
960 : }
961 647 : auto credentials = consumeConfigCredentials();
962 647 : loadAccount(credentials.archive_password_scheme,
963 : credentials.archive_password,
964 : credentials.archive_path);
965 647 : }
966 :
967 : bool
968 4 : JamiAccount::changeArchivePassword(const std::string& password_old, const std::string& password_new)
969 : {
970 : try {
971 4 : if (!accountManager_->changePassword(password_old, password_new)) {
972 6 : JAMI_ERROR("[Account {}] Unable to change archive password", getAccountID());
973 2 : return false;
974 : }
975 2 : editConfig([&](JamiAccountConfig& config) {
976 2 : config.archiveHasPassword = not password_new.empty();
977 2 : });
978 0 : } catch (const std::exception& ex) {
979 0 : JAMI_ERROR("[Account {}] Unable to change archive password: {}", getAccountID(), ex.what());
980 0 : if (password_old.empty()) {
981 0 : editConfig([&](JamiAccountConfig& config) { config.archiveHasPassword = true; });
982 0 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
983 0 : getAccountDetails());
984 : }
985 0 : return false;
986 0 : }
987 2 : if (password_old != password_new)
988 2 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
989 4 : getAccountDetails());
990 2 : return true;
991 : }
992 :
993 : bool
994 3 : JamiAccount::isPasswordValid(const std::string& password)
995 : {
996 3 : return accountManager_ and accountManager_->isPasswordValid(password);
997 : }
998 :
999 : std::vector<uint8_t>
1000 0 : JamiAccount::getPasswordKey(const std::string& password)
1001 : {
1002 0 : return accountManager_ ? accountManager_->getPasswordKey(password) : std::vector<uint8_t>();
1003 : }
1004 :
1005 : bool
1006 7 : JamiAccount::provideAccountAuthentication(const std::string& credentialsFromUser,
1007 : const std::string& scheme)
1008 : {
1009 7 : if (auto manager = std::dynamic_pointer_cast<ArchiveAccountManager>(accountManager_)) {
1010 7 : return manager->provideAccountAuthentication(credentialsFromUser, scheme);
1011 7 : }
1012 0 : JAMI_ERR("[LinkDevice] Invalid AccountManager instance while providing current account "
1013 : "authentication.");
1014 0 : return false;
1015 : }
1016 :
1017 : int32_t
1018 5 : JamiAccount::addDevice(const std::string& uriProvided)
1019 : {
1020 15 : JAMI_LOG("[LinkDevice] JamiAccount::addDevice({}, {})", getAccountID(), uriProvided);
1021 5 : if (not accountManager_) {
1022 0 : JAMI_ERR("[LinkDevice] Invalid AccountManager instance while adding a device.");
1023 0 : return static_cast<int32_t>(AccountManager::AddDeviceError::GENERIC);
1024 : }
1025 5 : auto authHandler = channelHandlers_.find(Uri::Scheme::AUTH);
1026 5 : if (authHandler == channelHandlers_.end())
1027 0 : return static_cast<int32_t>(AccountManager::AddDeviceError::GENERIC);
1028 10 : return accountManager_->addDevice(uriProvided,
1029 5 : config().archiveHasPassword
1030 5 : ? fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD
1031 : : fileutils::ARCHIVE_AUTH_SCHEME_NONE,
1032 10 : (AuthChannelHandler*) authHandler->second.get());
1033 : }
1034 :
1035 : bool
1036 0 : JamiAccount::cancelAddDevice(uint32_t op_token)
1037 : {
1038 0 : if (!accountManager_)
1039 0 : return false;
1040 0 : return accountManager_->cancelAddDevice(op_token);
1041 : }
1042 :
1043 : bool
1044 4 : JamiAccount::confirmAddDevice(uint32_t op_token)
1045 : {
1046 4 : if (!accountManager_)
1047 0 : return false;
1048 4 : return accountManager_->confirmAddDevice(op_token);
1049 : }
1050 :
1051 : bool
1052 31 : JamiAccount::exportArchive(const std::string& destinationPath,
1053 : std::string_view scheme,
1054 : const std::string& password)
1055 : {
1056 31 : if (auto manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) {
1057 31 : return manager->exportArchive(destinationPath, scheme, password);
1058 : }
1059 0 : return false;
1060 : }
1061 :
1062 : bool
1063 2 : JamiAccount::setValidity(std::string_view scheme,
1064 : const std::string& pwd,
1065 : const dht::InfoHash& id,
1066 : int64_t validity)
1067 : {
1068 2 : if (auto manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) {
1069 2 : if (manager->setValidity(scheme, pwd, id_, id, validity)) {
1070 2 : saveIdentity(id_, idPath_, DEVICE_ID_PATH);
1071 2 : return true;
1072 : }
1073 : }
1074 0 : return false;
1075 : }
1076 :
1077 : void
1078 4 : JamiAccount::forceReloadAccount()
1079 : {
1080 4 : editConfig([&](JamiAccountConfig& conf) {
1081 4 : conf.receipt.clear();
1082 4 : conf.receiptSignature.clear();
1083 4 : });
1084 4 : loadAccount();
1085 4 : }
1086 :
1087 : void
1088 0 : JamiAccount::unlinkConversations(const std::set<std::string>& removed)
1089 : {
1090 0 : std::lock_guard lock(configurationMutex_);
1091 0 : if (auto info = accountManager_->getInfo()) {
1092 0 : auto contacts = info->contacts->getContacts();
1093 0 : for (auto& [id, c] : contacts) {
1094 0 : if (removed.find(c.conversationId) != removed.end()) {
1095 0 : info->contacts->updateConversation(id, "");
1096 0 : JAMI_WARNING(
1097 : "[Account {}] Detected removed conversation ({}) in contact details for {}",
1098 : getAccountID(),
1099 : c.conversationId,
1100 : id.toString());
1101 : }
1102 : }
1103 0 : }
1104 0 : }
1105 :
1106 : bool
1107 772 : JamiAccount::isValidAccountDevice(const dht::crypto::Certificate& cert) const
1108 : {
1109 772 : if (accountManager_) {
1110 772 : if (auto info = accountManager_->getInfo()) {
1111 772 : if (info->contacts)
1112 772 : return info->contacts->isValidAccountDevice(cert).isValid();
1113 : }
1114 : }
1115 0 : return false;
1116 : }
1117 :
1118 : bool
1119 3 : JamiAccount::revokeDevice(const std::string& device,
1120 : std::string_view scheme,
1121 : const std::string& password)
1122 : {
1123 3 : if (not accountManager_)
1124 0 : return false;
1125 6 : return accountManager_->revokeDevice(
1126 6 : device, scheme, password, [this, device](AccountManager::RevokeDeviceResult result) {
1127 3 : emitSignal<libjami::ConfigurationSignal::DeviceRevocationEnded>(getAccountID(),
1128 3 : device,
1129 : static_cast<int>(
1130 : result));
1131 6 : });
1132 : return true;
1133 : }
1134 :
1135 : std::pair<std::string, std::string>
1136 632 : JamiAccount::saveIdentity(const dht::crypto::Identity id,
1137 : const std::filesystem::path& path,
1138 : const std::string& name)
1139 : {
1140 1264 : auto names = std::make_pair(name + ".key", name + ".crt");
1141 632 : if (id.first)
1142 632 : fileutils::saveFile(path / names.first, id.first->serialize(), 0600);
1143 632 : if (id.second)
1144 632 : fileutils::saveFile(path / names.second, id.second->getPacked(), 0600);
1145 632 : return names;
1146 0 : }
1147 :
1148 : // must be called while configurationMutex_ is locked
1149 : void
1150 651 : JamiAccount::loadAccount(const std::string& archive_password_scheme,
1151 : const std::string& archive_password,
1152 : const std::string& archive_path)
1153 : {
1154 651 : if (registrationState_ == RegistrationState::INITIALIZING)
1155 4 : return;
1156 :
1157 1941 : JAMI_DEBUG("[Account {:s}] Loading account", getAccountID());
1158 : AccountManager::OnChangeCallback callbacks {
1159 274 : [this](const std::string& uri, bool confirmed) {
1160 138 : if (!id_.first)
1161 2 : return;
1162 136 : if (jami::Manager::instance().syncOnRegister) {
1163 136 : dht::ThreadPool::io().run([w = weak(), uri, confirmed] {
1164 136 : if (auto shared = w.lock()) {
1165 136 : if (auto cm = shared->convModule(true)) {
1166 136 : auto activeConv = cm->getOneToOneConversation(uri);
1167 136 : if (!activeConv.empty())
1168 136 : cm->bootstrap(activeConv);
1169 136 : }
1170 136 : emitSignal<libjami::ConfigurationSignal::ContactAdded>(shared->getAccountID(),
1171 136 : uri,
1172 : confirmed);
1173 136 : }
1174 136 : });
1175 : }
1176 : },
1177 48 : [this](const std::string& uri, bool banned) {
1178 24 : if (!id_.first)
1179 0 : return;
1180 24 : dht::ThreadPool::io().run([w = weak(), uri, banned] {
1181 24 : if (auto shared = w.lock()) {
1182 : // Erase linked conversation's requests
1183 24 : if (auto convModule = shared->convModule(true))
1184 24 : convModule->removeContact(uri, banned);
1185 : // Remove current connections with contact
1186 : // Note: if contact is ourself, we don't close the connection
1187 : // because it's used for syncing other conversations.
1188 24 : if (shared->connectionManager_ && uri != shared->getUsername()) {
1189 23 : shared->connectionManager_->closeConnectionsWith(uri);
1190 : }
1191 : // Update client.
1192 24 : emitSignal<libjami::ConfigurationSignal::ContactRemoved>(shared->getAccountID(),
1193 24 : uri,
1194 : banned);
1195 24 : }
1196 24 : });
1197 : },
1198 120 : [this](const std::string& uri,
1199 : const std::string& conversationId,
1200 : const std::vector<uint8_t>& payload,
1201 240 : time_t received) {
1202 120 : if (!id_.first)
1203 0 : return;
1204 120 : dht::ThreadPool::io().run([w = weak(), uri, conversationId, payload, received] {
1205 120 : if (auto shared = w.lock()) {
1206 120 : shared->clearProfileCache(uri);
1207 120 : if (conversationId.empty()) {
1208 : // Old path
1209 0 : emitSignal<libjami::ConfigurationSignal::IncomingTrustRequest>(
1210 0 : shared->getAccountID(), conversationId, uri, payload, received);
1211 0 : return;
1212 : }
1213 : // Here account can be initializing
1214 120 : if (auto cm = shared->convModule(true)) {
1215 120 : auto activeConv = cm->getOneToOneConversation(uri);
1216 120 : if (activeConv != conversationId)
1217 91 : cm->onTrustRequest(uri, conversationId, payload, received);
1218 120 : }
1219 120 : }
1220 : });
1221 : },
1222 1510 : [this](const std::map<DeviceId, KnownDevice>& devices) {
1223 755 : std::map<std::string, std::string> ids;
1224 1621 : for (auto& d : devices) {
1225 866 : auto id = d.first.toString();
1226 866 : auto label = d.second.name.empty() ? id.substr(0, 8) : d.second.name;
1227 866 : ids.emplace(std::move(id), std::move(label));
1228 866 : }
1229 755 : runOnMainThread([id = getAccountID(), devices = std::move(ids)] {
1230 755 : emitSignal<libjami::ConfigurationSignal::KnownDevicesChanged>(id, devices);
1231 755 : });
1232 755 : },
1233 73 : [this](const std::string& conversationId, const std::string& deviceId) {
1234 : // Note: Do not retrigger on another thread. This has to be done
1235 : // at the same time of acceptTrustRequest a synced state between TrustRequest
1236 : // and convRequests.
1237 73 : if (auto cm = convModule(true))
1238 73 : cm->acceptConversationRequest(conversationId, deviceId);
1239 73 : },
1240 164 : [this](const std::string& uri, const std::string& convFromReq) {
1241 82 : dht::ThreadPool::io().run([w = weak(), convFromReq, uri] {
1242 82 : if (auto shared = w.lock()) {
1243 82 : auto cm = shared->convModule(true);
1244 : // Remove cached payload if there is one
1245 164 : auto requestPath = shared->cachePath_ / "requests" / uri;
1246 82 : dhtnet::fileutils::remove(requestPath);
1247 : // Following replay logic could incorrectly be triggered for conversations
1248 : // already fetched and used by other devices, so it is disabled for now.
1249 : /*if (!convFromReq.empty()) {
1250 : auto oldConv = cm->getOneToOneConversation(uri);
1251 : // If we previously removed the contact, and re-add it, we may
1252 : // receive a convId different from the request. In that case,
1253 : // we need to remove the current conversation and clone the old
1254 : // one (given by convFromReq).
1255 : // TODO: In the future, we may want to re-commit the messages we
1256 : // may have send in the request we sent.
1257 : if (oldConv != convFromReq
1258 : && cm->updateConvForContact(uri, oldConv, convFromReq)) {
1259 : cm->initReplay(oldConv, convFromReq);
1260 : cm->cloneConversationFrom(convFromReq, uri, oldConv);
1261 : }
1262 : }*/
1263 164 : }
1264 82 : });
1265 729 : }};
1266 :
1267 647 : const auto& conf = config();
1268 : try {
1269 647 : auto oldIdentity = id_.first ? id_.first->getPublicKey().getLongId() : DeviceId();
1270 647 : if (conf.managerUri.empty()) {
1271 1941 : accountManager_ = std::make_shared<ArchiveAccountManager>(
1272 647 : getAccountID(),
1273 : getPath(),
1274 35 : [this]() { return getAccountDetails(); },
1275 1263 : [this](DeviceSync&& syncData) {
1276 1263 : if (auto sm = syncModule()) {
1277 1263 : auto syncDataPtr = std::make_shared<SyncMsg>();
1278 1263 : syncDataPtr->ds = std::move(syncData);
1279 1263 : sm->syncWithConnected(syncDataPtr);
1280 1263 : }
1281 1263 : },
1282 1294 : conf.archivePath.empty() ? "archive.gz" : conf.archivePath,
1283 1294 : conf.nameServer);
1284 : } else {
1285 0 : accountManager_ = std::make_shared<ServerAccountManager>(getAccountID(),
1286 : getPath(),
1287 0 : conf.managerUri,
1288 0 : conf.nameServer);
1289 : }
1290 :
1291 1294 : auto id = accountManager_->loadIdentity(conf.tlsCertificateFile,
1292 647 : conf.tlsPrivateKeyFile,
1293 647 : conf.tlsPassword);
1294 :
1295 1294 : if (auto info = accountManager_->useIdentity(id,
1296 647 : conf.receipt,
1297 647 : conf.receiptSignature,
1298 647 : conf.managerUsername,
1299 : callbacks)) {
1300 : // normal loading path
1301 13 : id_ = std::move(id);
1302 13 : config_->username = info->accountId;
1303 39 : JAMI_WARNING("[Account {:s}] Loaded account identity", getAccountID());
1304 13 : if (info->identity.first->getPublicKey().getLongId() != oldIdentity) {
1305 0 : JAMI_WARNING("[Account {:s}] Identity changed", getAccountID());
1306 : {
1307 0 : std::lock_guard lk(moduleMtx_);
1308 0 : convModule_.reset();
1309 0 : }
1310 0 : convModule(); // convModule must absolutely be initialized in
1311 : // both branches of the if statement here in order
1312 : // for it to exist for subsequent use.
1313 : } else {
1314 13 : convModule()->setAccountManager(accountManager_);
1315 : }
1316 13 : if (not isEnabled()) {
1317 0 : setRegistrationState(RegistrationState::UNREGISTERED);
1318 : }
1319 13 : convModule()->loadConversations();
1320 634 : } else if (isEnabled()) {
1321 1902 : JAMI_WARNING("[Account {}] useIdentity failed!", getAccountID());
1322 634 : if (not conf.managerUri.empty() and archive_password.empty()) {
1323 0 : Migration::setState(accountID_, Migration::State::INVALID);
1324 0 : setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1325 0 : return;
1326 : }
1327 :
1328 634 : bool migrating = registrationState_ == RegistrationState::ERROR_NEED_MIGRATION;
1329 634 : setRegistrationState(RegistrationState::INITIALIZING);
1330 634 : auto fDeviceKey = dht::ThreadPool::computation()
1331 634 : .getShared<std::shared_ptr<dht::crypto::PrivateKey>>([]() {
1332 : return std::make_shared<dht::crypto::PrivateKey>(
1333 1268 : dht::crypto::PrivateKey::generate());
1334 634 : });
1335 :
1336 634 : std::unique_ptr<AccountManager::AccountCredentials> creds;
1337 634 : if (conf.managerUri.empty()) {
1338 634 : auto acreds = std::make_unique<ArchiveAccountManager::ArchiveAccountCredentials>();
1339 634 : auto archivePath = fileutils::getFullPath(idPath_, conf.archivePath);
1340 : // bool hasArchive = std::filesystem::is_regular_file(archivePath);
1341 :
1342 634 : if (!archive_path.empty()) {
1343 : // Importing external archive
1344 31 : acreds->scheme = "file";
1345 31 : acreds->uri = archive_path;
1346 603 : } else if (!conf.archive_url.empty() && conf.archive_url == "jami-auth") {
1347 : // Importing over a Peer2Peer TLS connection with DHT as DNS
1348 15 : JAMI_DEBUG("[Account {}] [LinkDevice] scheme p2p & uri {}", getAccountID(), conf.archive_url);
1349 5 : acreds->scheme = "p2p";
1350 5 : acreds->uri = conf.archive_url;
1351 598 : } else if (std::filesystem::is_regular_file(archivePath)) {
1352 : // Migrating local account
1353 4 : acreds->scheme = "local";
1354 4 : acreds->uri = archivePath.string();
1355 4 : acreds->updateIdentity = id;
1356 4 : migrating = true;
1357 : }
1358 634 : creds = std::move(acreds);
1359 634 : } else {
1360 0 : auto screds = std::make_unique<ServerAccountManager::ServerAccountCredentials>();
1361 0 : screds->username = conf.managerUsername;
1362 0 : creds = std::move(screds);
1363 0 : }
1364 634 : creds->password = archive_password;
1365 634 : bool hasPassword = !archive_password.empty();
1366 634 : if (hasPassword && archive_password_scheme.empty())
1367 10 : creds->password_scheme = fileutils::ARCHIVE_AUTH_SCHEME_PASSWORD;
1368 : else
1369 624 : creds->password_scheme = archive_password_scheme;
1370 :
1371 1902 : JAMI_WARNING("[Account {}] initAuthentication {} {}",
1372 : getAccountID(),
1373 : fmt::ptr(this),
1374 : fmt::ptr(accountManager_));
1375 :
1376 3170 : accountManager_->initAuthentication(
1377 : fDeviceKey,
1378 1268 : ip_utils::getDeviceName(),
1379 634 : std::move(creds),
1380 630 : [w = weak(),
1381 : this,
1382 : migrating,
1383 : hasPassword](const AccountInfo& info,
1384 : const std::map<std::string, std::string>& config,
1385 : std::string&& receipt,
1386 7548 : std::vector<uint8_t>&& receipt_signature) {
1387 630 : auto sthis = w.lock();
1388 630 : if (not sthis)
1389 0 : return;
1390 1890 : JAMI_LOG("[Account {}] Auth success! Device: {}", getAccountID(), info.deviceId);
1391 :
1392 630 : dhtnet::fileutils::check_dir(idPath_, 0700);
1393 :
1394 630 : auto id = info.identity;
1395 630 : editConfig([&](JamiAccountConfig& conf) {
1396 630 : std::tie(conf.tlsPrivateKeyFile, conf.tlsCertificateFile)
1397 1260 : = saveIdentity(id, idPath_, DEVICE_ID_PATH);
1398 630 : conf.tlsPassword = {};
1399 630 : auto passwordIt = config.find(
1400 : libjami::Account::ConfProperties::ARCHIVE_HAS_PASSWORD);
1401 630 : if (passwordIt != config.end() && !passwordIt->second.empty()) {
1402 630 : conf.archiveHasPassword = passwordIt->second == "true";
1403 : } else {
1404 0 : conf.archiveHasPassword = hasPassword;
1405 : }
1406 :
1407 630 : if (not conf.managerUri.empty()) {
1408 0 : conf.registeredName = conf.managerUsername;
1409 : #ifdef ENABLE_NAMESERVER
1410 0 : registeredName_ = conf.managerUsername;
1411 : #endif
1412 : }
1413 630 : conf.username = info.accountId;
1414 630 : conf.deviceName = accountManager_->getAccountDeviceName();
1415 :
1416 630 : auto nameServerIt = config.find(
1417 : libjami::Account::ConfProperties::Nameserver::URI);
1418 630 : if (nameServerIt != config.end() && !nameServerIt->second.empty()) {
1419 0 : conf.nameServer = nameServerIt->second;
1420 : }
1421 630 : auto displayNameIt = config.find(
1422 : libjami::Account::ConfProperties::DISPLAYNAME);
1423 630 : if (displayNameIt != config.end() && !displayNameIt->second.empty()) {
1424 34 : conf.displayName = displayNameIt->second;
1425 : }
1426 630 : conf.receipt = std::move(receipt);
1427 630 : conf.receiptSignature = std::move(receipt_signature);
1428 630 : conf.fromMap(config);
1429 630 : });
1430 630 : id_ = std::move(id);
1431 : {
1432 630 : std::lock_guard lk(moduleMtx_);
1433 630 : convModule_.reset();
1434 630 : }
1435 630 : if (migrating) {
1436 4 : Migration::setState(getAccountID(), Migration::State::SUCCESS);
1437 : }
1438 630 : if (not info.photo.empty() or not config_->displayName.empty())
1439 622 : emitSignal<libjami::ConfigurationSignal::AccountProfileReceived>(
1440 622 : getAccountID(), config_->displayName, info.photo);
1441 630 : setRegistrationState(RegistrationState::UNREGISTERED);
1442 630 : doRegister();
1443 630 : },
1444 0 : [w = weak(),
1445 : id,
1446 634 : accountId = getAccountID(),
1447 0 : migrating](AccountManager::AuthError error, const std::string& message) {
1448 0 : JAMI_WARNING("[Account {}] Auth error: {} {}", accountId, (int) error, message);
1449 0 : if ((id.first || migrating)
1450 0 : && error == AccountManager::AuthError::INVALID_ARGUMENTS) {
1451 : // In cast of a migration or manager connexion failure stop the migration
1452 : // and block the account
1453 0 : Migration::setState(accountId, Migration::State::INVALID);
1454 0 : if (auto acc = w.lock())
1455 0 : acc->setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
1456 : } else {
1457 : // In case of a DHT or backup import failure, just remove the account
1458 0 : if (auto acc = w.lock())
1459 0 : acc->setRegistrationState(RegistrationState::ERROR_GENERIC);
1460 0 : runOnMainThread([accountId = std::move(accountId)] {
1461 0 : Manager::instance().removeAccount(accountId, true);
1462 0 : });
1463 : }
1464 0 : },
1465 : callbacks);
1466 634 : }
1467 647 : } catch (const std::exception& e) {
1468 0 : JAMI_WARNING("[Account {}] Error loading account: {}", getAccountID(), e.what());
1469 0 : accountManager_.reset();
1470 0 : setRegistrationState(RegistrationState::ERROR_GENERIC);
1471 0 : }
1472 647 : }
1473 :
1474 : std::map<std::string, std::string>
1475 4406 : JamiAccount::getVolatileAccountDetails() const
1476 : {
1477 4406 : auto a = SIPAccountBase::getVolatileAccountDetails();
1478 4406 : a.emplace(libjami::Account::VolatileProperties::InstantMessaging::OFF_CALL, TRUE_STR);
1479 : #ifdef ENABLE_NAMESERVER
1480 4406 : auto registeredName = getRegisteredName();
1481 4406 : if (not registeredName.empty())
1482 2 : a.emplace(libjami::Account::VolatileProperties::REGISTERED_NAME, registeredName);
1483 : #endif
1484 4406 : a.emplace(libjami::Account::ConfProperties::PROXY_SERVER, proxyServerCached_);
1485 4406 : a.emplace(libjami::Account::VolatileProperties::DHT_BOUND_PORT, std::to_string(dhtBoundPort_));
1486 4406 : a.emplace(libjami::Account::VolatileProperties::DEVICE_ANNOUNCED,
1487 4406 : deviceAnnounced_ ? TRUE_STR : FALSE_STR);
1488 4406 : if (accountManager_) {
1489 4406 : if (auto info = accountManager_->getInfo()) {
1490 3770 : a.emplace(libjami::Account::ConfProperties::DEVICE_ID, info->deviceId);
1491 : }
1492 : }
1493 8812 : return a;
1494 4406 : }
1495 :
1496 : #ifdef ENABLE_NAMESERVER
1497 : void
1498 3 : JamiAccount::lookupName(const std::string& name)
1499 : {
1500 3 : std::lock_guard lock(configurationMutex_);
1501 3 : if (accountManager_)
1502 9 : accountManager_->lookupUri(name,
1503 3 : config().nameServer,
1504 3 : [acc = getAccountID(), name](const std::string& regName,
1505 : const std::string& address,
1506 : NameDirectory::Response response) {
1507 6 : emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(
1508 3 : acc, name, (int) response, address, regName);
1509 3 : });
1510 3 : }
1511 :
1512 : void
1513 3 : JamiAccount::lookupAddress(const std::string& addr)
1514 : {
1515 3 : std::lock_guard lock(configurationMutex_);
1516 3 : auto acc = getAccountID();
1517 3 : if (accountManager_)
1518 3 : accountManager_->lookupAddress(
1519 : addr,
1520 3 : [acc, addr](const std::string& regName,
1521 : const std::string& address,
1522 : NameDirectory::Response response) {
1523 6 : emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc,
1524 3 : addr,
1525 : (int) response,
1526 : address,
1527 : regName);
1528 3 : });
1529 3 : }
1530 :
1531 : void
1532 1 : JamiAccount::registerName(const std::string& name,
1533 : const std::string& scheme,
1534 : const std::string& password)
1535 : {
1536 1 : std::lock_guard lock(configurationMutex_);
1537 1 : if (accountManager_)
1538 2 : accountManager_->registerName(
1539 : name,
1540 : scheme,
1541 : password,
1542 1 : [acc = getAccountID(), name, w = weak()](NameDirectory::RegistrationResponse response,
1543 : const std::string& regName) {
1544 1 : auto res = (int) std::min(response, NameDirectory::RegistrationResponse::error);
1545 1 : if (response == NameDirectory::RegistrationResponse::success) {
1546 1 : if (auto this_ = w.lock()) {
1547 1 : if (this_->setRegisteredName(regName)) {
1548 1 : this_->editConfig([&](JamiAccountConfig& config) {
1549 1 : config.registeredName = regName;
1550 1 : });
1551 1 : emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1552 2 : this_->accountID_, this_->getVolatileAccountDetails());
1553 : }
1554 1 : }
1555 : }
1556 1 : emitSignal<libjami::ConfigurationSignal::NameRegistrationEnded>(acc, res, name);
1557 1 : });
1558 1 : }
1559 : #endif
1560 :
1561 : bool
1562 0 : JamiAccount::searchUser(const std::string& query)
1563 : {
1564 0 : if (accountManager_)
1565 0 : return accountManager_->searchUser(
1566 : query,
1567 0 : [acc = getAccountID(), query](const jami::NameDirectory::SearchResult& result,
1568 : jami::NameDirectory::Response response) {
1569 0 : jami::emitSignal<libjami::ConfigurationSignal::UserSearchEnded>(acc,
1570 : (int) response,
1571 0 : query,
1572 : result);
1573 0 : });
1574 0 : return false;
1575 : }
1576 :
1577 : void
1578 188 : JamiAccount::forEachPendingCall(const DeviceId& deviceId,
1579 : const std::function<void(const std::shared_ptr<SIPCall>&)>& cb)
1580 : {
1581 188 : std::vector<std::shared_ptr<SIPCall>> pc;
1582 : {
1583 188 : std::lock_guard lk(pendingCallsMutex_);
1584 188 : pc = std::move(pendingCalls_[deviceId]);
1585 188 : }
1586 281 : for (const auto& pendingCall : pc) {
1587 93 : cb(pendingCall);
1588 : }
1589 188 : }
1590 :
1591 : void
1592 587 : JamiAccount::registerAsyncOps()
1593 : {
1594 2273 : auto onLoad = [this, loaded = std::make_shared<std::atomic_uint>()] {
1595 1686 : if (++(*loaded) == 2u) {
1596 587 : runOnMainThread([w = weak()] {
1597 587 : if (auto s = w.lock()) {
1598 587 : std::lock_guard lock(s->configurationMutex_);
1599 587 : s->doRegister_();
1600 1174 : }
1601 587 : });
1602 : }
1603 2273 : };
1604 :
1605 1174 : loadCachedProxyServer([onLoad](const std::string&) { onLoad(); });
1606 :
1607 587 : if (upnpCtrl_) {
1608 1629 : JAMI_LOG("[Account {:s}] UPnP: attempting to map ports", getAccountID());
1609 :
1610 : // Release current mapping if any.
1611 543 : if (dhtUpnpMapping_.isValid()) {
1612 0 : upnpCtrl_->releaseMapping(dhtUpnpMapping_);
1613 : }
1614 :
1615 543 : dhtUpnpMapping_.enableAutoUpdate(true);
1616 :
1617 : // Set the notify callback.
1618 1086 : dhtUpnpMapping_.setNotifyCallback([w = weak(),
1619 : onLoad,
1620 543 : update = std::make_shared<bool>(false)](
1621 : dhtnet::upnp::Mapping::sharedPtr_t mapRes) {
1622 520 : if (auto accPtr = w.lock()) {
1623 520 : auto& dhtMap = accPtr->dhtUpnpMapping_;
1624 520 : const auto& accId = accPtr->getAccountID();
1625 :
1626 1560 : JAMI_LOG("[Account {:s}] DHT UPnP mapping changed to {:s}",
1627 : accId,
1628 : mapRes->toString(true));
1629 :
1630 520 : if (*update) {
1631 : // Check if we need to update the mapping and the registration.
1632 4 : if (dhtMap.getMapKey() != mapRes->getMapKey()
1633 4 : or dhtMap.getState() != mapRes->getState()) {
1634 : // The connectivity must be restarted, if either:
1635 : // - the state changed to "OPEN",
1636 : // - the state changed to "FAILED" and the mapping was in use.
1637 4 : if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN
1638 8 : or (mapRes->getState() == dhtnet::upnp::MappingState::FAILED
1639 4 : and dhtMap.getState() == dhtnet::upnp::MappingState::OPEN)) {
1640 : // Update the mapping and restart the registration.
1641 0 : dhtMap.updateFrom(mapRes);
1642 :
1643 0 : JAMI_WARNING(
1644 : "[Account {:s}] Allocated port changed to {}. Restarting the "
1645 : "registration",
1646 : accId,
1647 : accPtr->dhtPortUsed());
1648 :
1649 0 : accPtr->dht_->connectivityChanged();
1650 :
1651 : } else {
1652 : // Only update the mapping.
1653 4 : dhtMap.updateFrom(mapRes);
1654 : }
1655 : }
1656 : } else {
1657 516 : *update = true;
1658 : // Set connection info and load the account.
1659 516 : if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN) {
1660 0 : dhtMap.updateFrom(mapRes);
1661 0 : JAMI_LOG(
1662 : "[Account {:s}] Mapping {:s} successfully allocated: starting the DHT",
1663 : accId,
1664 : dhtMap.toString());
1665 : } else {
1666 1548 : JAMI_WARNING("[Account {:s}] Mapping request is in {:s} state: starting "
1667 : "the DHT anyway",
1668 : accId,
1669 : mapRes->getStateStr());
1670 : }
1671 :
1672 : // Load the account and start the DHT.
1673 516 : onLoad();
1674 : }
1675 520 : }
1676 520 : });
1677 :
1678 : // Request the mapping.
1679 543 : auto map = upnpCtrl_->reserveMapping(dhtUpnpMapping_);
1680 : // The returned mapping is invalid. Load the account now since
1681 : // we may never receive the callback.
1682 543 : if (not map) {
1683 539 : onLoad();
1684 : }
1685 543 : } else {
1686 : // No UPNP. Load the account and start the DHT. The local DHT
1687 : // might not be reachable for peers if we are behind a NAT.
1688 44 : onLoad();
1689 : }
1690 587 : }
1691 :
1692 : void
1693 1307 : JamiAccount::doRegister()
1694 : {
1695 1307 : std::lock_guard lock(configurationMutex_);
1696 1307 : if (not isUsable()) {
1697 270 : JAMI_WARNING("[Account {:s}] Account must be enabled and active to register, ignoring",
1698 : getAccountID());
1699 90 : return;
1700 : }
1701 :
1702 3651 : JAMI_LOG("[Account {:s}] Starting account…", getAccountID());
1703 :
1704 : // invalid state transitions:
1705 : // INITIALIZING: generating/loading certificates, unable to register
1706 : // NEED_MIGRATION: old account detected, user needs to migrate
1707 1217 : if (registrationState_ == RegistrationState::INITIALIZING
1708 587 : || registrationState_ == RegistrationState::ERROR_NEED_MIGRATION)
1709 630 : return;
1710 :
1711 587 : convModule(); // Init conv module before passing in trying
1712 587 : setRegistrationState(RegistrationState::TRYING);
1713 : /* if UPnP is enabled, then wait for IGD to complete registration */
1714 587 : if (upnpCtrl_ or proxyServerCached_.empty()) {
1715 587 : registerAsyncOps();
1716 : } else {
1717 0 : doRegister_();
1718 : }
1719 1307 : }
1720 :
1721 : std::vector<std::string>
1722 587 : JamiAccount::loadBootstrap() const
1723 : {
1724 587 : std::vector<std::string> bootstrap;
1725 587 : std::string_view stream(config().hostname), node_addr;
1726 1174 : while (jami::getline(stream, node_addr, ';'))
1727 587 : bootstrap.emplace_back(node_addr);
1728 1174 : for (const auto& b : bootstrap)
1729 587 : JAMI_DBG("[Account %s] Bootstrap node: %s", getAccountID().c_str(), b.c_str());
1730 1174 : return bootstrap;
1731 0 : }
1732 :
1733 : void
1734 29 : JamiAccount::trackBuddyPresence(const std::string& buddy_id, bool track)
1735 : {
1736 29 : std::string buddyUri;
1737 : try {
1738 29 : buddyUri = parseJamiUri(buddy_id);
1739 0 : } catch (...) {
1740 0 : JAMI_ERROR("[Account {:s}] Failed to track presence: invalid URI {:s}",
1741 : getAccountID(),
1742 : buddy_id);
1743 0 : return;
1744 0 : }
1745 87 : JAMI_LOG("[Account {:s}] {:s} presence for {:s}",
1746 : getAccountID(),
1747 : track ? "Track" : "Untrack",
1748 : buddy_id);
1749 :
1750 29 : auto h = dht::InfoHash(buddyUri);
1751 29 : std::unique_lock lock(buddyInfoMtx);
1752 29 : if (track) {
1753 29 : auto buddy = trackedBuddies_.emplace(h, BuddyInfo {h});
1754 29 : if (buddy.second) {
1755 29 : trackPresence(buddy.first->first, buddy.first->second);
1756 : }
1757 29 : auto it = presenceState_.find(buddyUri);
1758 29 : if (it != presenceState_.end() && it->second != PresenceState::DISCONNECTED) {
1759 1 : lock.unlock();
1760 1 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1761 : buddyUri,
1762 1 : static_cast<int>(it->second),
1763 : "");
1764 : }
1765 : } else {
1766 0 : auto buddy = trackedBuddies_.find(h);
1767 0 : if (buddy != trackedBuddies_.end()) {
1768 0 : if (auto dht = dht_)
1769 0 : if (dht->isRunning())
1770 0 : dht->cancelListen(h, std::move(buddy->second.listenToken));
1771 0 : trackedBuddies_.erase(buddy);
1772 : }
1773 : }
1774 29 : }
1775 :
1776 : void
1777 29 : JamiAccount::trackPresence(const dht::InfoHash& h, BuddyInfo& buddy)
1778 : {
1779 29 : auto dht = dht_;
1780 29 : if (not dht or not dht->isRunning()) {
1781 0 : return;
1782 : }
1783 : buddy.listenToken
1784 58 : = dht->listen<DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&& dev, bool expired) {
1785 : bool wasConnected, isConnected;
1786 : {
1787 56 : std::lock_guard lock(buddyInfoMtx);
1788 56 : auto buddy = trackedBuddies_.find(h);
1789 56 : if (buddy == trackedBuddies_.end())
1790 0 : return true;
1791 56 : wasConnected = buddy->second.devices_cnt > 0;
1792 56 : if (expired)
1793 28 : --buddy->second.devices_cnt;
1794 : else
1795 28 : ++buddy->second.devices_cnt;
1796 56 : isConnected = buddy->second.devices_cnt > 0;
1797 56 : }
1798 : // NOTE: the rest can use configurationMtx_, that can be locked during unregister so
1799 : // do not retrigger on dht
1800 56 : runOnMainThread([w = weak(), h, dev, expired, isConnected, wasConnected]() {
1801 56 : auto sthis = w.lock();
1802 56 : if (!sthis)
1803 0 : return;
1804 56 : if (not expired) {
1805 : // Retry messages every time a new device announce its presence
1806 28 : sthis->messageEngine_.onPeerOnline(h.toString());
1807 : }
1808 56 : if (isConnected and not wasConnected) {
1809 27 : sthis->onTrackedBuddyOnline(h);
1810 29 : } else if (not isConnected and wasConnected) {
1811 27 : sthis->onTrackedBuddyOffline(h);
1812 : }
1813 56 : });
1814 :
1815 56 : return true;
1816 29 : });
1817 29 : }
1818 :
1819 : std::map<std::string, bool>
1820 2 : JamiAccount::getTrackedBuddyPresence() const
1821 : {
1822 2 : std::lock_guard lock(buddyInfoMtx);
1823 2 : std::map<std::string, bool> presence_info;
1824 4 : for (const auto& buddy_info_p : trackedBuddies_)
1825 2 : presence_info.emplace(buddy_info_p.first.toString(), buddy_info_p.second.devices_cnt > 0);
1826 4 : return presence_info;
1827 2 : }
1828 :
1829 : void
1830 27 : JamiAccount::onTrackedBuddyOnline(const dht::InfoHash& contactId)
1831 : {
1832 27 : std::string id(contactId.toString());
1833 81 : JAMI_DEBUG("[Account {:s}] Buddy {} online", getAccountID(), id);
1834 27 : auto& state = presenceState_[id];
1835 27 : if (state < PresenceState::AVAILABLE) {
1836 27 : state = PresenceState::AVAILABLE;
1837 27 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1838 : id,
1839 : static_cast<int>(
1840 : PresenceState::AVAILABLE),
1841 : "");
1842 : }
1843 :
1844 27 : if (auto details = getContactInfo(id)) {
1845 5 : if (!details->confirmed) {
1846 5 : auto convId = convModule()->getOneToOneConversation(id);
1847 5 : if (convId.empty())
1848 0 : return;
1849 : // In this case, the TrustRequest was sent but never confirmed (cause the contact was
1850 : // offline maybe) To avoid the contact to never receive the conv request, retry there
1851 5 : std::lock_guard lock(configurationMutex_);
1852 5 : if (accountManager_) {
1853 : // Retrieve cached payload for trust request.
1854 10 : auto requestPath = cachePath_ / "requests" / id;
1855 5 : std::vector<uint8_t> payload;
1856 : try {
1857 8 : payload = fileutils::loadFile(requestPath);
1858 3 : } catch (...) {
1859 3 : }
1860 5 : if (payload.size() >= 64000) {
1861 0 : JAMI_WARNING(
1862 : "[Account {:s}] Trust request for contact {:s} is too big, reset payload",
1863 : getAccountID(),
1864 : id);
1865 0 : payload.clear();
1866 : }
1867 5 : accountManager_->sendTrustRequest(id, convId, payload);
1868 5 : }
1869 5 : }
1870 27 : }
1871 27 : }
1872 :
1873 : void
1874 27 : JamiAccount::onTrackedBuddyOffline(const dht::InfoHash& contactId)
1875 : {
1876 27 : auto id = contactId.toString();
1877 81 : JAMI_DEBUG("[Account {:s}] Buddy {} offline", getAccountID(), id);
1878 27 : auto& state = presenceState_[id];
1879 27 : if (state > PresenceState::DISCONNECTED) {
1880 27 : if (state == PresenceState::CONNECTED) {
1881 48 : JAMI_WARNING("[Account {:s}] Buddy {} is not present on the DHT, but P2P connected",
1882 : getAccountID(),
1883 : id);
1884 16 : return;
1885 : }
1886 11 : state = PresenceState::DISCONNECTED;
1887 11 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
1888 : id,
1889 : static_cast<int>(
1890 : PresenceState::DISCONNECTED),
1891 : "");
1892 : }
1893 27 : }
1894 :
1895 : void
1896 587 : JamiAccount::doRegister_()
1897 : {
1898 587 : if (registrationState_ != RegistrationState::TRYING) {
1899 0 : JAMI_ERROR("[Account {}] Already registered", getAccountID());
1900 0 : return;
1901 : }
1902 :
1903 1761 : JAMI_DEBUG("[Account {}] Starting account…", getAccountID());
1904 587 : const auto& conf = config();
1905 :
1906 : try {
1907 587 : if (not accountManager_ or not accountManager_->getInfo())
1908 0 : throw std::runtime_error("No identity configured for this account.");
1909 :
1910 587 : if (dht_->isRunning()) {
1911 3 : JAMI_ERROR("[Account {}] DHT already running (stopping it first).", getAccountID());
1912 1 : dht_->join();
1913 : }
1914 :
1915 587 : convModule()->clearPendingFetch();
1916 :
1917 : #ifdef ENABLE_NAMESERVER
1918 : // Look for registered name
1919 1174 : accountManager_->lookupAddress(
1920 587 : accountManager_->getInfo()->accountId,
1921 587 : [w = weak()](const std::string& regName,
1922 : const std::string& address,
1923 : const NameDirectory::Response& response) {
1924 587 : if (auto this_ = w.lock()) {
1925 582 : if (response == NameDirectory::Response::found
1926 582 : or response == NameDirectory::Response::notFound) {
1927 582 : const auto& nameResult = response == NameDirectory::Response::found
1928 : ? regName
1929 582 : : "";
1930 582 : if (this_->setRegisteredName(nameResult)) {
1931 0 : this_->editConfig([&](JamiAccountConfig& config) {
1932 0 : config.registeredName = nameResult;
1933 0 : });
1934 0 : emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1935 0 : this_->accountID_, this_->getVolatileAccountDetails());
1936 : }
1937 582 : }
1938 587 : }
1939 587 : });
1940 : #endif
1941 :
1942 587 : dht::DhtRunner::Config config {};
1943 587 : config.dht_config.node_config.network = 0;
1944 587 : config.dht_config.node_config.maintain_storage = false;
1945 587 : config.dht_config.node_config.persist_path = (cachePath_ / "dhtstate").string();
1946 587 : config.dht_config.id = id_;
1947 587 : config.dht_config.cert_cache_all = true;
1948 587 : config.push_node_id = getAccountID();
1949 587 : config.push_token = conf.deviceKey;
1950 587 : config.push_topic = conf.notificationTopic;
1951 587 : config.push_platform = conf.platform;
1952 587 : config.proxy_user_agent = jami::userAgent();
1953 587 : config.threaded = true;
1954 587 : config.peer_discovery = conf.dhtPeerDiscovery;
1955 587 : config.peer_publish = conf.dhtPeerDiscovery;
1956 587 : if (conf.proxyEnabled)
1957 0 : config.proxy_server = proxyServerCached_;
1958 :
1959 587 : if (not config.proxy_server.empty()) {
1960 0 : JAMI_LOG("[Account {}] Using proxy server {}", getAccountID(), config.proxy_server);
1961 0 : if (not config.push_token.empty()) {
1962 0 : JAMI_LOG(
1963 : "[Account {}] using push notifications with platform: {}, topic: {}, token: {}",
1964 : getAccountID(),
1965 : config.push_platform,
1966 : config.push_topic,
1967 : config.push_token);
1968 : }
1969 : }
1970 :
1971 : // check if dht peer service is enabled
1972 587 : if (conf.accountPeerDiscovery or conf.accountPublish) {
1973 0 : peerDiscovery_ = std::make_shared<dht::PeerDiscovery>();
1974 0 : if (conf.accountPeerDiscovery) {
1975 0 : JAMI_LOG("[Account {}] Starting Jami account discovery…", getAccountID());
1976 0 : startAccountDiscovery();
1977 : }
1978 0 : if (conf.accountPublish)
1979 0 : startAccountPublish();
1980 : }
1981 587 : dht::DhtRunner::Context context {};
1982 587 : context.peerDiscovery = peerDiscovery_;
1983 587 : context.rng = std::make_unique<std::mt19937_64>(dht::crypto::getDerivedRandomEngine(rand));
1984 :
1985 587 : auto dht_log_level = Manager::instance().dhtLogLevel;
1986 587 : if (dht_log_level > 0) {
1987 0 : context.logger = Logger::dhtLogger();
1988 : }
1989 1364 : context.certificateStore = [&](const dht::InfoHash& pk_id) {
1990 1364 : std::vector<std::shared_ptr<dht::crypto::Certificate>> ret;
1991 2728 : if (auto cert = certStore().getCertificate(pk_id.toString()))
1992 1364 : ret.emplace_back(std::move(cert));
1993 4092 : JAMI_LOG("Query for local certificate store: {}: {} found.",
1994 : pk_id.toString(),
1995 : ret.size());
1996 1364 : return ret;
1997 587 : };
1998 :
1999 5115 : context.statusChangedCallback = [this](dht::NodeStatus s4, dht::NodeStatus s6) {
2000 5115 : JAMI_LOG("[Account {}] DHT status: IPv4 {}; IPv6 {}",
2001 : getAccountID(),
2002 : dhtStatusStr(s4),
2003 : dhtStatusStr(s6));
2004 : RegistrationState state;
2005 1705 : auto newStatus = std::max(s4, s6);
2006 1705 : switch (newStatus) {
2007 566 : case dht::NodeStatus::Connecting:
2008 566 : state = RegistrationState::TRYING;
2009 566 : break;
2010 1139 : case dht::NodeStatus::Connected:
2011 1139 : state = RegistrationState::REGISTERED;
2012 1139 : break;
2013 0 : case dht::NodeStatus::Disconnected:
2014 0 : state = RegistrationState::UNREGISTERED;
2015 0 : break;
2016 0 : default:
2017 0 : state = RegistrationState::ERROR_GENERIC;
2018 0 : break;
2019 : }
2020 :
2021 1705 : setRegistrationState(state);
2022 2292 : };
2023 4573 : context.identityAnnouncedCb = [this](bool ok) {
2024 1168 : if (!ok)
2025 33 : return;
2026 2270 : accountManager_->startSync(
2027 2611 : [this](const std::shared_ptr<dht::crypto::Certificate>& crt) {
2028 1250 : if (jami::Manager::instance().syncOnRegister) {
2029 1250 : if (!crt)
2030 1139 : return;
2031 1250 : auto deviceId = crt->getLongId().toString();
2032 1250 : if (accountManager_->getInfo()->deviceId == deviceId)
2033 1139 : return;
2034 :
2035 111 : dht::ThreadPool::io().run([w = weak(), deviceId, crt] {
2036 111 : auto shared = w.lock();
2037 111 : if (!shared) return;
2038 111 : std::unique_lock lk(shared->connManagerMtx_);
2039 111 : shared->initConnectionManager();
2040 111 : lk.unlock();
2041 111 : std::shared_lock slk(shared->connManagerMtx_);
2042 : // NOTE: connectionManager_ and channelHandlers_ get initialized at the
2043 : // same time and are both protected by connManagerMtx_, so this check
2044 : // ensures that the access to channelHandlers_ below is valid.
2045 111 : if (!shared->connectionManager_)
2046 0 : return;
2047 111 : shared->requestMessageConnection(shared->getUsername(), crt->getLongId(), "sync");
2048 111 : if (!shared->syncModule()->isConnected(crt->getLongId())) {
2049 104 : shared->channelHandlers_[Uri::Scheme::SYNC]
2050 104 : ->connect(crt->getLongId(),
2051 : "",
2052 58 : [](std::shared_ptr<dhtnet::ChannelSocket> socket,
2053 58 : const DeviceId& deviceId) {});
2054 : }
2055 111 : });
2056 1250 : }
2057 : },
2058 5675 : [this] {
2059 1135 : if (jami::Manager::instance().syncOnRegister) {
2060 1135 : deviceAnnounced_ = true;
2061 :
2062 : // Bootstrap at the end to avoid to be long to load.
2063 1135 : dht::ThreadPool::io().run([w = weak()] {
2064 1135 : if (auto shared = w.lock())
2065 1135 : shared->convModule()->bootstrap();
2066 1135 : });
2067 1135 : emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
2068 2270 : accountID_, getVolatileAccountDetails());
2069 : }
2070 1135 : },
2071 1135 : publishPresence_);
2072 587 : };
2073 :
2074 587 : dht_->run(dhtPortUsed(), config, std::move(context));
2075 :
2076 1174 : for (const auto& bootstrap : loadBootstrap())
2077 1174 : dht_->bootstrap(bootstrap);
2078 :
2079 587 : dhtBoundPort_ = dht_->getBoundPort();
2080 :
2081 587 : accountManager_->setDht(dht_);
2082 :
2083 587 : std::unique_lock lkCM(connManagerMtx_);
2084 587 : initConnectionManager();
2085 587 : connectionManager_->onDhtConnected(*accountManager_->getInfo()->devicePk);
2086 587 : connectionManager_->onICERequest([this](const DeviceId& deviceId) {
2087 714 : std::promise<bool> accept;
2088 714 : std::future<bool> fut = accept.get_future();
2089 714 : accountManager_->findCertificate(
2090 3570 : deviceId, [this, &accept](const std::shared_ptr<dht::crypto::Certificate>& cert) {
2091 714 : if (!cert) {
2092 0 : accept.set_value(false);
2093 0 : return;
2094 : }
2095 714 : dht::InfoHash peer_account_id;
2096 714 : auto res = accountManager_->onPeerCertificate(cert,
2097 714 : this->config().dhtPublicInCalls,
2098 714 : peer_account_id);
2099 2142 : JAMI_LOG("[Account {}] [device {}] {} ICE request from {}",
2100 : getAccountID(),
2101 : cert->getLongId(),
2102 : res ? "Accepting" : "Discarding",
2103 : peer_account_id);
2104 714 : accept.set_value(res);
2105 : });
2106 714 : fut.wait();
2107 714 : auto result = fut.get();
2108 714 : return result;
2109 714 : });
2110 587 : connectionManager_->onChannelRequest(
2111 15816 : [this](const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name) {
2112 5922 : JAMI_LOG("[Account {}] [device {}] New channel requested: '{}'",
2113 : getAccountID(),
2114 : cert->getLongId(),
2115 : name);
2116 :
2117 1974 : if (this->config().turnEnabled && turnCache_) {
2118 1974 : auto addr = turnCache_->getResolvedTurn();
2119 1974 : if (addr == std::nullopt) {
2120 : // If TURN is enabled, but no TURN cached, there can be a temporary
2121 : // resolution error to solve. Sometimes, a connectivity change is not
2122 : // enough, so even if this case is really rare, it should be easy to avoid.
2123 24 : turnCache_->refresh();
2124 : }
2125 : }
2126 :
2127 1974 : auto uri = Uri(name);
2128 1974 : std::shared_lock lk(connManagerMtx_);
2129 1974 : auto itHandler = channelHandlers_.find(uri.scheme());
2130 1974 : if (itHandler != channelHandlers_.end() && itHandler->second)
2131 1875 : return itHandler->second->onRequest(cert, name);
2132 99 : return name == "sip";
2133 1974 : });
2134 587 : connectionManager_->onConnectionReady([this](const DeviceId& deviceId,
2135 : const std::string& name,
2136 17754 : std::shared_ptr<dhtnet::ChannelSocket> channel) {
2137 3806 : if (channel) {
2138 3802 : auto cert = channel->peerCertificate();
2139 3802 : if (!cert || !cert->issuer)
2140 0 : return;
2141 3802 : auto peerId = cert->issuer->getId().toString();
2142 : // A connection request can be sent just before member is banned and this must be ignored.
2143 3802 : if (accountManager()->getCertificateStatus(peerId)
2144 3802 : == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2145 62 : channel->shutdown();
2146 62 : return;
2147 : }
2148 3740 : if (name == "sip") {
2149 188 : cacheSIPConnection(std::move(channel), peerId, deviceId);
2150 3552 : } else if (name.find("git://") == 0) {
2151 1504 : auto sep = name.find_last_of('/');
2152 1504 : auto conversationId = name.substr(sep + 1);
2153 1504 : auto remoteDevice = name.substr(6, sep - 6);
2154 :
2155 1504 : if (channel->isInitiator()) {
2156 : // Check if wanted remote is our side (git://remoteDevice/conversationId)
2157 742 : return;
2158 : }
2159 :
2160 : // Check if pull from banned device
2161 762 : if (convModule()->isBanned(conversationId, remoteDevice)) {
2162 0 : JAMI_WARNING(
2163 : "[Account {:s}] [Conversation {}] Git server requested, but the "
2164 : "device is unauthorized ({:s}) ",
2165 : getAccountID(),
2166 : conversationId,
2167 : remoteDevice);
2168 0 : channel->shutdown();
2169 0 : return;
2170 : }
2171 :
2172 762 : auto sock = convModule()->gitSocket(deviceId.toString(), conversationId);
2173 762 : if (sock == channel) {
2174 : // The onConnectionReady is already used as client (for retrieving messages)
2175 : // So it's not the server socket
2176 0 : return;
2177 : }
2178 2286 : JAMI_LOG(
2179 : "[Account {:s}] [Conversation {}] [device {}] Git server requested",
2180 : accountID_,
2181 : conversationId,
2182 : deviceId.toString());
2183 762 : auto gs = std::make_unique<GitServer>(accountID_, conversationId, channel);
2184 762 : syncCnt_.fetch_add(1);
2185 762 : gs->setOnFetched([w = weak(), conversationId, deviceId](
2186 : const std::string& commit) {
2187 1004 : dht::ThreadPool::computation().run([w, conversationId, deviceId, commit]() {
2188 1004 : if (auto shared = w.lock()) {
2189 2008 : shared->convModule()->setFetched(conversationId,
2190 1004 : deviceId.toString(),
2191 1004 : commit);
2192 2008 : if (shared->syncCnt_.fetch_sub(1) == 1) {
2193 237 : emitSignal<libjami::ConversationSignal::ConversationCloned>(
2194 237 : shared->getAccountID().c_str());
2195 : }
2196 1004 : }
2197 1004 : });
2198 1004 : });
2199 762 : const dht::Value::Id serverId = ValueIdDist()(rand);
2200 : {
2201 762 : std::lock_guard lk(gitServersMtx_);
2202 762 : gitServers_[serverId] = std::move(gs);
2203 762 : }
2204 762 : channel->onShutdown([w = weak(), serverId]() {
2205 : // Run on main thread to avoid to be in mxSock's eventLoop
2206 762 : runOnMainThread([serverId, w]() {
2207 762 : if (auto sthis = w.lock()) {
2208 762 : std::lock_guard lk(sthis->gitServersMtx_);
2209 762 : sthis->gitServers_.erase(serverId);
2210 1524 : }
2211 762 : });
2212 762 : });
2213 2246 : } else {
2214 : // TODO move git://
2215 2048 : std::shared_lock lk(connManagerMtx_);
2216 2048 : auto uri = Uri(name);
2217 2048 : auto itHandler = channelHandlers_.find(uri.scheme());
2218 2048 : if (itHandler != channelHandlers_.end() && itHandler->second)
2219 2047 : itHandler->second->onReady(cert, name, std::move(channel));
2220 2048 : }
2221 4606 : }
2222 : });
2223 587 : lkCM.unlock();
2224 :
2225 587 : if (!conf.managerUri.empty() && accountManager_) {
2226 0 : dynamic_cast<ServerAccountManager*>(accountManager_.get())->onNeedsMigration([this]() {
2227 0 : editConfig([&](JamiAccountConfig& conf) {
2228 0 : conf.receipt.clear();
2229 0 : conf.receiptSignature.clear();
2230 0 : });
2231 0 : Migration::setState(accountID_, Migration::State::INVALID);
2232 0 : setRegistrationState(RegistrationState::ERROR_NEED_MIGRATION);
2233 0 : });
2234 0 : dynamic_cast<ServerAccountManager*>(accountManager_.get())
2235 0 : ->syncBlueprintConfig([this](const std::map<std::string, std::string>& config) {
2236 0 : editConfig([&](JamiAccountConfig& conf) { conf.fromMap(config); });
2237 0 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(
2238 0 : getAccountID(), getAccountDetails());
2239 0 : });
2240 : }
2241 :
2242 587 : std::lock_guard lock(buddyInfoMtx);
2243 587 : for (auto& buddy : trackedBuddies_) {
2244 0 : buddy.second.devices_cnt = 0;
2245 0 : trackPresence(buddy.first, buddy.second);
2246 : }
2247 587 : } catch (const std::exception& e) {
2248 0 : JAMI_ERR("Error registering DHT account: %s", e.what());
2249 0 : setRegistrationState(RegistrationState::ERROR_GENERIC);
2250 0 : }
2251 : }
2252 :
2253 : ConversationModule*
2254 27779 : JamiAccount::convModule(bool noCreation)
2255 : {
2256 27779 : if (noCreation)
2257 5127 : return convModule_.get();
2258 22652 : if (!accountManager() || currentDeviceId() == "") {
2259 0 : JAMI_ERROR("[Account {}] Calling convModule() with an uninitialized account",
2260 : getAccountID());
2261 0 : return nullptr;
2262 : }
2263 22652 : std::unique_lock lock(configurationMutex_);
2264 22652 : std::lock_guard lk(moduleMtx_);
2265 22652 : if (!convModule_) {
2266 1136 : convModule_ = std::make_unique<ConversationModule>(
2267 568 : shared(),
2268 568 : accountManager_,
2269 2694 : [this](auto&& syncMsg) {
2270 2694 : dht::ThreadPool::computation().run([w = weak(), syncMsg] {
2271 2694 : if (auto shared = w.lock()) {
2272 1347 : auto& config = shared->config();
2273 : // For JAMS account, we must update the server
2274 : // for now, only sync with the JAMS server for changes to the conversation list
2275 1347 : if (!config.managerUri.empty() && !syncMsg)
2276 0 : if (auto am = shared->accountManager())
2277 0 : am->syncDevices();
2278 1347 : if (auto sm = shared->syncModule())
2279 1347 : sm->syncWithConnected(syncMsg);
2280 : }
2281 : });
2282 1347 : },
2283 28272 : [this](auto&& uri, auto&& device, auto&& msg, auto token = 0) {
2284 : // No need to retrigger, sendTextMessage will call
2285 : // messageEngine_.sendMessage, already retriggering on
2286 : // main thread.
2287 14136 : auto deviceId = device ? device.toString() : "";
2288 28272 : return sendTextMessage(uri, deviceId, msg, token);
2289 14136 : },
2290 4376 : [this](const auto& convId, const auto& deviceId, auto cb, const auto& type) {
2291 2188 : dht::ThreadPool::io().run([w = weak(), convId, deviceId, cb = std::move(cb), type] {
2292 2188 : auto shared = w.lock();
2293 2188 : if (!shared)
2294 0 : return;
2295 3553 : if (auto socket = shared->convModule()->gitSocket(deviceId, convId)) {
2296 1365 : if (!cb(socket))
2297 0 : socket->shutdown();
2298 : else
2299 1365 : cb({});
2300 1365 : return;
2301 : }
2302 823 : std::shared_lock lkCM(shared->connManagerMtx_);
2303 823 : if (!shared->connectionManager_) {
2304 7 : lkCM.unlock();
2305 7 : cb({});
2306 7 : return;
2307 : }
2308 :
2309 4080 : shared->connectionManager_->connectDevice(
2310 1632 : DeviceId(deviceId),
2311 816 : fmt::format("git://{}/{}", deviceId, convId),
2312 2448 : [w,
2313 816 : cb = std::move(cb),
2314 816 : convId](std::shared_ptr<dhtnet::ChannelSocket> socket, const DeviceId&) {
2315 4080 : dht::ThreadPool::io().run([w,
2316 816 : cb = std::move(cb),
2317 816 : socket = std::move(socket),
2318 816 : convId] {
2319 816 : if (socket) {
2320 762 : socket->onShutdown([w, deviceId = socket->deviceId(), convId] {
2321 1524 : dht::ThreadPool::io().run([w, deviceId, convId] {
2322 1524 : if (auto shared = w.lock())
2323 : shared->convModule()
2324 762 : ->removeGitSocket(deviceId.toString(), convId);
2325 : });
2326 : });
2327 762 : if (!cb(socket))
2328 1 : socket->shutdown();
2329 : } else
2330 54 : cb({});
2331 : });
2332 : },
2333 : false,
2334 : false,
2335 816 : type);
2336 2195 : });
2337 2188 : },
2338 1218 : [this](const auto& convId, const auto& deviceId, auto&& cb, const auto& connectionType) {
2339 1218 : dht::ThreadPool::io().run([w = weak(),
2340 : convId,
2341 : deviceId,
2342 609 : cb = std::move(cb),
2343 : connectionType] {
2344 609 : auto shared = w.lock();
2345 609 : if (!shared)
2346 0 : return;
2347 609 : auto cm = shared->convModule();
2348 609 : std::shared_lock lkCM(shared->connManagerMtx_);
2349 609 : if (!shared->connectionManager_ || !cm || cm->isBanned(convId, deviceId)) {
2350 94 : asio::post(*Manager::instance().ioContext(), [cb = std::move(cb)] { cb({}); });
2351 47 : return;
2352 : }
2353 1124 : if (!shared->connectionManager_->isConnecting(DeviceId(deviceId),
2354 : fmt::format("swarm://{}",
2355 562 : convId))) {
2356 2116 : shared->connectionManager_->connectDevice(
2357 1058 : DeviceId(deviceId),
2358 529 : fmt::format("swarm://{}", convId),
2359 529 : [w, cb = std::move(cb)](std::shared_ptr<dhtnet::ChannelSocket> socket,
2360 : const DeviceId& deviceId) {
2361 1587 : dht::ThreadPool::io().run([w,
2362 529 : cb = std::move(cb),
2363 529 : socket = std::move(socket),
2364 : deviceId] {
2365 529 : if (socket) {
2366 299 : auto shared = w.lock();
2367 299 : if (!shared)
2368 0 : return;
2369 299 : auto remoteCert = socket->peerCertificate();
2370 299 : auto uri = remoteCert->issuer->getId().toString();
2371 299 : if (shared->accountManager()->getCertificateStatus(uri)
2372 299 : == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
2373 0 : cb(nullptr);
2374 0 : return;
2375 : }
2376 299 : shared->requestMessageConnection(uri, deviceId, "");
2377 299 : }
2378 529 : cb(socket);
2379 : });
2380 : });
2381 : }
2382 656 : });
2383 609 : },
2384 2 : [this](auto&& convId, auto&& from) {
2385 1 : accountManager_
2386 1 : ->findCertificate(dht::InfoHash(from),
2387 1 : [this, from, convId](
2388 : const std::shared_ptr<dht::crypto::Certificate>& cert) {
2389 1 : auto info = accountManager_->getInfo();
2390 1 : if (!cert || !info)
2391 0 : return;
2392 2 : info->contacts->onTrustRequest(dht::InfoHash(from),
2393 : cert->getSharedPublicKey(),
2394 : time(nullptr),
2395 : false,
2396 1 : convId,
2397 : {});
2398 : });
2399 1 : },
2400 1136 : autoLoadConversations_);
2401 : }
2402 22652 : return convModule_.get();
2403 22652 : }
2404 :
2405 : SyncModule*
2406 2829 : JamiAccount::syncModule()
2407 : {
2408 2829 : if (!accountManager() || currentDeviceId() == "") {
2409 0 : JAMI_ERR() << "Calling syncModule() with an uninitialized account.";
2410 0 : return nullptr;
2411 : }
2412 2829 : std::lock_guard lk(moduleMtx_);
2413 2829 : if (!syncModule_)
2414 559 : syncModule_ = std::make_unique<SyncModule>(shared());
2415 2829 : return syncModule_.get();
2416 2829 : }
2417 :
2418 : void
2419 13249 : JamiAccount::onTextMessage(const std::string& id,
2420 : const std::string& from,
2421 : const std::shared_ptr<dht::crypto::Certificate>& peerCert,
2422 : const std::map<std::string, std::string>& payloads)
2423 : {
2424 : try {
2425 13249 : const std::string fromUri {parseJamiUri(from)};
2426 13249 : SIPAccountBase::onTextMessage(id, fromUri, peerCert, payloads);
2427 13249 : } catch (...) {
2428 0 : }
2429 13249 : }
2430 :
2431 : void
2432 0 : JamiAccount::loadConversation(const std::string& convId)
2433 : {
2434 0 : if (auto cm = convModule(true))
2435 0 : cm->loadSingleConversation(convId);
2436 0 : }
2437 :
2438 : void
2439 758 : JamiAccount::doUnregister(bool forceShutdownConnections)
2440 : {
2441 758 : std::unique_lock lock(configurationMutex_);
2442 758 : if (registrationState_ >= RegistrationState::ERROR_GENERIC) {
2443 94 : return;
2444 : }
2445 :
2446 664 : std::mutex mtx;
2447 664 : std::condition_variable cv;
2448 664 : bool shutdown_complete {false};
2449 :
2450 664 : if (peerDiscovery_) {
2451 0 : peerDiscovery_->stopPublish(PEER_DISCOVERY_JAMI_SERVICE);
2452 0 : peerDiscovery_->stopDiscovery(PEER_DISCOVERY_JAMI_SERVICE);
2453 : }
2454 :
2455 664 : JAMI_WARN("[Account %s] Unregistering account %p", getAccountID().c_str(), this);
2456 664 : dht_->shutdown(
2457 664 : [&] {
2458 664 : JAMI_WARN("[Account %s] DHT shutdown complete", getAccountID().c_str());
2459 664 : std::lock_guard lock(mtx);
2460 664 : shutdown_complete = true;
2461 664 : cv.notify_all();
2462 664 : },
2463 : true);
2464 :
2465 : {
2466 664 : std::lock_guard lk(pendingCallsMutex_);
2467 664 : pendingCalls_.clear();
2468 664 : }
2469 :
2470 : // Stop all current P2P connections if account is disabled
2471 : // or if explicitly requested by the caller.
2472 : // NOTE: Leaving the connections open is useful when changing an account's config.
2473 664 : if (not isEnabled() || forceShutdownConnections)
2474 651 : shutdownConnections();
2475 :
2476 : // Release current UPnP mapping if any.
2477 664 : if (upnpCtrl_ and dhtUpnpMapping_.isValid()) {
2478 0 : upnpCtrl_->releaseMapping(dhtUpnpMapping_);
2479 : }
2480 :
2481 : {
2482 664 : std::unique_lock lock(mtx);
2483 1905 : cv.wait(lock, [&] { return shutdown_complete; });
2484 664 : }
2485 664 : dht_->join();
2486 664 : setRegistrationState(RegistrationState::UNREGISTERED);
2487 :
2488 664 : lock.unlock();
2489 :
2490 : #ifdef ENABLE_PLUGIN
2491 664 : jami::Manager::instance().getJamiPluginManager().getChatServicesManager().cleanChatSubjects(
2492 : getAccountID());
2493 : #endif
2494 758 : }
2495 :
2496 : void
2497 4220 : JamiAccount::setRegistrationState(RegistrationState state,
2498 : int detail_code,
2499 : const std::string& detail_str)
2500 : {
2501 4220 : if (registrationState_ != state) {
2502 3018 : if (state == RegistrationState::REGISTERED) {
2503 1755 : JAMI_WARNING("[Account {}] Connected", getAccountID());
2504 585 : turnCache_->refresh();
2505 585 : if (connectionManager_)
2506 578 : connectionManager_->storeActiveIpAddress();
2507 2433 : } else if (state == RegistrationState::TRYING) {
2508 1761 : JAMI_WARNING("[Account {}] Connecting…", getAccountID());
2509 : } else {
2510 1846 : deviceAnnounced_ = false;
2511 5538 : JAMI_WARNING("[Account {}] Disconnected", getAccountID());
2512 : }
2513 : }
2514 : // Update registrationState_ & emit signals
2515 4220 : Account::setRegistrationState(state, detail_code, detail_str);
2516 4220 : }
2517 :
2518 : void
2519 0 : JamiAccount::reloadContacts()
2520 : {
2521 0 : accountManager_->reloadContacts();
2522 0 : }
2523 :
2524 : void
2525 0 : JamiAccount::connectivityChanged()
2526 : {
2527 0 : JAMI_WARN("connectivityChanged");
2528 0 : if (not isUsable()) {
2529 : // nothing to do
2530 0 : return;
2531 : }
2532 :
2533 0 : if (auto cm = convModule())
2534 0 : cm->connectivityChanged();
2535 0 : dht_->connectivityChanged();
2536 : {
2537 0 : std::shared_lock lkCM(connManagerMtx_);
2538 0 : if (connectionManager_) {
2539 0 : connectionManager_->connectivityChanged();
2540 : // reset cache
2541 0 : connectionManager_->setPublishedAddress({});
2542 : }
2543 0 : }
2544 : }
2545 :
2546 : bool
2547 0 : JamiAccount::findCertificate(
2548 : const dht::InfoHash& h,
2549 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2550 : {
2551 0 : if (accountManager_)
2552 0 : return accountManager_->findCertificate(h, std::move(cb));
2553 0 : return false;
2554 : }
2555 :
2556 : bool
2557 0 : JamiAccount::findCertificate(
2558 : const dht::PkId& id, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
2559 : {
2560 0 : if (accountManager_)
2561 0 : return accountManager_->findCertificate(id, std::move(cb));
2562 0 : return false;
2563 : }
2564 :
2565 : bool
2566 86 : JamiAccount::findCertificate(const std::string& crt_id)
2567 : {
2568 86 : if (accountManager_)
2569 86 : return accountManager_->findCertificate(dht::InfoHash(crt_id));
2570 0 : return false;
2571 : }
2572 :
2573 : bool
2574 89 : JamiAccount::setCertificateStatus(const std::string& cert_id,
2575 : dhtnet::tls::TrustStore::PermissionStatus status)
2576 : {
2577 89 : bool done = accountManager_ ? accountManager_->setCertificateStatus(cert_id, status) : false;
2578 89 : if (done) {
2579 86 : findCertificate(cert_id);
2580 172 : emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(
2581 86 : getAccountID(), cert_id, dhtnet::tls::TrustStore::statusToStr(status));
2582 : }
2583 89 : return done;
2584 : }
2585 :
2586 : bool
2587 0 : JamiAccount::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
2588 : dhtnet::tls::TrustStore::PermissionStatus status,
2589 : bool local)
2590 : {
2591 0 : bool done = accountManager_ ? accountManager_->setCertificateStatus(cert, status, local)
2592 0 : : false;
2593 0 : if (done) {
2594 0 : findCertificate(cert->getId().toString());
2595 0 : emitSignal<libjami::ConfigurationSignal::CertificateStateChanged>(
2596 0 : getAccountID(), cert->getId().toString(), dhtnet::tls::TrustStore::statusToStr(status));
2597 : }
2598 0 : return done;
2599 : }
2600 :
2601 : std::vector<std::string>
2602 0 : JamiAccount::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
2603 : {
2604 0 : if (accountManager_)
2605 0 : return accountManager_->getCertificatesByStatus(status);
2606 0 : return {};
2607 : }
2608 :
2609 : bool
2610 0 : JamiAccount::isMessageTreated(dht::Value::Id id)
2611 : {
2612 0 : std::lock_guard lock(messageMutex_);
2613 0 : return !treatedMessages_.add(id);
2614 0 : }
2615 :
2616 : bool
2617 13 : JamiAccount::sha3SumVerify() const
2618 : {
2619 13 : return !noSha3sumVerification_;
2620 : }
2621 :
2622 : #ifdef LIBJAMI_TEST
2623 : void
2624 1 : JamiAccount::noSha3sumVerification(bool newValue)
2625 : {
2626 1 : noSha3sumVerification_ = newValue;
2627 1 : }
2628 : #endif
2629 :
2630 : std::map<std::string, std::string>
2631 135 : JamiAccount::getKnownDevices() const
2632 : {
2633 135 : std::lock_guard lock(configurationMutex_);
2634 135 : if (not accountManager_ or not accountManager_->getInfo())
2635 0 : return {};
2636 135 : std::map<std::string, std::string> ids;
2637 272 : for (const auto& d : accountManager_->getKnownDevices()) {
2638 137 : auto id = d.first.toString();
2639 137 : auto label = d.second.name.empty() ? id.substr(0, 8) : d.second.name;
2640 137 : ids.emplace(std::move(id), std::move(label));
2641 137 : }
2642 135 : return ids;
2643 135 : }
2644 :
2645 : void
2646 0 : JamiAccount::loadCachedUrl(const std::string& url,
2647 : const std::filesystem::path& cachePath,
2648 : const std::chrono::seconds& cacheDuration,
2649 : std::function<void(const dht::http::Response& response)> cb)
2650 : {
2651 0 : dht::ThreadPool::io().run([cb, url, cachePath, cacheDuration, w = weak()]() {
2652 : try {
2653 0 : std::string data;
2654 : {
2655 0 : std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2656 0 : data = fileutils::loadCacheTextFile(cachePath, cacheDuration);
2657 0 : }
2658 0 : dht::http::Response ret;
2659 0 : ret.body = std::move(data);
2660 0 : ret.status_code = 200;
2661 0 : cb(ret);
2662 0 : } catch (const std::exception& e) {
2663 0 : JAMI_LOG("Failed to load '{}' from '{}': {}", url, cachePath, e.what());
2664 :
2665 0 : if (auto sthis = w.lock()) {
2666 : auto req = std::make_shared<dht::http::Request>(
2667 0 : *Manager::instance().ioContext(),
2668 0 : url,
2669 0 : [cb, cachePath, w](const dht::http::Response& response) {
2670 0 : if (response.status_code == 200) {
2671 : try {
2672 0 : std::lock_guard lk(dhtnet::fileutils::getFileLock(cachePath));
2673 0 : fileutils::saveFile(cachePath,
2674 0 : (const uint8_t*) response.body.data(),
2675 : response.body.size(),
2676 : 0600);
2677 0 : JAMI_LOG("Cached result to '{}'", cachePath);
2678 0 : } catch (const std::exception& ex) {
2679 0 : JAMI_WARNING("Failed to save result to '{}': {}",
2680 : cachePath,
2681 : ex.what());
2682 0 : }
2683 0 : cb(response);
2684 : } else {
2685 : try {
2686 0 : if (std::filesystem::exists(cachePath)) {
2687 0 : JAMI_WARNING("Failed to download URL, using cached data");
2688 0 : std::string data;
2689 : {
2690 : std::lock_guard lk(
2691 0 : dhtnet::fileutils::getFileLock(cachePath));
2692 0 : data = fileutils::loadTextFile(cachePath);
2693 0 : }
2694 0 : dht::http::Response ret;
2695 0 : ret.body = std::move(data);
2696 0 : ret.status_code = 200;
2697 0 : cb(ret);
2698 0 : } else
2699 0 : throw std::runtime_error("No cached data");
2700 0 : } catch (...) {
2701 0 : cb(response);
2702 0 : }
2703 : }
2704 0 : if (auto req = response.request.lock())
2705 0 : if (auto sthis = w.lock())
2706 0 : sthis->requests_.erase(req);
2707 0 : });
2708 0 : sthis->requests_.emplace(req);
2709 0 : req->send();
2710 0 : }
2711 0 : }
2712 0 : });
2713 0 : }
2714 :
2715 : void
2716 587 : JamiAccount::loadCachedProxyServer(std::function<void(const std::string& proxy)> cb)
2717 : {
2718 587 : const auto& conf = config();
2719 587 : if (conf.proxyEnabled and proxyServerCached_.empty()) {
2720 0 : JAMI_DEBUG("[Account {:s}] Loading DHT proxy URL: {:s}", getAccountID(), conf.proxyListUrl);
2721 0 : if (conf.proxyListUrl.empty() or not conf.proxyListEnabled) {
2722 0 : cb(getDhtProxyServer(conf.proxyServer));
2723 : } else {
2724 0 : loadCachedUrl(conf.proxyListUrl,
2725 0 : cachePath_ / "dhtproxylist",
2726 0 : std::chrono::hours(24 * 3),
2727 0 : [w = weak(), cb = std::move(cb)](const dht::http::Response& response) {
2728 0 : if (auto sthis = w.lock()) {
2729 0 : if (response.status_code == 200) {
2730 0 : cb(sthis->getDhtProxyServer(response.body));
2731 : } else {
2732 0 : cb(sthis->getDhtProxyServer(sthis->config().proxyServer));
2733 : }
2734 0 : }
2735 0 : });
2736 : }
2737 : } else {
2738 587 : cb(proxyServerCached_);
2739 : }
2740 587 : }
2741 :
2742 : std::string
2743 0 : JamiAccount::getDhtProxyServer(const std::string& serverList)
2744 : {
2745 0 : if (proxyServerCached_.empty()) {
2746 0 : std::vector<std::string> proxys;
2747 : // Split the list of servers
2748 0 : std::sregex_iterator begin = {serverList.begin(), serverList.end(), PROXY_REGEX}, end;
2749 0 : for (auto it = begin; it != end; ++it) {
2750 0 : auto& match = *it;
2751 0 : if (match[5].matched and match[6].matched) {
2752 : try {
2753 0 : auto start = std::stoi(match[5]), end = std::stoi(match[6]);
2754 0 : for (auto p = start; p <= end; p++)
2755 0 : proxys.emplace_back(match[1].str() + match[2].str() + ":"
2756 0 : + std::to_string(p));
2757 0 : } catch (...) {
2758 0 : JAMI_WARN("Malformed proxy, ignore it");
2759 0 : continue;
2760 0 : }
2761 : } else {
2762 0 : proxys.emplace_back(match[0].str());
2763 : }
2764 0 : }
2765 0 : if (proxys.empty())
2766 0 : return {};
2767 : // Select one of the list as the current proxy.
2768 0 : auto randIt = proxys.begin();
2769 0 : std::advance(randIt,
2770 0 : std::uniform_int_distribution<unsigned long>(0, proxys.size() - 1)(rand));
2771 0 : proxyServerCached_ = *randIt;
2772 : // Cache it!
2773 0 : dhtnet::fileutils::check_dir(cachePath_, 0700);
2774 0 : auto proxyCachePath = cachePath_ / "dhtproxy";
2775 0 : std::ofstream file(proxyCachePath);
2776 0 : JAMI_DEBUG("Cache DHT proxy server: {}", proxyServerCached_);
2777 0 : Json::Value node(Json::objectValue);
2778 0 : node[getProxyConfigKey()] = proxyServerCached_;
2779 0 : if (file.is_open())
2780 0 : file << node;
2781 : else
2782 0 : JAMI_WARNING("Unable to write into {}", proxyCachePath);
2783 0 : }
2784 0 : return proxyServerCached_;
2785 : }
2786 :
2787 : MatchRank
2788 0 : JamiAccount::matches(std::string_view userName, std::string_view server) const
2789 : {
2790 0 : if (not accountManager_ or not accountManager_->getInfo())
2791 0 : return MatchRank::NONE;
2792 :
2793 0 : if (userName == accountManager_->getInfo()->accountId
2794 0 : || server == accountManager_->getInfo()->accountId
2795 0 : || userName == accountManager_->getInfo()->deviceId) {
2796 0 : JAMI_LOG("Matching account ID in request with username {}", userName);
2797 0 : return MatchRank::FULL;
2798 : } else {
2799 0 : return MatchRank::NONE;
2800 : }
2801 : }
2802 :
2803 : std::string
2804 295 : JamiAccount::getFromUri() const
2805 : {
2806 295 : const std::string uri = "<sip:" + accountManager_->getInfo()->accountId + "@ring.dht>";
2807 295 : if (not config().displayName.empty())
2808 590 : return "\"" + config().displayName + "\" " + uri;
2809 0 : return uri;
2810 295 : }
2811 :
2812 : std::string
2813 292 : JamiAccount::getToUri(const std::string& to) const
2814 : {
2815 292 : auto username = to;
2816 292 : string_replace(username, "sip:", "");
2817 292 : return fmt::format("<sips:{};transport=tls>", username);
2818 292 : }
2819 :
2820 : std::string
2821 5 : getDisplayed(const std::string& conversationId, const std::string& messageId)
2822 : {
2823 : // implementing https://tools.ietf.org/rfc/rfc5438.txt
2824 : return fmt::format(
2825 : "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
2826 : "<imdn><message-id>{}</message-id>\n"
2827 : "{}"
2828 : "<display-notification><status><displayed/></status></display-notification>\n"
2829 : "</imdn>",
2830 : messageId,
2831 10 : conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>");
2832 : }
2833 :
2834 : std::string
2835 7 : getPIDF(const std::string& note)
2836 : {
2837 : // implementing https://datatracker.ietf.org/doc/html/rfc3863
2838 : return fmt::format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
2839 : "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\">\n"
2840 : " <tuple>\n"
2841 : " <status>\n"
2842 : " <basic>{}</basic>\n"
2843 : " </status>\n"
2844 : " </tuple>\n"
2845 : "</presence>",
2846 7 : note);
2847 : }
2848 :
2849 : void
2850 5 : JamiAccount::setIsComposing(const std::string& conversationUri, bool isWriting)
2851 : {
2852 5 : Uri uri(conversationUri);
2853 5 : std::string conversationId = {};
2854 5 : if (uri.scheme() == Uri::Scheme::SWARM) {
2855 5 : conversationId = uri.authority();
2856 : } else {
2857 0 : return;
2858 : }
2859 :
2860 5 : if (auto cm = convModule(true)) {
2861 5 : if (auto typer = cm->getTypers(conversationId)) {
2862 5 : if (isWriting)
2863 4 : typer->addTyper(getUsername(), true);
2864 : else
2865 1 : typer->removeTyper(getUsername(), true);
2866 5 : }
2867 : }
2868 5 : }
2869 :
2870 : bool
2871 5 : JamiAccount::setMessageDisplayed(const std::string& conversationUri,
2872 : const std::string& messageId,
2873 : int status)
2874 : {
2875 5 : Uri uri(conversationUri);
2876 5 : std::string conversationId = {};
2877 5 : if (uri.scheme() == Uri::Scheme::SWARM)
2878 5 : conversationId = uri.authority();
2879 : auto sendMessage = status == (int) libjami::Account::MessageStates::DISPLAYED
2880 5 : && isReadReceiptEnabled();
2881 5 : if (!conversationId.empty())
2882 5 : sendMessage &= convModule()->onMessageDisplayed(getUsername(), conversationId, messageId);
2883 5 : if (sendMessage)
2884 10 : sendInstantMessage(uri.authority(),
2885 10 : {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}});
2886 5 : return true;
2887 5 : }
2888 :
2889 : std::string
2890 194 : JamiAccount::getContactHeader(const std::shared_ptr<SipTransport>& sipTransport)
2891 : {
2892 194 : if (sipTransport and sipTransport->get() != nullptr) {
2893 194 : auto transport = sipTransport->get();
2894 194 : auto* td = reinterpret_cast<tls::AbstractSIPTransport::TransportData*>(transport);
2895 194 : auto address = td->self->getLocalAddress().toString(true);
2896 194 : bool reliable = transport->flag & PJSIP_TRANSPORT_RELIABLE;
2897 : return fmt::format("\"{}\" <sips:{}{}{};transport={}>",
2898 194 : config().displayName,
2899 194 : id_.second->getId().toString(),
2900 194 : address.empty() ? "" : "@",
2901 : address,
2902 388 : reliable ? "tls" : "dtls");
2903 194 : } else {
2904 0 : JAMI_ERR("getContactHeader: no SIP transport provided");
2905 : return fmt::format("\"{}\" <sips:{}@ring.dht>",
2906 0 : config().displayName,
2907 0 : id_.second->getId().toString());
2908 : }
2909 : }
2910 :
2911 : void
2912 56 : JamiAccount::addContact(const std::string& uri, bool confirmed)
2913 : {
2914 56 : dht::InfoHash h(uri);
2915 56 : if (not h) {
2916 3 : JAMI_ERROR("addContact: invalid contact URI");
2917 1 : return;
2918 : }
2919 55 : auto conversation = convModule()->getOneToOneConversation(uri);
2920 55 : if (!confirmed && conversation.empty())
2921 54 : conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
2922 55 : std::unique_lock lock(configurationMutex_);
2923 55 : if (accountManager_)
2924 55 : accountManager_->addContact(h, confirmed, conversation);
2925 : else
2926 0 : JAMI_WARNING("[Account {}] addContact: account not loaded", getAccountID());
2927 55 : }
2928 :
2929 : void
2930 17 : JamiAccount::removeContact(const std::string& uri, bool ban)
2931 : {
2932 17 : std::lock_guard lock(configurationMutex_);
2933 17 : if (accountManager_)
2934 17 : accountManager_->removeContact(uri, ban);
2935 : else
2936 0 : JAMI_WARNING("[Account {}] removeContact: account not loaded", getAccountID());
2937 17 : }
2938 :
2939 : std::map<std::string, std::string>
2940 2 : JamiAccount::getContactDetails(const std::string& uri) const
2941 : {
2942 2 : std::lock_guard lock(configurationMutex_);
2943 2 : return accountManager_ ? accountManager_->getContactDetails(uri)
2944 4 : : std::map<std::string, std::string> {};
2945 2 : }
2946 :
2947 : std::optional<Contact>
2948 27 : JamiAccount::getContactInfo(const std::string& uri) const
2949 : {
2950 27 : std::lock_guard lock(configurationMutex_);
2951 54 : return accountManager_ ? accountManager_->getContactInfo(uri) : std::nullopt;
2952 27 : }
2953 :
2954 : std::vector<std::map<std::string, std::string>>
2955 0 : JamiAccount::getContacts(bool includeRemoved) const
2956 : {
2957 0 : std::lock_guard lock(configurationMutex_);
2958 0 : if (not accountManager_)
2959 0 : return {};
2960 0 : return accountManager_->getContacts(includeRemoved);
2961 0 : }
2962 :
2963 : /* trust requests */
2964 :
2965 : std::vector<std::map<std::string, std::string>>
2966 608 : JamiAccount::getTrustRequests() const
2967 : {
2968 608 : std::lock_guard lock(configurationMutex_);
2969 608 : return accountManager_ ? accountManager_->getTrustRequests()
2970 1216 : : std::vector<std::map<std::string, std::string>> {};
2971 608 : }
2972 :
2973 : bool
2974 25 : JamiAccount::acceptTrustRequest(const std::string& from, bool includeConversation)
2975 : {
2976 25 : dht::InfoHash h(from);
2977 25 : if (not h) {
2978 0 : JAMI_ERROR("addContact: invalid contact URI");
2979 0 : return false;
2980 : }
2981 25 : std::unique_lock lock(configurationMutex_);
2982 25 : if (accountManager_) {
2983 25 : if (!accountManager_->acceptTrustRequest(from, includeConversation)) {
2984 : // Note: unused for swarm
2985 : // Typically the case where the trust request doesn't exists, only incoming DHT messages
2986 0 : return accountManager_->addContact(h, true);
2987 : }
2988 25 : return true;
2989 : }
2990 0 : JAMI_WARNING("[Account {}] acceptTrustRequest: account not loaded", getAccountID());
2991 0 : return false;
2992 25 : }
2993 :
2994 : bool
2995 2 : JamiAccount::discardTrustRequest(const std::string& from)
2996 : {
2997 : // Remove 1:1 generated conv requests
2998 2 : auto requests = getTrustRequests();
2999 4 : for (const auto& req : requests) {
3000 2 : if (req.at(libjami::Account::TrustRequest::FROM) == from) {
3001 2 : convModule()->declineConversationRequest(
3002 2 : req.at(libjami::Account::TrustRequest::CONVERSATIONID));
3003 : }
3004 : }
3005 :
3006 : // Remove trust request
3007 2 : std::lock_guard lock(configurationMutex_);
3008 2 : if (accountManager_)
3009 2 : return accountManager_->discardTrustRequest(from);
3010 0 : JAMI_WARNING("[Account {:s}] discardTrustRequest: account not loaded", getAccountID());
3011 0 : return false;
3012 2 : }
3013 :
3014 : void
3015 3 : JamiAccount::declineConversationRequest(const std::string& conversationId)
3016 : {
3017 3 : auto peerId = convModule()->peerFromConversationRequest(conversationId);
3018 3 : convModule()->declineConversationRequest(conversationId);
3019 3 : if (!peerId.empty()) {
3020 3 : std::lock_guard lock(configurationMutex_);
3021 3 : if (auto info = accountManager_->getInfo()) {
3022 : // Verify if we have a trust request with this peer + convId
3023 3 : auto req = info->contacts->getTrustRequest(dht::InfoHash(peerId));
3024 6 : if (req.find(libjami::Account::TrustRequest::CONVERSATIONID) != req.end()
3025 6 : && req.at(libjami::Account::TrustRequest::CONVERSATIONID) == conversationId) {
3026 1 : accountManager_->discardTrustRequest(peerId);
3027 3 : JAMI_DEBUG("[Account {:s}] Declined trust request with {:s}",
3028 : getAccountID(),
3029 : peerId);
3030 : }
3031 3 : }
3032 3 : }
3033 3 : }
3034 :
3035 : void
3036 48 : JamiAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload)
3037 : {
3038 48 : dht::InfoHash h(to);
3039 48 : if (not h) {
3040 0 : JAMI_ERROR("addContact: invalid contact URI");
3041 0 : return;
3042 : }
3043 : // Here we cache payload sent by the client
3044 48 : auto requestPath = cachePath_ / "requests";
3045 48 : dhtnet::fileutils::recursive_mkdir(requestPath, 0700);
3046 48 : auto cachedFile = requestPath / to;
3047 48 : std::ofstream req(cachedFile, std::ios::trunc | std::ios::binary);
3048 48 : if (!req.is_open()) {
3049 0 : JAMI_ERROR("Unable to write data to {}", cachedFile);
3050 0 : return;
3051 : }
3052 :
3053 48 : if (not payload.empty()) {
3054 2 : req.write(reinterpret_cast<const char*>(payload.data()), payload.size());
3055 : }
3056 :
3057 48 : if (payload.size() >= 64000) {
3058 1 : JAMI_WARN() << "Trust request is too big. Remove payload";
3059 : }
3060 :
3061 48 : auto conversation = convModule()->getOneToOneConversation(to);
3062 48 : if (conversation.empty())
3063 0 : conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, h);
3064 48 : if (not conversation.empty()) {
3065 48 : std::lock_guard lock(configurationMutex_);
3066 48 : if (accountManager_)
3067 96 : accountManager_->sendTrustRequest(to,
3068 : conversation,
3069 96 : payload.size() >= 64000 ? std::vector<uint8_t> {}
3070 : : payload);
3071 : else
3072 0 : JAMI_WARNING("[Account {}] sendTrustRequest: account not loaded", getAccountID());
3073 48 : } else
3074 0 : JAMI_WARNING("[Account {}] sendTrustRequest: account not loaded", getAccountID());
3075 48 : }
3076 :
3077 : void
3078 233 : JamiAccount::forEachDevice(const dht::InfoHash& to,
3079 : std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
3080 : std::function<void(bool)>&& end)
3081 : {
3082 233 : accountManager_->forEachDevice(to, std::move(op), std::move(end));
3083 233 : }
3084 :
3085 : uint64_t
3086 14137 : JamiAccount::sendTextMessage(const std::string& to,
3087 : const std::string& deviceId,
3088 : const std::map<std::string, std::string>& payloads,
3089 : uint64_t refreshToken,
3090 : bool onlyConnected)
3091 : {
3092 14137 : Uri uri(to);
3093 14137 : if (uri.scheme() == Uri::Scheme::SWARM) {
3094 0 : sendInstantMessage(uri.authority(), payloads);
3095 0 : return 0;
3096 : }
3097 :
3098 14137 : std::string toUri;
3099 : try {
3100 14137 : toUri = parseJamiUri(to);
3101 0 : } catch (...) {
3102 0 : JAMI_ERROR("Failed to send a text message due to an invalid URI {}", to);
3103 0 : return 0;
3104 0 : }
3105 14137 : if (payloads.size() != 1) {
3106 0 : JAMI_ERROR("Multi-part im is not supported yet by JamiAccount");
3107 0 : return 0;
3108 : }
3109 14137 : return SIPAccountBase::sendTextMessage(toUri, deviceId, payloads, refreshToken, onlyConnected);
3110 14137 : }
3111 :
3112 : void
3113 14961 : JamiAccount::sendMessage(const std::string& to,
3114 : const std::string& deviceId,
3115 : const std::map<std::string, std::string>& payloads,
3116 : uint64_t token,
3117 : bool retryOnTimeout,
3118 : bool onlyConnected)
3119 : {
3120 14961 : std::string toUri;
3121 : try {
3122 14961 : toUri = parseJamiUri(to);
3123 0 : } catch (...) {
3124 0 : JAMI_ERROR("[Account {}] Failed to send a text message due to an invalid URI {}",
3125 : getAccountID(),
3126 : to);
3127 0 : if (!onlyConnected)
3128 0 : messageEngine_.onMessageSent(to, token, false, deviceId);
3129 0 : return;
3130 0 : }
3131 14961 : if (payloads.size() != 1) {
3132 0 : JAMI_ERROR("Multi-part im is not supported");
3133 0 : if (!onlyConnected)
3134 0 : messageEngine_.onMessageSent(toUri, token, false, deviceId);
3135 0 : return;
3136 : }
3137 :
3138 : // Use the Message channel if available
3139 14961 : std::shared_lock clk(connManagerMtx_);
3140 : auto* handler = static_cast<MessageChannelHandler*>(
3141 14961 : channelHandlers_[Uri::Scheme::MESSAGE].get());
3142 14961 : if (!handler) {
3143 37 : clk.unlock();
3144 37 : if (!onlyConnected)
3145 37 : messageEngine_.onMessageSent(to, token, false, deviceId);
3146 37 : return;
3147 : }
3148 :
3149 : /**
3150 : * Track sending state for a single message to one or more devices.
3151 : */
3152 : class SendMessageContext {
3153 : public:
3154 : using OnComplete = std::function<void(bool, bool)>;
3155 14924 : SendMessageContext(OnComplete onComplete) : onComplete(std::move(onComplete)) {}
3156 : /** Track new pending message for device */
3157 13481 : bool add(const DeviceId& device) {
3158 13481 : std::lock_guard lk(mtx);
3159 26962 : return devices.insert(device).second;
3160 13481 : }
3161 : /** Call after all messages are sent */
3162 14924 : void start() {
3163 14924 : std::unique_lock lk(mtx);
3164 14924 : started = true;
3165 14924 : checkComplete(lk);
3166 14924 : }
3167 : /** Complete pending message for device */
3168 13250 : bool complete(const DeviceId& device, bool success) {
3169 13250 : std::unique_lock lk(mtx);
3170 13250 : if (devices.erase(device) == 0)
3171 0 : return false;
3172 13250 : ++completeCount;
3173 13250 : if (success)
3174 13249 : ++successCount;
3175 13250 : checkComplete(lk);
3176 13250 : return true;
3177 13250 : }
3178 : bool empty() const {
3179 : std::lock_guard lk(mtx);
3180 : return devices.empty();
3181 : }
3182 2698 : bool pending(const DeviceId& device) const {
3183 2698 : std::lock_guard lk(mtx);
3184 5396 : return devices.find(device) != devices.end();
3185 2698 : }
3186 : private:
3187 : mutable std::mutex mtx;
3188 : OnComplete onComplete;
3189 : std::set<DeviceId> devices;
3190 : unsigned completeCount = 0;
3191 : unsigned successCount = 0;
3192 : bool started {false};
3193 :
3194 28174 : void checkComplete(std::unique_lock<std::mutex>& lk) {
3195 28174 : if (started && (devices.empty() || successCount)) {
3196 14925 : if (onComplete) {
3197 14924 : auto cb = std::move(onComplete);
3198 14924 : onComplete = {};
3199 14924 : lk.unlock();
3200 14924 : cb(successCount != 0, completeCount != 0);
3201 14924 : }
3202 : }
3203 28174 : }
3204 : };
3205 14924 : auto devices = std::make_shared<SendMessageContext>([
3206 : w= weak(),
3207 : to,
3208 : token,
3209 : deviceId,
3210 : onlyConnected,
3211 : retryOnTimeout
3212 28173 : ](bool success, bool sent) {
3213 14924 : if (auto acc = w.lock())
3214 14924 : acc->onMessageSent(to, token, deviceId, success, onlyConnected, sent && retryOnTimeout);
3215 29848 : });
3216 :
3217 : struct TextMessageCtx {
3218 : std::weak_ptr<JamiAccount> acc;
3219 : std::string peerId;
3220 : DeviceId deviceId;
3221 : std::shared_ptr<SendMessageContext> devices;
3222 : std::shared_ptr<dhtnet::ChannelSocket> sipChannel;
3223 : };
3224 :
3225 13250 : auto completed = [w = weak(), to, devices](const DeviceId& device, std::shared_ptr<dhtnet::ChannelSocket> conn, bool success) {
3226 13250 : if (!success)
3227 1 : if (auto acc = w.lock()) {
3228 1 : std::shared_lock clk(acc->connManagerMtx_);
3229 1 : if (auto* handler = static_cast<MessageChannelHandler*>(
3230 1 : acc->channelHandlers_[Uri::Scheme::MESSAGE].get())) {
3231 1 : handler->closeChannel(to, device, conn);
3232 : }
3233 2 : }
3234 13250 : devices->complete(device, success);
3235 28174 : };
3236 :
3237 14924 : const auto& payload = *payloads.begin();
3238 14924 : auto msg = std::make_shared<MessageChannelHandler::Message>();
3239 14924 : msg->id = token;
3240 14924 : msg->t = payload.first;
3241 14924 : msg->c = payload.second;
3242 14924 : auto device = deviceId.empty() ? DeviceId() : DeviceId(deviceId);
3243 14924 : if (deviceId.empty()) {
3244 3156 : auto conns = handler->getChannels(toUri);
3245 3156 : clk.unlock();
3246 4914 : for (const auto& conn : conns) {
3247 1758 : auto connDevice = conn->deviceId();
3248 1758 : if (!devices->add(connDevice))
3249 231 : continue;
3250 1527 : dht::ThreadPool::io().run([completed, connDevice, conn, msg] {
3251 1527 : completed(connDevice, conn, MessageChannelHandler::sendMessage(conn, *msg));
3252 1527 : });
3253 : }
3254 3156 : } else {
3255 11768 : if (auto conn = handler->getChannel(toUri, device)) {
3256 11723 : clk.unlock();
3257 11723 : devices->add(device);
3258 11723 : dht::ThreadPool::io().run([completed, device, conn, msg] {
3259 11723 : completed(device, conn, MessageChannelHandler::sendMessage(conn, *msg));
3260 11723 : });
3261 11723 : devices->start();
3262 11723 : return;
3263 11768 : }
3264 : }
3265 3201 : if (clk)
3266 45 : clk.unlock();
3267 :
3268 3201 : std::unique_lock lk(sipConnsMtx_);
3269 3221 : for (auto& [key, value] : sipConns_) {
3270 20 : if (key.first != to or value.empty())
3271 20 : continue;
3272 0 : if (!deviceId.empty() && key.second != device)
3273 0 : continue;
3274 0 : if (!devices->add(key.second))
3275 0 : continue;
3276 :
3277 0 : auto& conn = value.back();
3278 0 : auto& channel = conn.channel;
3279 :
3280 : // Set input token into callback
3281 0 : auto ctx = std::make_unique<TextMessageCtx>();
3282 0 : ctx->acc = weak();
3283 0 : ctx->peerId = to;
3284 0 : ctx->deviceId = key.second;
3285 0 : ctx->devices = devices;
3286 0 : ctx->sipChannel = channel;
3287 :
3288 : try {
3289 0 : auto res = sendSIPMessage(conn,
3290 : to,
3291 0 : ctx.release(),
3292 : token,
3293 : payloads,
3294 0 : [](void* token, pjsip_event* event) {
3295 0 : if (auto c = std::shared_ptr<TextMessageCtx>{(TextMessageCtx*) token})
3296 0 : runOnMainThread([
3297 0 : c = std::move(c),
3298 0 : code = event->body.tsx_state.tsx->status_code
3299 0 : ] {
3300 0 : bool success = code == PJSIP_SC_OK;
3301 : // Note: This can be called from PJSIP's eventloop while
3302 : // sipConnsMtx_ is locked. So we should retrigger the shutdown.
3303 0 : if (!success) {
3304 0 : JAMI_WARNING("Timeout when send a message, close current connection");
3305 0 : if (auto acc = c->acc.lock())
3306 0 : acc->shutdownSIPConnection(c->sipChannel,
3307 0 : c->peerId,
3308 0 : c->deviceId);
3309 : }
3310 0 : c->devices->complete(c->deviceId, code == PJSIP_SC_OK);
3311 0 : });
3312 0 : });
3313 0 : if (!res) {
3314 0 : devices->complete(key.second, false);
3315 0 : continue;
3316 : }
3317 0 : } catch (const std::runtime_error& ex) {
3318 0 : JAMI_WARNING("{}", ex.what());
3319 : // Remove connection in incorrect state
3320 0 : shutdownSIPConnection(channel, to, key.second);
3321 0 : devices->complete(key.second, false);
3322 0 : continue;
3323 0 : }
3324 :
3325 0 : if (key.second == device) {
3326 0 : devices->start();
3327 0 : return;
3328 : }
3329 0 : }
3330 3201 : lk.unlock();
3331 3201 : devices->start();
3332 :
3333 3201 : if (onlyConnected)
3334 18 : return;
3335 : // We are unable to send the message directly, try connecting
3336 :
3337 : // Get conversation id, which will be used by the iOS notification extension
3338 : // to load the conversation.
3339 2713 : auto extractIdFromJson = [](const std::string& jsonData) -> std::string {
3340 2713 : Json::Value parsed;
3341 2713 : if (json::parse(jsonData, parsed)) {
3342 2713 : auto value = parsed.get("id", Json::nullValue);
3343 2713 : if (value && value.isString()) {
3344 2713 : return value.asString();
3345 : }
3346 2713 : } else {
3347 0 : JAMI_WARNING("Unable to parse jsonData to get conversation ID");
3348 : }
3349 0 : return "";
3350 2713 : };
3351 :
3352 : // get request type
3353 3183 : auto payload_type = msg->t;
3354 3183 : if (payload_type == MIME_TYPE_GIT) {
3355 2713 : std::string id = extractIdFromJson(msg->c);
3356 2713 : if (!id.empty()) {
3357 2713 : payload_type += "/" + id;
3358 : }
3359 2713 : }
3360 :
3361 3183 : if (deviceId.empty()) {
3362 3138 : auto toH = dht::InfoHash(toUri);
3363 : // Find listening devices for this account
3364 3138 : accountManager_->forEachDevice(toH,
3365 3185 : [this,
3366 : to,
3367 : devices,
3368 : payload_type,
3369 : currentDevice = DeviceId(currentDeviceId())](
3370 2698 : const std::shared_ptr<dht::crypto::PublicKey>& dev) {
3371 : // Test if already sent
3372 3185 : auto deviceId = dev->getLongId();
3373 3185 : if (deviceId == currentDevice
3374 3185 : || devices->pending(deviceId)) {
3375 487 : return;
3376 : }
3377 :
3378 : // Else, ask for a channel to send the message
3379 2698 : requestMessageConnection(to, deviceId, payload_type);
3380 : });
3381 : } else {
3382 45 : requestMessageConnection(to, device, payload_type);
3383 : }
3384 61980 : }
3385 :
3386 : void
3387 14924 : JamiAccount::onMessageSent(const std::string& to, uint64_t id, const std::string& deviceId, bool success, bool onlyConnected, bool retry)
3388 : {
3389 14924 : if (!onlyConnected)
3390 14900 : messageEngine_.onMessageSent(to,
3391 : id,
3392 : success,
3393 : deviceId);
3394 :
3395 14924 : if (!success) {
3396 1676 : if (retry)
3397 1 : messageEngine_.onPeerOnline(to, deviceId);
3398 : }
3399 14924 : }
3400 :
3401 : dhtnet::IceTransportOptions
3402 113 : JamiAccount::getIceOptions() const
3403 : {
3404 113 : return connectionManager_->getIceOptions();
3405 : }
3406 :
3407 : void
3408 11 : JamiAccount::getIceOptions(std::function<void(dhtnet::IceTransportOptions&&)> cb) const
3409 : {
3410 11 : return connectionManager_->getIceOptions(std::move(cb));
3411 : }
3412 :
3413 : dhtnet::IpAddr
3414 100 : JamiAccount::getPublishedIpAddress(uint16_t family) const
3415 : {
3416 100 : return connectionManager_->getPublishedIpAddress(family);
3417 : }
3418 :
3419 : bool
3420 0 : JamiAccount::setPushNotificationToken(const std::string& token)
3421 : {
3422 0 : if (SIPAccountBase::setPushNotificationToken(token)) {
3423 0 : JAMI_WARNING("[Account {:s}] setPushNotificationToken: {:s}", getAccountID(), token);
3424 0 : if (dht_)
3425 0 : dht_->setPushNotificationToken(token);
3426 0 : return true;
3427 : }
3428 0 : return false;
3429 : }
3430 :
3431 : bool
3432 0 : JamiAccount::setPushNotificationTopic(const std::string& topic)
3433 : {
3434 0 : if (SIPAccountBase::setPushNotificationTopic(topic)) {
3435 0 : if (dht_)
3436 0 : dht_->setPushNotificationTopic(topic);
3437 0 : return true;
3438 : }
3439 0 : return false;
3440 : }
3441 :
3442 : bool
3443 0 : JamiAccount::setPushNotificationConfig(const std::map<std::string, std::string>& data)
3444 : {
3445 0 : if (SIPAccountBase::setPushNotificationConfig(data)) {
3446 0 : if (dht_) {
3447 0 : dht_->setPushNotificationPlatform(config_->platform);
3448 0 : dht_->setPushNotificationTopic(config_->notificationTopic);
3449 0 : dht_->setPushNotificationToken(config_->deviceKey);
3450 : }
3451 0 : return true;
3452 : }
3453 0 : return false;
3454 : }
3455 :
3456 : /**
3457 : * To be called by clients with relevant data when a push notification is received.
3458 : */
3459 : void
3460 0 : JamiAccount::pushNotificationReceived(const std::string& from,
3461 : const std::map<std::string, std::string>& data)
3462 : {
3463 0 : auto ret_future = dht_->pushNotificationReceived(data);
3464 0 : dht::ThreadPool::computation().run([id = getAccountID(), ret_future = ret_future.share()] {
3465 0 : JAMI_WARNING("[Account {:s}] pushNotificationReceived: {}", id, (uint8_t) ret_future.get());
3466 0 : });
3467 0 : }
3468 :
3469 : std::string
3470 8 : JamiAccount::getUserUri() const
3471 : {
3472 : #ifdef ENABLE_NAMESERVER
3473 8 : if (not registeredName_.empty())
3474 0 : return JAMI_URI_PREFIX + registeredName_;
3475 : #endif
3476 8 : return JAMI_URI_PREFIX + config().username;
3477 : }
3478 :
3479 : std::vector<libjami::Message>
3480 0 : JamiAccount::getLastMessages(const uint64_t& base_timestamp)
3481 : {
3482 0 : return SIPAccountBase::getLastMessages(base_timestamp);
3483 : }
3484 :
3485 : void
3486 0 : JamiAccount::startAccountPublish()
3487 : {
3488 0 : AccountPeerInfo info_pub;
3489 0 : info_pub.accountId = dht::InfoHash(accountManager_->getInfo()->accountId);
3490 0 : info_pub.displayName = config().displayName;
3491 0 : peerDiscovery_->startPublish<AccountPeerInfo>(PEER_DISCOVERY_JAMI_SERVICE, info_pub);
3492 0 : }
3493 :
3494 : void
3495 0 : JamiAccount::startAccountDiscovery()
3496 : {
3497 0 : auto id = dht::InfoHash(accountManager_->getInfo()->accountId);
3498 0 : peerDiscovery_->startDiscovery<AccountPeerInfo>(
3499 0 : PEER_DISCOVERY_JAMI_SERVICE, [this, id](AccountPeerInfo&& v, dht::SockAddr&&) {
3500 0 : std::lock_guard lc(discoveryMapMtx_);
3501 : // Make sure that account itself will not be recorded
3502 0 : if (v.accountId != id) {
3503 : // Create or find the old one
3504 0 : auto& dp = discoveredPeers_[v.accountId];
3505 0 : dp.displayName = v.displayName;
3506 0 : discoveredPeerMap_[v.accountId.toString()] = v.displayName;
3507 0 : if (dp.cleanupTask) {
3508 0 : dp.cleanupTask->cancel();
3509 : } else {
3510 : // Avoid repeat reception of same peer
3511 0 : JAMI_LOG("Account discovered: {}: {}", v.displayName, v.accountId.to_c_str());
3512 : // Send Added Peer and corrsponding accoundID
3513 0 : emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(getAccountID(),
3514 : v.accountId
3515 0 : .toString(),
3516 : 0,
3517 0 : v.displayName);
3518 : }
3519 0 : dp.cleanupTask = Manager::instance().scheduler().scheduleIn(
3520 0 : [w = weak(), p = v.accountId, a = v.displayName] {
3521 0 : if (auto this_ = w.lock()) {
3522 : {
3523 0 : std::lock_guard lc(this_->discoveryMapMtx_);
3524 0 : this_->discoveredPeers_.erase(p);
3525 0 : this_->discoveredPeerMap_.erase(p.toString());
3526 0 : }
3527 : // Send deleted peer
3528 0 : emitSignal<libjami::PresenceSignal::NearbyPeerNotification>(
3529 0 : this_->getAccountID(), p.toString(), 1, a);
3530 0 : }
3531 0 : JAMI_INFO("Account removed from discovery list: %s", a.c_str());
3532 0 : },
3533 0 : PEER_DISCOVERY_EXPIRATION);
3534 : }
3535 0 : });
3536 0 : }
3537 :
3538 : std::map<std::string, std::string>
3539 0 : JamiAccount::getNearbyPeers() const
3540 : {
3541 0 : return discoveredPeerMap_;
3542 : }
3543 :
3544 : void
3545 0 : JamiAccount::sendProfileToPeers()
3546 : {
3547 0 : if (!connectionManager_)
3548 0 : return;
3549 0 : std::set<std::string> peers;
3550 0 : const auto& accountUri = accountManager_->getInfo()->accountId;
3551 : // TODO: avoid using getConnectionList
3552 0 : for (const auto& connection : connectionManager_->getConnectionList()) {
3553 0 : const auto& device = connection.at("device");
3554 0 : const auto& peer = connection.at("peer");
3555 0 : if (!peers.emplace(peer).second)
3556 0 : continue;
3557 0 : if (peer == accountUri) {
3558 0 : sendProfile("", accountUri, device);
3559 0 : continue;
3560 : }
3561 0 : const auto& conversationId = convModule()->getOneToOneConversation(peer);
3562 0 : if (!conversationId.empty()) {
3563 0 : sendProfile(conversationId, peer, device);
3564 : }
3565 0 : }
3566 0 : }
3567 :
3568 : void
3569 0 : JamiAccount::updateProfile(const std::string& displayName,
3570 : const std::string& avatar,
3571 : const std::string& fileType,
3572 : int32_t flag)
3573 : {
3574 : // if the fileType is empty then only the display name will be upated
3575 :
3576 0 : const auto& accountUri = accountManager_->getInfo()->accountId;
3577 0 : const auto& path = profilePath();
3578 0 : const auto& profiles = idPath_ / "profiles";
3579 :
3580 : try {
3581 0 : if (!std::filesystem::exists(profiles)) {
3582 0 : std::filesystem::create_directories(profiles);
3583 : }
3584 0 : } catch (const std::exception& e) {
3585 0 : JAMI_ERROR("Failed to create profiles directory: {}", e.what());
3586 0 : return;
3587 0 : }
3588 :
3589 0 : const auto& vCardPath = profiles / fmt::format("{}.vcf", base64::encode(accountUri));
3590 :
3591 0 : auto profile = getProfileVcard();
3592 0 : if (profile.empty()) {
3593 0 : profile = vCard::utils::initVcard();
3594 : }
3595 :
3596 0 : profile["FN"] = displayName;
3597 0 : editConfig([&](JamiAccountConfig& config) { config.displayName = displayName; });
3598 0 : emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(getAccountID(),
3599 0 : getAccountDetails());
3600 :
3601 0 : if (!fileType.empty()) {
3602 0 : const std::string& key = "PHOTO;ENCODING=BASE64;TYPE=" + fileType;
3603 0 : if (flag == 0) {
3604 0 : vCard::utils::removeByKey(profile, "PHOTO");
3605 0 : const auto& avatarPath = std::filesystem::path(avatar);
3606 0 : if (std::filesystem::exists(avatarPath)) {
3607 : try {
3608 0 : profile[key] = base64::encode(fileutils::loadFile(avatarPath));
3609 0 : } catch (const std::exception& e) {
3610 0 : JAMI_ERROR("Failed to load avatar: {}", e.what());
3611 0 : }
3612 : }
3613 0 : } else if (flag == 1) {
3614 0 : vCard::utils::removeByKey(profile, "PHOTO");
3615 0 : profile[key] = avatar;
3616 : }
3617 0 : }
3618 0 : if (flag == 2) {
3619 0 : vCard::utils::removeByKey(profile, "PHOTO");
3620 : }
3621 : try {
3622 0 : std::filesystem::path tmpPath = vCardPath.string() + ".tmp";
3623 0 : std::ofstream file(tmpPath);
3624 0 : if (file.is_open()) {
3625 0 : file << vCard::utils::toString(profile);
3626 0 : file.close();
3627 0 : std::filesystem::rename(tmpPath, vCardPath);
3628 0 : fileutils::createFileLink(path, vCardPath, true);
3629 0 : emitSignal<libjami::ConfigurationSignal::ProfileReceived>(getAccountID(),
3630 : accountUri,
3631 0 : path.string());
3632 0 : sendProfileToPeers();
3633 : } else {
3634 0 : JAMI_ERROR("Unable to open file for writing: {}", tmpPath.string());
3635 : }
3636 0 : } catch (const std::exception& e) {
3637 0 : JAMI_ERROR("Error writing profile: {}", e.what());
3638 0 : }
3639 0 : }
3640 :
3641 : void
3642 647 : JamiAccount::setActiveCodecs(const std::vector<unsigned>& list)
3643 : {
3644 647 : Account::setActiveCodecs(list);
3645 647 : if (!hasActiveCodec(MEDIA_AUDIO))
3646 630 : setCodecActive(AV_CODEC_ID_OPUS);
3647 647 : if (!hasActiveCodec(MEDIA_VIDEO)) {
3648 630 : setCodecActive(AV_CODEC_ID_HEVC);
3649 630 : setCodecActive(AV_CODEC_ID_H264);
3650 630 : setCodecActive(AV_CODEC_ID_VP8);
3651 : }
3652 647 : config_->activeCodecs = getActiveCodecs(MEDIA_ALL);
3653 647 : }
3654 :
3655 : void
3656 9 : JamiAccount::sendInstantMessage(const std::string& convId,
3657 : const std::map<std::string, std::string>& msg)
3658 : {
3659 9 : auto members = convModule()->getConversationMembers(convId);
3660 9 : if (convId.empty() && members.empty()) {
3661 : // TODO remove, it's for old API for contacts
3662 0 : sendTextMessage(convId, "", msg);
3663 0 : return;
3664 : }
3665 27 : for (const auto& m : members) {
3666 18 : const auto& uri = m.at("uri");
3667 18 : auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3668 : // Announce to all members that a new message is sent
3669 18 : sendMessage(uri, "", msg, token, false, true);
3670 : }
3671 9 : }
3672 :
3673 : bool
3674 13249 : JamiAccount::handleMessage(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& from, const std::pair<std::string, std::string>& m)
3675 : {
3676 13249 : if (not cert or not cert->issuer)
3677 0 : return true; // stop processing message
3678 :
3679 13249 : if (cert->issuer->getId().to_view() != from) {
3680 0 : JAMI_WARNING("[Account {}] [device {}] handleMessage: invalid author {}", getAccountID(), cert->issuer->getId().to_view(), from);
3681 0 : return true;
3682 : }
3683 13249 : if (m.first == MIME_TYPE_GIT) {
3684 12919 : Json::Value json;
3685 12919 : if (!json::parse(m.second, json)) {
3686 0 : return true;
3687 : }
3688 :
3689 : // fetchNewCommits will do heavy stuff like fetching, avoid to block SIP socket
3690 25838 : dht::ThreadPool::io().run([
3691 : w = weak(),
3692 : from,
3693 12919 : deviceId = json["deviceId"].asString(),
3694 12919 : id = json["id"].asString(),
3695 12919 : commit = json["commit"].asString()
3696 : ] {
3697 12919 : if (auto shared = w.lock()) {
3698 12919 : if (auto cm = shared->convModule())
3699 12919 : cm->fetchNewCommits(from, deviceId, id, commit);
3700 12919 : }
3701 12919 : });
3702 12919 : return true;
3703 13249 : } else if (m.first == MIME_TYPE_INVITE) {
3704 106 : convModule()->onNeedConversationRequest(from, m.second);
3705 106 : return true;
3706 224 : } else if (m.first == MIME_TYPE_INVITE_JSON) {
3707 207 : Json::Value json;
3708 207 : if (!json::parse(m.second, json)) {
3709 0 : return true;
3710 : }
3711 207 : convModule()->onConversationRequest(from, json);
3712 207 : return true;
3713 224 : } else if (m.first == MIME_TYPE_IM_COMPOSING) {
3714 : try {
3715 4 : static const std::regex COMPOSING_REGEX("<state>\\s*(\\w+)\\s*<\\/state>");
3716 4 : std::smatch matched_pattern;
3717 4 : std::regex_search(m.second, matched_pattern, COMPOSING_REGEX);
3718 4 : bool isComposing {false};
3719 4 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3720 4 : isComposing = matched_pattern[1] == "active";
3721 : }
3722 4 : static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3723 4 : std::regex_search(m.second, matched_pattern, CONVID_REGEX);
3724 4 : std::string conversationId = "";
3725 4 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3726 4 : conversationId = matched_pattern[1];
3727 : }
3728 4 : if (!conversationId.empty()) {
3729 4 : if (auto cm = convModule(true)) {
3730 4 : if (auto typer = cm->getTypers(conversationId)) {
3731 4 : if (isComposing)
3732 3 : typer->addTyper(from);
3733 : else
3734 1 : typer->removeTyper(from);
3735 4 : }
3736 : }
3737 : }
3738 4 : return true;
3739 4 : } catch (const std::exception& e) {
3740 0 : JAMI_WARNING("Error parsing composing state: {}", e.what());
3741 0 : }
3742 13 : } else if (m.first == MIME_TYPE_IMDN) {
3743 : try {
3744 7 : static const std::regex IMDN_MSG_ID_REGEX("<message-id>\\s*(\\w+)\\s*<\\/message-id>");
3745 7 : std::smatch matched_pattern;
3746 :
3747 7 : std::regex_search(m.second, matched_pattern, IMDN_MSG_ID_REGEX);
3748 7 : std::string messageId;
3749 7 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3750 7 : messageId = matched_pattern[1];
3751 : } else {
3752 0 : JAMI_WARNING("Message displayed: unable to parse message ID");
3753 0 : return true;
3754 : }
3755 :
3756 7 : static const std::regex STATUS_REGEX("<status>\\s*<(\\w+)\\/>\\s*<\\/status>");
3757 7 : std::regex_search(m.second, matched_pattern, STATUS_REGEX);
3758 7 : bool isDisplayed {false};
3759 7 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3760 7 : isDisplayed = matched_pattern[1] == "displayed";
3761 : } else {
3762 0 : JAMI_WARNING("Message displayed: unable to parse status");
3763 0 : return true;
3764 : }
3765 :
3766 7 : static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>");
3767 7 : std::regex_search(m.second, matched_pattern, CONVID_REGEX);
3768 7 : std::string conversationId = "";
3769 7 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3770 7 : conversationId = matched_pattern[1];
3771 : }
3772 :
3773 7 : if (!isReadReceiptEnabled())
3774 0 : return true;
3775 7 : if (isDisplayed) {
3776 7 : if (convModule()->onMessageDisplayed(from, conversationId, messageId)) {
3777 18 : JAMI_DEBUG("[message {}] Displayed by peer", messageId);
3778 12 : emitSignal<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
3779 6 : accountID_,
3780 : conversationId,
3781 : from,
3782 : messageId,
3783 : static_cast<int>(libjami::Account::MessageStates::DISPLAYED));
3784 : }
3785 : }
3786 7 : return true;
3787 7 : } catch (const std::exception& e) {
3788 0 : JAMI_ERROR("Error parsing display notification: {}", e.what());
3789 0 : }
3790 6 : } else if (m.first == MIME_TYPE_PIDF) {
3791 6 : std::smatch matched_pattern;
3792 6 : static const std::regex BASIC_REGEX("<basic>([\\w\\s]+)<\\/basic>");
3793 6 : std::regex_search(m.second, matched_pattern, BASIC_REGEX);
3794 6 : std::string customStatus {};
3795 6 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
3796 6 : customStatus = matched_pattern[1];
3797 6 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
3798 : from,
3799 : static_cast<int>(
3800 : PresenceState::CONNECTED),
3801 : customStatus);
3802 : } else {
3803 0 : JAMI_WARNING("Presence: unable to parse status");
3804 : }
3805 6 : return true;
3806 6 : }
3807 :
3808 0 : return false;
3809 : }
3810 :
3811 : void
3812 280 : JamiAccount::callConnectionClosed(const DeviceId& deviceId, bool eraseDummy)
3813 : {
3814 280 : std::function<void(const DeviceId&, bool)> cb;
3815 : {
3816 280 : std::lock_guard lk(onConnectionClosedMtx_);
3817 280 : auto it = onConnectionClosed_.find(deviceId);
3818 280 : if (it != onConnectionClosed_.end()) {
3819 89 : if (eraseDummy) {
3820 89 : cb = std::move(it->second);
3821 89 : onConnectionClosed_.erase(it);
3822 : } else {
3823 : // In this case a new subcall is created and the callback
3824 : // will be re-called once with eraseDummy = true
3825 0 : cb = it->second;
3826 : }
3827 : }
3828 280 : }
3829 840 : dht::ThreadPool::io().run(
3830 560 : [w = weak(), cb = std::move(cb), id = deviceId, erase = std::move(eraseDummy)] {
3831 280 : if (auto acc = w.lock()) {
3832 280 : if (cb)
3833 89 : cb(id, erase);
3834 280 : }
3835 280 : });
3836 280 : }
3837 :
3838 : void
3839 3153 : JamiAccount::requestMessageConnection(const std::string& peerId,
3840 : const DeviceId& deviceId,
3841 : const std::string& connectionType)
3842 : {
3843 3153 : std::shared_lock lk(connManagerMtx_);
3844 : auto* handler = static_cast<MessageChannelHandler*>(
3845 3153 : channelHandlers_[Uri::Scheme::MESSAGE].get());
3846 3153 : if (!handler)
3847 3 : return;
3848 3150 : if (deviceId) {
3849 3150 : if (auto connected = handler->getChannel(peerId, deviceId)) {
3850 1795 : return;
3851 3150 : }
3852 : } else {
3853 0 : auto connected = handler->getChannels(peerId);
3854 0 : if (!connected.empty()) {
3855 0 : return;
3856 : }
3857 0 : }
3858 1355 : handler->connect(
3859 : deviceId,
3860 : "",
3861 710 : [w = weak(), peerId](std::shared_ptr<dhtnet::ChannelSocket> socket,
3862 : const DeviceId& deviceId) {
3863 710 : if (socket)
3864 636 : dht::ThreadPool::io().run([w, peerId, deviceId] {
3865 636 : if (auto acc = w.lock()) {
3866 636 : acc->messageEngine_.onPeerOnline(peerId);
3867 636 : acc->messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
3868 636 : if (!acc->presenceNote_.empty()) {
3869 : // If a presence note is set, send it to this device.
3870 4 : auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(acc->rand);
3871 : std::map<std::string, std::string> msg = {
3872 4 : {MIME_TYPE_PIDF, getPIDF(acc->presenceNote_)}
3873 16 : };
3874 4 : acc->sendMessage(peerId, deviceId.toString(), msg, token, false, true);
3875 4 : }
3876 636 : acc->convModule()->syncConversations(peerId, deviceId.toString());
3877 636 : }
3878 636 : });
3879 710 : },
3880 : connectionType);
3881 3153 : }
3882 :
3883 : void
3884 96 : JamiAccount::requestSIPConnection(const std::string& peerId,
3885 : const DeviceId& deviceId,
3886 : const std::string& connectionType,
3887 : bool forceNewConnection,
3888 : const std::shared_ptr<SIPCall>& pc)
3889 : {
3890 96 : if (peerId == getUsername()) {
3891 0 : if (!syncModule()->isConnected(deviceId))
3892 0 : channelHandlers_[Uri::Scheme::SYNC]
3893 0 : ->connect(deviceId,
3894 : "",
3895 0 : [](std::shared_ptr<dhtnet::ChannelSocket> socket,
3896 0 : const DeviceId& deviceId) {});
3897 : }
3898 :
3899 288 : JAMI_LOG("[Account {}] Request SIP connection to peer {} on device {}",
3900 : getAccountID(),
3901 : peerId,
3902 : deviceId);
3903 :
3904 : // If a connection already exists or is in progress, no need to do this
3905 96 : std::lock_guard lk(sipConnsMtx_);
3906 96 : auto id = std::make_pair(peerId, deviceId);
3907 :
3908 96 : if (sipConns_.find(id) != sipConns_.end()) {
3909 0 : JAMI_LOG("[Account {}] A SIP connection with {} already exists", getAccountID(), deviceId);
3910 0 : return;
3911 : }
3912 : // If not present, create it
3913 96 : std::shared_lock lkCM(connManagerMtx_);
3914 96 : if (!connectionManager_)
3915 0 : return;
3916 : // Note, Even if we send 50 "sip" request, the connectionManager_ will only use one socket.
3917 : // however, this will still ask for multiple channels, so only ask
3918 : // if there is no pending request
3919 96 : if (!forceNewConnection && connectionManager_->isConnecting(deviceId, "sip")) {
3920 0 : JAMI_LOG("[Account {}] Already connecting to {}", getAccountID(), deviceId);
3921 0 : return;
3922 : }
3923 288 : JAMI_LOG("[Account {}] Ask {} for a new SIP channel", getAccountID(), deviceId);
3924 288 : connectionManager_->connectDevice(
3925 : deviceId,
3926 : "sip",
3927 96 : [w = weak(),
3928 96 : id = std::move(id),
3929 96 : pc = std::move(pc)](std::shared_ptr<dhtnet::ChannelSocket> socket, const DeviceId&) {
3930 96 : if (socket)
3931 93 : return;
3932 3 : auto shared = w.lock();
3933 3 : if (!shared)
3934 0 : return;
3935 : // If this is triggered, this means that the
3936 : // connectDevice didn't get any response from the DHT.
3937 : // Stop searching pending call.
3938 3 : shared->callConnectionClosed(id.second, true);
3939 3 : if (pc)
3940 3 : pc->onFailure();
3941 3 : },
3942 : false,
3943 : forceNewConnection,
3944 : connectionType);
3945 96 : }
3946 :
3947 : bool
3948 575 : JamiAccount::isConnectedWith(const DeviceId& deviceId) const
3949 : {
3950 575 : std::shared_lock lkCM(connManagerMtx_);
3951 575 : if (connectionManager_)
3952 565 : return connectionManager_->isConnected(deviceId);
3953 10 : return false;
3954 575 : }
3955 :
3956 : void
3957 3 : JamiAccount::sendPresenceNote(const std::string& note)
3958 : {
3959 3 : if (auto info = accountManager_->getInfo()) {
3960 3 : if (!info || !info->contacts)
3961 0 : return;
3962 3 : presenceNote_ = note;
3963 3 : auto contacts = info->contacts->getContacts();
3964 3 : std::vector<std::pair<std::string, DeviceId>> keys;
3965 : {
3966 3 : std::shared_lock lkCM(connManagerMtx_);
3967 : auto* handler = static_cast<MessageChannelHandler*>(
3968 3 : channelHandlers_[Uri::Scheme::MESSAGE].get());
3969 3 : if (!handler)
3970 0 : return;
3971 5 : for (const auto& contact : contacts) {
3972 2 : auto peerId = contact.first.toString();
3973 2 : auto channels = handler->getChannels(peerId);
3974 4 : for (const auto& channel : channels) {
3975 2 : keys.emplace_back(peerId, channel->deviceId());
3976 : }
3977 2 : }
3978 3 : }
3979 3 : auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand);
3980 12 : std::map<std::string, std::string> msg = {{MIME_TYPE_PIDF, getPIDF(presenceNote_)}};
3981 5 : for (auto& key : keys) {
3982 2 : sendMessage(key.first, key.second.toString(), msg, token, false, true);
3983 : }
3984 3 : }
3985 : }
3986 :
3987 : void
3988 706 : JamiAccount::sendProfile(const std::string& convId,
3989 : const std::string& peerUri,
3990 : const std::string& deviceId)
3991 : {
3992 706 : auto accProfilePath = profilePath();
3993 706 : if (not std::filesystem::is_regular_file(accProfilePath))
3994 698 : return;
3995 8 : auto currentSha3 = fileutils::sha3File(accProfilePath);
3996 : // VCard sync for peerUri
3997 8 : if (not needToSendProfile(peerUri, deviceId, currentSha3)) {
3998 0 : JAMI_DEBUG("[Account {}] [device {}] Peer {} already got an up-to-date vCard", getAccountID(), deviceId, peerUri);
3999 0 : return;
4000 : }
4001 : // We need a new channel
4002 24 : transferFile(convId,
4003 16 : accProfilePath.string(),
4004 : deviceId,
4005 : "profile.vcf",
4006 : "",
4007 : 0,
4008 : 0,
4009 : currentSha3,
4010 : fileutils::lastWriteTimeInSeconds(accProfilePath),
4011 8 : [accId = getAccountID(), peerUri, deviceId]() {
4012 : // Mark the VCard as sent
4013 12 : auto sendDir = fileutils::get_cache_dir() / accId / "vcard" / peerUri;
4014 6 : auto path = sendDir / deviceId;
4015 6 : dhtnet::fileutils::recursive_mkdir(sendDir);
4016 6 : std::lock_guard lock(dhtnet::fileutils::getFileLock(path));
4017 6 : if (std::filesystem::is_regular_file(path))
4018 0 : return;
4019 6 : std::ofstream p(path);
4020 6 : });
4021 706 : }
4022 :
4023 : bool
4024 8 : JamiAccount::needToSendProfile(const std::string& peerUri,
4025 : const std::string& deviceId,
4026 : const std::string& sha3Sum)
4027 : {
4028 8 : std::string previousSha3 {};
4029 8 : auto vCardPath = cachePath_ / "vcard";
4030 8 : auto sha3Path = vCardPath / "sha3";
4031 8 : dhtnet::fileutils::check_dir(vCardPath, 0700);
4032 : try {
4033 11 : previousSha3 = fileutils::loadTextFile(sha3Path);
4034 3 : } catch (...) {
4035 3 : fileutils::saveFile(sha3Path, (const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
4036 3 : return true;
4037 3 : }
4038 5 : if (sha3Sum != previousSha3) {
4039 : // Incorrect sha3 stored. Update it
4040 0 : dhtnet::fileutils::removeAll(vCardPath, true);
4041 0 : dhtnet::fileutils::check_dir(vCardPath, 0700);
4042 0 : fileutils::saveFile(sha3Path, (const uint8_t*) sha3Sum.data(), sha3Sum.size(), 0600);
4043 0 : return true;
4044 : }
4045 5 : auto peerPath = vCardPath / peerUri;
4046 5 : dhtnet::fileutils::recursive_mkdir(peerPath);
4047 5 : return not std::filesystem::is_regular_file(peerPath / deviceId);
4048 8 : }
4049 :
4050 : bool
4051 0 : JamiAccount::sendSIPMessage(SipConnection& conn,
4052 : const std::string& to,
4053 : void* ctx,
4054 : uint64_t token,
4055 : const std::map<std::string, std::string>& data,
4056 : pjsip_endpt_send_callback cb)
4057 : {
4058 0 : auto transport = conn.transport;
4059 0 : auto channel = conn.channel;
4060 0 : if (!channel)
4061 0 : throw std::runtime_error(
4062 0 : "A SIP transport exists without Channel, this is a bug. Please report");
4063 0 : auto remote_address = channel->getRemoteAddress();
4064 0 : if (!remote_address)
4065 0 : return false;
4066 :
4067 : // Build SIP Message
4068 : // "deviceID@IP"
4069 0 : auto toURI = getToUri(fmt::format("{}@{}", to, remote_address.toString(true)));
4070 0 : std::string from = getFromUri();
4071 :
4072 : // Build SIP message
4073 0 : constexpr pjsip_method msg_method = {PJSIP_OTHER_METHOD,
4074 : sip_utils::CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE)};
4075 0 : pj_str_t pjFrom = sip_utils::CONST_PJ_STR(from);
4076 0 : pj_str_t pjTo = sip_utils::CONST_PJ_STR(toURI);
4077 :
4078 : // Create request.
4079 0 : pjsip_tx_data* tdata = nullptr;
4080 0 : pj_status_t status = pjsip_endpt_create_request(link_.getEndpoint(),
4081 : &msg_method,
4082 : &pjTo,
4083 : &pjFrom,
4084 : &pjTo,
4085 : nullptr,
4086 : nullptr,
4087 : -1,
4088 : nullptr,
4089 : &tdata);
4090 0 : if (status != PJ_SUCCESS) {
4091 0 : JAMI_ERROR("Unable to create request: {}", sip_utils::sip_strerror(status));
4092 0 : return false;
4093 : }
4094 :
4095 : // Add Date Header.
4096 : pj_str_t date_str;
4097 0 : constexpr auto key = sip_utils::CONST_PJ_STR("Date");
4098 : pjsip_hdr* hdr;
4099 0 : auto time = std::time(nullptr);
4100 0 : auto date = std::ctime(&time);
4101 : // the erase-remove idiom for a cstring, removes _all_ new lines with in date
4102 0 : *std::remove(date, date + strlen(date), '\n') = '\0';
4103 :
4104 : // Add Header
4105 : hdr = reinterpret_cast<pjsip_hdr*>(
4106 0 : pjsip_date_hdr_create(tdata->pool, &key, pj_cstr(&date_str, date)));
4107 0 : pjsip_msg_add_hdr(tdata->msg, hdr);
4108 :
4109 : // https://tools.ietf.org/html/rfc5438#section-6.3
4110 0 : auto token_str = to_hex_string(token);
4111 0 : auto pjMessageId = sip_utils::CONST_PJ_STR(token_str);
4112 : hdr = reinterpret_cast<pjsip_hdr*>(
4113 0 : pjsip_generic_string_hdr_create(tdata->pool, &STR_MESSAGE_ID, &pjMessageId));
4114 0 : pjsip_msg_add_hdr(tdata->msg, hdr);
4115 :
4116 : // Add user-agent header
4117 0 : sip_utils::addUserAgentHeader(getUserAgentName(), tdata);
4118 :
4119 : // Init tdata
4120 0 : const pjsip_tpselector tp_sel = SIPVoIPLink::getTransportSelector(transport->get());
4121 0 : status = pjsip_tx_data_set_transport(tdata, &tp_sel);
4122 0 : if (status != PJ_SUCCESS) {
4123 0 : JAMI_ERROR("Unable to create request: {}", sip_utils::sip_strerror(status));
4124 0 : return false;
4125 : }
4126 0 : im::fillPJSIPMessageBody(*tdata, data);
4127 :
4128 : // Because pjsip_endpt_send_request can take quite some time, move it in a io thread to avoid to block
4129 0 : dht::ThreadPool::io().run([w = weak(), tdata, ctx, cb = std::move(cb)] {
4130 0 : auto shared = w.lock();
4131 0 : if (!shared)
4132 0 : return;
4133 0 : auto status = pjsip_endpt_send_request(shared->link_.getEndpoint(), tdata, -1, ctx, cb);
4134 0 : if (status != PJ_SUCCESS)
4135 0 : JAMI_ERROR("Unable to send request: {}", sip_utils::sip_strerror(status));
4136 0 : });
4137 0 : return true;
4138 0 : }
4139 :
4140 : void
4141 120 : JamiAccount::clearProfileCache(const std::string& peerUri)
4142 : {
4143 120 : std::error_code ec;
4144 120 : std::filesystem::remove_all(cachePath_ / "vcard" / peerUri, ec);
4145 120 : }
4146 :
4147 : std::filesystem::path
4148 708 : JamiAccount::profilePath() const
4149 : {
4150 708 : return idPath_ / "profile.vcf";
4151 : }
4152 :
4153 : void
4154 188 : JamiAccount::cacheSIPConnection(std::shared_ptr<dhtnet::ChannelSocket>&& socket,
4155 : const std::string& peerId,
4156 : const DeviceId& deviceId)
4157 : {
4158 188 : std::unique_lock lk(sipConnsMtx_);
4159 : // Verify that the connection is not already cached
4160 188 : SipConnectionKey key(peerId, deviceId);
4161 188 : auto& connections = sipConns_[key];
4162 188 : auto conn = std::find_if(connections.begin(), connections.end(), [&](const auto& v) {
4163 2 : return v.channel == socket;
4164 : });
4165 188 : if (conn != connections.end()) {
4166 0 : JAMI_WARNING("[Account {}] Channel socket already cached with this peer", getAccountID());
4167 0 : return;
4168 : }
4169 :
4170 : // Convert to SIP transport
4171 188 : auto onShutdown = [w = weak(), peerId, key, socket]() {
4172 188 : dht::ThreadPool::io().run([w = std::move(w), peerId, key, socket] {
4173 188 : auto shared = w.lock();
4174 188 : if (!shared)
4175 0 : return;
4176 188 : shared->shutdownSIPConnection(socket, key.first, key.second);
4177 : // The connection can be closed during the SIP initialization, so
4178 : // if this happens, the request should be re-sent to ask for a new
4179 : // SIP channel to make the call pass through
4180 188 : shared->callConnectionClosed(key.second, false);
4181 188 : });
4182 376 : };
4183 376 : auto sip_tr = link_.sipTransportBroker->getChanneledTransport(shared(),
4184 : socket,
4185 564 : std::move(onShutdown));
4186 188 : if (!sip_tr) {
4187 0 : JAMI_ERROR("No channeled transport found");
4188 0 : return;
4189 : }
4190 : // Store the connection
4191 188 : connections.emplace_back(SipConnection {sip_tr, socket});
4192 564 : JAMI_WARNING("[Account {:s}] [device {}] New SIP channel opened", getAccountID(), deviceId);
4193 188 : lk.unlock();
4194 :
4195 : // Retry messages
4196 188 : messageEngine_.onPeerOnline(peerId);
4197 188 : messageEngine_.onPeerOnline(peerId, deviceId.toString(), true);
4198 :
4199 : // Connect pending calls
4200 188 : forEachPendingCall(deviceId, [&](const auto& pc) {
4201 93 : if (pc->getConnectionState() != Call::ConnectionState::TRYING
4202 93 : and pc->getConnectionState() != Call::ConnectionState::PROGRESSING)
4203 0 : return;
4204 93 : pc->setSipTransport(sip_tr, getContactHeader(sip_tr));
4205 93 : pc->setState(Call::ConnectionState::PROGRESSING);
4206 93 : if (auto remote_address = socket->getRemoteAddress()) {
4207 : try {
4208 93 : onConnectedOutgoingCall(pc, peerId, remote_address);
4209 0 : } catch (const VoipLinkException&) {
4210 : // In this case, the main scenario is that SIPStartCall failed because
4211 : // the ICE is dead and the TLS session didn't send any packet on that dead
4212 : // link (connectivity change, killed by the os, etc)
4213 : // Here, we don't need to do anything, the TLS will fail and will delete
4214 : // the cached transport
4215 : }
4216 : }
4217 : });
4218 188 : }
4219 :
4220 : void
4221 188 : JamiAccount::shutdownSIPConnection(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
4222 : const std::string& peerId,
4223 : const DeviceId& deviceId)
4224 : {
4225 188 : std::unique_lock lk(sipConnsMtx_);
4226 188 : SipConnectionKey key(peerId, deviceId);
4227 188 : auto it = sipConns_.find(key);
4228 188 : if (it != sipConns_.end()) {
4229 97 : auto& conns = it->second;
4230 97 : conns.erase(std::remove_if(conns.begin(),
4231 : conns.end(),
4232 98 : [&](auto v) { return v.channel == channel; }),
4233 97 : conns.end());
4234 97 : if (conns.empty()) {
4235 96 : sipConns_.erase(it);
4236 : }
4237 : }
4238 188 : lk.unlock();
4239 : // Shutdown after removal to let the callbacks do stuff if needed
4240 188 : if (channel)
4241 188 : channel->shutdown();
4242 188 : }
4243 :
4244 : std::string_view
4245 30071 : JamiAccount::currentDeviceId() const
4246 : {
4247 30071 : if (!accountManager_ or not accountManager_->getInfo())
4248 0 : return {};
4249 30071 : return accountManager_->getInfo()->deviceId;
4250 : }
4251 :
4252 : std::shared_ptr<TransferManager>
4253 120 : JamiAccount::dataTransfer(const std::string& id)
4254 : {
4255 120 : if (id.empty())
4256 50 : return nonSwarmTransferManager_;
4257 70 : return convModule()->dataTransfer(id);
4258 : }
4259 :
4260 : void
4261 0 : JamiAccount::monitor()
4262 : {
4263 0 : JAMI_DEBUG("[Account {:s}] Monitor connections", getAccountID());
4264 0 : JAMI_DEBUG("[Account {:s}] Using proxy: {:s}", getAccountID(), proxyServerCached_);
4265 :
4266 0 : if (auto cm = convModule())
4267 0 : cm->monitor();
4268 0 : std::shared_lock lkCM(connManagerMtx_);
4269 0 : if (connectionManager_)
4270 0 : connectionManager_->monitor();
4271 0 : }
4272 :
4273 : std::vector<std::map<std::string, std::string>>
4274 0 : JamiAccount::getConnectionList(const std::string& conversationId)
4275 : {
4276 0 : std::shared_lock lkCM(connManagerMtx_);
4277 0 : if (connectionManager_ && conversationId.empty()) {
4278 0 : return connectionManager_->getConnectionList();
4279 0 : } else if (connectionManager_ && convModule_) {
4280 0 : std::vector<std::map<std::string, std::string>> connectionList;
4281 0 : if (auto conv = convModule_->getConversation(conversationId)) {
4282 0 : for (const auto& deviceId : conv->getDeviceIdList()) {
4283 0 : auto connections = connectionManager_->getConnectionList(deviceId);
4284 0 : connectionList.reserve(connectionList.size() + connections.size());
4285 0 : std::move(connections.begin(),
4286 : connections.end(),
4287 : std::back_inserter(connectionList));
4288 0 : }
4289 0 : }
4290 0 : return connectionList;
4291 0 : } else {
4292 0 : return {};
4293 : }
4294 0 : }
4295 :
4296 : std::vector<std::map<std::string, std::string>>
4297 0 : JamiAccount::getChannelList(const std::string& connectionId)
4298 : {
4299 0 : std::shared_lock lkCM(connManagerMtx_);
4300 0 : if (!connectionManager_)
4301 0 : return {};
4302 0 : return connectionManager_->getChannelList(connectionId);
4303 0 : }
4304 :
4305 : void
4306 13 : JamiAccount::sendFile(const std::string& conversationId,
4307 : const std::filesystem::path& path,
4308 : const std::string& name,
4309 : const std::string& replyTo)
4310 : {
4311 13 : if (!std::filesystem::is_regular_file(path)) {
4312 0 : JAMI_ERROR("Invalid filename '{}'", path);
4313 0 : return;
4314 : }
4315 : // NOTE: this sendMessage is in a computation thread because
4316 : // sha3sum can take quite some time to computer if the user decide
4317 : // to send a big file
4318 13 : dht::ThreadPool::computation().run([w = weak(), conversationId, path, name, replyTo]() {
4319 13 : if (auto shared = w.lock()) {
4320 13 : Json::Value value;
4321 13 : auto tid = jami::generateUID(shared->rand);
4322 13 : value["tid"] = std::to_string(tid);
4323 13 : value["displayName"] = name.empty() ? path.filename().string() : name;
4324 13 : value["totalSize"] = std::to_string(fileutils::size(path));
4325 13 : value["sha3sum"] = fileutils::sha3File(path);
4326 13 : value["type"] = "application/data-transfer+json";
4327 :
4328 52 : shared->convModule()->sendMessage(
4329 13 : conversationId,
4330 13 : std::move(value),
4331 13 : replyTo,
4332 : true,
4333 26 : [accId = shared->getAccountID(), conversationId, tid, path](
4334 13 : const std::string& commitId) {
4335 : // Create a symlink to answer to re-ask
4336 26 : auto filelinkPath = fileutils::get_data_dir() / accId / "conversation_data"
4337 52 : / conversationId / (commitId + "_" + std::to_string(tid));
4338 13 : filelinkPath += path.extension();
4339 13 : if (path != filelinkPath && !std::filesystem::is_symlink(filelinkPath)) {
4340 13 : if (!fileutils::createFileLink(filelinkPath, path, true)) {
4341 0 : JAMI_WARNING(
4342 : "Unable to create symlink for file transfer {} - {}. Copy file",
4343 : filelinkPath,
4344 : path);
4345 0 : std::error_code ec;
4346 0 : auto success = std::filesystem::copy_file(path, filelinkPath, ec);
4347 0 : if (ec || !success) {
4348 0 : JAMI_ERROR("Unable to copy file for file transfer {} - {}",
4349 : filelinkPath,
4350 : path);
4351 : // Signal to notify clients that the operation failed.
4352 : // The fileId field sends the filePath.
4353 : // libjami::DataTransferEventCode::unsupported (2) is unused elsewhere.
4354 0 : emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4355 0 : accId,
4356 0 : conversationId,
4357 : commitId,
4358 0 : path.u8string(),
4359 : uint32_t(libjami::DataTransferEventCode::invalid));
4360 : } else {
4361 : // Signal to notify clients that the file is copied and can be
4362 : // safely deleted. The fileId field sends the filePath.
4363 : // libjami::DataTransferEventCode::created (1) is unused elsewhere.
4364 0 : emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4365 0 : accId,
4366 0 : conversationId,
4367 : commitId,
4368 0 : path.u8string(),
4369 : uint32_t(libjami::DataTransferEventCode::created));
4370 : }
4371 : } else {
4372 26 : emitSignal<libjami::DataTransferSignal::DataTransferEvent>(
4373 13 : accId,
4374 13 : conversationId,
4375 : commitId,
4376 26 : path.u8string(),
4377 : uint32_t(libjami::DataTransferEventCode::created));
4378 : }
4379 : }
4380 13 : });
4381 26 : }
4382 13 : });
4383 : }
4384 :
4385 : void
4386 8 : JamiAccount::transferFile(const std::string& conversationId,
4387 : const std::string& path,
4388 : const std::string& deviceId,
4389 : const std::string& fileId,
4390 : const std::string& interactionId,
4391 : size_t start,
4392 : size_t end,
4393 : const std::string& sha3Sum,
4394 : uint64_t lastWriteTime,
4395 : std::function<void()> onFinished)
4396 : {
4397 8 : std::string modified;
4398 8 : if (lastWriteTime != 0) {
4399 8 : modified = fmt::format("&modified={}", lastWriteTime);
4400 : }
4401 8 : auto fid = fileId == "profile.vcf" ? fmt::format("profile.vcf?sha3={}{}", sha3Sum, modified)
4402 24 : : fileId;
4403 8 : auto channelName = conversationId.empty() ? fmt::format("{}profile.vcf?sha3={}{}",
4404 : DATA_TRANSFER_SCHEME,
4405 : sha3Sum,
4406 : modified)
4407 : : fmt::format("{}{}/{}/{}",
4408 : DATA_TRANSFER_SCHEME,
4409 : conversationId,
4410 14 : currentDeviceId(),
4411 30 : fid);
4412 8 : std::shared_lock lkCM(connManagerMtx_);
4413 8 : if (!connectionManager_)
4414 0 : return;
4415 : connectionManager_
4416 24 : ->connectDevice(DeviceId(deviceId),
4417 : channelName,
4418 8 : [this,
4419 : conversationId,
4420 8 : path = std::move(path),
4421 : fileId,
4422 : interactionId,
4423 : start,
4424 : end,
4425 8 : onFinished = std::move(
4426 : onFinished)](std::shared_ptr<dhtnet::ChannelSocket> socket,
4427 16 : const DeviceId&) {
4428 8 : if (!socket)
4429 0 : return;
4430 40 : dht::ThreadPool::io().run([w = weak(),
4431 8 : path = std::move(path),
4432 8 : socket = std::move(socket),
4433 8 : conversationId = std::move(conversationId),
4434 8 : fileId,
4435 8 : interactionId,
4436 : start,
4437 : end,
4438 8 : onFinished = std::move(onFinished)] {
4439 8 : if (auto shared = w.lock())
4440 8 : if (auto dt = shared->dataTransfer(conversationId))
4441 24 : dt->transferFile(socket,
4442 8 : fileId,
4443 8 : interactionId,
4444 8 : path,
4445 : start,
4446 : end,
4447 24 : std::move(onFinished));
4448 8 : });
4449 : });
4450 8 : }
4451 :
4452 : void
4453 13 : JamiAccount::askForFileChannel(const std::string& conversationId,
4454 : const std::string& deviceId,
4455 : const std::string& interactionId,
4456 : const std::string& fileId,
4457 : size_t start,
4458 : size_t end)
4459 : {
4460 29 : auto tryDevice = [=](const auto& did) {
4461 29 : std::shared_lock lkCM(connManagerMtx_);
4462 29 : if (!connectionManager_)
4463 0 : return;
4464 :
4465 0 : auto channelName = fmt::format("{}{}/{}/{}",
4466 : DATA_TRANSFER_SCHEME,
4467 29 : conversationId,
4468 0 : currentDeviceId(),
4469 29 : fileId);
4470 29 : if (start != 0 || end != 0) {
4471 6 : channelName += fmt::format("?start={}&end={}", start, end);
4472 : }
4473 : // We can avoid to negotiate new sessions, as the file notif
4474 : // probably came from an online device or last connected device.
4475 58 : connectionManager_->connectDevice(
4476 : did,
4477 : channelName,
4478 41 : [w = weak(),
4479 29 : conversationId,
4480 29 : fileId,
4481 29 : interactionId,
4482 : start](std::shared_ptr<dhtnet::ChannelSocket> channel, const DeviceId&) {
4483 29 : if (!channel)
4484 17 : return;
4485 12 : dht::ThreadPool::io().run([w, conversationId, channel, fileId, interactionId, start] {
4486 12 : auto shared = w.lock();
4487 12 : if (!shared)
4488 0 : return;
4489 12 : auto dt = shared->dataTransfer(conversationId);
4490 12 : if (!dt)
4491 0 : return;
4492 12 : if (interactionId.empty())
4493 0 : dt->onIncomingProfile(channel);
4494 : else
4495 12 : dt->onIncomingFileTransfer(fileId, channel, start);
4496 12 : });
4497 : },
4498 : false);
4499 42 : };
4500 :
4501 13 : if (!deviceId.empty()) {
4502 : // Only ask for device
4503 1 : tryDevice(DeviceId(deviceId));
4504 : } else {
4505 : // Only ask for connected devices. For others we will attempt
4506 : // with new peer online
4507 40 : for (const auto& m : convModule()->getConversationMembers(conversationId)) {
4508 28 : accountManager_->forEachDevice(dht::InfoHash(m.at("uri")), [
4509 : tryDevice
4510 : ](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
4511 28 : tryDevice(dev->getLongId());
4512 28 : });
4513 12 : }
4514 : }
4515 13 : }
4516 :
4517 : void
4518 39 : JamiAccount::askForProfile(const std::string& conversationId,
4519 : const std::string& deviceId,
4520 : const std::string& memberUri)
4521 : {
4522 39 : std::shared_lock lkCM(connManagerMtx_);
4523 39 : if (!connectionManager_)
4524 0 : return;
4525 :
4526 : auto channelName = fmt::format("{}{}/profile/{}.vcf",
4527 : DATA_TRANSFER_SCHEME,
4528 : conversationId,
4529 0 : memberUri);
4530 : // We can avoid to negotiate new sessions, as the file notif
4531 : // probably came from an online device or last connected device.
4532 117 : connectionManager_->connectDevice(
4533 78 : DeviceId(deviceId),
4534 : channelName,
4535 72 : [this, conversationId](std::shared_ptr<dhtnet::ChannelSocket> channel, const DeviceId&) {
4536 39 : if (!channel)
4537 6 : return;
4538 33 : dht::ThreadPool::io().run([w = weak(), conversationId, channel] {
4539 33 : if (auto shared = w.lock())
4540 33 : if (auto dt = shared->dataTransfer(conversationId))
4541 66 : dt->onIncomingProfile(channel);
4542 33 : });
4543 : },
4544 : false);
4545 39 : }
4546 :
4547 : void
4548 1525 : JamiAccount::onPeerConnected(const std::string& peerId, bool connected)
4549 : {
4550 1525 : std::unique_lock lock(buddyInfoMtx);
4551 1525 : auto& state = presenceState_[peerId];
4552 1525 : auto it = trackedBuddies_.find(dht::InfoHash(peerId));
4553 1525 : auto isOnline = it != trackedBuddies_.end() && it->second.devices_cnt > 0;
4554 1525 : auto newState = connected ? PresenceState::CONNECTED : (isOnline ? PresenceState::AVAILABLE : PresenceState::DISCONNECTED);
4555 1525 : if (state != newState) {
4556 1521 : state = newState;
4557 1521 : lock.unlock();
4558 1521 : emitSignal<libjami::PresenceSignal::NewBuddyNotification>(getAccountID(),
4559 : peerId,
4560 : static_cast<int>(newState),
4561 : "");
4562 : }
4563 1525 : }
4564 :
4565 : void
4566 698 : JamiAccount::initConnectionManager()
4567 : {
4568 698 : if (!nonSwarmTransferManager_)
4569 : nonSwarmTransferManager_
4570 560 : = std::make_shared<TransferManager>(accountID_,
4571 560 : config().username,
4572 : "",
4573 1680 : dht::crypto::getDerivedRandomEngine(rand));
4574 698 : if (!connectionManager_) {
4575 569 : auto connectionManagerConfig = std::make_shared<dhtnet::ConnectionManager::Config>();
4576 569 : connectionManagerConfig->ioContext = Manager::instance().ioContext();
4577 569 : connectionManagerConfig->dht = dht();
4578 569 : connectionManagerConfig->certStore = certStore_;
4579 569 : connectionManagerConfig->id = identity();
4580 569 : connectionManagerConfig->upnpCtrl = upnpCtrl_;
4581 569 : connectionManagerConfig->turnServer = config().turnServer;
4582 569 : connectionManagerConfig->upnpEnabled = config().upnpEnabled;
4583 569 : connectionManagerConfig->turnServerUserName = config().turnServerUserName;
4584 569 : connectionManagerConfig->turnServerPwd = config().turnServerPwd;
4585 569 : connectionManagerConfig->turnServerRealm = config().turnServerRealm;
4586 569 : connectionManagerConfig->turnEnabled = config().turnEnabled;
4587 569 : connectionManagerConfig->cachePath = cachePath_;
4588 569 : connectionManagerConfig->logger = Logger::dhtLogger();
4589 569 : connectionManagerConfig->factory = Manager::instance().getIceTransportFactory();
4590 569 : connectionManagerConfig->turnCache = turnCache_;
4591 1138 : connectionManagerConfig->rng = std::make_unique<std::mt19937_64>(
4592 1138 : dht::crypto::getDerivedRandomEngine(rand));
4593 569 : connectionManager_ = std::make_unique<dhtnet::ConnectionManager>(connectionManagerConfig);
4594 569 : channelHandlers_[Uri::Scheme::SWARM]
4595 1138 : = std::make_unique<SwarmChannelHandler>(shared(), *connectionManager_.get());
4596 569 : channelHandlers_[Uri::Scheme::GIT]
4597 1138 : = std::make_unique<ConversationChannelHandler>(shared(), *connectionManager_.get());
4598 569 : channelHandlers_[Uri::Scheme::SYNC]
4599 1138 : = std::make_unique<SyncChannelHandler>(shared(), *connectionManager_.get());
4600 569 : channelHandlers_[Uri::Scheme::DATA_TRANSFER]
4601 1138 : = std::make_unique<TransferChannelHandler>(shared(), *connectionManager_.get());
4602 569 : channelHandlers_[Uri::Scheme::MESSAGE]
4603 1138 : = std::make_unique<MessageChannelHandler>(*connectionManager_.get(),
4604 13249 : [this](const auto& cert, std::string& type, const std::string& content) {
4605 26498 : onTextMessage("", cert->issuer->getId().toString(), cert, {{type, content}});
4606 13249 : },
4607 1525 : [w = weak()](const std::string& peer, bool connected) {
4608 1525 : asio::post(*Manager::instance().ioContext(), [w, peer, connected] {
4609 1525 : if (auto acc = w.lock())
4610 1525 : acc->onPeerConnected(peer, connected);
4611 1525 : });
4612 2094 : });
4613 569 : channelHandlers_[Uri::Scheme::AUTH]
4614 1138 : = std::make_unique<AuthChannelHandler>(shared(), *connectionManager_.get());
4615 :
4616 : #if TARGET_OS_IOS
4617 : connectionManager_->oniOSConnected([&](const std::string& connType, dht::InfoHash peer_h) {
4618 : if ((connType == "videoCall" || connType == "audioCall")
4619 : && jami::Manager::instance().isIOSExtension) {
4620 : bool hasVideo = connType == "videoCall";
4621 : emitSignal<libjami::ConversationSignal::CallConnectionRequest>("",
4622 : peer_h.toString(),
4623 : hasVideo);
4624 : return true;
4625 : }
4626 : return false;
4627 : });
4628 : #endif
4629 569 : }
4630 698 : }
4631 :
4632 : void
4633 796 : JamiAccount::updateUpnpController()
4634 : {
4635 796 : Account::updateUpnpController();
4636 796 : if (connectionManager_) {
4637 39 : auto config = connectionManager_->getConfig();
4638 39 : if (config)
4639 39 : config->upnpCtrl = upnpCtrl_;
4640 39 : }
4641 796 : }
4642 :
4643 : } // namespace jami
|