LCOV - code coverage report
Current view: top level - src/jamidht - account_manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 73.6 % 474 349
Test Date: 2026-08-23 08:52:56 Functions: 86.9 % 61 53

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

Generated by: LCOV version 2.0-1