LCOV - code coverage report
Current view: top level - src/jamidht - account_manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 76.0 % 475 361
Test Date: 2026-11-09 10:41:14 Functions: 90.3 % 62 56

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

Generated by: LCOV version 2.0-1