LCOV - code coverage report
Current view: top level - foo/src/jamidht - account_manager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 365 477 76.5 %
Date: 2025-09-15 07:46:53 Functions: 87 168 51.8 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2025 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : #include "account_manager.h"
      18             : #include "accountarchive.h"
      19             : #include "jamiaccount.h"
      20             : #include "base64.h"
      21             : #include "jami/account_const.h"
      22             : #include "account_schema.h"
      23             : #include "archiver.h"
      24             : #include "manager.h"
      25             : 
      26             : #include "libdevcrypto/Common.h"
      27             : #include "json_utils.h"
      28             : 
      29             : #include <opendht/thread_pool.h>
      30             : #include <opendht/crypto.h>
      31             : 
      32             : #include <exception>
      33             : #include <future>
      34             : #include <fstream>
      35             : #include <gnutls/ocsp.h>
      36             : 
      37             : namespace jami {
      38             : 
      39             : AccountManager::CertRequest
      40         640 : AccountManager::buildRequest(PrivateKey fDeviceKey)
      41             : {
      42         640 :     return dht::ThreadPool::computation().get<std::unique_ptr<dht::crypto::CertificateRequest>>(
      43         640 :         [fDeviceKey = std::move(fDeviceKey)] {
      44         640 :             auto request = std::make_unique<dht::crypto::CertificateRequest>();
      45         640 :             request->setName("Jami device");
      46         640 :             const auto& deviceKey = fDeviceKey.get();
      47         640 :             request->setUID(deviceKey->getPublicKey().getId().toString());
      48         640 :             request->sign(*deviceKey);
      49         640 :             return request;
      50         640 :         });
      51             : }
      52             : 
      53        1959 : AccountManager::~AccountManager() {
      54         653 :     if (dht_)
      55         583 :         dht_->join();
      56         653 : }
      57             : 
      58             : void
      59       17611 : AccountManager::onSyncData(DeviceSync&& sync, bool checkDevice)
      60             : {
      61       17611 :     auto sync_date = clock::time_point(clock::duration(sync.date));
      62       17611 :     if (checkDevice) {
      63             :         // If the DHT is used, we need to check the device here
      64           0 :         if (not info_->contacts->syncDevice(sync.owner->getLongId(), sync_date)) {
      65           0 :             return;
      66             :         }
      67             :     }
      68             : 
      69             :     // Sync known devices
      70       52833 :     JAMI_DEBUG("[Account {}] [Contacts] received device sync data ({:d} devices, {:d} contacts, {:d} requests)",
      71             :              accountId_,
      72             :              sync.devices_known.size() + sync.devices.size(),
      73             :              sync.peers.size(),
      74             :              sync.trust_requests.size());
      75       17611 :     for (const auto& d : sync.devices_known) {
      76           0 :         findCertificate(d.first, [this, d](const std::shared_ptr<dht::crypto::Certificate>& crt) {
      77           0 :             if (not crt)
      78           0 :                 return;
      79             :             // std::lock_guard lock(deviceListMutex_);
      80           0 :             foundAccountDevice(crt, d.second);
      81             :         });
      82             :     }
      83       52581 :     for (const auto& d : sync.devices) {
      84       34970 :         findCertificate(d.second.sha1,
      85       69940 :                         [this, d](const std::shared_ptr<dht::crypto::Certificate>& crt) {
      86       34970 :                             if (not crt || crt->getLongId() != d.first)
      87           0 :                                 return;
      88             :                             // std::lock_guard lock(deviceListMutex_);
      89       34970 :                             foundAccountDevice(crt, d.second.name);
      90             :                         });
      91             :     }
      92             :     // saveKnownDevices();
      93             : 
      94             :     // Sync contacts
      95       17611 :     if (!sync.peers.empty()) {
      96       34730 :         for (const auto &peer: sync.peers) {
      97       17365 :             info_->contacts->updateContact(peer.first, peer.second);
      98             :         }
      99       17365 :         info_->contacts->saveContacts();
     100             :     }
     101             : 
     102             :     // Sync trust requests
     103       17619 :     for (const auto& tr : sync.trust_requests)
     104          16 :         info_->contacts->onTrustRequest(tr.first,
     105           8 :                                         tr.second.device,
     106           8 :                                         tr.second.received,
     107             :                                         false,
     108           8 :                                         tr.second.conversationId,
     109             :                                         {});
     110             : }
     111             : 
     112             : dht::crypto::Identity
     113         653 : AccountManager::loadIdentity(const std::string& crt_path,
     114             :                              const std::string& key_path,
     115             :                              const std::string& key_pwd) const
     116             : {
     117             :     // Return to avoid unnecessary log if certificate or key is missing. Example case: when
     118             :     // importing an account when the certificate has not been unpacked from the archive.
     119         653 :     if (crt_path.empty() or key_path.empty())
     120         636 :         return {};
     121             : 
     122          51 :     JAMI_DEBUG("[Account {}] [Auth] Loading certificate from '{}' and key from '{}' at {}",
     123             :              accountId_,
     124             :              crt_path,
     125             :              key_path,
     126             :              path_);
     127             :     try {
     128          34 :         dht::crypto::Certificate dht_cert(fileutils::loadFile(crt_path, path_));
     129          34 :         dht::crypto::PrivateKey dht_key(fileutils::loadFile(key_path, path_), key_pwd);
     130          17 :         auto crt_id = dht_cert.getLongId();
     131          17 :         if (!crt_id or crt_id != dht_key.getPublicKey().getLongId()) {
     132           0 :             JAMI_ERROR("[Account {}] [Auth] Device certificate not matching public key!", accountId_);
     133           0 :             return {};
     134             :         }
     135          17 :         auto& issuer = dht_cert.issuer;
     136          17 :         if (not issuer) {
     137           0 :             JAMI_ERROR("[Account {}] [Auth] Device certificate {:s} has no issuer", accountId_, dht_cert.getId().to_view());
     138           0 :             return {};
     139             :         }
     140             :         // load revocation lists for device authority (account certificate).
     141          17 :         Manager::instance().certStore(accountId_).loadRevocations(*issuer);
     142             : 
     143          34 :         return {std::make_shared<dht::crypto::PrivateKey>(std::move(dht_key)),
     144          17 :                 std::make_shared<dht::crypto::Certificate>(std::move(dht_cert))};
     145          17 :     } catch (const std::exception& e) {
     146           0 :         JAMI_ERROR("[Account {}] [Auth] Error loading identity: {}", accountId_, e.what());
     147           0 :     }
     148           0 :     return {};
     149             : }
     150             : 
     151             : std::shared_ptr<dht::Value>
     152          13 : AccountManager::parseAnnounce(const std::string& announceBase64,
     153             :                               const std::string& accountId,
     154             :                               const std::string& deviceSha1,
     155             :                               const std::string& deviceSha256)
     156             : {
     157          13 :     auto announce_val = std::make_shared<dht::Value>();
     158             :     try {
     159          13 :         auto announce = base64::decode(announceBase64);
     160          13 :         msgpack::object_handle announce_msg = msgpack::unpack((const char*) announce.data(),
     161          26 :                                                               announce.size());
     162          13 :         announce_val->msgpack_unpack(announce_msg.get());
     163          13 :         if (not announce_val->checkSignature()) {
     164           0 :             JAMI_ERROR("[Auth] announce signature check failed");
     165           0 :             return {};
     166             :         }
     167          13 :         DeviceAnnouncement da;
     168          13 :         da.unpackValue(*announce_val);
     169          13 :         if (da.from.toString() != accountId) {
     170           0 :             JAMI_ERROR("[Auth] Account ID mismatch in announce (account: {}, in announce: {})", accountId, da.from.toString());
     171           0 :             return {};
     172             :         }
     173          13 :         if ((da.pk && da.pk->getLongId().to_view() != deviceSha256) || da.dev.toString() != deviceSha1) {
     174           0 :             JAMI_ERROR("[Auth] Device ID mismatch in announce (device: {}, in announce: {})", da.pk ? deviceSha256 : deviceSha1, da.pk ? da.pk->getLongId().to_view() : da.dev.toString());
     175           0 :             return {};
     176             :         }
     177          13 :     } catch (const std::exception& e) {
     178           0 :         JAMI_ERROR("[Auth] unable to read announce: {}", e.what());
     179           0 :         return {};
     180           0 :     }
     181          13 :     return announce_val;
     182          13 : }
     183             : 
     184             : const AccountInfo*
     185         653 : AccountManager::useIdentity(const dht::crypto::Identity& identity,
     186             :                             const std::string& receipt,
     187             :                             const std::vector<uint8_t>& receiptSignature,
     188             :                             const std::string& username,
     189             :                             const OnChangeCallback& onChange)
     190             : {
     191         653 :     if (receipt.empty() or receiptSignature.empty())
     192         640 :         return nullptr;
     193             : 
     194          13 :     if (not identity.first or not identity.second) {
     195           0 :         JAMI_ERROR("[Account {}] [Auth] no identity provided", accountId_);
     196           0 :         return nullptr;
     197             :     }
     198             : 
     199          13 :     auto accountCertificate = identity.second->issuer;
     200          13 :     if (not accountCertificate) {
     201           0 :         JAMI_ERROR("[Account {}] [Auth] device certificate must be issued by the account certificate", accountId_);
     202           0 :         return nullptr;
     203             :     }
     204             : 
     205             :     // match certificate chain
     206          13 :     auto contactList = std::make_unique<ContactList>(accountId_, accountCertificate, path_, onChange);
     207          13 :     auto result = contactList->isValidAccountDevice(*identity.second);
     208          13 :     if (not result) {
     209           0 :         JAMI_ERROR("[Account {}] [Auth] unable to use identity: device certificate chain is unable to be verified: {}", accountId_,
     210             :                  result.toString());
     211           0 :         return nullptr;
     212             :     }
     213             : 
     214          13 :     auto pk = accountCertificate->getSharedPublicKey();
     215          39 :     JAMI_LOG("[Account {}] [Auth] checking device receipt for account:{} device:{}", accountId_, pk->getId().toString(), identity.second->getLongId().toString());
     216          13 :     if (!pk->checkSignature({receipt.begin(), receipt.end()}, receiptSignature)) {
     217           0 :         JAMI_ERROR("[Account {}] [Auth] device receipt signature check failed", accountId_);
     218           0 :         return nullptr;
     219             :     }
     220             : 
     221          13 :     Json::Value root;
     222          13 :     if (!json::parse(receipt, root) || !root.isMember("announce")) {
     223           0 :         JAMI_ERROR("[Account {}] [Auth] device receipt parsing error", accountId_);
     224           0 :         return nullptr;
     225             :     }
     226             : 
     227          13 :     auto dev_id = root["dev"].asString();
     228          13 :     if (dev_id != identity.second->getId().toString()) {
     229           0 :         JAMI_ERROR("[Account {}] [Auth] device ID mismatch between receipt and certificate", accountId_);
     230           0 :         return nullptr;
     231             :     }
     232          13 :     auto id = root["id"].asString();
     233          13 :     if (id != pk->getId().toString()) {
     234           0 :         JAMI_ERROR("[Account {}] [Auth] account ID mismatch between receipt and certificate", accountId_);
     235           0 :         return nullptr;
     236             :     }
     237             : 
     238          13 :     auto devicePk = identity.first->getSharedPublicKey();
     239          13 :     if (!devicePk) {
     240           0 :         JAMI_ERROR("[Account {}] [Auth] No device pk found", accountId_);
     241           0 :         return nullptr;
     242             :     }
     243             : 
     244          26 :     auto announce = parseAnnounce(root["announce"].asString(), id, devicePk->getId().toString(), devicePk->getLongId().toString());
     245          13 :     if (not announce) {
     246           0 :         return nullptr;
     247             :     }
     248             : 
     249          13 :     onChange_ = std::move(onChange);
     250             : 
     251          13 :     auto info = std::make_unique<AccountInfo>();
     252          13 :     info->identity = identity;
     253          13 :     info->contacts = std::move(contactList);
     254          13 :     info->contacts->load();
     255          13 :     info->accountId = id;
     256          13 :     info->devicePk = std::move(devicePk);
     257          13 :     info->deviceId = info->devicePk->getLongId().toString();
     258          13 :     info->announce = std::move(announce);
     259          13 :     info->ethAccount = root["eth"].asString();
     260          13 :     info->username = username;
     261          13 :     info_ = std::move(info);
     262             : 
     263          39 :     JAMI_LOG("[Account {}] [Auth] Device {} receipt checked successfully for user {}", accountId_,
     264             :              info_->deviceId, id);
     265          13 :     return info_.get();
     266          13 : }
     267             : 
     268             : void
     269           0 : AccountManager::reloadContacts()
     270             : {
     271           0 :     if (info_) {
     272           0 :         info_->contacts->load();
     273             :     }
     274           0 : }
     275             : 
     276             : void
     277        1152 : AccountManager::startSync(const OnNewDeviceCb& cb, const OnDeviceAnnouncedCb& dcb, bool publishPresence)
     278             : {
     279             :     // Put device announcement
     280        1152 :     if (info_->announce) {
     281        1152 :         auto h = dht::InfoHash(info_->accountId);
     282        1152 :         if (publishPresence) {
     283        4604 :             dht_->put(
     284             :                 h,
     285        1151 :                 info_->announce,
     286        2302 :                 [dcb = std::move(dcb), h, accountId=accountId_](bool ok) {
     287        1151 :                     if (ok)
     288        3396 :                         JAMI_DEBUG("[Account {}] device announced at {}", accountId, h.toString());
     289             :                     // We do not care about the status, it's a permanent put, if this fail,
     290             :                     // this means the DHT is disconnected but the put will be retried when connected.
     291        1151 :                     if (dcb)
     292        1151 :                         dcb();
     293        1151 :                 },
     294             :                 {},
     295             :                 true);
     296             :         }
     297        1153 :         for (const auto& crl : info_->identity.second->issuer->getRevocationLists())
     298        1153 :             dht_->put(h, crl, dht::DoneCallback {}, {}, true);
     299        1152 :         dht_->listen<DeviceAnnouncement>(h, [this, cb = std::move(cb)](DeviceAnnouncement&& dev) {
     300        1276 :             findCertificate(dev.dev,
     301        1276 :                             [this, cb](const std::shared_ptr<dht::crypto::Certificate>& crt) {
     302        1276 :                                 foundAccountDevice(crt);
     303        1276 :                                 if (cb)
     304        1276 :                                     cb(crt);
     305        1276 :                             });
     306        1276 :             return true;
     307             :         });
     308        1152 :         dht_->listen<dht::crypto::RevocationList>(h, [this](dht::crypto::RevocationList&& crl) {
     309          12 :             if (crl.isSignedBy(*info_->identity.second->issuer)) {
     310          36 :                 JAMI_DEBUG("[Account {}] Found CRL for account.", accountId_);
     311          12 :                 certStore()
     312          12 :                     .pinRevocationList(info_->accountId,
     313          24 :                                        std::make_shared<dht::crypto::RevocationList>(
     314          12 :                                            std::move(crl)));
     315             :             }
     316          12 :             return true;
     317             :         });
     318        1152 :         syncDevices();
     319             :     } else {
     320           0 :         JAMI_ERROR("[Account {}] Unable to announce device: no announcement.", accountId_);
     321             :     }
     322             : 
     323        1152 :     auto inboxKey = dht::InfoHash::get("inbox:" + info_->devicePk->getId().toString());
     324        1152 :     dht_->listen<dht::TrustRequest>(inboxKey, [this](dht::TrustRequest&& v) {
     325         193 :         if (v.service != DHT_TYPE_NS)
     326           0 :             return true;
     327             : 
     328             :         // allowPublic always true for trust requests (only forbidden if banned)
     329         386 :         onPeerMessage(
     330         193 :             *v.owner,
     331             :             true,
     332         190 :             [this, v](const std::shared_ptr<dht::crypto::Certificate>&,
     333         648 :                       dht::InfoHash peer_account) mutable {
     334         570 :                 JAMI_WARNING("[Account {}] Got trust request (confirm: {}) from: {} / {}. ConversationId: {}",
     335             :                           accountId_,
     336             :                           v.confirm,
     337             :                           peer_account.toString(),
     338             :                           v.from.toString(),
     339             :                           v.conversationId);
     340         190 :                 if (info_)
     341         380 :                     if (info_->contacts->onTrustRequest(peer_account,
     342         190 :                                                         v.owner,
     343             :                                                         time(nullptr),
     344         190 :                                                         v.confirm,
     345         190 :                                                         v.conversationId,
     346         190 :                                                         std::move(v.payload))) {
     347          39 :                         if (v.confirm) // No need to send a confirmation as already accepted here
     348          35 :                             return;
     349          39 :                         auto conversationId = v.conversationId;
     350             :                         // Check if there was an old active conversation.
     351          39 :                         if (auto details = info_->contacts->getContactInfo(peer_account)) {
     352          39 :                             if (!details->conversationId.empty()) {
     353          39 :                                 if (details->conversationId == conversationId) {
     354             :                                     // Here, it's possible that we already have accepted the conversation
     355             :                                     // but contact were offline and sync failed.
     356             :                                     // So, retrigger the callback so upper layer will clone conversation if needed
     357             :                                     // instead of getting stuck in sync.
     358          35 :                                     info_->contacts->acceptConversation(conversationId, v.owner->getLongId().toString());
     359          35 :                                     return;
     360             :                                 }
     361           4 :                                 conversationId = details->conversationId;
     362          12 :                                 JAMI_WARNING("Accept with old convId: {}", conversationId);
     363             :                             }
     364          39 :                         }
     365           4 :                         sendTrustRequestConfirm(peer_account, conversationId);
     366          39 :                     }
     367             :             });
     368         193 :         return true;
     369             :     });
     370        1152 : }
     371             : 
     372             : const std::map<dht::PkId, KnownDevice>&
     373        1574 : AccountManager::getKnownDevices() const
     374             : {
     375        1574 :     return info_->contacts->getKnownDevices();
     376             : }
     377             : 
     378             : bool
     379       36246 : AccountManager::foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt,
     380             :                                    const std::string& name,
     381             :                                    const time_point& last_sync)
     382             : {
     383       36246 :     return info_->contacts->foundAccountDevice(crt, name, last_sync);
     384             : }
     385             : 
     386             : void
     387          17 : AccountManager::setAccountDeviceName(const std::string& name)
     388             : {
     389          17 :     if (info_)
     390          13 :         info_->contacts->setAccountDeviceName(DeviceId(info_->deviceId), name);
     391          17 : }
     392             : 
     393             : std::string
     394         636 : AccountManager::getAccountDeviceName() const
     395             : {
     396         636 :     if (info_)
     397        1272 :         return info_->contacts->getAccountDeviceName(DeviceId(info_->deviceId));
     398           0 :     return {};
     399             : }
     400             : 
     401             : bool
     402         910 : AccountManager::foundPeerDevice(const std::string& accountId, const std::shared_ptr<dht::crypto::Certificate>& crt,
     403             :                                 dht::InfoHash& peer_id)
     404             : {
     405         910 :     if (not crt)
     406           0 :         return false;
     407             : 
     408         910 :     auto top_issuer = crt;
     409        2730 :     while (top_issuer->issuer)
     410        1820 :         top_issuer = top_issuer->issuer;
     411             : 
     412             :     // Device certificate is unable to be self-signed
     413         910 :     if (top_issuer == crt) {
     414           0 :         JAMI_WARNING("[Account {}] Found invalid peer device: {}", accountId, crt->getLongId().toString());
     415           0 :         return false;
     416             :     }
     417             : 
     418             :     // Check peer certificate chain
     419             :     // Trust store with top issuer as the only CA
     420         910 :     dht::crypto::TrustList peer_trust;
     421         910 :     peer_trust.add(*top_issuer);
     422         910 :     if (not peer_trust.verify(*crt)) {
     423           0 :         JAMI_WARNING("[Account {}] Found invalid peer device: {}", accountId, crt->getLongId().toString());
     424           0 :         return false;
     425             :     }
     426             : 
     427             :     // Check cached OCSP response
     428         910 :     if (crt->ocspResponse and crt->ocspResponse->getCertificateStatus() != GNUTLS_OCSP_CERT_GOOD) {
     429           0 :         JAMI_WARNING("[Account {}] Certificate {} is disabled by cached OCSP response", accountId, crt->getLongId());
     430           0 :         return false;
     431             :     }
     432             : 
     433         910 :     peer_id = crt->issuer->getId();
     434        2730 :     JAMI_LOG("[Account {}] Found peer device: {} account:{} CA:{}",
     435             :               accountId,
     436             :               crt->getLongId().toString(),
     437             :               peer_id.toString(),
     438             :               top_issuer->getId().toString());
     439         910 :     return true;
     440         910 : }
     441             : 
     442             : void
     443         193 : AccountManager::onPeerMessage(const dht::crypto::PublicKey& peer_device,
     444             :                               bool allowPublic,
     445             :                               std::function<void(const std::shared_ptr<dht::crypto::Certificate>& crt,
     446             :                                                  const dht::InfoHash& peer_account)>&& cb)
     447             : {
     448             :     // quick check in case we already explicilty banned this device
     449         193 :     auto trustStatus = getCertificateStatus(peer_device.toString());
     450         193 :     if (trustStatus == dhtnet::tls::TrustStore::PermissionStatus::BANNED) {
     451           0 :         JAMI_WARNING("[Account {}] [Auth] Discarding message from banned device {}", accountId_, peer_device.toString());
     452           0 :         return;
     453             :     }
     454             : 
     455         193 :     findCertificate(peer_device.getId(),
     456         193 :                     [this, cb = std::move(cb), allowPublic](
     457         193 :                         const std::shared_ptr<dht::crypto::Certificate>& cert) {
     458         193 :                         dht::InfoHash peer_account_id;
     459         193 :                         if (onPeerCertificate(cert, allowPublic, peer_account_id)) {
     460         190 :                             cb(cert, peer_account_id);
     461             :                         }
     462         193 :                     });
     463             : }
     464             : 
     465             : bool
     466         910 : AccountManager::onPeerCertificate(const std::shared_ptr<dht::crypto::Certificate>& cert,
     467             :                                   bool allowPublic,
     468             :                                   dht::InfoHash& account_id)
     469             : {
     470         910 :     dht::InfoHash peer_account_id;
     471         910 :     if (not foundPeerDevice(accountId_, cert, peer_account_id)) {
     472           0 :         JAMI_WARNING("[Account {}] [Auth] Discarding message from invalid peer certificate", accountId_);
     473           0 :         return false;
     474             :     }
     475             : 
     476         910 :     if (not isAllowed(*cert, allowPublic)) {
     477          24 :         JAMI_WARNING("[Account {}] [Auth] Discarding message from unauthorized peer {}.",
     478             :                   accountId_, peer_account_id.toString());
     479           8 :         return false;
     480             :     }
     481             : 
     482         902 :     account_id = peer_account_id;
     483         902 :     return true;
     484             : }
     485             : 
     486             : bool
     487          55 : AccountManager::addContact(const dht::InfoHash& uri, bool confirmed, const std::string& conversationId)
     488             : {
     489          55 :     if (not info_) {
     490           0 :         JAMI_ERROR("addContact(): account not loaded");
     491           0 :         return false;
     492             :     }
     493         165 :     JAMI_WARNING("[Account {}] addContact {}", accountId_, confirmed);
     494          55 :     if (info_->contacts->addContact(uri, confirmed, conversationId)) {
     495          55 :         syncDevices();
     496          55 :         return true;
     497             :     }
     498           0 :     return false;
     499             : }
     500             : 
     501             : void
     502          17 : AccountManager::removeContact(const std::string& uri, bool banned)
     503             : {
     504          17 :     dht::InfoHash h(uri);
     505          17 :     if (not h) {
     506           0 :         JAMI_ERROR("[Account {}] removeContact: invalid contact URI", accountId_);
     507           0 :         return;
     508             :     }
     509          17 :     if (not info_) {
     510           0 :         JAMI_ERROR("[Account {}] removeContact: account not loaded", accountId_);
     511           0 :         return;
     512             :     }
     513          17 :     if (info_->contacts->removeContact(h, banned))
     514          17 :         syncDevices();
     515             : }
     516             : 
     517             : void
     518           0 : AccountManager::removeContactConversation(const std::string& uri)
     519             : {
     520           0 :     dht::InfoHash h(uri);
     521           0 :     if (not h) {
     522           0 :         JAMI_ERROR("[Account {}] removeContactConversation: invalid contact URI", accountId_);
     523           0 :         return;
     524             :     }
     525           0 :     if (not info_) {
     526           0 :         JAMI_ERROR("[Account {}] removeContactConversation: account not loaded", accountId_);
     527           0 :         return;
     528             :     }
     529           0 :     if (info_->contacts->removeContactConversation(h))
     530           0 :         syncDevices();
     531             : }
     532             : 
     533             : void
     534       17330 : AccountManager::updateContactConversation(
     535             :     const std::string& uri, const std::string& convId, bool added)
     536             : {
     537       17330 :     dht::InfoHash h(uri);
     538       17330 :     if (not h) {
     539           0 :         JAMI_ERROR("[Account {}] updateContactConversation: invalid contact URI", accountId_);
     540           0 :         return;
     541             :     }
     542       17330 :     if (not info_) {
     543           0 :         JAMI_ERROR("[Account {}] updateContactConversation: account not loaded", accountId_);
     544           0 :         return;
     545             :     }
     546       17330 :     info_->contacts->updateConversation(h, convId, added);
     547             :     // Also decline trust request if there is one
     548       17330 :     auto req = info_->contacts->getTrustRequest(h);
     549       34660 :     if (req.find(libjami::Account::TrustRequest::CONVERSATIONID) != req.end()
     550       34660 :         && req.at(libjami::Account::TrustRequest::CONVERSATIONID) == convId) {
     551           0 :         discardTrustRequest(uri);
     552             :     }
     553       17330 :     syncDevices();
     554       17330 : }
     555             : 
     556             : std::vector<std::map<std::string, std::string>>
     557         594 : AccountManager::getContacts(bool includeRemoved) const
     558             : {
     559         594 :     if (not info_) {
     560           0 :         JAMI_ERROR("[Account {}] getContacts(): account not loaded", accountId_);
     561           0 :         return {};
     562             :     }
     563         594 :     const auto& contacts = info_->contacts->getContacts();
     564         594 :     std::vector<std::map<std::string, std::string>> ret;
     565         594 :     ret.reserve(contacts.size());
     566             : 
     567         598 :     for (const auto& c : contacts) {
     568           4 :         if (!c.second.isActive() && !includeRemoved && !c.second.isBanned())
     569           0 :             continue;
     570           4 :         auto details = c.second.toMap();
     571           4 :         if (not details.empty()) {
     572           4 :             details["id"] = c.first.toString();
     573           4 :             ret.emplace_back(std::move(details));
     574             :         }
     575           4 :     }
     576         594 :     return ret;
     577         594 : }
     578             : 
     579             : /** Obtain details about one account contact in serializable form. */
     580             : std::map<std::string, std::string>
     581           4 : AccountManager::getContactDetails(const std::string& uri) const
     582             : {
     583           4 :     if (!info_) {
     584           0 :         JAMI_ERROR("[Account {}] getContactDetails(): account not loaded", accountId_);
     585           0 :         return {};
     586             :     }
     587           4 :     dht::InfoHash h(uri);
     588           4 :     if (not h) {
     589           0 :         JAMI_ERROR("[Account {}] getContactDetails: invalid contact URI", accountId_);
     590           0 :         return {};
     591             :     }
     592           4 :     return info_->contacts->getContactDetails(h);
     593             : }
     594             : 
     595             : std::optional<Contact>
     596         612 : AccountManager::getContactInfo(const std::string& uri) const
     597             : {
     598         612 :     if (!info_) {
     599           0 :         JAMI_ERROR("[Account {}] getContactInfo(): account not loaded", accountId_);
     600           0 :         return {};
     601             :     }
     602         612 :     dht::InfoHash h(uri);
     603         612 :     if (not h) {
     604           0 :         JAMI_ERROR("[Account {}] getContactInfo: invalid contact URI", accountId_);
     605           0 :         return {};
     606             :     }
     607         612 :     return info_->contacts->getContactInfo(h);
     608             : }
     609             : 
     610             : bool
     611       40332 : AccountManager::findCertificate(
     612             :     const dht::InfoHash& h,
     613             :     std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
     614             : {
     615       80664 :     if (auto cert = certStore().getCertificate(h.toString())) {
     616       39359 :         if (cb)
     617       39355 :             cb(cert);
     618         973 :     } else if (dht_) {
     619        1946 :         dht_->findCertificate(h,
     620         973 :                               [cb = std::move(cb), this](
     621        1776 :                                   const std::shared_ptr<dht::crypto::Certificate>& crt) {
     622         973 :                                   if (crt && info_) {
     623         888 :                                       certStore().pinCertificate(crt);
     624             :                                   }
     625         973 :                                   if (cb)
     626         891 :                                       cb(crt);
     627         973 :                               });
     628       40332 :     }
     629       40332 :     return true;
     630             : }
     631             : 
     632             : bool
     633         720 : AccountManager::findCertificate(
     634             :     const dht::PkId& id, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
     635             : {
     636        1440 :     if (auto cert = certStore().getCertificate(id.toString())) {
     637         719 :         if (cb)
     638         719 :             cb(cert);
     639           4 :     } else if (auto cert = certStore().getCertificateLegacy(fileutils::get_data_dir().string(),
     640           3 :                                                             id.toString())) {
     641           0 :         if (cb)
     642           0 :             cb(cert);
     643           1 :     } else if (cb)
     644         721 :         cb(nullptr);
     645         720 :     return true;
     646             : }
     647             : 
     648             : bool
     649          89 : AccountManager::setCertificateStatus(const std::string& cert_id,
     650             :                                      dhtnet::tls::TrustStore::PermissionStatus status)
     651             : {
     652          89 :     return info_ and info_->contacts->setCertificateStatus(cert_id, status);
     653             : }
     654             : 
     655             : bool
     656           0 : AccountManager::setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
     657             :                               dhtnet::tls::TrustStore::PermissionStatus status,
     658             :                               bool local)
     659             : {
     660           0 :     return info_ and info_->contacts->setCertificateStatus(cert, status, local);
     661             : }
     662             : 
     663             : std::vector<std::string>
     664           0 : AccountManager::getCertificatesByStatus(dhtnet::tls::TrustStore::PermissionStatus status)
     665             : {
     666           0 :     return info_ ? info_->contacts->getCertificatesByStatus(status) : std::vector<std::string> {};
     667             : }
     668             : 
     669             : dhtnet::tls::TrustStore::PermissionStatus
     670        7939 : AccountManager::getCertificateStatus(const std::string& cert_id) const
     671             : {
     672        7939 :     return info_ ? info_->contacts->getCertificateStatus(cert_id)
     673       15878 :                  : dhtnet::tls::TrustStore::PermissionStatus::UNDEFINED;
     674             : }
     675             : 
     676             : bool
     677         910 : AccountManager::isAllowed(const crypto::Certificate& crt, bool allowPublic)
     678             : {
     679         910 :     return info_ and info_->contacts->isAllowed(crt, allowPublic);
     680             : }
     681             : 
     682             : std::vector<std::map<std::string, std::string>>
     683         614 : AccountManager::getTrustRequests() const
     684             : {
     685         614 :     if (not info_) {
     686           0 :         JAMI_ERROR("[Account {}] getTrustRequests(): account not loaded", accountId_);
     687           0 :         return {};
     688             :     }
     689         614 :     return info_->contacts->getTrustRequests();
     690             : }
     691             : 
     692             : bool
     693         170 : AccountManager::acceptTrustRequest(const std::string& from, bool includeConversation)
     694             : {
     695         170 :     dht::InfoHash f(from);
     696         170 :     if (info_) {
     697         170 :         auto req = info_->contacts->getTrustRequest(dht::InfoHash(from));
     698         170 :         if (info_->contacts->acceptTrustRequest(f)) {
     699          36 :             sendTrustRequestConfirm(f,
     700             :                                     includeConversation
     701          72 :                                         ? req[libjami::Account::TrustRequest::CONVERSATIONID]
     702             :                                         : "");
     703          36 :             syncDevices();
     704          36 :             return true;
     705             :         }
     706         134 :         return false;
     707         170 :     }
     708           0 :     return false;
     709             : }
     710             : 
     711             : bool
     712           3 : AccountManager::discardTrustRequest(const std::string& from)
     713             : {
     714           3 :     dht::InfoHash f(from);
     715           3 :     return info_ and info_->contacts->discardTrustRequest(f);
     716             : }
     717             : 
     718             : void
     719          53 : AccountManager::sendTrustRequest(const std::string& to,
     720             :                                  const std::string& convId,
     721             :                                  const std::vector<uint8_t>& payload)
     722             : {
     723         159 :     JAMI_WARNING("[Account {}] AccountManager::sendTrustRequest", accountId_);
     724          53 :     auto toH = dht::InfoHash(to);
     725          53 :     if (not toH) {
     726           0 :         JAMI_ERROR("[Account {}] Unable to send trust request to invalid hash: {}", accountId_, to);
     727           0 :         return;
     728             :     }
     729          53 :     if (not info_) {
     730           0 :         JAMI_ERROR("[Account {}] sendTrustRequest(): account not loaded", accountId_);
     731           0 :         return;
     732             :     }
     733          53 :     if (info_->contacts->addContact(toH, false, convId)) {
     734           0 :         syncDevices();
     735             :     }
     736          53 :     forEachDevice(toH,
     737         171 :                   [this, toH, convId, payload](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
     738          57 :                       auto to = toH.toString();
     739         171 :                       JAMI_WARNING("[Account {}] Sending trust request to: {:s} / {:s} of size {:d}",
     740             :                                    accountId_, to, dev->getLongId(), payload.size());
     741         228 :                       dht_->putEncrypted(dht::InfoHash::get("inbox:" + dev->getId().toString()),
     742             :                                          dev,
     743         114 :                                          dht::TrustRequest(DHT_TYPE_NS, convId, payload),
     744          57 :                                          [to, size = payload.size()](bool ok) {
     745          57 :                                              if (!ok)
     746          51 :                                                  JAMI_ERROR("Tried to send request {:s} (size: "
     747             :                                                             "{:d}), but put failed",
     748             :                                                             to,
     749             :                                                             size);
     750          57 :                                          });
     751          57 :                   });
     752             : }
     753             : 
     754             : void
     755          40 : AccountManager::sendTrustRequestConfirm(const dht::InfoHash& toH, const std::string& convId)
     756             : {
     757         120 :     JAMI_WARNING("[Account {}] AccountManager::sendTrustRequestConfirm to {} (conversation {})", accountId_, toH, convId);
     758          80 :     dht::TrustRequest answer {DHT_TYPE_NS, convId};
     759          40 :     answer.confirm = true;
     760             : 
     761          40 :     if (!convId.empty() && info_)
     762          40 :         info_->contacts->acceptConversation(convId);
     763             : 
     764          40 :     forEachDevice(toH, [this, toH, answer](const std::shared_ptr<dht::crypto::PublicKey>& dev) {
     765         126 :         JAMI_WARNING("[Account {}] sending trust request reply: {} / {}",
     766             :                      accountId_, toH, dev->getLongId());
     767          42 :         dht_->putEncrypted(dht::InfoHash::get("inbox:" + dev->getId().toString()), dev, answer);
     768          42 :     });
     769          40 : }
     770             : 
     771             : void
     772        3774 : AccountManager::forEachDevice(
     773             :     const dht::InfoHash& to,
     774             :     std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)>&& op,
     775             :     std::function<void(bool)>&& end)
     776             : {
     777        3774 :     if (not dht_) {
     778           0 :         JAMI_ERROR("[Account {}] forEachDevice: no dht", accountId_);
     779           0 :         if (end)
     780           0 :             end(false);
     781           0 :         return;
     782             :     }
     783        3774 :     dht_->get<dht::crypto::RevocationList>(to, [to, this](dht::crypto::RevocationList&& crl) {
     784           0 :         certStore().pinRevocationList(to.toString(), std::move(crl));
     785           0 :         return true;
     786             :     });
     787             : 
     788             :     struct State
     789             :     {
     790             :         const dht::InfoHash to;
     791             :         const std::string accountId;
     792             :         // Note: state is initialized to 1, because we need to wait that the get is finished
     793             :         unsigned remaining {1};
     794             :         std::set<dht::PkId> treatedDevices {};
     795             :         std::function<void(const std::shared_ptr<dht::crypto::PublicKey>&)> onDevice;
     796             :         std::function<void(bool)> onEnd;
     797             : 
     798        3774 :         State(dht::InfoHash to, std::string accountId)
     799        3774 :             : to(std::move(to)), accountId(std::move(accountId)) {}
     800             : 
     801        7580 :         void found(const std::shared_ptr<dht::crypto::PublicKey>& pk)
     802             :         {
     803        7580 :             remaining--;
     804        7580 :             if (pk && *pk) {
     805        3805 :                 auto longId = pk->getLongId();
     806        3805 :                 if (treatedDevices.emplace(longId).second) {
     807        3795 :                     onDevice(pk);
     808             :                 }
     809             :             }
     810        7580 :             ended();
     811        7580 :         }
     812             : 
     813        7580 :         void ended()
     814             :         {
     815        7580 :             if (remaining == 0 && onEnd) {
     816         969 :                 JAMI_LOG("[Account {}] Found {:d} device(s) for {}", accountId, treatedDevices.size(), to);
     817         323 :                 onEnd(not treatedDevices.empty());
     818         323 :                 onDevice = {};
     819         323 :                 onEnd = {};
     820             :             }
     821        7580 :         }
     822             :     };
     823        3774 :     auto state = std::make_shared<State>(to, accountId_);
     824        3774 :     state->onDevice = std::move(op);
     825        3774 :     state->onEnd = std::move(end);
     826             : 
     827        3774 :     dht_->get<DeviceAnnouncement>(
     828             :         to,
     829        7612 :         [this, to, state](DeviceAnnouncement&& dev) {
     830        3806 :             if (dev.from != to)
     831           0 :                 return true;
     832        3806 :             state->remaining++;
     833        3806 :             findCertificate(dev.dev, [state](const std::shared_ptr<dht::crypto::Certificate>& cert) {
     834        7612 :                 state->found(cert ? cert->getSharedPublicKey()
     835        3806 :                                   : std::shared_ptr<dht::crypto::PublicKey> {});
     836        3806 :             });
     837        3806 :             return true;
     838             :         },
     839        3774 :         [state](bool /*ok*/) { state->found({}); });
     840        3774 : }
     841             : 
     842             : void
     843           3 : AccountManager::lookupUri(const std::string& name,
     844             :                           const std::string& defaultServer,
     845             :                           LookupCallback cb)
     846             : {
     847           3 :     nameDir_.get().lookupUri(name, defaultServer, std::move(cb));
     848           3 : }
     849             : 
     850             : void
     851         685 : AccountManager::lookupAddress(const std::string& addr, LookupCallback cb)
     852             : {
     853         685 :     nameDir_.get().lookupAddress(addr, cb);
     854         685 : }
     855             : 
     856             : dhtnet::tls::CertificateStore&
     857       41957 : AccountManager::certStore() const
     858             : {
     859       41957 :     return Manager::instance().certStore(info_->contacts->accountId());
     860             : }
     861             : 
     862             : } // namespace jami

Generated by: LCOV version 1.14