Files
jami-daemon/src/ringdht/ringaccount.h
Adrien Béraud e18892fbd9 contrib: bump opendht to 1.5.0
* fix reported address issue
* fix SockAddr endianness issue
* fix memory leak
* minor performance improvements
* InfoHash: add operator bool()

Change-Id: I47ca3dedc1b96aa8082183eaf23fb9efc092b97a
2017-11-28 06:53:29 -05:00

594 lines
22 KiB
C++

/*
* Copyright (C) 2014-2017 Savoir-faire Linux Inc.
*
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
* Author: Simon Désaulniers <simon.desaulniers@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "security/tls_session.h"
#include "security/diffie-hellman.h"
#include "sip/sipaccountbase.h"
#include "noncopyable.h"
#include "ip_utils.h"
#include "ring_types.h" // enable_if_base_of
#include <opendht/dhtrunner.h>
#include <opendht/default_types.h>
#include <pjsip/sip_types.h>
#include <vector>
#include <map>
#include <chrono>
#include <list>
#include <future>
#if HAVE_RINGNS
#include "namedirectory.h"
#endif
/**
* @file ringaccount.h
* @brief Ring Account is build on top of SIPAccountBase and uses DHT to handle call connectivity.
*/
namespace YAML {
class Node;
class Emitter;
}
namespace dev
{
template <unsigned N> class FixedHash;
using h160 = FixedHash<20>;
using Address = h160;
}
namespace ring {
class IceTransport;
struct Contact;
struct AccountArchive;
class RingAccount : public SIPAccountBase {
public:
constexpr static const char* const ACCOUNT_TYPE = "RING";
constexpr static const in_port_t DHT_DEFAULT_PORT = 4222;
constexpr static const char* const DHT_DEFAULT_BOOTSTRAP = "bootstrap.ring.cx";
constexpr static const char* const DHT_TYPE_NS = "cx.ring";
/* constexpr */ static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE;
const char* getAccountType() const override {
return ACCOUNT_TYPE;
}
std::shared_ptr<RingAccount> shared() {
return std::static_pointer_cast<RingAccount>(shared_from_this());
}
std::shared_ptr<RingAccount const> shared() const {
return std::static_pointer_cast<RingAccount const>(shared_from_this());
}
/**
* Constructor
* @param accountID The account identifier
*/
RingAccount(const std::string& accountID, bool presenceEnabled);
~RingAccount();
/**
* Serialize internal state of this account for configuration
* @param YamlEmitter the configuration engine which generate the configuration file
*/
virtual void serialize(YAML::Emitter &out) override;
/**
* Populate the internal state for this account based on info stored in the configuration file
* @param The configuration node for this account
*/
virtual void unserialize(const YAML::Node &node) override;
/**
* Return an map containing the internal state of this account. Client application can use this method to manage
* account info.
* @return A map containing the account information.
*/
virtual std::map<std::string, std::string> getAccountDetails() const override;
/**
* Retrieve volatile details such as recent registration errors
* @return std::map< std::string, std::string > The account volatile details
*/
virtual std::map<std::string, std::string> getVolatileAccountDetails() const override;
/**
* Actually useless, since config loading is done in init()
*/
void loadConfig() override {}
/**
* Adds an account id to the list of accounts to track on the DHT for
* buddy presence.
*
* @param buddy_id The buddy id.
*/
void trackBuddyPresence(const std::string& buddy_id);
/**
* Tells for each tracked account id if it has been seen online so far
* in the last DeviceAnnouncement::TYPE.expiration minutes.
*
* @return map of buddy_uri to bool (online or not)
*/
std::map<std::string, bool> getTrackedBuddyPresence();
/**
* Connect to the DHT.
*/
void doRegister() override;
/**
* Disconnect from the DHT.
*/
void doUnregister(std::function<void(bool)> cb = {}) override;
/**
* @return pj_str_t "From" uri based on account information.
* From RFC3261: "The To header field first and foremost specifies the desired
* logical" recipient of the request, or the address-of-record of the
* user or resource that is the target of this request. [...] As such, it is
* very important that the From URI not contain IP addresses or the FQDN
* of the host on which the UA is running, since these are not logical
* names."
*/
std::string getFromUri() const;
/**
* This method adds the correct scheme, hostname and append
* the ;transport= parameter at the end of the uri, in accordance with RFC3261.
* It is expected that "port" is present in the internal hostname_.
*
* @return pj_str_t "To" uri based on @param username
* @param username A string formatted as : "username"
*/
std::string getToUri(const std::string& username) const override;
/**
* In the current version of Ring, "srv" uri is obtained in the preformated
* way: hostname:port. This method adds the correct scheme and append
* the ;transport= parameter at the end of the uri, in accordance with RFC3261.
*
* @return pj_str_t "server" uri based on @param hostPort
* @param hostPort A string formatted as : "hostname:port"
*/
std::string getServerUri() const { return ""; };
/**
* Get the contact header for
* @return pj_str_t The contact header based on account information
*/
pj_str_t getContactHeader(pjsip_transport* = nullptr) override;
void setReceivedParameter(const std::string &received) {
receivedParameter_ = received;
via_addr_.host.ptr = (char *) receivedParameter_.c_str();
via_addr_.host.slen = receivedParameter_.size();
}
std::string getReceivedParameter() const {
return receivedParameter_;
}
pjsip_host_port *
getViaAddr() {
return &via_addr_;
}
/* Returns true if the username and/or hostname match this account */
MatchRank matches(const std::string &username, const std::string &hostname) const override;
/**
* Implementation of Account::newOutgoingCall()
* Note: keep declaration before newOutgoingCall template.
*/
std::shared_ptr<Call> newOutgoingCall(const std::string& toUrl) override;
/**
* Create outgoing SIPCall.
* @param[in] toUrl The address to call
* @return std::shared_ptr<T> A shared pointer on the created call.
* The type of this instance is given in template argument.
* This type can be any base class of SIPCall class (included).
*/
#ifndef RING_UWP
template <class T=SIPCall>
std::shared_ptr<enable_if_base_of<T, SIPCall> >
newOutgoingCall(const std::string& toUrl);
#else
template <class T>
std::shared_ptr<T>
newOutgoingCall(const std::string& toUrl);
#endif
/**
* Create incoming SIPCall.
* @param[in] from The origin of the call
* @return std::shared_ptr<T> A shared pointer on the created call.
* The type of this instance is given in template argument.
* This type can be any base class of SIPCall class (included).
*/
virtual std::shared_ptr<SIPCall>
newIncomingCall(const std::string& from = {}) override;
virtual bool isTlsEnabled() const override {
return true;
}
virtual bool isSrtpEnabled() const {
return true;
}
virtual sip_utils::KeyExchangeProtocol getSrtpKeyExchange() const override {
return sip_utils::KeyExchangeProtocol::SDES;
}
virtual bool getSrtpFallback() const override {
return false;
}
bool setCertificateStatus(const std::string& cert_id, tls::TrustStore::PermissionStatus status);
std::vector<std::string> getCertificatesByStatus(tls::TrustStore::PermissionStatus status);
bool findCertificate(const std::string& id);
bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {});
/* contact requests */
std::vector<std::map<std::string, std::string>> getTrustRequests() const;
bool acceptTrustRequest(const std::string& from);
bool discardTrustRequest(const std::string& from);
/**
* Add contact to the account contact list.
* Set confirmed if we know the contact also added us.
*/
void addContact(const std::string& uri, bool confirmed = false);
void removeContact(const std::string& uri, bool banned = true);
std::vector<std::map<std::string, std::string>> getContacts() const;
///
/// Obtain details about one account contact in serializable form.
///
std::map<std::string, std::string> getContactDetails(const std::string& uri) const;
void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload);
void sendTrustRequestConfirm(const dht::InfoHash& to);
virtual void sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads, uint64_t id) override;
/* Devices */
void addDevice(const std::string& password);
bool revokeDevice(const std::string& password, const std::string& device);
std::map<std::string, std::string> getKnownDevices() const;
bool changeArchivePassword(const std::string& password_old, const std::string& password_new);
void connectivityChanged() override;
// overloaded methods
void flush() override;
#if HAVE_RINGNS
void lookupName(const std::string& name);
void lookupAddress(const std::string& address);
void registerName(const std::string& password, const std::string& name);
#endif
dht::DhtRunner& dht() { return dht_; }
void forEachDevice(const dht::InfoHash& to,
std::function<void(const std::shared_ptr<RingAccount>&,
const dht::InfoHash&)> op,
std::function<void(bool)> end = {});
private:
NON_COPYABLE(RingAccount);
using clock = std::chrono::system_clock;
using time_point = clock::time_point;
/**
* Private structures
*/
struct PendingCall;
struct PendingMessage;
struct TrustRequest;
struct KnownDevice;
struct DeviceAnnouncement;
struct DeviceSync;
struct BuddyInfo;
void syncDevices();
void onReceiveDeviceSync(DeviceSync&& sync);
#if HAVE_RINGNS
std::reference_wrapper<NameDirectory> nameDir_;
std::string nameServer_;
std::string registeredName_;
#endif
/**
* Compute archive encryption key and DHT storage location from password and PIN.
*/
static std::pair<std::vector<uint8_t>, dht::InfoHash> computeKeys(const std::string& password, const std::string& pin, bool previous=false);
/**
* Update tracking info when buddy appears offline.
*
* @param buddy_info_it An iterator over the map trackedBuddies_
*/
void onTrackedBuddyOffline(std::map<dht::InfoHash, BuddyInfo>::iterator& buddy_info_it);
/**
* Update tracking info when buddy appears offline.
*
* @param buddy_info_it An iterator over the map trackedBuddies_
* @param device_id The device id
*/
void onTrackedBuddyOnline(std::map<dht::InfoHash, BuddyInfo>::iterator& buddy_info_it, const dht::InfoHash& device_id);
void doRegister_();
void incomingCall(dht::IceCandidates&& msg, const std::shared_ptr<dht::crypto::Certificate>& from_cert, const dht::InfoHash& from);
const dht::ValueType USER_PROFILE_TYPE = {9, "User profile", std::chrono::hours(24 * 7)};
void handleEvents();
void startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string toUri);
void onConnectedOutgoingCall(SIPCall& call, const std::string& to_id, IpAddr target);
/**
* Set the internal state for this account, mainly used to manage account details from the client application.
* @param The map containing the account information.
*/
virtual void setAccountDetails(const std::map<std::string, std::string> &details) override;
/**
* Start a SIP Call
* @param call The current call
* @return true if all is correct
*/
bool SIPStartCall(SIPCall& call, IpAddr target);
/**
* Inform that a potential account device have been found.
* Returns true if the device have been validated to be part of this account
*/
bool foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, const std::string& name = {}, const time_point& last_sync = time_point::min());
/**
* Inform that a potential peer device have been found.
* Returns true only if the device certificate is a valid Ring device certificate.
* In that case (true is returned) the account_id parameter is set to the peer account ID.
*/
static bool foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, dht::InfoHash& account_id);
/**
* For a call with (from_device, from_account), check the peer certificate chain (cert_list, cert_num)
* with session check status.
* Put deserialized certificate to cert_out;
*/
static pj_status_t checkPeerTlsCertificate(dht::InfoHash from_device, dht::InfoHash from_account,
unsigned status,
const gnutls_datum_t* cert_list, unsigned cert_num,
std::shared_ptr<dht::crypto::Certificate>& cert_out);
/**
* Check that a peer is authorised to talk to us.
* If everything is in order, calls the callback with the
* peer certificate chain (down to the peer device certificate),
* and the peer account id.
*/
void onPeerMessage(const dht::InfoHash& peer_device, std::function<void(const std::shared_ptr<dht::crypto::Certificate>& crt, const dht::InfoHash& account_id)>);
void onTrustRequest(const dht::InfoHash& peer_account, const dht::InfoHash& peer_device, time_t received , bool confirm, std::vector<uint8_t>&& payload);
/**
* Maps require port via UPnP
*/
bool mapPortUPnP();
void igdChanged();
dht::DhtRunner dht_ {};
dht::crypto::Identity identity_ {};
dht::InfoHash callKey_;
void handlePendingCallList();
bool handlePendingCall(PendingCall& pc, bool incoming);
/**
* DHT calls waiting for ICE negotiation
*/
std::list<PendingCall> pendingCalls_;
/**
* Incoming DHT calls that are not yet actual SIP calls.
*/
std::list<PendingCall> pendingSipCalls_;
std::set<dht::Value::Id> treatedCalls_ {};
mutable std::mutex callsMutex_ {};
std::map<dht::Value::Id, PendingMessage> sentMessages_;
std::set<dht::Value::Id> treatedMessages_ {};
std::string ringAccountId_ {};
std::string ringDeviceId_ {};
std::string ringDeviceName_ {};
std::string idPath_ {};
std::string cachePath_ {};
std::string dataPath_ {};
std::string ethPath_ {};
std::string ethAccount_ {};
std::string archivePath_ {};
bool archiveHasPassword_ {true};
std::string receipt_ {};
std::vector<uint8_t> receiptSignature_ {};
dht::Value announceVal_;
std::map<dht::InfoHash, TrustRequest> trustRequests_;
void loadTrustRequests();
void saveTrustRequests() const;
std::map<dht::InfoHash, Contact> contacts_;
void loadContacts();
void saveContacts() const;
void updateContact(const dht::InfoHash&, const Contact&);
// Trust store with Ring account main certificate as the only CA
dht::crypto::TrustList accountTrust_;
// Trust store for to match peer certificates
tls::TrustStore trust_;
std::shared_ptr<dht::Value> announce_;
/* this ring account associated devices */
std::map<dht::InfoHash, KnownDevice> knownDevices_;
/* tracked buddies presence */
std::recursive_mutex buddyInfoMtx;
std::map<dht::InfoHash, BuddyInfo> trackedBuddies_;
void loadAccount(const std::string& archive_password = {}, const std::string& archive_pin = {}, const std::string& archive_path = {});
void loadAccountFromFile(const std::string& archive_path, const std::string& archive_password);
void loadAccountFromDHT(const std::string& archive_password, const std::string& archive_pin);
void loadAccountFromArchive(AccountArchive&& archive, const std::string& archive_password);
bool hasCertificate() const;
bool hasPrivateKey() const;
bool useIdentity(const dht::crypto::Identity& id);
static bool needsMigration(const dht::crypto::Identity& id);
std::string makeReceipt(const dht::crypto::Identity& id);
void createRingDevice(const dht::crypto::Identity& id);
void initRingDevice(const AccountArchive& a);
void migrateAccount(const std::string& pwd, dht::crypto::Identity& device);
static bool updateCertificates(AccountArchive& archive, dht::crypto::Identity& device);
void createAccount(const std::string& archive_password, dht::crypto::Identity&& migrate);
void updateArchive(AccountArchive& content) const;
void saveArchive(AccountArchive& content, const std::string& pwd);
AccountArchive readArchive(const std::string& pwd) const;
std::vector<dht::SockAddr> loadBootstrap() const;
static std::pair<std::string, std::string> saveIdentity(const dht::crypto::Identity id, const std::string& path, const std::string& name);
void saveNodes(const std::vector<dht::NodeExport>&) const;
void saveValues(const std::vector<dht::ValuesExport>&) const;
void loadTreatedCalls();
void saveTreatedCalls() const;
void loadTreatedMessages();
void saveTreatedMessages() const;
void loadKnownDevices();
void saveKnownDevices() const;
void replyToIncomingIceMsg(const std::shared_ptr<SIPCall>&,
const std::shared_ptr<IceTransport>&,
const dht::IceCandidates&,
const std::shared_ptr<dht::crypto::Certificate>& from_cert,
const dht::InfoHash& from);
static tls::DhParams loadDhParams(const std::string path);
/**
* If privkeyPath_ is a valid private key file (PEM or DER),
* and certPath_ a valid certificate file, load and returns them.
* Otherwise, generate a new identity and returns it.
*/
dht::crypto::Identity loadIdentity(const std::string& crt_path, const std::string& key_path, const std::string& key_pwd) const;
std::vector<dht::NodeExport> loadNodes() const;
std::vector<dht::ValuesExport> loadValues() const;
bool dhtPublicInCalls_ {true};
/**
* DHT port preference
*/
in_port_t dhtPort_ {};
/**
* DHT port actually used,
* this holds the actual port used for DHT, which may not be the port
* selected in the configuration in the case that UPnP is used and the
* configured port is already used by another client
*/
UsedPort dhtPortUsed_ {};
/**
* The TLS settings, used only if tls is chosen as a sip transport.
*/
void generateDhParams();
std::shared_future<tls::DhParams> dhParams_;
std::mutex dhParamsMtx_;
std::condition_variable dhParamsCv_;
bool allowPeersFromHistory_ {true};
bool allowPeersFromContact_ {true};
bool allowPeersFromTrusted_ {true};
/**
* Optional: "received" parameter from VIA header
*/
std::string receivedParameter_ {};
/**
* Optional: "rport" parameter from VIA header
*/
int rPort_ {-1};
/**
* Optional: via_addr construct from received parameters
*/
pjsip_host_port via_addr_ {};
char contactBuffer_[PJSIP_MAX_URL_SIZE] {};
pj_str_t contact_ {contactBuffer_, 0};
pjsip_transport* via_tp_ {nullptr};
template <class... Args>
std::shared_ptr<IceTransport> createIceTransport(const Args&... args);
void registerDhtAddress(IceTransport&);
};
} // namespace ring