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