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