/* * Copyright (C) 2014-2017 Savoir-faire Linux Inc. * * Author: Adrien Béraud * Author: Simon Désaulniers * * 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 #include #include #include #include #include #include #include #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 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 DHT_PORT_RANGE; const char* getAccountType() const override { return ACCOUNT_TYPE; } std::shared_ptr shared() { return std::static_pointer_cast(shared_from_this()); } std::shared_ptr shared() const { return std::static_pointer_cast(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 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 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 getTrackedBuddyPresence(); /** * Connect to the DHT. */ void doRegister() override; /** * Disconnect from the DHT. */ void doUnregister(std::function 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 newOutgoingCall(const std::string& toUrl) override; /** * Create outgoing SIPCall. * @param[in] toUrl The address to call * @return std::shared_ptr 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 std::shared_ptr > newOutgoingCall(const std::string& toUrl); #else template std::shared_ptr newOutgoingCall(const std::string& toUrl); #endif /** * Create incoming SIPCall. * @param[in] from The origin of the call * @return std::shared_ptr 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 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 getCertificatesByStatus(tls::TrustStore::PermissionStatus status); bool findCertificate(const std::string& id); bool findCertificate(const dht::InfoHash& h, std::function&)>&& cb = {}); /* contact requests */ std::vector> 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> getContacts() const; /// /// Obtain details about one account contact in serializable form. /// std::map getContactDetails(const std::string& uri) const; void sendTrustRequest(const std::string& to, const std::vector& payload); void sendTrustRequestConfirm(const dht::InfoHash& to); virtual void sendTextMessage(const std::string& to, const std::map& payloads, uint64_t id) override; /* Devices */ void addDevice(const std::string& password); bool revokeDevice(const std::string& password, const std::string& device); std::map 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&, const dht::InfoHash&)> op, std::function 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 nameDir_; std::string nameServer_; std::string registeredName_; #endif /** * Compute archive encryption key and DHT storage location from password and PIN. */ static std::pair, 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::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::iterator& buddy_info_it, const dht::InfoHash& device_id); void doRegister_(); void incomingCall(dht::IceCandidates&& msg, const std::shared_ptr& 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& 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 &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& 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& 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& 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& 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&& 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 pendingCalls_; /** * Incoming DHT calls that are not yet actual SIP calls. */ std::list pendingSipCalls_; std::set treatedCalls_ {}; mutable std::mutex callsMutex_ {}; std::map sentMessages_; std::set 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 receiptSignature_ {}; dht::Value announceVal_; std::map trustRequests_; void loadTrustRequests(); void saveTrustRequests() const; std::map 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 announce_; /* this ring account associated devices */ std::map knownDevices_; /* tracked buddies presence */ std::recursive_mutex buddyInfoMtx; std::map 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 loadBootstrap() const; static std::pair saveIdentity(const dht::crypto::Identity id, const std::string& path, const std::string& name); void saveNodes(const std::vector&) const; void saveValues(const std::vector&) const; void loadTreatedCalls(); void saveTreatedCalls() const; void loadTreatedMessages(); void saveTreatedMessages() const; void loadKnownDevices(); void saveKnownDevices() const; void replyToIncomingIceMsg(const std::shared_ptr&, const std::shared_ptr&, const dht::IceCandidates&, const std::shared_ptr& 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 loadNodes() const; std::vector 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 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 std::shared_ptr createIceTransport(const Args&... args); void registerDhtAddress(IceTransport&); }; } // namespace ring