LCOV - code coverage report
Current view: top level - src/jamidht - account_manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 75.2 % 475 357
Test Date: 2026-06-13 09:18:46 Functions: 53.2 % 173 92

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

Generated by: LCOV version 2.0-1