archiver.cpp 9.36 KB
Newer Older
1
/*
2
 *  Copyright (C) 2016-2019 Savoir-faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
 *
 *  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 "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>
32 33
#include <json/json.h>
#include <zlib.h>
34 35

#include <sys/stat.h>
36
#include <fstream>
37

Adrien Béraud's avatar
Adrien Béraud committed
38
namespace jami {
39
namespace archiver {
40

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
std::map<std::string, std::string>
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;
66 67
}

68
Json::Value
Adrien Béraud's avatar
Adrien Béraud committed
69
accountToJsonValue(const std::map<std::string, std::string>& details) {
70
    Json::Value root;
Adrien Béraud's avatar
Adrien Béraud committed
71 72
    for (const auto& i : details) {
        if (i.first == DRing::Account::ConfProperties::Ringtone::PATH) {
73
            // Ringtone path is not exportable
Adrien Béraud's avatar
Adrien Béraud committed
74 75 76
        } else if (i.first == DRing::Account::ConfProperties::TLS::CA_LIST_FILE ||
                   i.first == DRing::Account::ConfProperties::TLS::CERTIFICATE_FILE ||
                   i.first == DRing::Account::ConfProperties::TLS::PRIVATE_KEY_FILE) {
77
            // replace paths by the files content
Adrien Béraud's avatar
Adrien Béraud committed
78
            std::ifstream ifs(i.second);
79
            std::string fileContent((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
80
            root[i.first] = fileContent;
81
        } else
Adrien Béraud's avatar
Adrien Béraud committed
82
            root[i.first] = i.second;
83 84 85
    }

    return root;
86 87 88
}

int
Adrien Béraud's avatar
Adrien Béraud committed
89 90 91
exportAccounts(const std::vector<std::string>& accountIDs,
                        const std::string& filepath,
                        const std::string& password)
92 93
{
    if (filepath.empty() || !accountIDs.size()) {
Adrien Béraud's avatar
Adrien Béraud committed
94
        JAMI_ERR("Missing arguments");
95 96 97
        return EINVAL;
    }

Adrien Béraud's avatar
Adrien Béraud committed
98
    std::size_t found = filepath.find_last_of(DIR_SEPARATOR_CH);
99 100 101 102
    auto toDir = filepath.substr(0,found);
    auto filename = filepath.substr(found+1);

    if (!fileutils::isDirectory(toDir)) {
Adrien Béraud's avatar
Adrien Béraud committed
103
        JAMI_ERR("%s is not a directory", toDir.c_str());
104 105 106 107 108 109 110 111 112 113
        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()) {
Adrien Béraud's avatar
Adrien Béraud committed
114
            JAMI_WARN("Can't export account %s", accountIDs[i].c_str());
115 116 117 118 119 120 121
            continue;
        }

        auto jsonAccount = accountToJsonValue(detailsMap);
        array.append(jsonAccount);
    }
    root["accounts"] = array;
Guillaume Roguez's avatar
Guillaume Roguez committed
122 123 124 125
    Json::StreamWriterBuilder wbuilder;
    wbuilder["commentStyle"] = "None";
    wbuilder["indentation"] = "";
    auto output = Json::writeString(wbuilder, root);
126 127 128 129 130 131

    // Compress
    std::vector<uint8_t> compressed;
    try {
        compressed = compress(output);
    } catch (const std::runtime_error& ex) {
Adrien Béraud's avatar
Adrien Béraud committed
132
        JAMI_ERR("Export failed: %s", ex.what());
133 134 135 136 137 138 139 140 141 142
        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) {
Adrien Béraud's avatar
Adrien Béraud committed
143
        JAMI_ERR("Export failed: %s", ex.what());
144 145 146 147 148 149
        return EIO;
    }
    return 0;
}

int
150
importAccounts(const std::string& archivePath, const std::string& password)
151 152
{
    if (archivePath.empty()) {
Adrien Béraud's avatar
Adrien Béraud committed
153
        JAMI_ERR("Missing arguments");
154 155 156 157 158 159 160 161
        return EINVAL;
    }

    // Read file
    std::vector<uint8_t> file;
    try {
        file = fileutils::loadFile(archivePath);
    } catch (const std::exception& ex) {
Adrien Béraud's avatar
Adrien Béraud committed
162
        JAMI_ERR("Read failed: %s", ex.what());
163 164 165 166 167 168 169
        return ENOENT;
    }

    // Decrypt
    try {
        file = dht::crypto::aesDecrypt(file, password);
    } catch (const std::exception& ex) {
Adrien Béraud's avatar
Adrien Béraud committed
170
        JAMI_ERR("Decryption failed: %s", ex.what());
171 172 173 174 175 176 177
        return EPERM;
    }

    // Decompress
    try {
        file = decompress(file);
    } catch (const std::exception& ex) {
Adrien Béraud's avatar
Adrien Béraud committed
178
        JAMI_ERR("Decompression failed: %s", ex.what());
179 180 181 182
        return ERANGE;
    }

    try {
Guillaume Roguez's avatar
Guillaume Roguez committed
183 184
        const auto* char_file_begin = reinterpret_cast<const char*>(&file[0]);
        const auto* char_file_end = reinterpret_cast<const char*>(&file[file.size()]);
185 186

        // Add
Guillaume Roguez's avatar
Guillaume Roguez committed
187
        std::string err;
188
        Json::Value root;
Guillaume Roguez's avatar
Guillaume Roguez committed
189 190 191
        Json::CharReaderBuilder rbuilder;
        auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
        if (!reader->parse(char_file_begin, char_file_end, &root, &err)) {
Adrien Béraud's avatar
Adrien Béraud committed
192
            JAMI_ERR() << "Failed to parse " << err;
193 194 195 196 197 198
            return ERANGE;
        }

        auto& accounts = root["accounts"];
        for (int i = 0, n = accounts.size(); i < n; ++i) {
            // Generate a new account id
Adrien Béraud's avatar
Adrien Béraud committed
199
            auto accountId = jami::Manager::instance().getNewAccountId();
200
            auto details = jsonValueToAccount(accounts[i], accountId);
Adrien Béraud's avatar
Adrien Béraud committed
201
            jami::Manager::instance().addAccount(details, accountId);
202 203
        }
    } catch (const std::exception& ex) {
Adrien Béraud's avatar
Adrien Béraud committed
204
        JAMI_ERR("Import failed: %s", ex.what());
205 206 207 208 209 210
        return ERANGE;
    }
    return 0;
}

std::vector<uint8_t>
211
compress(const std::string& str)
212 213 214 215
{
    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());
216
    outbuffer.resize(destSize);
217 218 219 220

    if (ret != Z_OK) {
        std::ostringstream oss;
        oss << "Exception during zlib compression: (" << ret << ") ";
221
        throw std::runtime_error(oss.str());
222 223 224 225 226
    }

    return outbuffer;
}

227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
void
compressGzip(const std::string& str, const std::string& path)
{
    auto fi = gzopen(path.c_str(), "wb");
    gzwrite(fi, str.data(), str.size());
    gzclose(fi);
}

std::vector<uint8_t>
decompressGzip(const std::string& path)
{
    std::vector<uint8_t> out;
    auto fi = gzopen(path.c_str(),"rb");
    gzrewind(fi);
    while (not gzeof(fi)) {
        std::array<uint8_t, 32768> outbuffer;
        int len = gzread(fi, outbuffer.data(), outbuffer.size());
        if (len == -1) {
            gzclose(fi);
            throw std::runtime_error("Exception during gzip decompression");
        }
        out.insert(out.end(), outbuffer.begin(), outbuffer.begin() +  len);
    }
    gzclose(fi);
    return out;
}

254
std::vector<uint8_t>
255
decompress(const std::vector<uint8_t>& str)
256 257 258 259 260
{
    z_stream zs; // z_stream is zlib's control structure
    memset(&zs, 0, sizeof(zs));

    if (inflateInit(&zs) != Z_OK)
261
        throw std::runtime_error("inflateInit failed while decompressing.");
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

    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;
}

Adrien Béraud's avatar
Adrien Béraud committed
297
}} // namespace jami::archiver