Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #pragma once
18 :
19 : #include "def.h"
20 : #ifdef HAVE_CONFIG_H
21 : #include "config.h"
22 : #endif
23 :
24 : #include "sip/sipaccountbase.h"
25 : #include "jami/datatransfer_interface.h"
26 : #include "jamidht/conversation.h"
27 : #include "data_transfer.h"
28 : #include "uri.h"
29 : #include "jamiaccount_config.h"
30 :
31 : #include "noncopyable.h"
32 : #include "gitserver.h"
33 : #include "channel_handler.h"
34 : #include "conversation_module.h"
35 : #include "sync_module.h"
36 : #include "conversationrepository.h"
37 : #include "namedirectory.h"
38 :
39 : #include <dhtnet/diffie-hellman.h>
40 : #include <dhtnet/tls_session.h>
41 : #include <dhtnet/multiplexed_socket.h>
42 : #include <dhtnet/certstore.h>
43 : #include <dhtnet/connectionmanager.h>
44 : #include <dhtnet/upnp/mapping.h>
45 : #include <dhtnet/ip_utils.h>
46 : #include <dhtnet/fileutils.h>
47 :
48 : #include <opendht/dhtrunner.h>
49 : #include <opendht/default_types.h>
50 : #include <opendht/dht_proxy_server.h>
51 :
52 : #include <pjsip/sip_types.h>
53 : #include <json/json.h>
54 :
55 : #include <chrono>
56 : #include <functional>
57 : #include <future>
58 : #include <list>
59 : #include <map>
60 : #include <optional>
61 : #include <vector>
62 : #include <filesystem>
63 : #include <shared_mutex>
64 :
65 : namespace dev {
66 : template<unsigned N>
67 : class FixedHash;
68 : using h160 = FixedHash<20>;
69 : using Address = h160;
70 : } // namespace dev
71 :
72 : namespace jami {
73 :
74 : class IceTransport;
75 : struct Contact;
76 : struct AccountArchive;
77 : class DhtPeerConnector;
78 : class AccountManager;
79 : struct AccountInfo;
80 : class SipTransport;
81 : class ChanneledOutgoingTransfer;
82 : class SyncModule;
83 : class PresenceManager;
84 : namespace test {
85 : class Account_factoryTest;
86 : }
87 : class CollaborativeEditing;
88 :
89 : using SipConnectionKey = std::pair<std::string /* uri */, DeviceId>;
90 :
91 : static constexpr const char MIME_TYPE_IM_COMPOSING[] {"application/im-iscomposing+xml"};
92 :
93 : /**
94 : * @brief Ring Account is build on top of SIPAccountBase and uses DHT to handle call connectivity.
95 : */
96 : class JamiAccount : public SIPAccountBase
97 : {
98 : public:
99 : constexpr static auto ACCOUNT_TYPE = ACCOUNT_TYPE_JAMI;
100 : constexpr static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE {4000, 8888};
101 : constexpr static int ICE_STREAMS_COUNT {1};
102 : constexpr static int ICE_COMP_COUNT_PER_STREAM {1};
103 :
104 3753 : std::string_view getAccountType() const override { return ACCOUNT_TYPE; }
105 :
106 7915 : std::shared_ptr<JamiAccount> shared() { return std::static_pointer_cast<JamiAccount>(shared_from_this()); }
107 : std::shared_ptr<JamiAccount const> shared() const
108 : {
109 : return std::static_pointer_cast<JamiAccount const>(shared_from_this());
110 : }
111 61571 : std::weak_ptr<JamiAccount> weak() { return std::static_pointer_cast<JamiAccount>(shared_from_this()); }
112 : std::weak_ptr<JamiAccount const> weak() const
113 : {
114 : return std::static_pointer_cast<JamiAccount const>(shared_from_this());
115 : }
116 :
117 25047 : const JamiAccountConfig& config() const { return *static_cast<const JamiAccountConfig*>(&Account::config()); }
118 :
119 840 : JamiAccountConfig::Credentials consumeConfigCredentials()
120 : {
121 840 : auto* conf = static_cast<JamiAccountConfig*>(config_.get());
122 840 : return std::move(conf->credentials);
123 : }
124 :
125 : void loadConfig() override;
126 :
127 : /**
128 : * Constructor
129 : * @param accountID The account identifier
130 : */
131 : JamiAccount(const std::string& accountId);
132 :
133 : ~JamiAccount() noexcept;
134 :
135 : /**
136 : * Retrieve volatile details such as recent registration errors
137 : * @return std::map< std::string, std::string > The account volatile details
138 : */
139 : virtual std::map<std::string, std::string> getVolatileAccountDetails() const override;
140 :
141 818 : std::unique_ptr<AccountConfig> buildConfig() const override
142 : {
143 818 : return std::make_unique<JamiAccountConfig>(getAccountID(), idPath_);
144 : }
145 :
146 : /**
147 : * Adds an account id to the list of accounts to track on the DHT for
148 : * buddy presence.
149 : *
150 : * @param buddy_id The buddy id.
151 : */
152 : void trackBuddyPresence(const std::string& buddy_id, bool track);
153 :
154 : /**
155 : * Tells for each tracked account id if it has been seen online so far
156 : * in the last DeviceAnnouncement::TYPE.expiration minutes.
157 : *
158 : * @return map of buddy_uri to bool (online or not)
159 : */
160 : std::map<std::string, bool> getTrackedBuddyPresence() const;
161 :
162 : void setActiveCodecs(const std::vector<unsigned>& list) override;
163 :
164 : /**
165 : * Connect to the DHT.
166 : */
167 : void doRegister() override;
168 :
169 : /**
170 : * Disconnect from the DHT.
171 : */
172 : void doUnregister(bool forceShutdownConnections = false) override;
173 :
174 : /**
175 : * Set the registration state of the specified link
176 : * @param state The registration state of underlying VoIPLink
177 : */
178 : void setRegistrationState(RegistrationState state, int detail_code = 0, const std::string& detail_str = {}) override;
179 :
180 : /**
181 : * @return pj_str_t "From" uri based on account information.
182 : * From RFC3261: "The To header field first and foremost specifies the desired
183 : * logical" recipient of the request, or the address-of-record of the
184 : * user or resource that is the target of this request. [...] As such, it is
185 : * very important that the From URI not contain IP addresses or the FQDN
186 : * of the host on which the UA is running, since these are not logical
187 : * names."
188 : */
189 : std::string getFromUri() const override;
190 :
191 : /**
192 : * This method adds the correct scheme, hostname and append
193 : * the ;transport= parameter at the end of the uri, in accordance with RFC3261.
194 : * It is expected that "port" is present in the internal hostname_.
195 : *
196 : * @return pj_str_t "To" uri based on @param username
197 : * @param username A string formatted as : "username"
198 : */
199 : std::string getToUri(const std::string& username) const override;
200 :
201 : /**
202 : * In the current version, "srv" uri is obtained in the preformated
203 : * way: hostname:port. This method adds the correct scheme and append
204 : * the ;transport= parameter at the end of the uri, in accordance with RFC3261.
205 : *
206 : * @return pj_str_t "server" uri based on @param hostPort
207 : * @param hostPort A string formatted as : "hostname:port"
208 : */
209 : std::string getServerUri() const { return ""; };
210 :
211 : void setIsComposing(const std::string& conversationUri, bool isWriting) override;
212 :
213 : bool setMessageDisplayed(const std::string& conversationUri, const std::string& messageId, int status) override;
214 :
215 : /**
216 : * Get the contact header for
217 : * @return The contact header based on account information
218 : */
219 : std::string getContactHeader(const std::shared_ptr<SipTransport>& sipTransport);
220 :
221 : /* Returns true if the username and/or hostname match this account */
222 : MatchRank matches(std::string_view username, std::string_view hostname) const override;
223 :
224 : /**
225 : * Create outgoing SIPCall.
226 : * @note Accepts several urls:
227 : * + jami:uri for calling someone
228 : * + swarm:id for calling a group (will host or join if an active call is detected)
229 : * + rdv:id/uri/device/confId to join a specific conference hosted on (uri, device)
230 : * @param[in] toUrl The address to call
231 : * @param[in] mediaList list of medias
232 : * @return A shared pointer on the created call.
233 : */
234 : std::shared_ptr<Call> newOutgoingCall(std::string_view toUrl,
235 : const std::vector<libjami::MediaMap>& mediaList) override;
236 :
237 : /**
238 : * Create incoming SIPCall.
239 : * @param[in] from The origin of the call
240 : * @param mediaList A list of media
241 : * @param sipTr: SIP Transport
242 : * @return A shared pointer on the created call.
243 : */
244 : std::shared_ptr<SIPCall> newIncomingCall(const std::string& from,
245 : const std::vector<libjami::MediaMap>& mediaList,
246 : const std::shared_ptr<SipTransport>& sipTr = {}) override;
247 :
248 : void onTextMessage(const std::string& id,
249 : const std::string& from,
250 : const std::shared_ptr<dht::crypto::Certificate>& peerCert,
251 : const std::map<std::string, std::string>& payloads) override;
252 : void loadConversation(const std::string& convId);
253 :
254 0 : virtual bool isTlsEnabled() const override { return true; }
255 450 : bool isSrtpEnabled() const override { return true; }
256 :
257 : bool setCertificateStatus(const std::string& cert_id, dhtnet::tls::TrustStore::PermissionStatus status);
258 : bool setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
259 : dhtnet::tls::TrustStore::PermissionStatus status,
260 : bool local = true);
261 : std::vector<std::string> getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status);
262 :
263 : bool findCertificate(const std::string& id);
264 : bool findCertificate(const dht::InfoHash& h,
265 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {});
266 : bool findCertificate(const dht::PkId& h,
267 : std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {});
268 :
269 : /* contact requests */
270 : std::vector<std::map<std::string, std::string>> getTrustRequests() const;
271 : // Note: includeConversation used for compatibility test. Do not change
272 : bool acceptTrustRequest(const std::string& from, bool includeConversation = true);
273 : bool discardTrustRequest(const std::string& from);
274 : void declineConversationRequest(const std::string& conversationId);
275 :
276 : /**
277 : * Add contact to the account contact list.
278 : * Set confirmed if we know the contact also added us.
279 : */
280 : void addContact(const std::string& uri, bool confirmed = false);
281 : void removeContact(const std::string& uri, bool banned = true);
282 : std::vector<std::map<std::string, std::string>> getContacts(bool includeRemoved = false) const;
283 :
284 : ///
285 : /// Obtain details about one account contact in serializable form.
286 : ///
287 : std::map<std::string, std::string> getContactDetails(const std::string& uri) const;
288 : std::optional<Contact> getContactInfo(const std::string& uri) const;
289 :
290 : void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload);
291 : void sendMessage(const std::string& to,
292 : const std::string& deviceId,
293 : const std::map<std::string, std::string>& payloads,
294 : uint64_t id,
295 : bool retryOnTimeout = true,
296 : bool onlyConnected = false) override;
297 :
298 : uint64_t sendTextMessage(const std::string& to,
299 : const std::string& deviceId,
300 : const std::map<std::string, std::string>& payloads,
301 : uint64_t refreshToken = 0,
302 : bool onlyConnected = false) override;
303 : void sendInstantMessage(const std::string& convId, const std::map<std::string, std::string>& msg);
304 :
305 : /**
306 : * Create and return ICE options.
307 : */
308 : dhtnet::IceTransportOptions getIceOptions() const override;
309 : void getIceOptions(std::function<void(dhtnet::IceTransportOptions&&)> cb) const;
310 : dhtnet::IpAddr getPublishedIpAddress(uint16_t family = PF_UNSPEC) const override;
311 :
312 : /* Devices - existing device */
313 : /**
314 : * Initiates the process of adding a new device to this account
315 : * @param uriProvided The URI provided by the new device to be added
316 : * @return A positive operation ID if successful, or a negative value indicating an AddDeviceError:
317 : * - INVALID_URI (-1): The provided URI is invalid
318 : * - ALREADY_LINKING (-2): A device linking operation is already in progress
319 : * - GENERIC (-3): A generic error occurred during the process
320 : */
321 : int32_t addDevice(const std::string& uriProvided);
322 : bool cancelAddDevice(uint32_t op_token);
323 : bool confirmAddDevice(uint32_t op_token);
324 : /* Devices - new device */
325 : bool provideAccountAuthentication(const std::string& credentialsFromUser, const std::string& scheme);
326 :
327 : /**
328 : * Export the archive to a file
329 : * @param destinationPath
330 : * @param (optional) password, if not provided, will update the contacts only if the archive
331 : * doesn't have a password
332 : * @return if the archive was exported
333 : */
334 : bool exportArchive(const std::string& destinationPath,
335 : std::string_view scheme = {},
336 : const std::string& password = {});
337 : bool revokeDevice(const std::string& device, std::string_view scheme = {}, const std::string& password = {});
338 : std::map<std::string, std::string> getKnownDevices() const;
339 :
340 : bool isPasswordValid(const std::string& password);
341 : std::vector<uint8_t> getPasswordKey(const std::string& password);
342 :
343 : bool changeArchivePassword(const std::string& password_old, const std::string& password_new);
344 :
345 : void connectivityChanged() override;
346 :
347 : // overloaded methods
348 : void flush() override;
349 :
350 : void lookupName(const std::string& name);
351 : void lookupAddress(const std::string& address);
352 : void registerName(const std::string& name, const std::string& scheme, const std::string& password);
353 : bool searchUser(const std::string& nameQuery);
354 :
355 : /// \return true if the given DHT message identifier has been treated
356 : /// \note if message has not been treated yet this method store this id and returns true at
357 : /// further calls
358 : bool isMessageTreated(dht::Value::Id id);
359 :
360 723 : std::shared_ptr<dht::DhtRunner> dht() { return dht_; }
361 :
362 3746 : const dht::crypto::Identity& identity() const { return id_; }
363 :
364 2718 : PresenceManager* presenceManager() const { return presenceManager_.get(); }
365 :
366 : void forEachDevice(const dht::InfoHash& to,
367 : std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
368 : std::function<void(bool)>&& end = {});
369 :
370 : bool setPushNotificationToken(const std::string& pushDeviceToken = "") override;
371 : bool setPushNotificationTopic(const std::string& topic) override;
372 : bool setPushNotificationConfig(const std::map<std::string, std::string>& data) override;
373 :
374 : /**
375 : * To be called by clients with relevant data when a push notification is received.
376 : */
377 : void pushNotificationReceived(const std::string& from, const std::map<std::string, std::string>& data);
378 :
379 : std::string getUserUri() const override;
380 :
381 : /**
382 : * Get last messages (should be used to retrieve messages when launching the client)
383 : * @param base_timestamp
384 : */
385 : std::vector<libjami::Message> getLastMessages(const uint64_t& base_timestamp) override;
386 :
387 : /**
388 : * Start Publish the Jami Account onto the Network
389 : */
390 : void startAccountPublish();
391 :
392 : /**
393 : * Start Discovery the Jami Account from the Network
394 : */
395 : void startAccountDiscovery();
396 :
397 : void saveConfig() const override;
398 :
399 831 : inline void editConfig(std::function<void(JamiAccountConfig& conf)>&& edit)
400 : {
401 1662 : Account::editConfig([&](AccountConfig& conf) { edit(*static_cast<JamiAccountConfig*>(&conf)); });
402 831 : }
403 :
404 : /**
405 : * Get current discovered peers account id and display name
406 : */
407 : std::map<std::string, std::string> getNearbyPeers() const override;
408 :
409 : void sendProfileToPeers();
410 :
411 : /**
412 : * Update the profile vcard and send it to peers
413 : * @param displayName Current or new display name
414 : * @param avatar Current or new avatar
415 : * @param flag 0 for path to avatar, 1 for base64 avatar
416 : */
417 : void updateProfile(const std::string& displayName,
418 : const std::string& avatar,
419 : const std::string& fileType,
420 : const std::string& botOwner,
421 : int32_t flag) override;
422 :
423 : #ifdef LIBJAMI_TEST
424 1 : dhtnet::ConnectionManager& connectionManager() { return *connectionManager_; }
425 :
426 : /**
427 : * Only used for tests, disable sha3sum verification for transfers.
428 : * @param newValue
429 : */
430 : void noSha3sumVerification(bool newValue);
431 :
432 0 : void publishPresence(bool newValue) { publishPresence_ = newValue; }
433 : #endif
434 :
435 : /**
436 : * This should be called before flushing the account.
437 : * ConnectionManager needs the account to exists
438 : */
439 : void shutdownConnections();
440 :
441 : std::string_view currentDeviceId() const;
442 :
443 : // Received a new commit notification
444 :
445 : bool handleMessage(const std::shared_ptr<dht::crypto::Certificate>& cert,
446 : const std::string& from,
447 : const std::pair<std::string, std::string>& message) override;
448 :
449 : void monitor();
450 : // conversationId optional
451 : std::vector<std::map<std::string, std::string>> getConnectionList(const std::string& conversationId = "");
452 : std::vector<std::map<std::string, std::string>> getConversationConnectivity(const std::string& conversationId);
453 : std::vector<std::map<std::string, std::string>> getConversationTrackedMembers(const std::string& conversationId);
454 : std::vector<std::map<std::string, std::string>> getChannelList(const std::string& connectionId);
455 :
456 : // File transfer
457 : void sendFile(const std::string& conversationId,
458 : const std::filesystem::path& path,
459 : const std::string& name,
460 : const std::string& replyTo);
461 :
462 : void transferFile(const std::string& conversationId,
463 : const std::string& path,
464 : const std::string& deviceId,
465 : const std::string& fileId,
466 : const std::string& interactionId,
467 : size_t start = 0,
468 : size_t end = 0,
469 : const std::string& sha3Sum = "",
470 : uint64_t lastWriteTime = 0,
471 : std::function<void()> onFinished = {});
472 :
473 : void askForFileChannel(const std::string& conversationId,
474 : const std::string& deviceId,
475 : const std::string& interactionId,
476 : const std::string& fileId,
477 : size_t start = 0,
478 : size_t end = 0);
479 :
480 : void askForProfile(const std::string& conversationId, const std::string& deviceId, const std::string& memberUri);
481 :
482 : /**
483 : * Retrieve linked transfer manager
484 : * @param id conversationId or empty for fallback
485 : * @return linked transfer manager
486 : */
487 : std::shared_ptr<TransferManager> dataTransfer(const std::string& id = "");
488 :
489 : /**
490 : * Used to get the instance of the ConversationModule class which is
491 : * responsible for managing conversations and messages between users.
492 : * @param noCreate whether or not to create a new instance
493 : * @return conversationModule instance
494 : */
495 : ConversationModule* convModule(bool noCreation = false);
496 : SyncModule* syncModule();
497 :
498 : /**
499 : * Retrieve the (lazily created) collaborative editing manager, which handles
500 : * real-time shared text documents inside this account's conversations.
501 : */
502 : std::shared_ptr<CollaborativeEditing> collaborativeEditing();
503 :
504 : /**
505 : * Check (via the cache) if we need to send our profile to a specific device
506 : * @param peerUri Uri that will receive the profile
507 : * @param deviceId Device that will receive the profile
508 : * @param sha3Sum SHA3 hash of the profile
509 : */
510 : // Note: when swarm will be merged, this can be moved in transferManager
511 : bool needToSendProfile(const std::string& peerUri, const std::string& deviceId, const std::string& sha3Sum);
512 : /**
513 : * Send Profile via cached SIP connection
514 : * @param convId Conversation's identifier (can be empty for self profile on sync)
515 : * @param peerUri Uri that will receive the profile
516 : * @param deviceId Device that will receive the profile
517 : */
518 : void sendProfile(const std::string& convId, const std::string& peerUri, const std::string& deviceId);
519 : /**
520 : * Send profile via cached SIP connection
521 : * @param peerUri Uri that will receive the profile
522 : * @param deviceId Device that will receive the profile
523 : */
524 : void sendProfile(const std::string& peerUri, const std::string& deviceId);
525 : /**
526 : * Clear sent profiles (because of a removed contact or new trust request)
527 : * @param peerUri Uri used to clear cache
528 : */
529 : void clearProfileCache(const std::string& peerUri);
530 :
531 : std::filesystem::path profilePath() const;
532 :
533 45946 : const std::shared_ptr<AccountManager>& accountManager() { return accountManager_; }
534 :
535 : bool sha3SumVerify() const;
536 :
537 : /**
538 : * Change certificate's validity period
539 : * @param pwd Password for the archive
540 : * @param id Certificate to update ({} for updating the whole chain)
541 : * @param validity New validity
542 : * @note forceReloadAccount may be necessary to retrigger the migration
543 : */
544 : bool setValidity(std::string_view scheme, const std::string& pwd, const dht::InfoHash& id, int64_t validity);
545 : /**
546 : * Try to reload the account to force the identity to be updated
547 : */
548 : void forceReloadAccount();
549 :
550 : void reloadContacts();
551 :
552 : /**
553 : * Make sure appdata/contacts.yml contains correct information
554 : * @param removedConv The current removed conversations
555 : */
556 : void unlinkConversations(const std::set<std::string>& removedConv);
557 :
558 : bool isValidAccountDevice(const dht::crypto::Certificate& cert) const;
559 :
560 : /**
561 : * Join incoming call to hosted conference
562 : * @param callId The call to join
563 : * @param destination conversation/uri/device/confId to join
564 : */
565 : void handleIncomingConversationCall(const std::string& callId, const std::string& destination);
566 :
567 : /**
568 : * The DRT component is composed on some special nodes, that are usually present but not
569 : * connected. This kind of node corresponds to devices with push notifications & proxy and are
570 : * stored in the mobile nodes
571 : */
572 429 : bool isMobile() const { return config().proxyEnabled and not config().deviceKey.empty(); }
573 :
574 : #ifdef LIBJAMI_TEST
575 1 : std::map<Uri::Scheme, std::unique_ptr<ChannelHandlerInterface>>& channelHandlers() { return channelHandlers_; };
576 : #endif
577 :
578 : /**
579 : * Ask a device for a realtime channel on a collaborative document.
580 : * A refusal (the peer does not have the document open) needs nothing done,
581 : * and an accepted socket is handed to CollaborativeEditing by the handler's
582 : * onReady, so no callback is taken here.
583 : * @param deviceId The device to dial
584 : * @param documentId The document (repository) id
585 : */
586 : void connectYdocDevice(const DeviceId& deviceId, const std::string& documentId);
587 :
588 30157 : dhtnet::tls::CertificateStore& certStore() const { return *certStore_; }
589 :
590 : /// Returns true if `peerAccountUri` is an active
591 : /// contact of this account.
592 : bool isContact(const std::string& peerAccountUri) const;
593 :
594 1270 : class ServiceManager& serviceManager() { return *serviceManager_; }
595 : const class ServiceManager& serviceManager() const { return *serviceManager_; }
596 3 : bool hasServiceManager() const { return serviceManager_ != nullptr; }
597 :
598 : /* Service-exposure high-level API. Implemented in jamiaccount.cpp. */
599 :
600 : /// Send a discovery query to every known device of `peerUri` and return a
601 : /// monotonically-increasing request id. Responses are delivered through the
602 : /// `libjami::ServiceSignal::PeerServicesReceived` signal with this id as
603 : /// first argument.
604 : uint32_t queryPeerServices(const std::string& peerUri);
605 :
606 : /// Open a TCP-tunnel listener on 127.0.0.1:`localPort` (0 = pick a free
607 : /// port) that forwards each accepted connection to `serviceId` on
608 : /// `peerUri`'s `deviceId`. Returns a tunnel id, or an empty string on
609 : /// failure. `serviceName` is purely informational.
610 : std::string openServiceTunnel(const std::string& peerUri,
611 : const std::string& deviceId,
612 : const std::string& serviceId,
613 : const std::string& serviceName,
614 : uint16_t localPort);
615 :
616 : /// Close a tunnel previously created with openServiceTunnel.
617 : bool closeServiceTunnel(const std::string& tunnelId);
618 :
619 : /// Server-side: shutdown every active inbound tunnel currently serving
620 : /// `serviceId`. Called when a local exposed service is removed or
621 : /// disabled so that already-established peer connections are torn down.
622 : void closeServerTunnelsForService(const std::string& serviceId);
623 :
624 : /// Snapshot of currently active client tunnels for this account.
625 : std::vector<std::map<std::string, std::string>> getActiveServiceTunnels() const;
626 : /**
627 : * Check if a Device is connected
628 : * @param deviceId
629 : * @return true if connected
630 : */
631 : bool isConnectedWith(const DeviceId& deviceId) const;
632 :
633 : /**
634 : * Send a presence note
635 : * @param note
636 : */
637 : void sendPresenceNote(const std::string& note);
638 :
639 : private:
640 : NON_COPYABLE(JamiAccount);
641 :
642 : using clock = std::chrono::system_clock;
643 : using time_point = clock::time_point;
644 :
645 : /**
646 : * Private structures
647 : */
648 : struct PendingCall;
649 : struct PendingMessage;
650 : struct DiscoveredPeer;
651 : class SendMessageContext;
652 :
653 0 : inline std::string getProxyConfigKey() const
654 : {
655 0 : const auto& conf = config();
656 0 : return dht::InfoHash::get(conf.proxyServer + conf.proxyListUrl).toString();
657 : }
658 :
659 : void scheduleAccountReady() const;
660 : AccountManager::OnChangeCallback setupAccountCallbacks();
661 :
662 : void onContactAdded(const std::string& uri, bool confirmed);
663 : void onContactRemoved(const std::string& uri, bool banned);
664 : void onIncomingTrustRequest(const std::string& uri,
665 : const std::string& conversationId,
666 : const std::vector<uint8_t>& payload,
667 : TimePoint received,
668 : TimePoint invited = {});
669 : void onKnownDevicesChanged(const std::map<DeviceId, KnownDevice>& devices);
670 : void onConversationRequestAccepted(const std::string& conversationId, const std::string& deviceId);
671 : void onContactConfirmed(const std::string& uri, const std::string& convFromReq);
672 :
673 : void conversationNeedsSyncing(std::shared_ptr<SyncMsg>&& syncMsg);
674 : uint64_t conversationSendMessage(const std::string& uri,
675 : const DeviceId& device,
676 : const std::map<std::string, std::string>& msg,
677 : uint64_t token = 0);
678 : void onConversationNeedSocket(const std::string& convId,
679 : const std::string& deviceId,
680 : ChannelCb&& cb,
681 : const std::string& type,
682 : bool noNewSocket = false);
683 : void onConversationNeedSwarmSocket(const std::string& convId,
684 : const std::string& deviceId,
685 : ChannelCb&& cb,
686 : const std::string& type,
687 : bool noNewSocket = false);
688 : void conversationOneToOneReceive(const std::string& convId, const std::string& from);
689 :
690 : std::unique_ptr<AccountManager::AccountCredentials> buildAccountCredentials(
691 : const JamiAccountConfig& conf,
692 : const dht::crypto::Identity& id,
693 : const std::string& archive_password_scheme,
694 : const std::string& archive_password,
695 : const std::string& archive_path,
696 : bool& migrating,
697 : bool& hasPassword);
698 :
699 : void onAuthenticationSuccess(bool migrating,
700 : bool hasPassword,
701 : const AccountInfo& info,
702 : const std::map<std::string, std::string>& configMap,
703 : std::string&& receipt,
704 : std::vector<uint8_t>&& receiptSignature);
705 :
706 : static void onAuthenticationError(const std::weak_ptr<JamiAccount>& w,
707 : bool hadIdentity,
708 : bool migrating,
709 : std::string accountId,
710 : AccountManager::AuthError error,
711 : const std::string& message);
712 :
713 : void onPeerConnected(const std::string& peerId, bool connected);
714 :
715 : void doRegister_();
716 :
717 : void lookupRegisteredName(const std::string& regName, const NameDirectory::Response& response);
718 : dht::DhtRunner::Config initDhtConfig(const JamiAccountConfig& conf);
719 : dht::DhtRunner::Context initDhtContext();
720 : void onAccountDeviceFound(const std::shared_ptr<dht::crypto::Certificate>& crt);
721 : void onAccountDeviceAnnounced();
722 :
723 : /**
724 : * Open a sync connection to one of our account's devices. This wakes the
725 : * device up, so it must only be called when there is something new to
726 : * synchronize with it (see SyncModule::needsSync / onSyncListChanged).
727 : */
728 : void connectSyncDevice(const DeviceId& deviceId);
729 :
730 : /**
731 : * React to a contact or conversation-list change (local or learned from a
732 : * peer): bump the local sync version, then (re)connect to and push the new
733 : * state to the account's other devices that are not up to date. Offline
734 : * devices are reached on their next presence announcement.
735 : */
736 : void onSyncListChanged();
737 :
738 : bool onICERequest(const DeviceId& deviceId);
739 :
740 : /**
741 : * Pin (or unpin) the account's organization certificate authority in the
742 : * trust store according to the allowPeersFromTrusted setting.
743 : */
744 : void updateTrustedCa();
745 :
746 : bool onChannelRequest(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name);
747 : void onNewDeviceConnection(const std::shared_ptr<dht::crypto::Certificate>& cert);
748 : void onConnectionReady(const DeviceId& deviceId,
749 : const std::string& name,
750 : std::shared_ptr<dhtnet::ChannelSocket> channel);
751 :
752 823 : const dht::ValueType USER_PROFILE_TYPE = {9, "User profile", std::chrono::hours(24 * 7)};
753 :
754 : void startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& toUri);
755 :
756 : void onConnectedOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& to_id, dhtnet::IpAddr target);
757 :
758 : /**
759 : * Start a SIP Call
760 : * @param call The current call
761 : * @return true if all is correct
762 : */
763 : bool SIPStartCall(SIPCall& call, const dhtnet::IpAddr& target);
764 :
765 : /**
766 : * Update tracking info when buddy appears offline.
767 : */
768 : void onTrackedBuddyOffline(const std::string&);
769 :
770 : /**
771 : * Update tracking info when buddy appears offline.
772 : */
773 : void onTrackedBuddyOnline(const std::string&);
774 :
775 : /**
776 : * Maps require port via UPnP and other async ops
777 : */
778 : void registerAsyncOps();
779 : /**
780 : * Add port mapping callback function.
781 : */
782 : void onPortMappingAdded(uint16_t port_used, bool success);
783 : void forEachPendingCall(const DeviceId& deviceId, const std::function<void(const std::shared_ptr<SIPCall>&)>& cb);
784 :
785 : void loadAccount(const std::string& archive_password_scheme = {},
786 : const std::string& archive_password = {},
787 : const std::string& archive_path = {});
788 :
789 : std::vector<std::string> loadBootstrap() const;
790 :
791 : static std::pair<std::string, std::string> saveIdentity(const dht::crypto::Identity& id,
792 : const std::filesystem::path& path,
793 : const std::string& name);
794 :
795 : void replyToIncomingIceMsg(const std::shared_ptr<SIPCall>&,
796 : const std::shared_ptr<IceTransport>&,
797 : const std::shared_ptr<IceTransport>&,
798 : const dht::IceCandidates&,
799 : const std::shared_ptr<dht::crypto::Certificate>& from_cert,
800 : const dht::InfoHash& from);
801 :
802 : void loadCachedUrl(const std::string& url,
803 : const std::filesystem::path& cachePath,
804 : const std::chrono::seconds& cacheDuration,
805 : const std::function<void(const dht::http::Response& response)>& cb);
806 :
807 : std::string getDhtProxyServer(const std::string& serverList);
808 : void loadCachedProxyServer(std::function<void(const std::string&)> cb);
809 :
810 : void newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const Uri& uri);
811 : std::shared_ptr<SIPCall> newSwarmOutgoingCallHelper(const Uri& uri, const std::vector<libjami::MediaMap>& mediaList);
812 : std::shared_ptr<SIPCall> createSubCall(const std::shared_ptr<SIPCall>& mainCall);
813 :
814 : std::filesystem::path cachePath_ {};
815 : std::filesystem::path dataPath_ {};
816 :
817 : mutable std::mutex registeredNameMutex_;
818 : std::string registeredName_;
819 :
820 740 : bool setRegisteredName(const std::string& name)
821 : {
822 740 : std::lock_guard<std::mutex> lock(registeredNameMutex_);
823 740 : if (registeredName_ != name) {
824 1 : registeredName_ = name;
825 1 : return true;
826 : }
827 739 : return false;
828 740 : }
829 4870 : std::string getRegisteredName() const
830 : {
831 4870 : std::lock_guard<std::mutex> lock(registeredNameMutex_);
832 9740 : return registeredName_;
833 4870 : }
834 :
835 : std::shared_ptr<dht::Logger> logger_;
836 : std::shared_ptr<dhtnet::tls::CertificateStore> certStore_;
837 :
838 : std::unique_ptr<class ServiceManager> serviceManager_;
839 :
840 : /// Per-request state for an in-flight queryPeerServices() call. Defined
841 : /// out-of-line in jamiaccount.cpp so callers don't need to pull in asio
842 : /// just to compile this header.
843 : struct PendingSvcQuery;
844 :
845 : /// Emit the terminal `PeerServicesReceived` signal for `requestId` with
846 : /// the given status (matching `libjami::ServiceSignal::PeerServicesStatus`)
847 : /// and JSON payload, then drop the request from the pending tables.
848 : void finalizeSvcQuery(uint32_t requestId, int status, const std::string& servicesJson);
849 :
850 : /// Build the JSON array describing `peerUri`'s cached services, tagging each
851 : /// entry's "available" flag from the presence system (a device is available
852 : /// when it is currently announced online). `forceAvailableDevice`, when
853 : /// non-null, is always flagged available -- used right after a device
854 : /// answers a discovery query, before its DHT presence announcement has been
855 : /// observed. Returns an empty string when the peer has no cached services.
856 : std::string buildPeerServicesJson(const std::string& peerUri, const DeviceId* forceAvailableDevice = nullptr);
857 :
858 : mutable std::mutex pendingSvcQueriesMtx_;
859 : std::map<uint32_t, std::shared_ptr<PendingSvcQuery>> pendingSvcQueries_;
860 :
861 : std::shared_ptr<dht::DhtRunner> dht_ {};
862 : std::shared_ptr<AccountManager> accountManager_;
863 : dht::crypto::Identity id_ {};
864 :
865 : std::shared_ptr<dht::DhtProxyServer> dhtProxyServer_;
866 :
867 : mutable std::mutex messageMutex_ {};
868 : std::map<dht::Value::Id, PendingMessage> sentMessages_;
869 : dhtnet::fileutils::IdList treatedMessages_;
870 :
871 : /* tracked buddies presence */
872 : std::unique_ptr<PresenceManager> presenceManager_;
873 : uint64_t presenceListenerToken_ {0};
874 : uint64_t svcPresenceListenerToken_ {0};
875 :
876 : std::atomic_int syncCnt_ {0};
877 :
878 : /**
879 : * DHT port actually used.
880 : * This holds the actual DHT port, which might different from the port
881 : * set in the configuration. This can be the case if UPnP is used.
882 : */
883 : in_port_t dhtPortUsed()
884 : {
885 : return (upnpCtrl_ and dhtUpnpMapping_.isValid()) ? dhtUpnpMapping_.getExternalPort() : config().dhtPort;
886 : }
887 :
888 : /**
889 : * Release the DHT UPnP mapping, if one is currently held, and reset it so that
890 : * a subsequent registration requests a new one.
891 : */
892 : void releaseDhtUpnpMapping();
893 :
894 : friend class test::Account_factoryTest;
895 :
896 : /* Current UPNP mapping */
897 : dhtnet::upnp::Mapping dhtUpnpMapping_ {dhtnet::upnp::PortType::UDP};
898 :
899 : /**
900 : * Proxy
901 : */
902 : std::string proxyServerCached_ {};
903 :
904 : /**
905 : * Optional: via_addr construct from received parameters
906 : */
907 : pjsip_host_port via_addr_ {};
908 :
909 : pjsip_transport* via_tp_ {nullptr};
910 :
911 : /** ConnectionManager is thread-safe.
912 : * The shared mutex protects the pointer while allowing
913 : * multiple threads to access the ConnectionManager concurrently */
914 : mutable std::shared_mutex connManagerMtx_ {};
915 : std::unique_ptr<dhtnet::ConnectionManager> connectionManager_;
916 :
917 : virtual void updateUpnpController() override;
918 :
919 : std::mutex discoveryMapMtx_;
920 : std::shared_ptr<dht::PeerDiscovery> peerDiscovery_;
921 : std::map<dht::InfoHash, DiscoveredPeer> discoveredPeers_;
922 : std::map<std::string, std::string> discoveredPeerMap_;
923 :
924 : std::set<std::shared_ptr<dht::http::Request>> requests_;
925 :
926 : mutable std::mutex sipConnsMtx_ {};
927 : struct SipConnection
928 : {
929 : std::shared_ptr<SipTransport> transport;
930 : // Needs to keep track of that channel to access underlying ICE
931 : // information, as the SipTransport use a generic transport
932 : std::shared_ptr<dhtnet::ChannelSocket> channel;
933 : };
934 : // NOTE: here we use a vector to avoid race conditions. In fact the contact
935 : // can ask for a SIP channel when we are creating a new SIP Channel with this
936 : // peer too.
937 : std::map<SipConnectionKey, std::vector<SipConnection>> sipConns_;
938 :
939 : std::mutex pendingCallsMutex_;
940 : std::map<DeviceId, std::vector<std::shared_ptr<SIPCall>>> pendingCalls_;
941 :
942 : std::mutex onConnectionClosedMtx_ {};
943 : std::map<DeviceId, std::function<void(const DeviceId&, bool)>> onConnectionClosed_ {};
944 : /**
945 : * onConnectionClosed contains callbacks that need to be called if a sub call is failing
946 : * @param deviceId The device we are calling
947 : * @param eraseDummy Erase the dummy call (a temporary subcall that must be stop when we will
948 : * not create new subcalls)
949 : */
950 : void callConnectionClosed(const DeviceId& deviceId, bool eraseDummy);
951 :
952 : /**
953 : * Ask a device to open a channeled SIP socket
954 : * @param peerId The contact who owns the device
955 : * @param deviceId The device to ask
956 : * @param forceNewConnection If we want a new SIP connection
957 : * @param pc A pending call to stop if the request fails
958 : * @note triggers cacheSIPConnection
959 : */
960 : void requestSIPConnection(const std::string& peerId,
961 : const DeviceId& deviceId,
962 : const std::string& connectionType,
963 : bool forceNewConnection = false,
964 : const std::shared_ptr<SIPCall>& pc = {});
965 : /**
966 : * Store a new SIP connection into sipConnections_
967 : * @param channel The new sip channel
968 : * @param peerId The contact who owns the device
969 : * @param deviceId Device linked to that transport
970 : */
971 : void cacheSIPConnection(std::shared_ptr<dhtnet::ChannelSocket>&& channel,
972 : const std::string& peerId,
973 : const DeviceId& deviceId);
974 : /**
975 : * Shutdown a SIP connection
976 : * @param channel The channel to close
977 : * @param peerId The contact who owns the device
978 : * @param deviceId Device linked to that transport
979 : */
980 : void shutdownSIPConnection(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
981 : const std::string& peerId,
982 : const DeviceId& deviceId);
983 :
984 : void requestMessageConnection(const std::string& peerId,
985 : const DeviceId& deviceId,
986 : const std::string& connectionType);
987 :
988 : /**
989 : * Called once per message channel to a device, whichever side opened it.
990 : */
991 : void onMessageChannelReady(const std::string& peerId, const DeviceId& deviceId);
992 :
993 : // File transfers
994 : std::mutex transfersMtx_ {};
995 : std::set<std::string> incomingFileTransfers_ {};
996 :
997 : void onMessageSent(
998 : const std::string& to, uint64_t id, const std::string& deviceId, bool success, bool onlyConnected, bool retry);
999 :
1000 : std::mutex gitServersMtx_ {};
1001 : std::map<dht::Value::Id, std::unique_ptr<GitServer>> gitServers_ {};
1002 :
1003 : //// File transfer (for profiles)
1004 : std::shared_ptr<TransferManager> nonSwarmTransferManager_;
1005 :
1006 : std::atomic_bool deviceAnnounced_ {false};
1007 :
1008 : // Debounce timer coalescing bursts of contact/conversation-list changes
1009 : // (e.g. during initial sync) into a single sync-propagation pass.
1010 : std::mutex syncListChangedMtx_;
1011 : std::shared_ptr<asio::steady_timer> syncListChangedTimer_;
1012 : std::atomic_bool noSha3sumVerification_ {false};
1013 :
1014 : bool publishPresence_ {true};
1015 :
1016 : std::map<Uri::Scheme, std::unique_ptr<ChannelHandlerInterface>> channelHandlers_ {};
1017 :
1018 : std::unique_ptr<ConversationModule> convModule_;
1019 : std::mutex moduleMtx_;
1020 : std::unique_ptr<SyncModule> syncModule_;
1021 : std::shared_ptr<CollaborativeEditing> collaborativeEditing_;
1022 :
1023 : std::mutex rdvMtx_;
1024 :
1025 : int dhtBoundPort_ {0};
1026 :
1027 : void initConnectionManager();
1028 :
1029 : enum class PresenceState : int { DISCONNECTED = 0, AVAILABLE, CONNECTED };
1030 : std::map<std::string, PresenceState> presenceState_;
1031 : mutable std::mutex presenceStateMtx_;
1032 : std::string presenceNote_;
1033 : };
1034 :
1035 : static inline std::ostream&
1036 : operator<<(std::ostream& os, const JamiAccount& acc)
1037 : {
1038 : os << "[Account " << acc.getAccountID() << "] ";
1039 : return os;
1040 : }
1041 :
1042 : } // namespace jami
|