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