diff --git a/bin/dbus/configurationmanager-introspec.xml b/bin/dbus/configurationmanager-introspec.xml index 431023769b9a647acab1b2da6a392b653c3e00f7..50f4a6108b4180bc5d388df8d0df4e51f6dcf28e 100644 --- a/bin/dbus/configurationmanager-introspec.xml +++ b/bin/dbus/configurationmanager-introspec.xml @@ -193,6 +193,54 @@ </arg> </method> + <method name="importAccounts" tp:name-for-bindings="importAccounts"> + <tp:docstring> + Import previously exported accounts + </tp:docstring> + <arg type="s" name="path" direction="in"> + <tp:docstring> + Path of the file to import + </tp:docstring> + </arg> + <arg type="s" name="password" direction="in"> + <tp:docstring> + Decryption password + </tp:docstring> + </arg> + <arg type="i" direction="out"> + <tp:docstring> + <p>Return code, 0 for success.</p> + </tp:docstring> + </arg> + </method> + + <method name="exportAccounts" tp:name-for-bindings="exportAccounts"> + <tp:docstring> + Export account configuration to an encrypted file. + </tp:docstring> + <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="VectorString"/> + <arg type="as" name="accountIDs" direction="in"> + <tp:docstring> + A list of account IDs + </tp:docstring> + </arg> + <arg type="s" name="filepath" direction="in"> + <tp:docstring> + Where to export the account + </tp:docstring> + </arg> + <arg type="s" name="password" direction="in"> + <tp:docstring> + File encryption password + </tp:docstring> + </arg> + <arg type="i" direction="out"> + <tp:docstring> + <p>Return code, 0 for success.</p> + </tp:docstring> + </arg> + </method> + <method name="registerAllAccounts" tp:name-for-bindings="registerAllAccounts"> <tp:docstring> Send account registration (REGISTER) for all accounts, even if they are not enabled. diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp index 9cd02e5f2044998d07feaaf147062dc6ecf26d72..bb3cfa37820dd19718c47db1b7084acb04472c1a 100644 --- a/bin/dbus/dbusconfigurationmanager.cpp +++ b/bin/dbus/dbusconfigurationmanager.cpp @@ -527,3 +527,15 @@ DBusConfigurationManager::getVolume(const std::string& device) -> decltype(DRing { return DRing::getVolume(device); } + +auto +DBusConfigurationManager::exportAccounts(const std::vector<std::string>& accountIDs, const std::string& filepath, const std::string& password) -> decltype(DRing::exportAccounts(accountIDs, filepath, password)) +{ + return DRing::exportAccounts(accountIDs, filepath, password); +} + +auto +DBusConfigurationManager::importAccounts(const std::string& archivePath, const std::string& password) -> decltype(DRing::importAccounts(archivePath, password)) +{ + return DRing::importAccounts(archivePath, password); +} diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h index 931ebdf593b519d63918af1fe02bb3529acf0dd7..e40eaf20872b8d97dae6fe1f7f81f94d84b1a0f7 100644 --- a/bin/dbus/dbusconfigurationmanager.h +++ b/bin/dbus/dbusconfigurationmanager.h @@ -137,6 +137,8 @@ class DBusConfigurationManager : bool acceptTrustRequest(const std::string& accountId, const std::string& from); bool discardTrustRequest(const std::string& accountId, const std::string& from); void sendTrustRequest(const std::string& accountId, const std::string& to, const std::vector<uint8_t>& payload); + int exportAccounts(const std::vector<std::string>& accountIDs, const std::string& filepath, const std::string& password); + int importAccounts(const std::string& archivePath, const std::string& password); }; #endif // __RING_DBUSCONFIGURATIONMANAGER_H__ diff --git a/configure.ac b/configure.ac index f55779c00669eb02b53731b86ef3316748048b6b..951db4bdef2fbd79d24dd57ccb63cd1c07fd56f0 100644 --- a/configure.ac +++ b/configure.ac @@ -157,6 +157,7 @@ AC_TYPE_SIZE_T AC_HEADER_TIME AC_C_VOLATILE AC_CHECK_TYPES([ptrdiff_t]) +AC_CHECK_LIB(zlib, zlib) PKG_PROG_PKG_CONFIG() @@ -236,7 +237,7 @@ AS_IF([test -n "${CONTRIB_DIR}"], [ export PKG_CONFIG_PATH_CUSTOM ]) export PKG_CONFIG_PATH="${CONTRIB_DIR}/lib/pkgconfig:${CONTRIB_DIR}/lib64/pkgconfig:$PKG_CONFIG_PATH" - LDFLAGS="${LDFLAGS} -L${CONTRIB_DIR}/lib" + LDFLAGS="${LDFLAGS} -L${CONTRIB_DIR}/lib -lz" AS_IF([test "${SYS}" = "darwin"], [ export LD_LIBRARY_PATH="${CONTRIB_DIR}/lib:$LD_LIBRARY_PATH" diff --git a/contrib/src/opendht/rules.mak b/contrib/src/opendht/rules.mak index 4281f262c772b775c4dc6d99d3b9689134385831..b05b627fe8edf5a1e2f236572e209708db88cf6b 100644 --- a/contrib/src/opendht/rules.mak +++ b/contrib/src/opendht/rules.mak @@ -1,5 +1,5 @@ # OPENDHT -OPENDHT_VERSION := 281b62dfd529a226e94d0da19e01acf07871a797 +OPENDHT_VERSION := 8ae95f1284f3c00c41832ef4d76196481543f59e OPENDHT_URL := https://github.com/savoirfairelinux/opendht/archive/$(OPENDHT_VERSION).tar.gz PKGS += opendht diff --git a/src/Makefile.am b/src/Makefile.am index 52b19cd417b41e3a9c8d933e3577c79e0361e82d..43dc74404ec618bcc5a0a5e12c40d1c9e6aa7afe 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -116,6 +116,7 @@ libring_la_SOURCES = \ account.cpp \ logger.cpp \ fileutils.cpp \ + archiver.cpp \ threadloop.cpp \ ip_utils.h \ ip_utils.cpp \ @@ -137,6 +138,7 @@ libring_la_SOURCES = \ call.h \ logger.h \ fileutils.h \ + archiver.h \ noncopyable.h \ utf8_utils.h \ ring_types.h \ diff --git a/src/archiver.cpp b/src/archiver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f242f4f7630772b6ce8b1f8b091d09e32e7e3075 --- /dev/null +++ b/src/archiver.cpp @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2016 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.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. + */ + +#include "archiver.h" + +#include <json/json.h> + +#include "client/ring_signal.h" +#include "account_const.h" +#include "configurationmanager_interface.h" + +#include "manager.h" +#include "fileutils.h" +#include "logger.h" + +#include <opendht/crypto.h> + +#include <fstream> +#include <sys/stat.h> + +namespace ring { + +Archiver& +Archiver::instance() +{ + // Meyers singleton + static Archiver instance_; + return instance_; +} + +Archiver::Archiver() +{ + +} + +int +Archiver::exportAccounts(std::vector<std::string> accountIDs, + std::string filepath, + std::string password) +{ + if (filepath.empty() || !accountIDs.size()) { + RING_ERR("Missing arguments"); + return EINVAL; + } + + std::size_t found = filepath.find_last_of(DIR_SEPARATOR_STR); + auto toDir = filepath.substr(0,found); + auto filename = filepath.substr(found+1); + + if (!fileutils::isDirectory(toDir)) { + RING_ERR("%s is not a directory", toDir.c_str()); + return ENOTDIR; + } + + // Add + Json::Value root; + Json::Value array; + + for (size_t i = 0; i < accountIDs.size(); ++i) { + auto detailsMap = Manager::instance().getAccountDetails(accountIDs[i]); + if (detailsMap.empty()) { + RING_WARN("Can't export account %s", accountIDs[i].c_str()); + continue; + } + + auto jsonAccount = accountToJsonValue(detailsMap); + array.append(jsonAccount); + } + root["accounts"] = array; + Json::FastWriter fastWriter; + std::string output = fastWriter.write(root); + + // Compress + std::vector<uint8_t> compressed; + try { + compressed = compress(output); + } catch (const std::runtime_error& ex) { + RING_ERR("Export failed: %s", ex.what()); + return 1; + } + + // Encrypt using provided password + auto encrypted = dht::crypto::aesEncrypt(compressed, password); + + // Write + try { + fileutils::saveFile(toDir + DIR_SEPARATOR_STR + filename, encrypted); + } catch (const std::runtime_error& ex) { + RING_ERR("Export failed: %s", ex.what()); + return EIO; + } + return 0; +} + +Json::Value +Archiver::accountToJsonValue(std::map<std::string, std::string> details) { + Json::Value root; + std::map<std::string, std::string>::iterator iter; + for (iter = details.begin(); iter != details.end(); ++iter) { + + if (iter->first.compare(DRing::Account::ConfProperties::Ringtone::PATH) == 0) { + // Ringtone path is not exportable + } else if (iter->first.compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0 || + iter->first.compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0 || + iter->first.compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) { + // replace paths by the files content + std::ifstream ifs(iter->second); + std::string fileContent((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); + root[iter->first] = fileContent; + + } else + root[iter->first] = iter->second; + } + + return root; +} + +int +Archiver::importAccounts(std::string archivePath, std::string password) +{ + if (archivePath.empty()) { + RING_ERR("Missing arguments"); + return EINVAL; + } + + // Read file + std::vector<uint8_t> file; + try { + file = fileutils::loadFile(archivePath); + } catch (const std::exception& ex) { + RING_ERR("Read failed: %s", ex.what()); + return ENOENT; + } + + // Decrypt + try { + file = dht::crypto::aesDecrypt(file, password); + } catch (const std::exception& ex) { + RING_ERR("Decryption failed: %s", ex.what()); + return EPERM; + } + + // Decompress + try { + file = decompress(file); + } catch (const std::exception& ex) { + RING_ERR("Decompression failed: %s", ex.what()); + return ERANGE; + } + + try { + // Decode string + std::string decoded {file.begin(), file.end()}; + + // Add + Json::Value root; + Json::Reader reader; + if (!reader.parse(decoded.c_str(),root)) { + RING_ERR("Failed to parse %s", reader.getFormattedErrorMessages().c_str()); + return ERANGE; + } + + auto& accounts = root["accounts"]; + for (int i = 0, n = accounts.size(); i < n; ++i) { + // Generate a new account id + auto accountId = ring::Manager::instance().getNewAccountId(); + auto details = jsonValueToAccount(accounts[i], accountId); + ring::Manager::instance().addAccount(details, accountId); + } + } catch (const std::exception& ex) { + RING_ERR("Import failed: %s", ex.what()); + return ERANGE; + } + return 0; +} + +std::map<std::string, std::string> +Archiver::jsonValueToAccount(Json::Value& value, const std::string& accountId) { + auto idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId; + fileutils::check_dir(idPath_.c_str(), 0700); + auto detailsMap = DRing::getAccountTemplate(value[DRing::Account::ConfProperties::TYPE].asString()); + + for( Json::ValueIterator itr = value.begin() ; itr != value.end() ; itr++ ) { + if (itr->asString().empty()) + continue; + if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CA_LIST_FILE) == 0) { + std::string fileContent(itr->asString()); + fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "ca.key", {fileContent.begin(), fileContent.end()}, 0600); + + } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) == 0) { + std::string fileContent(itr->asString()); + fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.key", {fileContent.begin(), fileContent.end()}, 0600); + + } else if (itr.key().asString().compare(DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE) == 0) { + std::string fileContent(itr->asString()); + fileutils::saveFile(idPath_ + DIR_SEPARATOR_STR "dht.crt", {fileContent.begin(), fileContent.end()}, 0600); + } else + detailsMap[itr.key().asString()] = itr->asString(); + } + + return detailsMap; +} + +std::vector<uint8_t> +Archiver::compress(const std::string& str, int compressionlevel) +{ + auto destSize = compressBound(str.size()); + std::vector<uint8_t> outbuffer(destSize); + int ret = ::compress(reinterpret_cast<Bytef*>(outbuffer.data()), &destSize, (Bytef*)str.data(), str.size()); + + if (ret != Z_OK) { + std::ostringstream oss; + oss << "Exception during zlib compression: (" << ret << ") "; + throw(std::runtime_error(oss.str())); + } + + return outbuffer; +} + +std::vector<uint8_t> +Archiver::decompress(const std::vector<uint8_t>& str) +{ + z_stream zs; // z_stream is zlib's control structure + memset(&zs, 0, sizeof(zs)); + + if (inflateInit(&zs) != Z_OK) + throw(std::runtime_error("inflateInit failed while decompressing.")); + + zs.next_in = (Bytef*)str.data(); + zs.avail_in = str.size(); + + int ret; + std::vector<uint8_t> out; + + // get the decompressed bytes blockwise using repeated calls to inflate + do { + std::array<uint8_t, 32768> outbuffer; + zs.next_out = reinterpret_cast<Bytef*>(outbuffer.data()); + zs.avail_out = outbuffer.size(); + + ret = inflate(&zs, 0); + if (ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) + break; + + if (out.size() < zs.total_out) { + // append the block to the output string + out.insert(out.end(), outbuffer.begin(), outbuffer.begin() + zs.total_out - out.size()); + } + } while (ret == Z_OK); + + inflateEnd(&zs); + + // an error occurred that was not EOF + if (ret != Z_STREAM_END) { + std::ostringstream oss; + oss << "Exception during zlib decompression: (" << ret << ") " << zs.msg; + throw(std::runtime_error(oss.str())); + } + + return out; +} + +} // namespace ring diff --git a/src/archiver.h b/src/archiver.h new file mode 100644 index 0000000000000000000000000000000000000000..6513e37cc5833614db402456bb7e1c09172054b8 --- /dev/null +++ b/src/archiver.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.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 + +#include "noncopyable.h" + +#include <string> +#include <vector> +#include <map> +#include <zlib.h> + +namespace Json { +class Value; +}; + +namespace ring { + +/** + * Archiver is used to generate/read encrypted archives + */ +class Archiver { +public: + static Archiver& instance(); + + Archiver(); + + /** + * Create a protected archive containing a list of accounts + * @param accountIDs The accounts to exports + * @param filepath The filepath where to put the resulting archive + * @param password The mandatory password to set on the archive + * @returns 0 for OK, error code otherwise + */ + int exportAccounts(std::vector<std::string> accountIDs, + std::string filepath, + std::string password); + + /** + * Read a protected archive and add accounts found in it + * Warning: this function must be called from a registered pjsip thread + * @param archivePath The path to the archive file + * @param password The password to read the archive + * @returns 0 for OK, error code otherwise + */ + int importAccounts(std::string archivePath, std::string password); + + /** + * Compress a STL string using zlib with given compression level and return + * the binary data. + */ + std::vector<uint8_t> compress(const std::string& str, int compressionlevel = Z_BEST_COMPRESSION); + + /** + * Decompress an STL string using zlib and return the original data. + */ + std::vector<uint8_t> decompress(const std::vector<uint8_t>& dat); + +private: + NON_COPYABLE(Archiver); + + static Json::Value accountToJsonValue(std::map<std::string, std::string> details); + static std::map<std::string, std::string> jsonValueToAccount(Json::Value& value, const std::string& accountId); +}; + +} // namespace ring diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp index d8fb9441afdff80bf41771a4ae27139d7ef5ab48..e4737e65297d42fed5b8b1eef1d87cac2ac0d3f4 100644 --- a/src/client/configurationmanager.cpp +++ b/src/client/configurationmanager.cpp @@ -34,6 +34,7 @@ #endif #include "logger.h" #include "fileutils.h" +#include "archiver.h" #include "ip_utils.h" #include "sip/sipaccount.h" #include "ringdht/ringaccount.h" @@ -320,6 +321,21 @@ sendTrustRequest(const std::string& accountId, const std::string& to, const std: acc->sendTrustRequest(to, payload); } +/* + * Import/Export accounts + */ +int +exportAccounts(std::vector<std::string> accountIDs, std::string filepath, std::string password) +{ + return ring::Archiver::instance().exportAccounts(accountIDs, filepath, password); +} + +int +importAccounts(std::string archivePath, std::string password) +{ + return ring::Archiver::instance().importAccounts(archivePath, password); +} + ///This function is used as a base for new accounts for clients that support it std::map<std::string, std::string> getAccountTemplate(const std::string& accountType) diff --git a/src/dring/configurationmanager_interface.h b/src/dring/configurationmanager_interface.h index 8836b81717cb5b7b8c9a0dbca096c09a587247e8..56b3370be0153a9b552f2f3e5bbb6906dae533dc 100644 --- a/src/dring/configurationmanager_interface.h +++ b/src/dring/configurationmanager_interface.h @@ -150,6 +150,12 @@ bool discardTrustRequest(const std::string& accountId, const std::string& from); void sendTrustRequest(const std::string& accountId, const std::string& to, const std::vector<uint8_t>& payload = {}); +/* + * Import/Export accounts + */ +int exportAccounts(std::vector<std::string> accountIDs, std::string filepath, std::string password); +int importAccounts(std::string archivePath, std::string password); + struct AudioSignal { struct DeviceEvent { constexpr static const char* name = "audioDeviceEvent"; diff --git a/src/manager.cpp b/src/manager.cpp index 54a91831073e3011e6993bdebf9909189dc7ea4c..4a3b82c4f7618799c6f4d791385b2108e59c76e3 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -2433,9 +2433,8 @@ Manager::setAccountDetails(const std::string& accountID, } std::string -Manager::addAccount(const std::map<std::string, std::string>& details) +Manager::getNewAccountId() { - /** @todo Deal with both the accountMap_ and the Configuration */ std::string newAccountID; static std::uniform_int_distribution<uint64_t> rand_acc_id; @@ -2448,8 +2447,16 @@ Manager::addAccount(const std::map<std::string, std::string>& details) } while (std::find(accountList.begin(), accountList.end(), newAccountID) != accountList.end()); - // Get the type + return newAccountID; +} +std::string +Manager::addAccount(const std::map<std::string, std::string>& details, const std::string& accountId) +{ + /** @todo Deal with both the accountMap_ and the Configuration */ + auto newAccountID = accountId.empty() ? getNewAccountId() : accountId; + + // Get the type const char* accountType; if (details.find(Conf::CONFIG_ACCOUNT_TYPE) != details.end()) accountType = (*details.find(Conf::CONFIG_ACCOUNT_TYPE)).second.c_str(); diff --git a/src/manager.h b/src/manager.h index 476e239ef21be8c5786506476264473578ec4c63..eb3edcd4e23f901b6800ed92533837686edcea41 100644 --- a/src/manager.h +++ b/src/manager.h @@ -499,12 +499,20 @@ class Manager { void setAccountActive(const std::string& accountID, bool active); + /** + * Return a new random accountid that is not present in the list + * @return A brand new accountid + */ + std::string getNewAccountId(); + /** * Add a new account, and give it a new account ID automatically * @param details The new account parameters + * @param accountId optionnal predetermined accountid to use * @return The account Id given to the new account */ - std::string addAccount(const std::map<std::string, std::string> &details); + std::string addAccount(const std::map<std::string, std::string> &details, + const std::string& accountId = {}); /** * Delete an existing account, unregister VoIPLink associated, and diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp index 15658d2bb503376a6eccc89d800cdfd5d6612202..2515e7ce5432a4c72a6d90e930c5e552e65b4fd5 100644 --- a/src/ringdht/ringaccount.cpp +++ b/src/ringdht/ringaccount.cpp @@ -425,7 +425,7 @@ void RingAccount::unserialize(const YAML::Node &node) using yaml_utils::parseValue; SIPAccountBase::unserialize(node); - parseValue(node, Conf::DHT_PORT_KEY, dhtPort_); + parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_HISTORY, allowPeersFromHistory_); parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_CONTACT, allowPeersFromContact_); parseValue(node, Conf::DHT_ALLOW_PEERS_FROM_TRUSTED, allowPeersFromTrusted_); @@ -1251,6 +1251,9 @@ RingAccount::loadDhParams(const std::string path) void RingAccount::generateDhParams() { + //make sure cachePath_ is writable + fileutils::check_dir(cachePath_.c_str(), 0700); + std::packaged_task<decltype(loadDhParams)> task(loadDhParams); dhParams_ = task.get_future(); std::thread task_td(std::move(task), cachePath_ + DIR_SEPARATOR_STR "dhParams");