summary refs log tree commit diff
path: root/src/Cache.cpp
diff options
context:
space:
mode:
authorZhymabek Roman <61125068+ZhymabekRoman@users.noreply.github.com>2023-01-02 21:59:29 +0600
committerGitHub <noreply@github.com>2023-01-02 21:59:29 +0600
commit59410a99ac9b5a90356adc2192b8c6107456c78f (patch)
treef42e081641cee3d62057208f3af1de3afddd02e6 /src/Cache.cpp
parentAppImage: reduce package size (diff)
parentA whole new year full of excitement and possibilities! (diff)
downloadnheko-59410a99ac9b5a90356adc2192b8c6107456c78f.tar.xz
Merge branch 'master' into master
Diffstat (limited to 'src/Cache.cpp')
-rw-r--r--src/Cache.cpp352
1 files changed, 230 insertions, 122 deletions
diff --git a/src/Cache.cpp b/src/Cache.cpp

index a83b73f7..d018665a 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp
@@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2017 Konstantinos Sideris <siderisk@auth.gr> // SPDX-FileCopyrightText: 2021 Nheko Contributors // SPDX-FileCopyrightText: 2022 Nheko Contributors +// SPDX-FileCopyrightText: 2023 Nheko Contributors // // SPDX-License-Identifier: GPL-3.0-or-later @@ -39,7 +40,7 @@ //! Should be changed when a breaking change occurs in the cache format. //! This will reset client's data. -static const std::string CURRENT_CACHE_FORMAT_VERSION{"2022.04.08"}; +static const std::string CURRENT_CACHE_FORMAT_VERSION{"2022.11.06"}; //! Keys used for the DB static const std::string_view NEXT_BATCH_KEY("next_batch"); @@ -293,7 +294,10 @@ Cache::setup() // NOTE(Nico): We may want to use (MDB_MAPASYNC | MDB_WRITEMAP) in the future, but // it can really mess up our database, so we shouldn't. For now, hopefully // NOMETASYNC is fast enough. - env_.open(cacheDirectory_.toStdString().c_str(), MDB_NOMETASYNC | MDB_NOSYNC); + // + // 2022-10-28: Disable the nosync flags again in the hope to crack down on some database + // corruption. + env_.open(cacheDirectory_.toStdString().c_str()); //, MDB_NOMETASYNC | MDB_NOSYNC); } catch (const lmdb::error &e) { if (e.code() != MDB_VERSION_MISMATCH && e.code() != MDB_INVALID) { throw std::runtime_error("LMDB initialization failed" + std::string(e.what())); @@ -337,13 +341,13 @@ Cache::setup() txn.commit(); - loadSecrets({ - {mtx::secret_storage::secrets::cross_signing_master, false}, - {mtx::secret_storage::secrets::cross_signing_self_signing, false}, - {mtx::secret_storage::secrets::cross_signing_user_signing, false}, - {mtx::secret_storage::secrets::megolm_backup_v1, false}, - {"pickle_secret", true}, - }); + loadSecretsFromStore( + { + {"pickle_secret", true}, + }, + [this](const std::string &, bool, const std::string &value) { + this->pickle_secret_ = value; + }); } static void @@ -358,7 +362,8 @@ fatalSecretError() "have multiple reasons. Check if your D-Bus service is running and you have configured a " "service like KWallet, Gnome Keyring, KeePassXC or the equivalent for your platform. If " "you are having trouble, feel free to open an issue here: " - "https://github.com/Nheko-Reborn/nheko/issues")); + "https://github.com/Nheko-Reborn/nheko/issues"), + QMessageBox::StandardButton::Close); QCoreApplication::exit(1); exit(1); @@ -376,7 +381,9 @@ secretName(std::string name, bool internal) } void -Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad) +Cache::loadSecretsFromStore( + std::vector<std::pair<std::string, bool>> toLoad, + std::function<void(const std::string &name, bool internal, const std::string &value)> callback) { auto settings = UserSettings::instance()->qsettings(); @@ -394,12 +401,11 @@ Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad) if (value.isEmpty()) { nhlog::db()->info("Restored empty secret '{}'.", name.toStdString()); } else { - std::unique_lock lock(secret_storage.mtx); - secret_storage.secrets[name.toStdString()] = value.toStdString(); + callback(name_, internal, value.toStdString()); } } // if we emit the databaseReady signal directly it won't be received - QTimer::singleShot(0, this, [this] { loadSecrets({}); }); + QTimer::singleShot(0, this, [this, callback] { loadSecretsFromStore({}, callback); }); return; } @@ -415,7 +421,8 @@ Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad) connect(job, &QKeychain::ReadPasswordJob::finished, this, - [this, name, toLoad, job](QKeychain::Job *) mutable { + [this, name, toLoad, job, name_ = name_, internal = internal, callback]( + QKeychain::Job *) mutable { nhlog::db()->debug("Finished reading '{}'", toLoad.begin()->first); const QString secret = job->textData(); if (job->error() && job->error() != QKeychain::Error::EntryNotFound) { @@ -429,40 +436,72 @@ Cache::loadSecrets(std::vector<std::pair<std::string, bool>> toLoad) if (secret.isEmpty()) { nhlog::db()->debug("Restored empty secret '{}'.", name.toStdString()); } else { - std::unique_lock lock(secret_storage.mtx); - secret_storage.secrets[name.toStdString()] = secret.toStdString(); + callback(name_, internal, secret.toStdString()); } // load next secret toLoad.erase(toLoad.begin()); // You can't start a job from the finish signal of a job. - QTimer::singleShot(0, this, [this, toLoad] { loadSecrets(toLoad); }); + QTimer::singleShot( + 0, this, [this, toLoad, callback] { loadSecretsFromStore(toLoad, callback); }); }); nhlog::db()->debug("Reading '{}'", name_); job->start(); } std::optional<std::string> -Cache::secret(const std::string name_, bool internal) +Cache::secret(const std::string &name_, bool internal) { auto name = secretName(name_, internal); - std::unique_lock lock(secret_storage.mtx); - if (auto secret = secret_storage.secrets.find(name.toStdString()); - secret != secret_storage.secrets.end()) - return secret->second; - else + + auto txn = ro_txn(env_); + std::string_view value; + auto db_name = "secret." + name.toStdString(); + if (!syncStateDb_.get(txn, db_name, value)) + return std::nullopt; + + mtx::secret_storage::AesHmacSha2EncryptedData data = nlohmann::json::parse(value); + + auto decrypted = mtx::crypto::decrypt(data, mtx::crypto::to_binary_buf(pickle_secret_), name_); + if (decrypted.empty()) return std::nullopt; + else + return decrypted; } void -Cache::storeSecret(const std::string name_, const std::string secret, bool internal) +Cache::storeSecret(const std::string &name_, const std::string &secret, bool internal) { auto name = secretName(name_, internal); - { - std::unique_lock lock(secret_storage.mtx); - secret_storage.secrets[name.toStdString()] = secret; - } + + auto txn = lmdb::txn::begin(env_); + + auto encrypted = + mtx::crypto::encrypt(secret, mtx::crypto::to_binary_buf(pickle_secret_), name_); + + auto db_name = "secret." + name.toStdString(); + syncStateDb_.put(txn, db_name, nlohmann::json(encrypted).dump()); + txn.commit(); + emit secretChanged(name_); +} + +void +Cache::deleteSecret(const std::string &name_, bool internal) +{ + auto name = secretName(name_, internal); + + auto txn = lmdb::txn::begin(env_); + std::string_view value; + auto db_name = "secret." + name.toStdString(); + syncStateDb_.del(txn, db_name, value); + txn.commit(); +} + +void +Cache::storeSecretInStore(const std::string name_, const std::string secret) +{ + auto name = secretName(name_, true); auto settings = UserSettings::instance()->qsettings(); if (settings->value(QStringLiteral("run_without_secure_secrets_service"), false).toBool()) { @@ -503,13 +542,9 @@ Cache::storeSecret(const std::string name_, const std::string secret, bool inter } void -Cache::deleteSecret(const std::string name, bool internal) +Cache::deleteSecretFromStore(const std::string name, bool internal) { auto name_ = secretName(name, internal); - { - std::unique_lock lock(secret_storage.mtx); - secret_storage.secrets.erase(name_.toStdString()); - } auto settings = UserSettings::instance()->qsettings(); if (settings->value(QStringLiteral("run_without_secure_secrets_service"), false).toBool()) { @@ -535,13 +570,8 @@ std::string Cache::pickleSecret() { if (pickle_secret_.empty()) { - auto s = secret("pickle_secret", true); - if (!s) { - this->pickle_secret_ = mtx::client::utils::random_token(64, true); - storeSecret("pickle_secret", pickle_secret_, true); - } else { - this->pickle_secret_ = *s; - } + this->pickle_secret_ = mtx::client::utils::random_token(64, true); + storeSecretInStore("pickle_secret", pickle_secret_); } return pickle_secret_; @@ -910,6 +940,29 @@ Cache::getMegolmSessionData(const MegolmSessionIndex &index) // void +Cache::saveOlmSessions(std::vector<std::pair<std::string, mtx::crypto::OlmSessionPtr>> sessions, + uint64_t timestamp) +{ + using namespace mtx::crypto; + + auto txn = lmdb::txn::begin(env_); + for (const auto &[curve25519, session] : sessions) { + auto db = getOlmSessionsDb(txn, curve25519); + + const auto pickled = pickle<SessionObject>(session.get(), pickle_secret_); + const auto session_id = mtx::crypto::session_id(session.get()); + + StoredOlmSession stored_session; + stored_session.pickled_session = pickled; + stored_session.last_message_ts = timestamp; + + db.put(txn, session_id, nlohmann::json(stored_session).dump()); + } + + txn.commit(); +} + +void Cache::saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session, uint64_t timestamp) @@ -936,19 +989,20 @@ Cache::getOlmSession(const std::string &curve25519, const std::string &session_i { using namespace mtx::crypto; - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); + try { + auto txn = ro_txn(env_); + auto db = getOlmSessionsDb(txn, curve25519); - std::string_view pickled; - bool found = db.get(txn, session_id, pickled); + std::string_view pickled; + bool found = db.get(txn, session_id, pickled); - txn.commit(); + if (found) { + auto data = nlohmann::json::parse(pickled).get<StoredOlmSession>(); + return unpickle<SessionObject>(data.pickled_session, pickle_secret_); + } - if (found) { - auto data = nlohmann::json::parse(pickled).get<StoredOlmSession>(); - return unpickle<SessionObject>(data.pickled_session, pickle_secret_); + } catch (...) { } - return std::nullopt; } @@ -957,26 +1011,28 @@ Cache::getLatestOlmSession(const std::string &curve25519) { using namespace mtx::crypto; - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); - - std::string_view session_id, pickled_session; + try { + auto txn = ro_txn(env_); + auto db = getOlmSessionsDb(txn, curve25519); - std::optional<StoredOlmSession> currentNewest; + std::string_view session_id, pickled_session; - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(session_id, pickled_session, MDB_NEXT)) { - auto data = nlohmann::json::parse(pickled_session).get<StoredOlmSession>(); - if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts) - currentNewest = data; - } - cursor.close(); + std::optional<StoredOlmSession> currentNewest; - txn.commit(); + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(session_id, pickled_session, MDB_NEXT)) { + auto data = nlohmann::json::parse(pickled_session).get<StoredOlmSession>(); + if (!currentNewest || currentNewest->last_message_ts < data.last_message_ts) + currentNewest = data; + } + cursor.close(); - return currentNewest ? std::optional(unpickle<SessionObject>(currentNewest->pickled_session, - pickle_secret_)) - : std::nullopt; + return currentNewest ? std::optional(unpickle<SessionObject>(currentNewest->pickled_session, + pickle_secret_)) + : std::nullopt; + } catch (...) { + return std::nullopt; + } } std::vector<std::string> @@ -984,20 +1040,22 @@ Cache::getOlmSessions(const std::string &curve25519) { using namespace mtx::crypto; - auto txn = lmdb::txn::begin(env_); - auto db = getOlmSessionsDb(txn, curve25519); - - std::string_view session_id, unused; - std::vector<std::string> res; + try { + auto txn = ro_txn(env_); + auto db = getOlmSessionsDb(txn, curve25519); - auto cursor = lmdb::cursor::open(txn, db); - while (cursor.get(session_id, unused, MDB_NEXT)) - res.emplace_back(session_id); - cursor.close(); + std::string_view session_id, unused; + std::vector<std::string> res; - txn.commit(); + auto cursor = lmdb::cursor::open(txn, db); + while (cursor.get(session_id, unused, MDB_NEXT)) + res.emplace_back(session_id); + cursor.close(); - return res; + return res; + } catch (...) { + return {}; + } } void @@ -1147,11 +1205,7 @@ Cache::deleteData() nhlog::db()->info("deleted cache files from disk"); } - deleteSecret(mtx::secret_storage::secrets::megolm_backup_v1); - deleteSecret(mtx::secret_storage::secrets::cross_signing_master); - deleteSecret(mtx::secret_storage::secrets::cross_signing_user_signing); - deleteSecret(mtx::secret_storage::secrets::cross_signing_self_signing); - deleteSecret("pickle_secret", true); + deleteSecretFromStore("pickle_secret", true); } } @@ -1360,7 +1414,8 @@ Cache::runMigrations() }}, {"2021.08.31", [this]() { - storeSecret("pickle_secret", "secret", true); + storeSecretInStore("pickle_secret", "secret"); + this->pickle_secret_ = "secret"; return true; }}, {"2022.04.08", @@ -1416,6 +1471,22 @@ Cache::runMigrations() return false; } }}, + {"2022.11.06", + [this]() { + loadSecretsFromStore( + { + {mtx::secret_storage::secrets::cross_signing_master, false}, + {mtx::secret_storage::secrets::cross_signing_self_signing, false}, + {mtx::secret_storage::secrets::cross_signing_user_signing, false}, + {mtx::secret_storage::secrets::megolm_backup_v1, false}, + }, + [this](const std::string &name, bool internal, const std::string &value) { + this->storeSecret(name, value, internal); + QTimer::singleShot( + 0, this, [this, name, internal] { deleteSecretFromStore(name, internal); }); + }); + return true; + }}, }; nhlog::db()->info("Running migrations, this may take a while!"); @@ -1599,7 +1670,7 @@ Cache::calculateRoomReadStatus(const std::string &room_id) } void -Cache::updateState(const std::string &room, const mtx::responses::StateEvents &state) +Cache::updateState(const std::string &room, const mtx::responses::StateEvents &state, bool wipe) { auto txn = lmdb::txn::begin(env_); auto statesdb = getStatesDb(txn, room); @@ -1607,6 +1678,12 @@ Cache::updateState(const std::string &room, const mtx::responses::StateEvents &s auto membersdb = getMembersDb(txn, room); auto eventsDb = getEventsDb(txn, room); + if (wipe) { + membersdb.drop(txn); + statesdb.drop(txn); + stateskeydb.drop(txn); + } + saveStateEvents(txn, statesdb, stateskeydb, membersdb, eventsDb, room, state.events); RoomInfo updatedInfo; @@ -1948,7 +2025,8 @@ Cache::saveInvite(lmdb::txn &txn, auto display_name = msg->content.display_name.empty() ? msg->state_key : msg->content.display_name; - MemberInfo tmp{display_name, msg->content.avatar_url, msg->content.is_direct}; + MemberInfo tmp{ + display_name, msg->content.avatar_url, msg->content.reason, msg->content.is_direct}; membersdb.put(txn, msg->state_key, nlohmann::json(tmp).dump()); } else { @@ -2144,18 +2222,22 @@ Cache::roomIds() std::string Cache::previousBatchToken(const std::string &room_id) { - auto txn = lmdb::txn::begin(env_, nullptr); - auto orderDb = getEventOrderDb(txn, room_id); + auto txn = ro_txn(env_); + try { + auto orderDb = getEventOrderDb(txn, room_id); - auto cursor = lmdb::cursor::open(txn, orderDb); - std::string_view indexVal, val; - if (!cursor.get(indexVal, val, MDB_FIRST)) { - return ""; - } + auto cursor = lmdb::cursor::open(txn, orderDb); + std::string_view indexVal, val; + if (!cursor.get(indexVal, val, MDB_FIRST)) { + return ""; + } - auto j = nlohmann::json::parse(val); + auto j = nlohmann::json::parse(val); - return j.value("prev_batch", ""); + return j.value("prev_batch", ""); + } catch (...) { + return ""; + } } Cache::Messages @@ -3070,6 +3152,29 @@ Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_ } } +std::optional<MemberInfo> +Cache::getInviteMember(const std::string &room_id, const std::string &user_id) +{ + if (user_id.empty() || !env_.handle()) + return std::nullopt; + + try { + auto txn = ro_txn(env_); + + auto membersdb = getInviteMembersDb(txn, room_id); + + std::string_view info; + if (membersdb.get(txn, user_id, info)) { + MemberInfo m = nlohmann::json::parse(info).get<MemberInfo>(); + return m; + } + } catch (std::exception &e) { + nhlog::db()->warn( + "Failed to read member ({}) in invite room ({}): {}", user_id, room_id, e.what()); + } + return std::nullopt; +} + std::vector<RoomMember> Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len) { @@ -3180,7 +3285,7 @@ Cache::firstPendingMessage(const std::string &room_id) auto txn = lmdb::txn::begin(env_); auto pending = getPendingMessagesDb(txn, room_id); - { + try { auto pendingCursor = lmdb::cursor::open(txn, pending); std::string_view tsIgnored, pendingTxn; while (pendingCursor.get(tsIgnored, pendingTxn, MDB_NEXT)) { @@ -3196,7 +3301,6 @@ Cache::firstPendingMessage(const std::string &room_id) from_json(nlohmann::json::parse(event), te); pendingCursor.close(); - txn.commit(); return te; } catch (std::exception &e) { nhlog::db()->error("Failed to parse message from cache {}", e.what()); @@ -3204,10 +3308,8 @@ Cache::firstPendingMessage(const std::string &room_id) continue; } } + } catch (const lmdb::error &e) { } - - txn.commit(); - return std::nullopt; } @@ -3777,9 +3879,9 @@ Cache::spaces() std::string_view room_data; if (roomsDb_.get(txn, space_id, room_data)) { RoomInfo tmp = nlohmann::json::parse(std::move(room_data)).get<RoomInfo>(); - ret.insert(QString::fromUtf8(space_id.data(), (qsizetype)space_id.size()), tmp); + ret.insert(QString::fromUtf8(space_id.data(), (int)space_id.size()), tmp); } else { - ret.insert(QString::fromUtf8(space_id.data(), (qsizetype)space_id.size()), + ret.insert(QString::fromUtf8(space_id.data(), (int)space_id.size()), std::nullopt); } } @@ -3969,33 +4071,36 @@ Cache::hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes using namespace mtx::events; using namespace mtx::events::state; - auto txn = lmdb::txn::begin(env_); - auto db = getStatesDb(txn, room_id); + auto txn = ro_txn(env_); + try { + auto db = getStatesDb(txn, room_id); - int64_t min_event_level = std::numeric_limits<int64_t>::max(); - int64_t user_level = std::numeric_limits<int64_t>::min(); + int64_t min_event_level = std::numeric_limits<int64_t>::max(); + int64_t user_level = std::numeric_limits<int64_t>::min(); - std::string_view event; - bool res = db.get(txn, to_string(EventType::RoomPowerLevels), event); + std::string_view event; + bool res = db.get(txn, to_string(EventType::RoomPowerLevels), event); - if (res) { - try { - StateEvent<PowerLevels> msg = - nlohmann::json::parse(std::string_view(event.data(), event.size())) - .get<StateEvent<PowerLevels>>(); + if (res) { + try { + StateEvent<PowerLevels> msg = + nlohmann::json::parse(std::string_view(event.data(), event.size())) + .get<StateEvent<PowerLevels>>(); - user_level = msg.content.user_level(user_id); + user_level = msg.content.user_level(user_id); - for (const auto &ty : eventTypes) - min_event_level = std::min(min_event_level, msg.content.state_level(to_string(ty))); - } catch (const nlohmann::json::exception &e) { - nhlog::db()->warn("failed to parse m.room.power_levels event: {}", e.what()); + for (const auto &ty : eventTypes) + min_event_level = + std::min(min_event_level, msg.content.state_level(to_string(ty))); + } catch (const nlohmann::json::exception &e) { + nhlog::db()->warn("failed to parse m.room.power_levels event: {}", e.what()); + } } - } - txn.commit(); - - return user_level >= min_event_level; + return user_level >= min_event_level; + } catch (...) { + return false; + } } std::vector<std::string> @@ -4885,6 +4990,8 @@ to_json(nlohmann::json &j, const MemberInfo &info) j["avatar_url"] = info.avatar_url; if (info.is_direct) j["is_direct"] = info.is_direct; + if (!info.reason.empty()) + j["reason"] = info.reason; } void @@ -4893,6 +5000,7 @@ from_json(const nlohmann::json &j, MemberInfo &info) info.name = j.at("name").get<std::string>(); info.avatar_url = j.at("avatar_url").get<std::string>(); info.is_direct = j.value("is_direct", false); + info.reason = j.value("reason", ""); } void @@ -4938,7 +5046,7 @@ to_json(nlohmann::json &obj, const GroupSessionData &msg) void from_json(const nlohmann::json &obj, GroupSessionData &msg) { - msg.message_index = obj.at("message_index").get<uint64_t>(); + msg.message_index = obj.at("message_index").get<uint32_t>(); msg.timestamp = obj.value("ts", 0ULL); msg.trusted = obj.value("trust", true);