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: 355 471 75.4 %
Date: 2026-02-28 10:41:24 Functions: 90 170 52.9 %

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

Generated by: LCOV version 1.14