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