From a762cef0a93661fe6563c89da8f0cdfca713cdbb Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Sat, 9 May 2015 19:47:53 +0200 Subject: [PATCH 01/20] Catch and handle all errors from libgcrypt. --- src/core/Database.cpp | 31 ++++-- src/core/Database.h | 7 +- src/crypto/Crypto.cpp | 148 +++++++++++++++++++------- src/crypto/Crypto.h | 5 + src/crypto/SymmetricCipher.cpp | 37 ++++++- src/crypto/SymmetricCipher.h | 22 ++-- src/crypto/SymmetricCipherBackend.h | 15 +-- src/crypto/SymmetricCipherGcrypt.cpp | 119 ++++++++++++++++----- src/crypto/SymmetricCipherGcrypt.h | 18 ++-- src/crypto/SymmetricCipherSalsa20.h | 13 +-- src/format/KeePass1Reader.cpp | 38 +++++-- src/format/KeePass2RandomStream.cpp | 54 ++++++++-- src/format/KeePass2RandomStream.h | 12 ++- src/format/KeePass2Reader.cpp | 38 +++++-- src/format/KeePass2Writer.cpp | 28 ++++- src/format/KeePass2XmlReader.cpp | 11 +- src/format/KeePass2XmlWriter.cpp | 23 +++- src/format/KeePass2XmlWriter.h | 6 +- src/gui/DatabaseWidget.cpp | 7 +- src/keys/CompositeKey.cpp | 43 ++++++-- src/keys/CompositeKey.h | 5 +- src/streams/LayeredStream.cpp | 5 - src/streams/LayeredStream.h | 1 - src/streams/StoreDataStream.cpp | 4 + src/streams/SymmetricCipherStream.cpp | 36 ++++++- src/streams/SymmetricCipherStream.h | 11 +- tests/TestKeePass2RandomStream.cpp | 27 +++-- tests/TestKeys.cpp | 15 ++- tests/TestSymmetricCipher.cpp | 37 ++++--- 29 files changed, 622 insertions(+), 194 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 4c888eab9..098cc06af 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -188,32 +188,51 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo) m_data.compressionAlgo = algo; } -void Database::setTransformRounds(quint64 rounds) +bool Database::setTransformRounds(quint64 rounds) { if (m_data.transformRounds != rounds) { + quint64 oldRounds = m_data.transformRounds; + m_data.transformRounds = rounds; if (m_data.hasKey) { - setKey(m_data.key); + if (!setKey(m_data.key)) { + m_data.transformRounds = oldRounds; + return false; + } } } + + return true; } -void Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime) +bool Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, + bool updateChangedTime) { + bool ok; + QString errorString; + + QByteArray transformedMasterKey = + key.transform(transformSeed, transformRounds(), &ok, &errorString); + if (!ok) { + return false; + } + m_data.key = key; m_data.transformSeed = transformSeed; - m_data.transformedMasterKey = key.transform(transformSeed, transformRounds()); + m_data.transformedMasterKey = transformedMasterKey; m_data.hasKey = true; if (updateChangedTime) { m_metadata->setMasterKeyChanged(Tools::currentDateTimeUtc()); } Q_EMIT modifiedImmediate(); + + return true; } -void Database::setKey(const CompositeKey& key) +bool Database::setKey(const CompositeKey& key) { - setKey(key, randomGen()->randomArray(32)); + return setKey(key, randomGen()->randomArray(32)); } bool Database::hasKey() const diff --git a/src/core/Database.h b/src/core/Database.h index 3e0c675df..97ccad234 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -90,13 +90,14 @@ public: void setCipher(const Uuid& cipher); void setCompressionAlgo(Database::CompressionAlgorithm algo); - void setTransformRounds(quint64 rounds); - void setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime = true); + bool setTransformRounds(quint64 rounds); + bool setKey(const CompositeKey& key, const QByteArray& transformSeed, + bool updateChangedTime = true); /** * Sets the database key and generates a random transform seed. */ - void setKey(const CompositeKey& key); + bool setKey(const CompositeKey& key); bool hasKey() const; bool verifyKey(const CompositeKey& key) const; void recycleEntry(Entry* entry); diff --git a/src/crypto/Crypto.cpp b/src/crypto/Crypto.cpp index bf3076e85..d4fc6493e 100644 --- a/src/crypto/Crypto.cpp +++ b/src/crypto/Crypto.cpp @@ -142,66 +142,138 @@ bool Crypto::checkAlgorithms() } bool Crypto::selfTest() +{ + return testSha256() && testAes256() && testTwofish() && testSalsa20(); +} + +void Crypto::raiseError(const QString& str) +{ + m_errorStr = str; + qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); +} + +bool Crypto::testSha256() { QByteArray sha256Test = CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", CryptoHash::Sha256); if (sha256Test != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) { - m_errorStr = "SHA-256 mismatch."; - qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); + raiseError("SHA-256 mismatch."); return false; } + return true; +} + +bool Crypto::testAes256() +{ QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d")); + bool ok; - SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, key, iv); - if (aes256Encrypt.process(plainText) != cipherText) { - m_errorStr = "AES-256 encryption mismatch."; - qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); + SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); + if (!aes256Encrypt.init(key, iv)) { + raiseError(aes256Encrypt.errorString()); + return false; + } + QByteArray encryptedText = aes256Encrypt.process(plainText, &ok); + if (!ok) { + raiseError(aes256Encrypt.errorString()); + return false; + } + if (encryptedText != cipherText) { + raiseError("AES-256 encryption mismatch."); return false; } - SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv); - if (aes256Descrypt.process(cipherText) != plainText) { - m_errorStr = "AES-256 decryption mismatch."; - qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); + SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); + if (!aes256Descrypt.init(key, iv)) { + raiseError(aes256Descrypt.errorString()); return false; } - - // Twofish - cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c"); - cipherText.append(QByteArray::fromHex("657b1e7960b30fb7c8d62e72ae37c3a0")); - - SymmetricCipher twofishEncrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, key, iv); - if (twofishEncrypt.process(plainText) != cipherText) { - m_errorStr = "Twofish encryption mismatch."; - qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); + QByteArray decryptedText = aes256Descrypt.process(cipherText, &ok); + if (!ok) { + raiseError(aes256Descrypt.errorString()); return false; } - - SymmetricCipher twofishDecrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv); - if (twofishDecrypt.process(cipherText) != plainText) { - m_errorStr = "Twofish decryption mismatch."; - qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); - return false; - } - - QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112"); - QByteArray salsa20iv = QByteArray::fromHex("0000000000000000"); - QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000"); - QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F"); - - SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream, - SymmetricCipher::Encrypt, salsa20Key, salsa20iv); - - if (salsa20Stream.process(salsa20Plain) != salsa20Cipher) { - m_errorStr = "Salsa20 stream cipher mismatch."; - qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr)); + if (decryptedText != plainText) { + raiseError("AES-256 decryption mismatch."); + return false; + } + + return true; +} + +bool Crypto::testTwofish() +{ + QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); + QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); + QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); + plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); + QByteArray cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c"); + cipherText.append(QByteArray::fromHex("657b1e7960b30fb7c8d62e72ae37c3a0")); + bool ok; + + SymmetricCipher twofishEncrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); + if (!twofishEncrypt.init(key, iv)) { + raiseError(twofishEncrypt.errorString()); + return false; + } + QByteArray encryptedText = twofishEncrypt.process(plainText, &ok); + if (!ok) { + raiseError(twofishEncrypt.errorString()); + return false; + } + if (encryptedText != cipherText) { + raiseError("Twofish encryption mismatch."); + return false; + } + + + SymmetricCipher twofishDecrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); + if (!twofishDecrypt.init(key, iv)) { + raiseError(twofishEncrypt.errorString()); + return false; + } + QByteArray decryptedText = twofishDecrypt.process(cipherText, &ok); + if (!ok) { + raiseError(twofishDecrypt.errorString()); + return false; + } + if (decryptedText != plainText) { + raiseError("Twofish encryption mismatch."); + return false; + } + + return true; +} + +bool Crypto::testSalsa20() +{ + QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112"); + QByteArray salsa20iv = QByteArray::fromHex("0000000000000000"); + QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000"); + QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F"); + bool ok; + + SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream, + SymmetricCipher::Encrypt); + if (!salsa20Stream.init(salsa20Key, salsa20iv)) { + raiseError(salsa20Stream.errorString()); + return false; + } + + QByteArray salsaProcessed = salsa20Stream.process(salsa20Plain, &ok); + if (!ok) { + raiseError(salsa20Stream.errorString()); + return false; + } + if (salsaProcessed != salsa20Cipher) { + raiseError("Salsa20 stream cipher mismatch."); return false; } diff --git a/src/crypto/Crypto.h b/src/crypto/Crypto.h index 9926f14b2..07f6454fe 100644 --- a/src/crypto/Crypto.h +++ b/src/crypto/Crypto.h @@ -34,6 +34,11 @@ private: Crypto(); static bool checkAlgorithms(); static bool selfTest(); + static void raiseError(const QString& str); + static bool testSha256(); + static bool testAes256(); + static bool testTwofish(); + static bool testSalsa20(); static bool m_initalized; static QString m_errorStr; diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp index 5f9222653..454548caa 100644 --- a/src/crypto/SymmetricCipher.cpp +++ b/src/crypto/SymmetricCipher.cpp @@ -22,17 +22,39 @@ #include "crypto/SymmetricCipherSalsa20.h" SymmetricCipher::SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, - SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv) + SymmetricCipher::Direction direction) : m_backend(createBackend(algo, mode, direction)) + , m_initialized(false) { - m_backend->setKey(key); - m_backend->setIv(iv); } SymmetricCipher::~SymmetricCipher() { } +bool SymmetricCipher::init(const QByteArray& key, const QByteArray& iv) +{ + if (!m_backend->init()) { + return false; + } + + if (!m_backend->setKey(key)) { + return false; + } + + if (!m_backend->setIv(iv)) { + return false; + } + + m_initialized = true; + return true; +} + +bool SymmetricCipher::isInitalized() const +{ + return m_initialized; +} + SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, SymmetricCipher::Direction direction) { @@ -55,12 +77,17 @@ SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorith } } -void SymmetricCipher::reset() +bool SymmetricCipher::reset() { - m_backend->reset(); + return m_backend->reset(); } int SymmetricCipher::blockSize() const { return m_backend->blockSize(); } + +QString SymmetricCipher::errorString() const +{ + return m_backend->errorString(); +} diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h index d036e532e..65cab76fa 100644 --- a/src/crypto/SymmetricCipher.h +++ b/src/crypto/SymmetricCipher.h @@ -20,6 +20,7 @@ #include #include +#include #include "core/Global.h" #include "crypto/SymmetricCipherBackend.h" @@ -48,30 +49,35 @@ public: }; SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, - SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv); + SymmetricCipher::Direction direction); ~SymmetricCipher(); - inline QByteArray process(const QByteArray& data) { - return m_backend->process(data); + bool init(const QByteArray& key, const QByteArray& iv); + bool isInitalized() const; + + inline QByteArray process(const QByteArray& data, bool* ok) { + return m_backend->process(data, ok); } - inline void processInPlace(QByteArray& data) { - m_backend->processInPlace(data); + inline bool processInPlace(QByteArray& data) { + return m_backend->processInPlace(data); } - inline void processInPlace(QByteArray& data, quint64 rounds) { + inline bool processInPlace(QByteArray& data, quint64 rounds) { Q_ASSERT(rounds > 0); - m_backend->processInPlace(data, rounds); + return m_backend->processInPlace(data, rounds); } - void reset(); + bool reset(); int blockSize() const; + QString errorString() const; private: static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, SymmetricCipher::Direction direction); const QScopedPointer m_backend; + bool m_initialized; Q_DISABLE_COPY(SymmetricCipher) }; diff --git a/src/crypto/SymmetricCipherBackend.h b/src/crypto/SymmetricCipherBackend.h index e6110cba5..763ffb40d 100644 --- a/src/crypto/SymmetricCipherBackend.h +++ b/src/crypto/SymmetricCipherBackend.h @@ -24,15 +24,18 @@ class SymmetricCipherBackend { public: virtual ~SymmetricCipherBackend() {} - virtual void setKey(const QByteArray& key) = 0; - virtual void setIv(const QByteArray& iv) = 0; + virtual bool init() = 0; + virtual bool setKey(const QByteArray& key) = 0; + virtual bool setIv(const QByteArray& iv) = 0; - virtual QByteArray process(const QByteArray& data) = 0; - virtual void processInPlace(QByteArray& data) = 0; - virtual void processInPlace(QByteArray& data, quint64 rounds) = 0; + virtual QByteArray process(const QByteArray& data, bool* ok) = 0; + virtual bool processInPlace(QByteArray& data) = 0; + virtual bool processInPlace(QByteArray& data, quint64 rounds) = 0; - virtual void reset() = 0; + virtual bool reset() = 0; virtual int blockSize() const = 0; + + virtual QString errorString() const = 0; }; #endif // KEEPASSX_SYMMETRICCIPHERBACKEND_H diff --git a/src/crypto/SymmetricCipherGcrypt.cpp b/src/crypto/SymmetricCipherGcrypt.cpp index cf52bafab..5c4fe6ab3 100644 --- a/src/crypto/SymmetricCipherGcrypt.cpp +++ b/src/crypto/SymmetricCipherGcrypt.cpp @@ -22,22 +22,12 @@ SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, SymmetricCipher::Direction direction) - : m_algo(gcryptAlgo(algo)) + : m_ctx(Q_NULLPTR) + , m_algo(gcryptAlgo(algo)) , m_mode(gcryptMode(mode)) , m_direction(direction) , m_blockSize(-1) { - Q_ASSERT(Crypto::initalized()); - - gcry_error_t error; - - error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0); - Q_ASSERT(error == 0); // TODO: real error checking - - size_t blockSizeT; - error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, Q_NULLPTR, &blockSizeT); - Q_ASSERT(error == 0); - m_blockSize = blockSizeT; } SymmetricCipherGcrypt::~SymmetricCipherGcrypt() @@ -83,21 +73,65 @@ int SymmetricCipherGcrypt::gcryptMode(SymmetricCipher::Mode mode) } } -void SymmetricCipherGcrypt::setKey(const QByteArray& key) +void SymmetricCipherGcrypt::setErrorString(gcry_error_t err) +{ + const char* gcryptError = gcry_strerror(err); + const char* gcryptErrorSource = gcry_strsource(err); + + m_errorString = QString("%1/%2").arg(QString::fromLocal8Bit(gcryptErrorSource), + QString::fromLocal8Bit(gcryptError)); +} + +bool SymmetricCipherGcrypt::init() +{ + Q_ASSERT(Crypto::initalized()); + + gcry_error_t error; + + error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0); + if (error != 0) { + setErrorString(error); + return false; + } + + size_t blockSizeT; + error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, Q_NULLPTR, &blockSizeT); + if (error != 0) { + setErrorString(error); + return false; + } + + m_blockSize = blockSizeT; + return true; +} + +bool SymmetricCipherGcrypt::setKey(const QByteArray& key) { m_key = key; gcry_error_t error = gcry_cipher_setkey(m_ctx, m_key.constData(), m_key.size()); - Q_ASSERT(error == 0); + + if (error != 0) { + setErrorString(error); + return false; + } + + return true; } -void SymmetricCipherGcrypt::setIv(const QByteArray& iv) +bool SymmetricCipherGcrypt::setIv(const QByteArray& iv) { m_iv = iv; gcry_error_t error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size()); - Q_ASSERT(error == 0); + + if (error != 0) { + setErrorString(error); + return false; + } + + return true; } -QByteArray SymmetricCipherGcrypt::process(const QByteArray& data) +QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok) { // TODO: check block size @@ -113,12 +147,16 @@ QByteArray SymmetricCipherGcrypt::process(const QByteArray& data) error = gcry_cipher_encrypt(m_ctx, result.data(), data.size(), data.constData(), data.size()); } - Q_ASSERT(error == 0); + if (error != 0) { + setErrorString(error); + *ok = false; + } + *ok = true; return result; } -void SymmetricCipherGcrypt::processInPlace(QByteArray& data) +bool SymmetricCipherGcrypt::processInPlace(QByteArray& data) { // TODO: check block size @@ -131,10 +169,15 @@ void SymmetricCipherGcrypt::processInPlace(QByteArray& data) error = gcry_cipher_encrypt(m_ctx, data.data(), data.size(), Q_NULLPTR, 0); } - Q_ASSERT(error == 0); + if (error != 0) { + setErrorString(error); + return false; + } + + return true; } -void SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds) +bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds) { // TODO: check block size @@ -146,28 +189,52 @@ void SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds) if (m_direction == SymmetricCipher::Decrypt) { for (quint64 i = 0; i != rounds; ++i) { error = gcry_cipher_decrypt(m_ctx, rawData, size, Q_NULLPTR, 0); - Q_ASSERT(error == 0); + + if (error != 0) { + setErrorString(error); + return false; + } } } else { for (quint64 i = 0; i != rounds; ++i) { error = gcry_cipher_encrypt(m_ctx, rawData, size, Q_NULLPTR, 0); - Q_ASSERT(error == 0); + + if (error != 0) { + setErrorString(error); + return false; + } } } + + return true; } -void SymmetricCipherGcrypt::reset() +bool SymmetricCipherGcrypt::reset() { gcry_error_t error; error = gcry_cipher_reset(m_ctx); - Q_ASSERT(error == 0); + if (error != 0) { + setErrorString(error); + return false; + } + error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size()); - Q_ASSERT(error == 0); + if (error != 0) { + setErrorString(error); + return false; + } + + return true; } int SymmetricCipherGcrypt::blockSize() const { return m_blockSize; } + +QString SymmetricCipherGcrypt::errorString() const +{ + return m_errorString; +} diff --git a/src/crypto/SymmetricCipherGcrypt.h b/src/crypto/SymmetricCipherGcrypt.h index c22059a4e..bc1560097 100644 --- a/src/crypto/SymmetricCipherGcrypt.h +++ b/src/crypto/SymmetricCipherGcrypt.h @@ -29,19 +29,24 @@ public: SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, SymmetricCipher::Direction direction); ~SymmetricCipherGcrypt(); - void setKey(const QByteArray& key); - void setIv(const QByteArray& iv); - QByteArray process(const QByteArray& data); - void processInPlace(QByteArray& data); - void processInPlace(QByteArray& data, quint64 rounds); + bool init(); + bool setKey(const QByteArray& key); + bool setIv(const QByteArray& iv); - void reset(); + QByteArray process(const QByteArray& data, bool* ok); + bool processInPlace(QByteArray& data); + bool processInPlace(QByteArray& data, quint64 rounds); + + bool reset(); int blockSize() const; + QString errorString() const; + private: static int gcryptAlgo(SymmetricCipher::Algorithm algo); static int gcryptMode(SymmetricCipher::Mode mode); + void setErrorString(gcry_error_t err); gcry_cipher_hd_t m_ctx; const int m_algo; @@ -50,6 +55,7 @@ private: QByteArray m_key; QByteArray m_iv; int m_blockSize; + QString m_errorString; }; #endif // KEEPASSX_SYMMETRICCIPHERGCRYPT_H diff --git a/src/crypto/SymmetricCipherSalsa20.h b/src/crypto/SymmetricCipherSalsa20.h index e3e4726f8..6a5c24562 100644 --- a/src/crypto/SymmetricCipherSalsa20.h +++ b/src/crypto/SymmetricCipherSalsa20.h @@ -28,17 +28,18 @@ public: SymmetricCipherSalsa20(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, SymmetricCipher::Direction direction); ~SymmetricCipherSalsa20(); + bool init(); void setAlgorithm(SymmetricCipher::Algorithm algo); void setMode(SymmetricCipher::Mode mode); void setDirection(SymmetricCipher::Direction direction); - void setKey(const QByteArray& key); - void setIv(const QByteArray& iv); + bool setKey(const QByteArray& key); + bool setIv(const QByteArray& iv); - QByteArray process(const QByteArray& data); - void processInPlace(QByteArray& data); - void processInPlace(QByteArray& data, quint64 rounds); + QByteArray process(const QByteArray& data, bool* ok); + bool processInPlace(QByteArray& data); + bool processInPlace(QByteArray& data, quint64 rounds); - void reset(); + bool reset(); int blockSize() const; private: diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp index d4760b9e7..a15c832b3 100644 --- a/src/format/KeePass1Reader.cpp +++ b/src/format/KeePass1Reader.cpp @@ -154,14 +154,16 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor raiseError("Invalid number of transform rounds"); return Q_NULLPTR; } - m_db->setTransformRounds(m_transformRounds); + if (!m_db->setTransformRounds(m_transformRounds)) { + raiseError(tr("Unable to calculate master key")); + return Q_NULLPTR; + } qint64 contentPos = m_device->pos(); QScopedPointer cipherStream(testKeys(password, keyfileData, contentPos)); if (!cipherStream) { - raiseError("Unable to create cipher stream"); return Q_NULLPTR; } @@ -234,7 +236,10 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor key.addKey(newFileKey); } - db->setKey(key); + if (!db->setKey(key)) { + raiseError(tr("Unable to calculate master key")); + return Q_NULLPTR; + } return db.take(); } @@ -326,16 +331,26 @@ SymmetricCipherStream* KeePass1Reader::testKeys(const QString& password, const Q } QByteArray finalKey = key(passwordData, keyfileData); + if (finalKey.isEmpty()) { + return Q_NULLPTR; + } if (m_encryptionFlags & KeePass1::Rijndael) { cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Aes256, - SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)); + SymmetricCipher::Cbc, SymmetricCipher::Decrypt)); } else { cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Twofish, - SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)); + SymmetricCipher::Cbc, SymmetricCipher::Decrypt)); } - cipherStream->open(QIODevice::ReadOnly); + if (!cipherStream->init(finalKey, m_encryptionIV)) { + raiseError(cipherStream->errorString()); + return Q_NULLPTR; + } + if (!cipherStream->open(QIODevice::ReadOnly)) { + raiseError(cipherStream->errorString()); + return Q_NULLPTR; + } bool success = verifyKey(cipherStream.data()); @@ -372,9 +387,18 @@ QByteArray KeePass1Reader::key(const QByteArray& password, const QByteArray& key key.setPassword(password); key.setKeyfileData(keyfileData); + bool ok; + QString errorString; + QByteArray transformedKey = key.transform(m_transformSeed, m_transformRounds, &ok, &errorString); + + if (!ok) { + raiseError(errorString); + return QByteArray(); + } + CryptoHash hash(CryptoHash::Sha256); hash.addData(m_masterSeed); - hash.addData(key.transform(m_transformSeed, m_transformRounds)); + hash.addData(transformedKey); return hash.result(); } diff --git a/src/format/KeePass2RandomStream.cpp b/src/format/KeePass2RandomStream.cpp index 8b4a7c8e2..1944e5d5b 100644 --- a/src/format/KeePass2RandomStream.cpp +++ b/src/format/KeePass2RandomStream.cpp @@ -20,14 +20,19 @@ #include "crypto/CryptoHash.h" #include "format/KeePass2.h" -KeePass2RandomStream::KeePass2RandomStream(const QByteArray& key) - : m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt, - CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV) +KeePass2RandomStream::KeePass2RandomStream() + : m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt) , m_offset(0) { } -QByteArray KeePass2RandomStream::randomBytes(int size) +bool KeePass2RandomStream::init(const QByteArray& key) +{ + return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), + KeePass2::INNER_STREAM_SALSA20_IV); +} + +QByteArray KeePass2RandomStream::randomBytes(int size, bool* ok) { QByteArray result; @@ -35,7 +40,10 @@ QByteArray KeePass2RandomStream::randomBytes(int size) while (bytesRemaining > 0) { if (m_buffer.size() == m_offset) { - loadBlock(); + if (!loadBlock()) { + *ok = false; + return QByteArray(); + } } int bytesToCopy = qMin(bytesRemaining, m_buffer.size() - m_offset); @@ -44,12 +52,20 @@ QByteArray KeePass2RandomStream::randomBytes(int size) bytesRemaining -= bytesToCopy; } + *ok = true; return result; } -QByteArray KeePass2RandomStream::process(const QByteArray& data) +QByteArray KeePass2RandomStream::process(const QByteArray& data, bool* ok) { - QByteArray randomData = randomBytes(data.size()); + bool randomBytesOk; + + QByteArray randomData = randomBytes(data.size(), &randomBytesOk); + if (!randomBytesOk) { + *ok = false; + return QByteArray(); + } + QByteArray result; result.resize(data.size()); @@ -57,23 +73,39 @@ QByteArray KeePass2RandomStream::process(const QByteArray& data) result[i] = data[i] ^ randomData[i]; } + *ok = true; return result; } -void KeePass2RandomStream::processInPlace(QByteArray& data) +bool KeePass2RandomStream::processInPlace(QByteArray& data) { - QByteArray randomData = randomBytes(data.size()); + bool ok; + QByteArray randomData = randomBytes(data.size(), &ok); + if (!ok) { + return false; + } for (int i = 0; i < data.size(); i++) { data[i] = data[i] ^ randomData[i]; } + + return true; } -void KeePass2RandomStream::loadBlock() +QString KeePass2RandomStream::errorString() const +{ + return m_cipher.errorString(); +} + +bool KeePass2RandomStream::loadBlock() { Q_ASSERT(m_offset == m_buffer.size()); m_buffer.fill('\0', m_cipher.blockSize()); - m_cipher.processInPlace(m_buffer); + if (!m_cipher.processInPlace(m_buffer)) { + return false; + } m_offset = 0; + + return true; } diff --git a/src/format/KeePass2RandomStream.h b/src/format/KeePass2RandomStream.h index c5bcf8cdb..c951a95ad 100644 --- a/src/format/KeePass2RandomStream.h +++ b/src/format/KeePass2RandomStream.h @@ -25,13 +25,15 @@ class KeePass2RandomStream { public: - explicit KeePass2RandomStream(const QByteArray& key); - QByteArray randomBytes(int size); - QByteArray process(const QByteArray& data); - void processInPlace(QByteArray& data); + KeePass2RandomStream(); + bool init(const QByteArray& key); + QByteArray randomBytes(int size, bool* ok); + QByteArray process(const QByteArray& data, bool* ok); + bool processInPlace(QByteArray& data); + QString errorString() const; private: - void loadBlock(); + bool loadBlock(); SymmetricCipher m_cipher; QByteArray m_buffer; diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 72b4d8e06..20d20d862 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -96,16 +96,26 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke return Q_NULLPTR; } - m_db->setKey(key, m_transformSeed, false); + if (!m_db->setKey(key, m_transformSeed, false)) { + raiseError(tr("Unable to calculate master key")); + return Q_NULLPTR; + } CryptoHash hash(CryptoHash::Sha256); hash.addData(m_masterSeed); hash.addData(m_db->transformedMasterKey()); QByteArray finalKey = hash.result(); - SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256, SymmetricCipher::Cbc, - SymmetricCipher::Decrypt, finalKey, m_encryptionIV); - cipherStream.open(QIODevice::ReadOnly); + SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256, + SymmetricCipher::Cbc, SymmetricCipher::Decrypt); + if (!cipherStream.init(finalKey, m_encryptionIV)) { + raiseError(cipherStream.errorString()); + return Q_NULLPTR; + } + if (!cipherStream.open(QIODevice::ReadOnly)) { + raiseError(cipherStream.errorString()); + return Q_NULLPTR; + } QByteArray realStart = cipherStream.read(32); @@ -115,7 +125,10 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke } HashedBlockStream hashedStream(&cipherStream); - hashedStream.open(QIODevice::ReadOnly); + if (!hashedStream.open(QIODevice::ReadOnly)) { + raiseError(hashedStream.errorString()); + return Q_NULLPTR; + } QIODevice* xmlDevice; QScopedPointer ioCompressor; @@ -126,11 +139,18 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke else { ioCompressor.reset(new QtIOCompressor(&hashedStream)); ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); - ioCompressor->open(QIODevice::ReadOnly); + if (!ioCompressor->open(QIODevice::ReadOnly)) { + raiseError(ioCompressor->errorString()); + return Q_NULLPTR; + } xmlDevice = ioCompressor.data(); } - KeePass2RandomStream randomStream(m_protectedStreamKey); + KeePass2RandomStream randomStream; + if (!randomStream.init(m_protectedStreamKey)) { + raiseError(randomStream.errorString()); + return Q_NULLPTR; + } QScopedPointer buffer; @@ -340,7 +360,9 @@ void KeePass2Reader::setTansformRounds(const QByteArray& data) raiseError("Invalid transform rounds size"); } else { - m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER)); + if (!m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER))) { + raiseError(tr("Unable to calculate master key")); + } } } diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index 69e24746d..f233ac733 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -88,13 +88,20 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) CHECK_RETURN(writeData(header.data())); SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc, - SymmetricCipher::Encrypt, finalKey, encryptionIV); - cipherStream.open(QIODevice::WriteOnly); + SymmetricCipher::Encrypt); + cipherStream.init(finalKey, encryptionIV); + if (!cipherStream.open(QIODevice::WriteOnly)) { + raiseError(cipherStream.errorString()); + return; + } m_device = &cipherStream; CHECK_RETURN(writeData(startBytes)); HashedBlockStream hashedStream(&cipherStream); - hashedStream.open(QIODevice::WriteOnly); + if (!hashedStream.open(QIODevice::WriteOnly)) { + raiseError(hashedStream.errorString()); + return; + } QScopedPointer ioCompressor; @@ -104,14 +111,25 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) else { ioCompressor.reset(new QtIOCompressor(&hashedStream)); ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); - ioCompressor->open(QIODevice::WriteOnly); + if (!ioCompressor->open(QIODevice::WriteOnly)) { + raiseError(ioCompressor->errorString()); + return; + } m_device = ioCompressor.data(); } - KeePass2RandomStream randomStream(protectedStreamKey); + KeePass2RandomStream randomStream; + if (!randomStream.init(protectedStreamKey)) { + raiseError(randomStream.errorString()); + return; + } KeePass2XmlWriter xmlWriter; xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash); + + if (xmlWriter.hasError()) { + raiseError(xmlWriter.errorString()); + } } bool KeePass2Writer::writeData(const QByteArray& data) diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp index a5ceb2186..c38448861 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/KeePass2XmlReader.cpp @@ -809,7 +809,16 @@ void KeePass2XmlReader::parseEntryString(Entry* entry) if (isProtected && !value.isEmpty()) { if (m_randomStream) { - value = QString::fromUtf8(m_randomStream->process(QByteArray::fromBase64(value.toLatin1()))); + QByteArray ciphertext = QByteArray::fromBase64(value.toLatin1()); + bool ok; + QByteArray plaintext = m_randomStream->process(ciphertext, &ok); + if (!ok) { + value.clear(); + raiseError(m_randomStream->errorString()); + } + else { + value = QString::fromUtf8(plaintext); + } } else { raiseError("Unable to decrypt entry string"); diff --git a/src/format/KeePass2XmlWriter.cpp b/src/format/KeePass2XmlWriter.cpp index 603649812..dda98011c 100644 --- a/src/format/KeePass2XmlWriter.cpp +++ b/src/format/KeePass2XmlWriter.cpp @@ -28,6 +28,7 @@ KeePass2XmlWriter::KeePass2XmlWriter() : m_db(Q_NULLPTR) , m_meta(Q_NULLPTR) , m_randomStream(Q_NULLPTR) + , m_error(false) { m_xml.setAutoFormatting(true); m_xml.setAutoFormattingIndent(-1); // 1 tab @@ -65,6 +66,16 @@ void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db) writeDatabase(&file, db); } +bool KeePass2XmlWriter::hasError() +{ + return m_error; +} + +QString KeePass2XmlWriter::errorString() +{ + return m_errorStr; +} + void KeePass2XmlWriter::generateIdMap() { QList allEntries = m_db->rootGroup()->entriesRecursive(true); @@ -340,7 +351,11 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry) if (protect) { if (m_randomStream) { m_xml.writeAttribute("Protected", "True"); - QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8()); + bool ok; + QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8(), &ok); + if (!ok) { + raiseError(m_randomStream->errorString()); + } value = QString::fromLatin1(rawData.toBase64()); } else { @@ -527,3 +542,9 @@ QString KeePass2XmlWriter::colorPartToString(int value) return str; } + +void KeePass2XmlWriter::raiseError(const QString& errorMessage) +{ + m_error = true; + m_errorStr = errorMessage; +} diff --git a/src/format/KeePass2XmlWriter.h b/src/format/KeePass2XmlWriter.h index 16812fb9c..ea6212448 100644 --- a/src/format/KeePass2XmlWriter.h +++ b/src/format/KeePass2XmlWriter.h @@ -39,7 +39,7 @@ public: void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = Q_NULLPTR, const QByteArray& headerHash = QByteArray()); void writeDatabase(const QString& filename, Database* db); - bool error(); + bool hasError(); QString errorString(); private: @@ -74,12 +74,16 @@ private: void writeTriState(const QString& qualifiedName, Group::TriState triState); QString colorPartToString(int value); + void raiseError(const QString& errorMessage); + QXmlStreamWriter m_xml; Database* m_db; Metadata* m_meta; KeePass2RandomStream* m_randomStream; QByteArray m_headerHash; QHash m_idMap; + bool m_error; + QString m_errorStr; }; #endif // KEEPASSX_KEEPASS2XMLWRITER_H diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 369c69037..8d79c1a92 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -599,8 +599,13 @@ void DatabaseWidget::updateMasterKey(bool accepted) { if (accepted) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - m_db->setKey(m_changeMasterKeyWidget->newMasterKey()); + bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey()); QApplication::restoreOverrideCursor(); + + if (!result) { + MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key")); + return; + } } else if (!m_db->hasKey()) { Q_EMIT closeRequest(); diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 8f6c5319e..314851867 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -81,34 +81,62 @@ QByteArray CompositeKey::rawKey() const return cryptoHash.result(); } -QByteArray CompositeKey::transform(const QByteArray& seed, quint64 rounds) const +QByteArray CompositeKey::transform(const QByteArray& seed, quint64 rounds, + bool* ok, QString* errorString) const { Q_ASSERT(seed.size() == 32); Q_ASSERT(rounds > 0); + bool okLeft; + QString errorStringLeft; + bool okRight; + QString errorStringRight; + QByteArray key = rawKey(); - QFuture future = QtConcurrent::run(transformKeyRaw, key.left(16), seed, rounds); - QByteArray result2 = transformKeyRaw(key.right(16), seed, rounds); + QFuture future = QtConcurrent::run(transformKeyRaw, key.left(16), seed, rounds, &okLeft, &errorStringLeft); + QByteArray result2 = transformKeyRaw(key.right(16), seed, rounds, &okRight, &errorStringRight); QByteArray transformed; transformed.append(future.result()); transformed.append(result2); + *ok = (okLeft && okRight); + + if (!okLeft) { + *errorString = errorStringLeft; + return QByteArray(); + } + + if (!okRight) { + *errorString = errorStringRight; + return QByteArray(); + } + return CryptoHash::hash(transformed, CryptoHash::Sha256); } QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray& seed, - quint64 rounds) + quint64 rounds, bool* ok, QString* errorString) { QByteArray iv(16, 0); SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, - SymmetricCipher::Encrypt, seed, iv); + SymmetricCipher::Encrypt); + if (!cipher.init(seed, iv)) { + *ok = false; + *errorString = cipher.errorString(); + return QByteArray(); + } QByteArray result = key; - cipher.processInPlace(result, rounds); + if (!cipher.processInPlace(result, rounds)) { + *ok = false; + *errorString = cipher.errorString(); + return QByteArray(); + } + *ok = true; return result; } @@ -151,7 +179,8 @@ void TransformKeyBenchmarkThread::run() QByteArray iv(16, 0); SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, - SymmetricCipher::Encrypt, seed, iv); + SymmetricCipher::Encrypt); + cipher.init(seed, iv); QTime t; t.start(); diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h index ff738e61a..3290d3671 100644 --- a/src/keys/CompositeKey.h +++ b/src/keys/CompositeKey.h @@ -34,14 +34,15 @@ public: CompositeKey& operator=(const CompositeKey& key); QByteArray rawKey() const; - QByteArray transform(const QByteArray& seed, quint64 rounds) const; + QByteArray transform(const QByteArray& seed, quint64 rounds, + bool* ok, QString* errorString) const; void addKey(const Key& key); static int transformKeyBenchmark(int msec); private: static QByteArray transformKeyRaw(const QByteArray& key, const QByteArray& seed, - quint64 rounds); + quint64 rounds, bool* ok, QString* errorString); QList m_keys; }; diff --git a/src/streams/LayeredStream.cpp b/src/streams/LayeredStream.cpp index b71eb228a..9f7783bd3 100644 --- a/src/streams/LayeredStream.cpp +++ b/src/streams/LayeredStream.cpp @@ -34,11 +34,6 @@ bool LayeredStream::isSequential() const return true; } -QString LayeredStream::errorString() const -{ - return m_baseDevice->errorString(); -} - bool LayeredStream::open(QIODevice::OpenMode mode) { if (isOpen()) { diff --git a/src/streams/LayeredStream.h b/src/streams/LayeredStream.h index 285c16ddf..b243e55b2 100644 --- a/src/streams/LayeredStream.h +++ b/src/streams/LayeredStream.h @@ -31,7 +31,6 @@ public: virtual ~LayeredStream(); bool isSequential() const Q_DECL_OVERRIDE; - virtual QString errorString() const; bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE; protected: diff --git a/src/streams/StoreDataStream.cpp b/src/streams/StoreDataStream.cpp index da94b851f..18d7db980 100644 --- a/src/streams/StoreDataStream.cpp +++ b/src/streams/StoreDataStream.cpp @@ -41,6 +41,10 @@ QByteArray StoreDataStream::storedData() const qint64 StoreDataStream::readData(char* data, qint64 maxSize) { qint64 bytesRead = LayeredStream::readData(data, maxSize); + if (bytesRead == -1) { + setErrorString(m_baseDevice->errorString()); + return -1; + } m_storedData.append(data, bytesRead); diff --git a/src/streams/SymmetricCipherStream.cpp b/src/streams/SymmetricCipherStream.cpp index a6f5b1f5e..48ceb8f4d 100644 --- a/src/streams/SymmetricCipherStream.cpp +++ b/src/streams/SymmetricCipherStream.cpp @@ -18,10 +18,9 @@ #include "SymmetricCipherStream.h" SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo, - SymmetricCipher::Mode mode, SymmetricCipher::Direction direction, - const QByteArray& key, const QByteArray& iv) + SymmetricCipher::Mode mode, SymmetricCipher::Direction direction) : LayeredStream(baseDevice) - , m_cipher(new SymmetricCipher(algo, mode, direction, key, iv)) + , m_cipher(new SymmetricCipher(algo, mode, direction)) , m_bufferPos(0) , m_bufferFilling(false) , m_error(false) @@ -33,6 +32,25 @@ SymmetricCipherStream::~SymmetricCipherStream() close(); } +bool SymmetricCipherStream::init(const QByteArray& key, const QByteArray& iv) +{ + m_isInitalized = m_cipher->init(key, iv); + if (!m_isInitalized) { + setErrorString(m_cipher->errorString()); + } + + return m_isInitalized; +} + +bool SymmetricCipherStream::open(QIODevice::OpenMode mode) +{ + if (!m_isInitalized) { + return false; + } + + return LayeredStream::open(mode); +} + bool SymmetricCipherStream::reset() { if (isWritable()) { @@ -108,7 +126,11 @@ bool SymmetricCipherStream::readBlock() return false; } else { - m_cipher->processInPlace(m_buffer); + if (!m_cipher->processInPlace(m_buffer)) { + m_error = true; + setErrorString(m_cipher->errorString()); + return false; + } m_bufferPos = 0; m_bufferFilling = false; @@ -187,7 +209,11 @@ bool SymmetricCipherStream::writeBlock(bool lastBlock) return true; } - m_cipher->processInPlace(m_buffer); + if (!m_cipher->processInPlace(m_buffer)) { + m_error = true; + setErrorString(m_cipher->errorString()); + return false; + } if (m_baseDevice->write(m_buffer) != m_buffer.size()) { m_error = true; diff --git a/src/streams/SymmetricCipherStream.h b/src/streams/SymmetricCipherStream.h index 53e252f24..c1865babc 100644 --- a/src/streams/SymmetricCipherStream.h +++ b/src/streams/SymmetricCipherStream.h @@ -29,11 +29,13 @@ class SymmetricCipherStream : public LayeredStream Q_OBJECT public: - SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, - SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv); + SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo, + SymmetricCipher::Mode mode, SymmetricCipher::Direction direction); ~SymmetricCipherStream(); - bool reset(); - void close(); + bool init(const QByteArray& key, const QByteArray& iv); + bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE; + bool reset() Q_DECL_OVERRIDE; + void close() Q_DECL_OVERRIDE; protected: qint64 readData(char* data, qint64 maxSize) Q_DECL_OVERRIDE; @@ -48,6 +50,7 @@ private: int m_bufferPos; bool m_bufferFilling; bool m_error; + bool m_isInitalized; }; #endif // KEEPASSX_SYMMETRICCIPHERSTREAM_H diff --git a/tests/TestKeePass2RandomStream.cpp b/tests/TestKeePass2RandomStream.cpp index 7963e9af8..b41f1e87b 100644 --- a/tests/TestKeePass2RandomStream.cpp +++ b/tests/TestKeePass2RandomStream.cpp @@ -39,8 +39,8 @@ void TestKeePass2RandomStream::test() const int Size = 128; - SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt, - CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV); + SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); + QVERIFY(cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV)); const QByteArray data(QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5" "2b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6" @@ -59,20 +59,27 @@ void TestKeePass2RandomStream::test() } - KeePass2RandomStream randomStream(key); + KeePass2RandomStream randomStream; + bool ok; + QVERIFY(randomStream.init(key)); QByteArray randomStreamData; - randomStreamData.append(randomStream.process(data.mid(0, 7))); - randomStreamData.append(randomStream.process(data.mid(7, 1))); + randomStreamData.append(randomStream.process(data.mid(0, 7), &ok)); + QVERIFY(ok); + randomStreamData.append(randomStream.process(data.mid(7, 1), &ok)); + QVERIFY(ok); QByteArray tmpData = data.mid(8, 12); randomStream.processInPlace(tmpData); randomStreamData.append(tmpData); - randomStreamData.append(randomStream.process(data.mid(20, 44))); - randomStreamData.append(randomStream.process(data.mid(64, 64))); + randomStreamData.append(randomStream.process(data.mid(20, 44), &ok)); + QVERIFY(ok); + randomStreamData.append(randomStream.process(data.mid(64, 64), &ok)); + QVERIFY(ok); - SymmetricCipher cipherEncrypt(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt, - CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV); - QByteArray cipherDataEncrypt = cipherEncrypt.process(data); + SymmetricCipher cipherEncrypt(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); + QVERIFY(cipherEncrypt.init(CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV)); + QByteArray cipherDataEncrypt = cipherEncrypt.process(data, &ok); + QVERIFY(ok); QCOMPARE(randomStreamData.size(), Size); diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index d6758d655..770af52de 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -43,6 +43,8 @@ void TestKeys::testComposite() CompositeKey* compositeKey1 = new CompositeKey(); PasswordKey* passwordKey1 = new PasswordKey(); PasswordKey* passwordKey2 = new PasswordKey("test"); + bool ok; + QString errorString; // make sure that addKey() creates a copy of the keys compositeKey1->addKey(*passwordKey1); @@ -50,13 +52,15 @@ void TestKeys::testComposite() delete passwordKey1; delete passwordKey2; - QByteArray transformed = compositeKey1->transform(QByteArray(32, '\0'), 1); + QByteArray transformed = compositeKey1->transform(QByteArray(32, '\0'), 1, &ok, &errorString); + QVERIFY(ok); QCOMPARE(transformed.size(), 32); // make sure the subkeys are copied CompositeKey* compositeKey2 = compositeKey1->clone(); delete compositeKey1; - QCOMPARE(compositeKey2->transform(QByteArray(32, '\0'), 1), transformed); + QCOMPARE(compositeKey2->transform(QByteArray(32, '\0'), 1, &ok, &errorString), transformed); + QVERIFY(ok); delete compositeKey2; CompositeKey* compositeKey3 = new CompositeKey(); @@ -130,7 +134,7 @@ void TestKeys::testCreateFileKey() compositeKey.addKey(fileKey); Database* dbOrg = new Database(); - dbOrg->setKey(compositeKey); + QVERIFY(dbOrg->setKey(compositeKey)); dbOrg->metadata()->setName(dbName); QBuffer dbBuffer; @@ -182,7 +186,10 @@ void TestKeys::benchmarkTransformKey() QByteArray seed(32, '\x4B'); + bool ok; + QString errorString; + QBENCHMARK { - compositeKey.transform(seed, 1e6); + compositeKey.transform(seed, 1e6, &ok, &errorString); } } diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index 6d4e94f6f..ec9badd90 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -42,17 +42,20 @@ void TestSymmetricCipher::testAes256CbcEncryption() plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6"); cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d")); + bool ok; - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, - key, iv); + SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); + QVERIFY(cipher.init(key, iv)); QCOMPARE(cipher.blockSize(), 16); - QCOMPARE(cipher.process(plainText), + QCOMPARE(cipher.process(plainText, &ok), cipherText); + QVERIFY(ok); QBuffer buffer; SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, - SymmetricCipher::Encrypt, key, iv); + SymmetricCipher::Encrypt); + QVERIFY(stream.init(key, iv)); buffer.open(QIODevice::WriteOnly); stream.open(QIODevice::WriteOnly); QVERIFY(stream.reset()); @@ -86,18 +89,22 @@ void TestSymmetricCipher::testAes256CbcDecryption() cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d")); QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a"); plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51")); + bool ok; - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv); + SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); + QVERIFY(cipher.init(key, iv)); QCOMPARE(cipher.blockSize(), 16); - QCOMPARE(cipher.process(cipherText), + QCOMPARE(cipher.process(cipherText, &ok), plainText); + QVERIFY(ok); // padded with 16 0x16 bytes QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2"); QBuffer buffer(&cipherTextPadded); SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, - SymmetricCipher::Decrypt, key, iv); + SymmetricCipher::Decrypt); + QVERIFY(stream.init(key, iv)); buffer.open(QIODevice::ReadOnly); stream.open(QIODevice::ReadOnly); @@ -123,16 +130,20 @@ void TestSymmetricCipher::testSalsa20() QByteArray key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112"); QByteArray iv = QByteArray::fromHex("0000000000000000"); + bool ok; - SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt, key, iv); + SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); + QVERIFY(cipher.init(key, iv)); QByteArray cipherTextA; for (int i = 0; i < 8; i++) { - cipherTextA.append(cipher.process(QByteArray(64, '\0'))); + cipherTextA.append(cipher.process(QByteArray(64, '\0'), &ok)); + QVERIFY(ok); } cipher.reset(); - QByteArray cipherTextB = cipher.process(QByteArray(512, '\0')); + QByteArray cipherTextB = cipher.process(QByteArray(512, '\0'), &ok); + QVERIFY(ok); cipher.reset(); QByteArray expectedCipherText1; @@ -180,7 +191,8 @@ void TestSymmetricCipher::testPadding() buffer.open(QIODevice::ReadWrite); SymmetricCipherStream streamEnc(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, - SymmetricCipher::Encrypt, key, iv); + SymmetricCipher::Encrypt); + QVERIFY(streamEnc.init(key, iv)); streamEnc.open(QIODevice::WriteOnly); streamEnc.write(plainText); streamEnc.close(); @@ -189,7 +201,8 @@ void TestSymmetricCipher::testPadding() QCOMPARE(buffer.buffer().size(), 16); SymmetricCipherStream streamDec(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc, - SymmetricCipher::Decrypt, key, iv); + SymmetricCipher::Decrypt); + QVERIFY(streamDec.init(key, iv)); streamDec.open(QIODevice::ReadOnly); QByteArray decrypted = streamDec.readAll(); QCOMPARE(decrypted, plainText); From f6243675c9fccd034e9423257a3191f3cd44ad99 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Sat, 9 May 2015 19:57:16 +0200 Subject: [PATCH 02/20] Warn if result of processInPlace() is unchecked. Fix callers accordingly. --- src/crypto/SymmetricCipher.h | 4 ++-- src/crypto/SymmetricCipherBackend.h | 4 ++-- src/crypto/SymmetricCipherGcrypt.h | 4 ++-- src/format/KeePass2RandomStream.h | 2 +- src/format/KeePass2XmlReader.cpp | 4 +++- src/keys/CompositeKey.cpp | 2 +- tests/TestKeePass2RandomStream.cpp | 4 ++-- 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h index 65cab76fa..b8b3eb130 100644 --- a/src/crypto/SymmetricCipher.h +++ b/src/crypto/SymmetricCipher.h @@ -59,11 +59,11 @@ public: return m_backend->process(data, ok); } - inline bool processInPlace(QByteArray& data) { + inline bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT { return m_backend->processInPlace(data); } - inline bool processInPlace(QByteArray& data, quint64 rounds) { + inline bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT { Q_ASSERT(rounds > 0); return m_backend->processInPlace(data, rounds); } diff --git a/src/crypto/SymmetricCipherBackend.h b/src/crypto/SymmetricCipherBackend.h index 763ffb40d..8f19b8ed0 100644 --- a/src/crypto/SymmetricCipherBackend.h +++ b/src/crypto/SymmetricCipherBackend.h @@ -29,8 +29,8 @@ public: virtual bool setIv(const QByteArray& iv) = 0; virtual QByteArray process(const QByteArray& data, bool* ok) = 0; - virtual bool processInPlace(QByteArray& data) = 0; - virtual bool processInPlace(QByteArray& data, quint64 rounds) = 0; + virtual bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT = 0; + virtual bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT = 0; virtual bool reset() = 0; virtual int blockSize() const = 0; diff --git a/src/crypto/SymmetricCipherGcrypt.h b/src/crypto/SymmetricCipherGcrypt.h index bc1560097..367ee5b95 100644 --- a/src/crypto/SymmetricCipherGcrypt.h +++ b/src/crypto/SymmetricCipherGcrypt.h @@ -35,8 +35,8 @@ public: bool setIv(const QByteArray& iv); QByteArray process(const QByteArray& data, bool* ok); - bool processInPlace(QByteArray& data); - bool processInPlace(QByteArray& data, quint64 rounds); + bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT; + bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT; bool reset(); int blockSize() const; diff --git a/src/format/KeePass2RandomStream.h b/src/format/KeePass2RandomStream.h index c951a95ad..022c8399f 100644 --- a/src/format/KeePass2RandomStream.h +++ b/src/format/KeePass2RandomStream.h @@ -29,7 +29,7 @@ public: bool init(const QByteArray& key); QByteArray randomBytes(int size, bool* ok); QByteArray process(const QByteArray& data, bool* ok); - bool processInPlace(QByteArray& data); + bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT; QString errorString() const; private: diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp index c38448861..ed5715cbe 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/KeePass2XmlReader.cpp @@ -877,7 +877,9 @@ QPair KeePass2XmlReader::parseEntryBinary(Entry* entry) && (attr.value("Protected") == "True"); if (isProtected && !value.isEmpty()) { - m_randomStream->processInPlace(value); + if (!m_randomStream->processInPlace(value)) { + raiseError(m_randomStream->errorString()); + } } } diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 314851867..4fae5f915 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -186,7 +186,7 @@ void TransformKeyBenchmarkThread::run() t.start(); do { - cipher.processInPlace(key, 100); + Q_UNUSED(cipher.processInPlace(key, 100)); m_rounds += 100; } while (t.elapsed() < m_msec); } diff --git a/tests/TestKeePass2RandomStream.cpp b/tests/TestKeePass2RandomStream.cpp index b41f1e87b..cb8813371 100644 --- a/tests/TestKeePass2RandomStream.cpp +++ b/tests/TestKeePass2RandomStream.cpp @@ -49,7 +49,7 @@ void TestKeePass2RandomStream::test() QByteArray cipherPad; cipherPad.fill('\0', Size); - cipher.processInPlace(cipherPad); + QVERIFY(cipher.processInPlace(cipherPad)); QByteArray cipherData; cipherData.resize(Size); @@ -68,7 +68,7 @@ void TestKeePass2RandomStream::test() randomStreamData.append(randomStream.process(data.mid(7, 1), &ok)); QVERIFY(ok); QByteArray tmpData = data.mid(8, 12); - randomStream.processInPlace(tmpData); + QVERIFY(randomStream.processInPlace(tmpData)); randomStreamData.append(tmpData); randomStreamData.append(randomStream.process(data.mid(20, 44), &ok)); QVERIFY(ok); From cfffdae5734c99c6882ac8bc8647ef0c6a7afa75 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Sat, 9 May 2015 20:11:20 +0200 Subject: [PATCH 03/20] Improve error reporing of layered streams. --- src/streams/HashedBlockStream.cpp | 6 ++++++ src/streams/SymmetricCipherStream.cpp | 3 ++- tests/TestHashedBlockStream.cpp | 12 ++++++------ tests/TestSymmetricCipher.cpp | 16 ++++++++-------- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/streams/HashedBlockStream.cpp b/src/streams/HashedBlockStream.cpp index 39e16de2e..cd43dc0bf 100644 --- a/src/streams/HashedBlockStream.cpp +++ b/src/streams/HashedBlockStream.cpp @@ -150,6 +150,7 @@ bool HashedBlockStream::readHashedBlock() if (m_blockSize == 0) { if (hash.count('\0') != 32) { m_error = true; + setErrorString("Invalid hash of final block."); return false; } @@ -166,6 +167,7 @@ bool HashedBlockStream::readHashedBlock() if (hash != CryptoHash::hash(m_buffer, CryptoHash::Sha256)) { m_error = true; + setErrorString("Mismatch between hash and data."); return false; } @@ -213,6 +215,7 @@ bool HashedBlockStream::writeHashedBlock() { if (!Endian::writeInt32(m_blockIndex, m_baseDevice, ByteOrder)) { m_error = true; + setErrorString(m_baseDevice->errorString()); return false; } m_blockIndex++; @@ -227,17 +230,20 @@ bool HashedBlockStream::writeHashedBlock() if (m_baseDevice->write(hash) != hash.size()) { m_error = true; + setErrorString(m_baseDevice->errorString()); return false; } if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) { m_error = true; + setErrorString(m_baseDevice->errorString()); return false; } if (!m_buffer.isEmpty()) { if (m_baseDevice->write(m_buffer) != m_buffer.size()) { m_error = true; + setErrorString(m_baseDevice->errorString()); return false; } diff --git a/src/streams/SymmetricCipherStream.cpp b/src/streams/SymmetricCipherStream.cpp index 48ceb8f4d..28525b39f 100644 --- a/src/streams/SymmetricCipherStream.cpp +++ b/src/streams/SymmetricCipherStream.cpp @@ -147,6 +147,7 @@ bool SymmetricCipherStream::readBlock() else if (padLength > m_cipher->blockSize()) { // invalid padding m_error = true; + setErrorString("Invalid padding."); return false; } else { @@ -217,7 +218,7 @@ bool SymmetricCipherStream::writeBlock(bool lastBlock) if (m_baseDevice->write(m_buffer) != m_buffer.size()) { m_error = true; - // TODO: copy error string + setErrorString(m_cipher->errorString()); return false; } else { diff --git a/tests/TestHashedBlockStream.cpp b/tests/TestHashedBlockStream.cpp index 09179fef2..2a37d0aa4 100644 --- a/tests/TestHashedBlockStream.cpp +++ b/tests/TestHashedBlockStream.cpp @@ -36,15 +36,15 @@ void TestHashedBlockStream::testWriteRead() QByteArray data = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); QBuffer buffer; - buffer.open(QIODevice::ReadWrite); + QVERIFY(buffer.open(QIODevice::ReadWrite)); HashedBlockStream writer(&buffer, 16); - writer.open(QIODevice::WriteOnly); + QVERIFY(writer.open(QIODevice::WriteOnly)); HashedBlockStream reader(&buffer); - reader.open(QIODevice::ReadOnly); + QVERIFY(reader.open(QIODevice::ReadOnly)); - writer.write(data.left(16)); + QCOMPARE(writer.write(data.left(16)), qint64(16)); QVERIFY(writer.reset()); buffer.reset(); QCOMPARE(reader.read(17), data.left(16)); @@ -52,7 +52,7 @@ void TestHashedBlockStream::testWriteRead() buffer.reset(); buffer.buffer().clear(); - writer.write(data.left(10)); + QCOMPARE(writer.write(data.left(10)), qint64(10)); QVERIFY(writer.reset()); buffer.reset(); QCOMPARE(reader.read(5), data.left(5)); @@ -62,7 +62,7 @@ void TestHashedBlockStream::testWriteRead() buffer.reset(); buffer.buffer().clear(); - writer.write(data.left(20)); + QCOMPARE(writer.write(data.left(20)), qint64(20)); QVERIFY(writer.reset()); buffer.reset(); QCOMPARE(reader.read(20), data.left(20)); diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index ec9badd90..55a1bbe37 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -57,12 +57,12 @@ void TestSymmetricCipher::testAes256CbcEncryption() SymmetricCipher::Encrypt); QVERIFY(stream.init(key, iv)); buffer.open(QIODevice::WriteOnly); - stream.open(QIODevice::WriteOnly); + QVERIFY(stream.open(QIODevice::WriteOnly)); QVERIFY(stream.reset()); buffer.reset(); buffer.buffer().clear(); - stream.write(plainText.left(16)); + QCOMPARE(stream.write(plainText.left(16)), qint64(16)); QCOMPARE(buffer.data(), cipherText.left(16)); QVERIFY(stream.reset()); // make sure padding is written @@ -70,13 +70,13 @@ void TestSymmetricCipher::testAes256CbcEncryption() buffer.reset(); buffer.buffer().clear(); - stream.write(plainText.left(10)); + QCOMPARE(stream.write(plainText.left(10)), qint64(10)); QVERIFY(buffer.data().isEmpty()); QVERIFY(stream.reset()); buffer.reset(); buffer.buffer().clear(); - stream.write(plainText.left(10)); + QCOMPARE(stream.write(plainText.left(10)), qint64(10)); stream.close(); QCOMPARE(buffer.data().size(), 16); } @@ -106,20 +106,20 @@ void TestSymmetricCipher::testAes256CbcDecryption() SymmetricCipher::Decrypt); QVERIFY(stream.init(key, iv)); buffer.open(QIODevice::ReadOnly); - stream.open(QIODevice::ReadOnly); + QVERIFY(stream.open(QIODevice::ReadOnly)); QCOMPARE(stream.read(10), plainText.left(10)); buffer.reset(); - stream.reset(); + QVERIFY(stream.reset()); QCOMPARE(stream.read(20), plainText.left(20)); buffer.reset(); - stream.reset(); + QVERIFY(stream.reset()); QCOMPARE(stream.read(16), plainText.left(16)); buffer.reset(); - stream.reset(); + QVERIFY(stream.reset()); QCOMPARE(stream.read(100), plainText); } From e0d4b4b625df1a53048fb8662475f6a021a519c0 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Sat, 9 May 2015 21:10:52 +0200 Subject: [PATCH 04/20] Adapt Salsa20 backend to the new interface. --- src/crypto/SymmetricCipherSalsa20.cpp | 33 ++++++++++++++++++++++----- src/crypto/SymmetricCipherSalsa20.h | 2 ++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/crypto/SymmetricCipherSalsa20.cpp b/src/crypto/SymmetricCipherSalsa20.cpp index d17325442..7e477656a 100644 --- a/src/crypto/SymmetricCipherSalsa20.cpp +++ b/src/crypto/SymmetricCipherSalsa20.cpp @@ -33,23 +33,32 @@ SymmetricCipherSalsa20::~SymmetricCipherSalsa20() { } -void SymmetricCipherSalsa20::setKey(const QByteArray& key) +bool SymmetricCipherSalsa20::init() +{ + return true; +} + +bool SymmetricCipherSalsa20::setKey(const QByteArray& key) { Q_ASSERT((key.size() == 16) || (key.size() == 32)); m_key = key; ECRYPT_keysetup(&m_ctx, reinterpret_cast(m_key.constData()), m_key.size()*8, 64); + + return true; } -void SymmetricCipherSalsa20::setIv(const QByteArray& iv) +bool SymmetricCipherSalsa20::setIv(const QByteArray& iv) { Q_ASSERT(iv.size() == 8); m_iv = iv; ECRYPT_ivsetup(&m_ctx, reinterpret_cast(m_iv.constData())); + + return true; } -QByteArray SymmetricCipherSalsa20::process(const QByteArray& data) +QByteArray SymmetricCipherSalsa20::process(const QByteArray& data, bool* ok) { Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0)); @@ -59,18 +68,21 @@ QByteArray SymmetricCipherSalsa20::process(const QByteArray& data) ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast(data.constData()), reinterpret_cast(result.data()), data.size()); + *ok = true; return result; } -void SymmetricCipherSalsa20::processInPlace(QByteArray& data) +bool SymmetricCipherSalsa20::processInPlace(QByteArray& data) { Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0)); ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast(data.constData()), reinterpret_cast(data.data()), data.size()); + + return true; } -void SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds) +bool SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds) { Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0)); @@ -78,14 +90,23 @@ void SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds) ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast(data.constData()), reinterpret_cast(data.data()), data.size()); } + + return true; } -void SymmetricCipherSalsa20::reset() +bool SymmetricCipherSalsa20::reset() { ECRYPT_ivsetup(&m_ctx, reinterpret_cast(m_iv.constData())); + + return true; } int SymmetricCipherSalsa20::blockSize() const { return 64; } + +QString SymmetricCipherSalsa20::errorString() const +{ + return QString(); +} diff --git a/src/crypto/SymmetricCipherSalsa20.h b/src/crypto/SymmetricCipherSalsa20.h index 6a5c24562..443d4ec8b 100644 --- a/src/crypto/SymmetricCipherSalsa20.h +++ b/src/crypto/SymmetricCipherSalsa20.h @@ -42,6 +42,8 @@ public: bool reset(); int blockSize() const; + QString errorString() const; + private: ECRYPT_ctx m_ctx; QByteArray m_key; From 4362c3ea38ac68af126adf7816b049bb3dea3809 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Sat, 9 May 2015 23:38:04 +0200 Subject: [PATCH 05/20] Handle cipher errors in TransformKeyBenchmarkThread. --- src/gui/DatabaseSettingsWidget.cpp | 5 ++++- src/keys/CompositeKey.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index bba11f9aa..007c44a08 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -130,7 +130,10 @@ void DatabaseSettingsWidget::reject() void DatabaseSettingsWidget::transformRoundsBenchmark() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - m_ui->transformRoundsSpinBox->setValue(CompositeKey::transformKeyBenchmark(1000)); + int rounds = CompositeKey::transformKeyBenchmark(1000); + if (rounds != -1) { + m_ui->transformRoundsSpinBox->setValue(rounds); + } QApplication::restoreOverrideCursor(); } diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 4fae5f915..a0f2373c8 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -186,7 +186,10 @@ void TransformKeyBenchmarkThread::run() t.start(); do { - Q_UNUSED(cipher.processInPlace(key, 100)); + if (!cipher.processInPlace(key, 100)) { + m_rounds = -1; + return; + } m_rounds += 100; } while (t.elapsed() < m_msec); } From ade684d501bb48c6002e3248f4e0b823d3a02d8c Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Sun, 10 May 2015 00:02:08 +0200 Subject: [PATCH 06/20] Crypto::selfTest(): test AES in ECB mode. --- src/crypto/Crypto.cpp | 51 +++++++++++++++++++++++++++++++++++++++---- src/crypto/Crypto.h | 3 ++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/crypto/Crypto.cpp b/src/crypto/Crypto.cpp index d4fc6493e..e32e2d2d3 100644 --- a/src/crypto/Crypto.cpp +++ b/src/crypto/Crypto.cpp @@ -143,7 +143,7 @@ bool Crypto::checkAlgorithms() bool Crypto::selfTest() { - return testSha256() && testAes256() && testTwofish() && testSalsa20(); + return testSha256() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20(); } void Crypto::raiseError(const QString& str) @@ -165,7 +165,7 @@ bool Crypto::testSha256() return true; } -bool Crypto::testAes256() +bool Crypto::testAes256Cbc() { QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f"); @@ -186,7 +186,7 @@ bool Crypto::testAes256() return false; } if (encryptedText != cipherText) { - raiseError("AES-256 encryption mismatch."); + raiseError("AES-256 CBC encryption mismatch."); return false; } @@ -201,7 +201,50 @@ bool Crypto::testAes256() return false; } if (decryptedText != plainText) { - raiseError("AES-256 decryption mismatch."); + raiseError("AES-256 CBC decryption mismatch."); + return false; + } + + return true; +} + +bool Crypto::testAes256Ecb() +{ + QByteArray key = QByteArray::fromHex("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + QByteArray iv = QByteArray::fromHex("00000000000000000000000000000000"); + QByteArray plainText = QByteArray::fromHex("00112233445566778899AABBCCDDEEFF"); + plainText.append(QByteArray::fromHex("00112233445566778899AABBCCDDEEFF")); + QByteArray cipherText = QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089"); + cipherText.append(QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089")); + bool ok; + + SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); + if (!aes256Encrypt.init(key, iv)) { + raiseError(aes256Encrypt.errorString()); + return false; + } + QByteArray encryptedText = aes256Encrypt.process(plainText, &ok); + if (!ok) { + raiseError(aes256Encrypt.errorString()); + return false; + } + if (encryptedText != cipherText) { + raiseError("AES-256 ECB encryption mismatch."); + return false; + } + + SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Decrypt); + if (!aes256Descrypt.init(key, iv)) { + raiseError(aes256Descrypt.errorString()); + return false; + } + QByteArray decryptedText = aes256Descrypt.process(cipherText, &ok); + if (!ok) { + raiseError(aes256Descrypt.errorString()); + return false; + } + if (decryptedText != plainText) { + raiseError("AES-256 ECB decryption mismatch."); return false; } diff --git a/src/crypto/Crypto.h b/src/crypto/Crypto.h index 07f6454fe..b801cbb54 100644 --- a/src/crypto/Crypto.h +++ b/src/crypto/Crypto.h @@ -36,7 +36,8 @@ private: static bool selfTest(); static void raiseError(const QString& str); static bool testSha256(); - static bool testAes256(); + static bool testAes256Cbc(); + static bool testAes256Ecb(); static bool testTwofish(); static bool testSalsa20(); From 58061af959d309265409cc5054fb96429fb4b8e4 Mon Sep 17 00:00:00 2001 From: Amir Pakdel Date: Tue, 12 May 2015 15:12:17 -0400 Subject: [PATCH 07/20] Bug #218 Do not accept faulty files as Key File. Moreover, do not clear keys unless we have a key to add. --- src/gui/ChangeMasterKeyWidget.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index e0dd06fdc..ee0c8b106 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -96,7 +96,7 @@ QLabel* ChangeMasterKeyWidget::headlineLabel() void ChangeMasterKeyWidget::generateKey() { - m_key.clear(); + bool cleared = false; if (m_ui->passwordGroup->isChecked()) { if (m_ui->enterPasswordEdit->text() == m_ui->repeatPasswordEdit->text()) { @@ -107,6 +107,8 @@ void ChangeMasterKeyWidget::generateKey() return; } } + m_key.clear(); + cleared = true; m_key.addKey(PasswordKey(m_ui->enterPasswordEdit->text())); } else { @@ -120,8 +122,12 @@ void ChangeMasterKeyWidget::generateKey() FileKey fileKey; QString errorMsg; if (!fileKey.load(m_ui->keyFileCombo->currentText(), &errorMsg)) { - // TODO: error handling + MessageBox::critical(this, tr("Failed to set key file"), + tr("Failed to set %1 as the Key file:\n%2") + .arg(m_ui->keyFileCombo->currentText(), errorMsg)); + return; } + if (!cleared) m_key.clear(); m_key.addKey(fileKey); } From a599787a25aa71d0609b3ce9d9ab544456770933 Mon Sep 17 00:00:00 2001 From: Amir Pakdel Date: Tue, 12 May 2015 15:50:10 -0400 Subject: [PATCH 08/20] Bug #290 Show realted menu option to current entry only if the corresponding field is not empty. --- src/gui/DatabaseWidget.cpp | 50 ++++++++++++++++++++++++++++++++++++++ src/gui/DatabaseWidget.h | 5 ++++ src/gui/MainWindow.cpp | 12 ++++----- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 369c69037..c0fe81f32 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -906,3 +906,53 @@ bool DatabaseWidget::isGroupSelected() const { return m_groupView->currentGroup() != Q_NULLPTR; } + +bool DatabaseWidget::hasTitle() +{ + Entry* currentEntry = m_entryView->currentEntry(); + if (!currentEntry) { + Q_ASSERT(false); + return false; + } + return !currentEntry->title().isEmpty(); +} + +bool DatabaseWidget::hasUsername() +{ + Entry* currentEntry = m_entryView->currentEntry(); + if (!currentEntry) { + Q_ASSERT(false); + return false; + } + return !currentEntry->username().isEmpty(); +} + +bool DatabaseWidget::hasPassword() +{ + Entry* currentEntry = m_entryView->currentEntry(); + if (!currentEntry) { + Q_ASSERT(false); + return false; + } + return !currentEntry->password().isEmpty(); +} + +bool DatabaseWidget::hasUrl() +{ + Entry* currentEntry = m_entryView->currentEntry(); + if (!currentEntry) { + Q_ASSERT(false); + return false; + } + return !currentEntry->url().isEmpty(); +} + +bool DatabaseWidget::hasNotes() +{ + Entry* currentEntry = m_entryView->currentEntry(); + if (!currentEntry) { + Q_ASSERT(false); + return false; + } + return !currentEntry->notes().isEmpty(); +} diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index a38af731e..7e565de03 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -80,6 +80,11 @@ public: QList entryHeaderViewSizes() const; void setEntryViewHeaderSizes(const QList& sizes); void clearAllWidgets(); + bool hasTitle(); + bool hasUsername(); + bool hasPassword(); + bool hasUrl(); + bool hasNotes(); Q_SIGNALS: void closeRequest(); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 93084f643..5f3d1b365 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -286,14 +286,14 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch); m_ui->actionEntryEdit->setEnabled(singleEntrySelected); m_ui->actionEntryDelete->setEnabled(entriesSelected); - m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected); - m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected); - m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected); - m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected); - m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected); + m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->hasTitle()); + m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected && dbWidget->hasUsername()); + m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected && dbWidget->hasPassword()); + m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->hasUrl()); + m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->hasUrl()); m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected); m_ui->actionEntryAutoType->setEnabled(singleEntrySelected); - m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected); + m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->hasUrl()); m_ui->actionGroupNew->setEnabled(groupSelected); m_ui->actionGroupEdit->setEnabled(groupSelected); m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); From b45437d5026cae101dc9f4b88da36749f3362e0d Mon Sep 17 00:00:00 2001 From: Amir Pakdel Date: Tue, 12 May 2015 15:54:39 -0400 Subject: [PATCH 09/20] Refactored DatabaseWidget::currentEntryHas*() --- src/gui/DatabaseWidget.cpp | 10 +++++----- src/gui/DatabaseWidget.h | 10 +++++----- src/gui/MainWindow.cpp | 12 ++++++------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index c0fe81f32..6729a06e5 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -907,7 +907,7 @@ bool DatabaseWidget::isGroupSelected() const return m_groupView->currentGroup() != Q_NULLPTR; } -bool DatabaseWidget::hasTitle() +bool DatabaseWidget::currentEntryHasTitle() { Entry* currentEntry = m_entryView->currentEntry(); if (!currentEntry) { @@ -917,7 +917,7 @@ bool DatabaseWidget::hasTitle() return !currentEntry->title().isEmpty(); } -bool DatabaseWidget::hasUsername() +bool DatabaseWidget::currentEntryHasUsername() { Entry* currentEntry = m_entryView->currentEntry(); if (!currentEntry) { @@ -927,7 +927,7 @@ bool DatabaseWidget::hasUsername() return !currentEntry->username().isEmpty(); } -bool DatabaseWidget::hasPassword() +bool DatabaseWidget::currentEntryHasPassword() { Entry* currentEntry = m_entryView->currentEntry(); if (!currentEntry) { @@ -937,7 +937,7 @@ bool DatabaseWidget::hasPassword() return !currentEntry->password().isEmpty(); } -bool DatabaseWidget::hasUrl() +bool DatabaseWidget::currentEntryHasUrl() { Entry* currentEntry = m_entryView->currentEntry(); if (!currentEntry) { @@ -947,7 +947,7 @@ bool DatabaseWidget::hasUrl() return !currentEntry->url().isEmpty(); } -bool DatabaseWidget::hasNotes() +bool DatabaseWidget::currentEntryHasNotes() { Entry* currentEntry = m_entryView->currentEntry(); if (!currentEntry) { diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h index 7e565de03..1ba3dd1f5 100644 --- a/src/gui/DatabaseWidget.h +++ b/src/gui/DatabaseWidget.h @@ -80,11 +80,11 @@ public: QList entryHeaderViewSizes() const; void setEntryViewHeaderSizes(const QList& sizes); void clearAllWidgets(); - bool hasTitle(); - bool hasUsername(); - bool hasPassword(); - bool hasUrl(); - bool hasNotes(); + bool currentEntryHasTitle(); + bool currentEntryHasUsername(); + bool currentEntryHasPassword(); + bool currentEntryHasUrl(); + bool currentEntryHasNotes(); Q_SIGNALS: void closeRequest(); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 5f3d1b365..ce814ae98 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -286,14 +286,14 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode) m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch); m_ui->actionEntryEdit->setEnabled(singleEntrySelected); m_ui->actionEntryDelete->setEnabled(entriesSelected); - m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->hasTitle()); - m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected && dbWidget->hasUsername()); - m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected && dbWidget->hasPassword()); - m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->hasUrl()); - m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->hasUrl()); + m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTitle()); + m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername()); + m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected && dbWidget->currentEntryHasPassword()); + m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl()); + m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl()); m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected); m_ui->actionEntryAutoType->setEnabled(singleEntrySelected); - m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->hasUrl()); + m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl()); m_ui->actionGroupNew->setEnabled(groupSelected); m_ui->actionGroupEdit->setEnabled(groupSelected); m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup()); From 05b5446e94b425dc5b0826cfe9223399d277f4a6 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Sun, 10 May 2015 22:46:04 +0200 Subject: [PATCH 10/20] Protect opened databases with a file lock. Closes #18 --- src/CMakeLists.txt | 13 ++ src/core/qlockfile.cpp | 337 ++++++++++++++++++++++++++++++++++ src/core/qlockfile.h | 79 ++++++++ src/core/qlockfile_p.h | 104 +++++++++++ src/core/qlockfile_unix.cpp | 199 ++++++++++++++++++++ src/core/qlockfile_win.cpp | 178 ++++++++++++++++++ src/gui/DatabaseTabWidget.cpp | 34 ++++ src/gui/DatabaseTabWidget.h | 2 + 8 files changed, 946 insertions(+) create mode 100644 src/core/qlockfile.cpp create mode 100644 src/core/qlockfile.h create mode 100644 src/core/qlockfile_p.h create mode 100644 src/core/qlockfile_unix.cpp create mode 100644 src/core/qlockfile_win.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7ffc168b2..26d194cf7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,6 +43,7 @@ set(keepassx_SOURCES core/ListDeleter.h core/Metadata.cpp core/PasswordGenerator.cpp + core/qlockfile.cpp core/qsavefile.cpp core/qsavefile_p.h core/SignalMultiplexer.cpp @@ -130,6 +131,18 @@ if(NOT GCRYPT_HAS_SALSA20) ) endif() +if(UNIX) + set(keepassx_SOURCES + ${keepassx_SOURCES} + core/qlockfile_unix.cpp + ) +elseif(MINGW) + set(keepassx_SOURCES + ${keepassx_SOURCES} + core/qlockfile_win.cpp + ) +endif() + set(keepassx_SOURCES_MAINEXE main.cpp ) diff --git a/src/core/qlockfile.cpp b/src/core/qlockfile.cpp new file mode 100644 index 000000000..980f8ed85 --- /dev/null +++ b/src/core/qlockfile.cpp @@ -0,0 +1,337 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlockfile.h" +#include "qlockfile_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QLockFile + \inmodule QtCore + \brief The QLockFile class provides locking between processes using a file. + \since 5.1 + + A lock file can be used to prevent multiple processes from accessing concurrently + the same resource. For instance, a configuration file on disk, or a socket, a port, + a region of shared memory... + + Serialization is only guaranteed if all processes that access the shared resource + use QLockFile, with the same file path. + + QLockFile supports two use cases: + to protect a resource for a short-term operation (e.g. verifying if a configuration + file has changed before saving new settings), and for long-lived protection of a + resource (e.g. a document opened by a user in an editor) for an indefinite amount of time. + + When protecting for a short-term operation, it is acceptable to call lock() and wait + until any running operation finishes. + When protecting a resource over a long time, however, the application should always + call setStaleLockTime(0) and then tryLock() with a short timeout, in order to + warn the user that the resource is locked. + + If the process holding the lock crashes, the lock file stays on disk and can prevent + any other process from accessing the shared resource, ever. For this reason, QLockFile + tries to detect such a "stale" lock file, based on the process ID written into the file, + and (in case that process ID got reused meanwhile), on the last modification time of + the lock file (30s by default, for the use case of a short-lived operation). + If the lock file is found to be stale, it will be deleted. + + For the use case of protecting a resource over a long time, you should therefore call + setStaleLockTime(0), and when tryLock() returns LockFailedError, inform the user + that the document is locked, possibly using getLockInfo() for more details. +*/ + +/*! + \enum QLockFile::LockError + + This enum describes the result of the last call to lock() or tryLock(). + + \value NoError The lock was acquired successfully. + \value LockFailedError The lock could not be acquired because another process holds it. + \value PermissionError The lock file could not be created, for lack of permissions + in the parent directory. + \value UnknownError Another error happened, for instance a full partition + prevented writing out the lock file. +*/ + +/*! + Constructs a new lock file object. + The object is created in an unlocked state. + When calling lock() or tryLock(), a lock file named \a fileName will be created, + if it doesn't already exist. + + \sa lock(), unlock() +*/ +QLockFile::QLockFile(const QString &fileName) + : d_ptr(new QLockFilePrivate(fileName)) +{ +} + +/*! + Destroys the lock file object. + If the lock was acquired, this will release the lock, by deleting the lock file. +*/ +QLockFile::~QLockFile() +{ + unlock(); +} + +/*! + Sets \a staleLockTime to be the time in milliseconds after which + a lock file is considered stale. + The default value is 30000, i.e. 30 seconds. + If your application typically keeps the file locked for more than 30 seconds + (for instance while saving megabytes of data for 2 minutes), you should set + a bigger value using setStaleLockTime(). + + The value of \a staleLockTime is used by lock() and tryLock() in order + to determine when an existing lock file is considered stale, i.e. left over + by a crashed process. This is useful for the case where the PID got reused + meanwhile, so the only way to detect a stale lock file is by the fact that + it has been around for a long time. + + \sa staleLockTime() +*/ +void QLockFile::setStaleLockTime(int staleLockTime) +{ + Q_D(QLockFile); + d->staleLockTime = staleLockTime; +} + +/*! + Returns the time in milliseconds after which + a lock file is considered stale. + + \sa setStaleLockTime() +*/ +int QLockFile::staleLockTime() const +{ + Q_D(const QLockFile); + return d->staleLockTime; +} + +/*! + Returns \c true if the lock was acquired by this QLockFile instance, + otherwise returns \c false. + + \sa lock(), unlock(), tryLock() +*/ +bool QLockFile::isLocked() const +{ + Q_D(const QLockFile); + return d->isLocked; +} + +/*! + Creates the lock file. + + If another process (or another thread) has created the lock file already, + this function will block until that process (or thread) releases it. + + Calling this function multiple times on the same lock from the same + thread without unlocking first is not allowed. This function will + \e dead-lock when the file is locked recursively. + + Returns \c true if the lock was acquired, false if it could not be acquired + due to an unrecoverable error, such as no permissions in the parent directory. + + \sa unlock(), tryLock() +*/ +bool QLockFile::lock() +{ + return tryLock(-1); +} + +/*! + Attempts to create the lock file. This function returns \c true if the + lock was obtained; otherwise it returns \c false. If another process (or + another thread) has created the lock file already, this function will + wait for at most \a timeout milliseconds for the lock file to become + available. + + Note: Passing a negative number as the \a timeout is equivalent to + calling lock(), i.e. this function will wait forever until the lock + file can be locked if \a timeout is negative. + + If the lock was obtained, it must be released with unlock() + before another process (or thread) can successfully lock it. + + Calling this function multiple times on the same lock from the same + thread without unlocking first is not allowed, this function will + \e always return false when attempting to lock the file recursively. + + \sa lock(), unlock() +*/ +bool QLockFile::tryLock(int timeout) +{ + Q_D(QLockFile); + QElapsedTimer timer; + if (timeout > 0) + timer.start(); + int sleepTime = 100; + Q_FOREVER { + d->lockError = d->tryLock_sys(); + switch (d->lockError) { + case NoError: + d->isLocked = true; + return true; + case PermissionError: + case UnknownError: + return false; + case LockFailedError: + if (!d->isLocked && d->isApparentlyStale()) { + // Stale lock from another thread/process + // Ensure two processes don't remove it at the same time + QLockFile rmlock(d->fileName + QLatin1String(".rmlock")); + if (rmlock.tryLock()) { + if (d->isApparentlyStale() && d->removeStaleLock()) + continue; + } + } + break; + } + if (timeout == 0 || (timeout > 0 && timer.hasExpired(timeout))) + return false; + QLockFileThread::msleep(sleepTime); + if (sleepTime < 5 * 1000) + sleepTime *= 2; + } + // not reached + return false; +} + +/*! + \fn void QLockFile::unlock() + Releases the lock, by deleting the lock file. + + Calling unlock() without locking the file first, does nothing. + + \sa lock(), tryLock() +*/ + +/*! + Retrieves information about the current owner of the lock file. + + If tryLock() returns \c false, and error() returns LockFailedError, + this function can be called to find out more information about the existing + lock file: + \list + \li the PID of the application (returned in \a pid) + \li the \a hostname it's running on (useful in case of networked filesystems), + \li the name of the application which created it (returned in \a appname), + \endlist + + Note that tryLock() automatically deleted the file if there is no + running application with this PID, so LockFailedError can only happen if there is + an application with this PID (it could be unrelated though). + + This can be used to inform users about the existing lock file and give them + the choice to delete it. After removing the file using removeStaleLockFile(), + the application can call tryLock() again. + + This function returns \c true if the information could be successfully retrieved, false + if the lock file doesn't exist or doesn't contain the expected data. + This can happen if the lock file was deleted between the time where tryLock() failed + and the call to this function. Simply call tryLock() again if this happens. +*/ +bool QLockFile::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const +{ + Q_D(const QLockFile); + return d->getLockInfo(pid, hostname, appname); +} + +bool QLockFilePrivate::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const +{ + QFile reader(fileName); + if (!reader.open(QIODevice::ReadOnly)) + return false; + + QByteArray pidLine = reader.readLine(); + pidLine.chop(1); + QByteArray appNameLine = reader.readLine(); + appNameLine.chop(1); + QByteArray hostNameLine = reader.readLine(); + hostNameLine.chop(1); + if (pidLine.isEmpty()) + return false; + + qint64 thePid = pidLine.toLongLong(); + if (pid) + *pid = thePid; + if (appname) + *appname = QString::fromUtf8(appNameLine); + if (hostname) + *hostname = QString::fromUtf8(hostNameLine); + return thePid > 0; +} + +/*! + Attempts to forcefully remove an existing lock file. + + Calling this is not recommended when protecting a short-lived operation: QLockFile + already takes care of removing lock files after they are older than staleLockTime(). + + This method should only be called when protecting a resource for a long time, i.e. + with staleLockTime(0), and after tryLock() returned LockFailedError, and the user + agreed on removing the lock file. + + Returns \c true on success, false if the lock file couldn't be removed. This happens + on Windows, when the application owning the lock is still running. +*/ +bool QLockFile::removeStaleLockFile() +{ + Q_D(QLockFile); + if (d->isLocked) { + qWarning("removeStaleLockFile can only be called when not holding the lock"); + return false; + } + return d->removeStaleLock(); +} + +/*! + Returns the lock file error status. + + If tryLock() returns \c false, this function can be called to find out + the reason why the locking failed. +*/ +QLockFile::LockError QLockFile::error() const +{ + Q_D(const QLockFile); + return d->lockError; +} + +QT_END_NAMESPACE diff --git a/src/core/qlockfile.h b/src/core/qlockfile.h new file mode 100644 index 000000000..673026f21 --- /dev/null +++ b/src/core/qlockfile.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCKFILE_H +#define QLOCKFILE_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QLockFilePrivate; + +class QLockFile +{ +public: + QLockFile(const QString &fileName); + ~QLockFile(); + + bool lock(); + bool tryLock(int timeout = 0); + void unlock(); + + void setStaleLockTime(int); + int staleLockTime() const; + + bool isLocked() const; + bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const; + bool removeStaleLockFile(); + + enum LockError { + NoError = 0, + LockFailedError = 1, + PermissionError = 2, + UnknownError = 3 + }; + LockError error() const; + +protected: + QScopedPointer d_ptr; + +private: + Q_DECLARE_PRIVATE(QLockFile) + Q_DISABLE_COPY(QLockFile) +}; + +QT_END_NAMESPACE + +#endif // QLOCKFILE_H diff --git a/src/core/qlockfile_p.h b/src/core/qlockfile_p.h new file mode 100644 index 000000000..93092683f --- /dev/null +++ b/src/core/qlockfile_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOCKFILE_P_H +#define QLOCKFILE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qlockfile.h" + +#include +#include + +#ifdef Q_OS_WIN +#include +#endif + +QT_BEGIN_NAMESPACE + +class QLockFileThread : public QThread +{ +public: + static void msleep(unsigned long msecs) { QThread::msleep(msecs); } +}; + +class QLockFilePrivate +{ +public: + QLockFilePrivate(const QString &fn) + : fileName(fn), +#ifdef Q_OS_WIN + fileHandle(INVALID_HANDLE_VALUE), +#else + fileHandle(-1), +#endif + staleLockTime(30 * 1000), // 30 seconds + lockError(QLockFile::NoError), + isLocked(false) + { + } + QLockFile::LockError tryLock_sys(); + bool removeStaleLock(); + bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const; + // Returns \c true if the lock belongs to dead PID, or is old. + // The attempt to delete it will tell us if it was really stale or not, though. + bool isApparentlyStale() const; + +#ifdef Q_OS_UNIX + static int checkFcntlWorksAfterFlock(); +#endif + + QString fileName; +#ifdef Q_OS_WIN + Qt::HANDLE fileHandle; +#else + int fileHandle; +#endif + int staleLockTime; // "int milliseconds" is big enough for 24 days + QLockFile::LockError lockError; + bool isLocked; +}; + +QT_END_NAMESPACE + +#endif /* QLOCKFILE_P_H */ diff --git a/src/core/qlockfile_unix.cpp b/src/core/qlockfile_unix.cpp new file mode 100644 index 000000000..99678c0ba --- /dev/null +++ b/src/core/qlockfile_unix.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlockfile_p.h" + +#include +#include +#include +#include +#include + +#include // flock +#include // kill +#include // kill +#include + +#include + +QT_BEGIN_NAMESPACE + +#define EINTR_LOOP(var, cmd) \ + do { \ + var = cmd; \ + } while (var == -1 && errno == EINTR) + +// don't call QT_OPEN or ::open +// call qt_safe_open +static inline int qt_safe_open(const char *pathname, int flags, mode_t mode = 0777) +{ +#ifdef O_CLOEXEC + flags |= O_CLOEXEC; +#endif + int fd; + EINTR_LOOP(fd, ::open(pathname, flags, mode)); + + // unknown flags are ignored, so we have no way of verifying if + // O_CLOEXEC was accepted + if (fd != -1) + ::fcntl(fd, F_SETFD, FD_CLOEXEC); + return fd; +} + +static inline qint64 qt_safe_write(int fd, const void *data, qint64 len) +{ + qint64 ret = 0; + EINTR_LOOP(ret, ::write(fd, data, len)); + return ret; +} + +static QString localHostName() // from QHostInfo::localHostName() +{ + char hostName[512]; + if (gethostname(hostName, sizeof(hostName)) == -1) + return QString(); + hostName[sizeof(hostName) - 1] = '\0'; + return QString::fromLocal8Bit(hostName); +} + +// ### merge into qt_safe_write? +static qint64 qt_write_loop(int fd, const char *data, qint64 len) +{ + qint64 pos = 0; + while (pos < len) { + const qint64 ret = qt_safe_write(fd, data + pos, len - pos); + if (ret == -1) // e.g. partition full + return pos; + pos += ret; + } + return pos; +} + +static bool setNativeLocks(int fd) +{ +#if defined(LOCK_EX) && defined(LOCK_NB) + if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs + return false; +#endif + struct flock flockData; + flockData.l_type = F_WRLCK; + flockData.l_whence = SEEK_SET; + flockData.l_start = 0; + flockData.l_len = 0; // 0 = entire file + flockData.l_pid = getpid(); + if (fcntl(fd, F_SETLK, &flockData) == -1) // for networked filesystems + return false; + return true; +} + +QLockFile::LockError QLockFilePrivate::tryLock_sys() +{ + // Assemble data, to write in a single call to write + // (otherwise we'd have to check every write call) + // Use operator% from the fast builder to avoid multiple memory allocations. + QByteArray fileData = QByteArray::number(QCoreApplication::applicationPid()) + '\n' + + qAppName().toUtf8() + '\n' + + localHostName().toUtf8() + '\n'; + + const QByteArray lockFileName = QFile::encodeName(fileName); + const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd < 0) { + switch (errno) { + case EEXIST: + return QLockFile::LockFailedError; + case EACCES: + case EROFS: + return QLockFile::PermissionError; + default: + return QLockFile::UnknownError; + } + } + // Ensure nobody else can delete the file while we have it + if (!setNativeLocks(fd)) + qWarning() << "setNativeLocks failed:" << strerror(errno); + + if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) { + close(fd); + if (!QFile::remove(fileName)) + qWarning("QLockFile: Could not remove our own lock file %s.", qPrintable(fileName)); + return QLockFile::UnknownError; // partition full + } + + // We hold the lock, continue. + fileHandle = fd; + + return QLockFile::NoError; +} + +bool QLockFilePrivate::removeStaleLock() +{ + const QByteArray lockFileName = QFile::encodeName(fileName); + const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY, 0644); + if (fd < 0) // gone already? + return false; + bool success = setNativeLocks(fd) && (::unlink(lockFileName) == 0); + close(fd); + return success; +} + +bool QLockFilePrivate::isApparentlyStale() const +{ + qint64 pid; + QString hostname, appname; + if (!getLockInfo(&pid, &hostname, &appname)) + return false; + if (hostname.isEmpty() || hostname == localHostName()) { + if (::kill(pid, 0) == -1 && errno == ESRCH) + return true; // PID doesn't exist anymore + } + const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime()); + return staleLockTime > 0 && age > staleLockTime; +} + +void QLockFile::unlock() +{ + Q_D(QLockFile); + if (!d->isLocked) + return; + close(d->fileHandle); + d->fileHandle = -1; + if (!QFile::remove(d->fileName)) { + qWarning() << "Could not remove our own lock file" << d->fileName << "maybe permissions changed meanwhile?"; + // This is bad because other users of this lock file will now have to wait for the stale-lock-timeout... + } + QFile::remove(d->fileName); + d->lockError = QLockFile::NoError; + d->isLocked = false; +} + +QT_END_NAMESPACE diff --git a/src/core/qlockfile_win.cpp b/src/core/qlockfile_win.cpp new file mode 100644 index 000000000..4ca07eb7e --- /dev/null +++ b/src/core/qlockfile_win.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef _UNICODE +#define _UNICODE +#endif + +#ifndef UNICODE +#define UNICODE +#endif + +#include "qlockfile_p.h" + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static inline QByteArray localHostName() +{ + return qgetenv("COMPUTERNAME"); +} + +static inline bool fileExists(const wchar_t *fileName) +{ + WIN32_FILE_ATTRIBUTE_DATA data; + return GetFileAttributesEx(fileName, GetFileExInfoStandard, &data); +} + +QLockFile::LockError QLockFilePrivate::tryLock_sys() +{ + const ushort* nativePath = QDir::toNativeSeparators(fileName).utf16(); + // When writing, allow others to read. + // When reading, QFile will allow others to read and write, all good. + // Adding FILE_SHARE_DELETE would allow forceful deletion of stale files, + // but Windows doesn't allow recreating it while this handle is open anyway, + // so this would only create confusion (can't lock, but no lock file to read from). + const DWORD dwShareMode = FILE_SHARE_READ; +#ifndef Q_OS_WINRT + SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE }; + HANDLE fh = CreateFile((const wchar_t*)nativePath, + GENERIC_WRITE, + dwShareMode, + &securityAtts, + CREATE_NEW, // error if already exists + FILE_ATTRIBUTE_NORMAL, + NULL); +#else // !Q_OS_WINRT + HANDLE fh = CreateFile2((const wchar_t*)nativePath, + GENERIC_WRITE, + dwShareMode, + CREATE_NEW, // error if already exists + NULL); +#endif // Q_OS_WINRT + if (fh == INVALID_HANDLE_VALUE) { + const DWORD lastError = GetLastError(); + switch (lastError) { + case ERROR_SHARING_VIOLATION: + case ERROR_ALREADY_EXISTS: + case ERROR_FILE_EXISTS: + return QLockFile::LockFailedError; + case ERROR_ACCESS_DENIED: + // readonly file, or file still in use by another process. + // Assume the latter if the file exists, since we don't create it readonly. + return fileExists((const wchar_t*)nativePath) + ? QLockFile::LockFailedError + : QLockFile::PermissionError; + default: + qWarning() << "Got unexpected locking error" << lastError; + return QLockFile::UnknownError; + } + } + + // We hold the lock, continue. + fileHandle = fh; + // Assemble data, to write in a single call to write + // (otherwise we'd have to check every write call) + QByteArray fileData; + fileData += QByteArray::number(QCoreApplication::applicationPid()); + fileData += '\n'; + fileData += QCoreApplication::applicationName().toUtf8(); + fileData += '\n'; + fileData += localHostName(); + fileData += '\n'; + DWORD bytesWritten = 0; + QLockFile::LockError error = QLockFile::NoError; + if (!WriteFile(fh, fileData.constData(), fileData.size(), &bytesWritten, NULL) || !FlushFileBuffers(fh)) + error = QLockFile::UnknownError; // partition full + return error; +} + +bool QLockFilePrivate::removeStaleLock() +{ + // QFile::remove fails on Windows if the other process is still using the file, so it's not stale. + return QFile::remove(fileName); +} + +bool QLockFilePrivate::isApparentlyStale() const +{ + qint64 pid; + QString hostname, appname; + if (!getLockInfo(&pid, &hostname, &appname)) + return false; + + // On WinRT there seems to be no way of obtaining information about other + // processes due to sandboxing +#ifndef Q_OS_WINRT + if (hostname == QString::fromLocal8Bit(localHostName())) { + HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); + if (!procHandle) + return true; + // We got a handle but check if process is still alive + DWORD dwR = ::WaitForSingleObject(procHandle, 0); + ::CloseHandle(procHandle); + if (dwR == WAIT_TIMEOUT) + return true; + } +#endif // !Q_OS_WINRT + const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime()); + return staleLockTime > 0 && age > staleLockTime; +} + +void QLockFile::unlock() +{ + Q_D(QLockFile); + if (!d->isLocked) + return; + CloseHandle(d->fileHandle); + int attempts = 0; + static const int maxAttempts = 500; // 500ms + while (!QFile::remove(d->fileName) && ++attempts < maxAttempts) { + // Someone is reading the lock file right now (on Windows this prevents deleting it). + QLockFileThread::msleep(1); + } + if (attempts == maxAttempts) { + qWarning() << "Could not remove our own lock file" << d->fileName << ". Either other users of the lock file are reading it constantly for 500 ms, or we (no longer) have permissions to delete the file"; + // This is bad because other users of this lock file will now have to wait for the stale-lock-timeout... + } + d->lockError = QLockFile::NoError; + d->isLocked = false; +} + +QT_END_NAMESPACE diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp index f55269fd5..dbf458e0b 100644 --- a/src/gui/DatabaseTabWidget.cpp +++ b/src/gui/DatabaseTabWidget.cpp @@ -36,6 +36,7 @@ DatabaseManagerStruct::DatabaseManagerStruct() : dbWidget(Q_NULLPTR) + , lockFile(Q_NULLPTR) , saveToFilename(false) , modified(false) , readOnly(false) @@ -142,8 +143,35 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw, } file.close(); + QLockFile* lockFile = new QLockFile(QString("%1/.%2.lock").arg(fileInfo.canonicalPath(), fileInfo.fileName())); + lockFile->setStaleLockTime(0); + + if (!dbStruct.readOnly && !lockFile->tryLock()) { + // for now silently ignore if we can't create a lock file + // due to lack of permissions + if (lockFile->error() != QLockFile::PermissionError) { + QMessageBox::StandardButton result = MessageBox::question(this, tr("Open database"), + tr("The database you are trying to open is locked by another instance of KeePassX.\n" + "Do you want to open it anyway? Alternatively the database is opened read-only."), + QMessageBox::Yes | QMessageBox::No); + + if (result == QMessageBox::No) { + dbStruct.readOnly = true; + delete lockFile; + lockFile = Q_NULLPTR; + } + else { + // take over the lock file if possible + if (lockFile->removeStaleLockFile()) { + lockFile->tryLock(); + } + } + } + } + Database* db = new Database(); dbStruct.dbWidget = new DatabaseWidget(db, this); + dbStruct.lockFile = lockFile; dbStruct.saveToFilename = !dbStruct.readOnly; dbStruct.filePath = fileInfo.absoluteFilePath(); @@ -238,6 +266,7 @@ void DatabaseTabWidget::deleteDatabase(Database* db) removeTab(index); toggleTabbar(); m_dbList.remove(db); + delete dbStruct.lockFile; delete dbStruct.dbWidget; delete db; @@ -311,6 +340,11 @@ bool DatabaseTabWidget::saveDatabaseAs(Database* db) dbStruct.canonicalFilePath = fileInfo.canonicalFilePath(); dbStruct.fileName = fileInfo.fileName(); dbStruct.dbWidget->updateFilename(dbStruct.filePath); + QString lockFileName = QString("%1/.%2.lock") + .arg(fileInfo.canonicalPath(), fileInfo.fileName()); + dbStruct.lockFile = new QLockFile(lockFileName); + dbStruct.lockFile->setStaleLockTime(0); + dbStruct.lockFile->tryLock(); updateTabName(db); updateLastDatabases(dbStruct.filePath); return true; diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h index dc5f1b139..2ad3010df 100644 --- a/src/gui/DatabaseTabWidget.h +++ b/src/gui/DatabaseTabWidget.h @@ -21,6 +21,7 @@ #include #include +#include "core/qlockfile.h" #include "format/KeePass2Writer.h" #include "gui/DatabaseWidget.h" @@ -34,6 +35,7 @@ struct DatabaseManagerStruct DatabaseManagerStruct(); DatabaseWidget* dbWidget; + QLockFile* lockFile; QString filePath; QString canonicalFilePath; QString fileName; From eeb940c0dc7d4b888564d82734c27f25ae94981a Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Tue, 12 May 2015 22:20:42 +0200 Subject: [PATCH 11/20] Fix plugin path detection when installed with DESTDIR. This is in no way perfect but should cover most common cases. Closes #291 --- CMakeLists.txt | 6 +++--- src/config-keepassx.h.cmake | 2 +- src/core/FilePath.cpp | 32 ++++++++++++++++++++++---------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bc6e316f..400c68480 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,9 +149,9 @@ elseif(APPLE) else() include(GNUInstallDirs) - set(BIN_INSTALL_DIR "${CMAKE_INSTALL_FULL_BINDIR}") - set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/keepassx") - set(DATA_INSTALL_DIR "${CMAKE_INSTALL_FULL_DATADIR}/keepassx") + set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}") + set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/keepassx") + set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassx") endif() if(WITH_TESTS) diff --git a/src/config-keepassx.h.cmake b/src/config-keepassx.h.cmake index 305da341f..197c0d328 100644 --- a/src/config-keepassx.h.cmake +++ b/src/config-keepassx.h.cmake @@ -8,8 +8,8 @@ #define KEEPASSX_SOURCE_DIR "${CMAKE_SOURCE_DIR}" #define KEEPASSX_BINARY_DIR "${CMAKE_BINARY_DIR}" +#define KEEPASSX_PREFIX_DIR "${CMAKE_INSTALL_PREFIX}" #define KEEPASSX_PLUGIN_DIR "${PLUGIN_INSTALL_DIR}" - #define KEEPASSX_DATA_DIR "${DATA_INSTALL_DIR}" #cmakedefine HAVE_PR_SET_DUMPABLE 1 diff --git a/src/core/FilePath.cpp b/src/core/FilePath.cpp index e414fdec1..5366fe5c7 100644 --- a/src/core/FilePath.cpp +++ b/src/core/FilePath.cpp @@ -49,13 +49,20 @@ QString FilePath::pluginPath(const QString& name) pluginPaths << QCoreApplication::applicationDirPath(); - QString systemPluginDir = KEEPASSX_PLUGIN_DIR; - if (systemPluginDir != ".") { - if (!QDir(systemPluginDir).isAbsolute()) { - systemPluginDir = QCoreApplication::applicationDirPath() + "/../" + systemPluginDir; - systemPluginDir = QDir(systemPluginDir).canonicalPath(); + QString configuredPluginDir = KEEPASSX_PLUGIN_DIR; + if (configuredPluginDir != ".") { + if (QDir(configuredPluginDir).isAbsolute()) { + pluginPaths << configuredPluginDir; + } + else { + QString relativePluginDir = QString("%1/../%2") + .arg(QCoreApplication::applicationDirPath(), configuredPluginDir); + pluginPaths << QDir(relativePluginDir).canonicalPath(); + + QString absolutePluginDir = QString("%1/%2") + .arg(KEEPASSX_PREFIX_DIR, configuredPluginDir); + pluginPaths << QDir(absolutePluginDir).canonicalPath(); } - pluginPaths << systemPluginDir; } QStringList dirFilter; @@ -164,6 +171,9 @@ QIcon FilePath::onOffIcon(const QString& category, const QString& name) FilePath::FilePath() { + const QString appDirPath = QCoreApplication::applicationDirPath(); + bool isDataDirAbsolute = QDir::isAbsolutePath(KEEPASSX_DATA_DIR); + if (false) { } #ifdef QT_DEBUG @@ -171,17 +181,19 @@ FilePath::FilePath() } #endif #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) - else if (testSetDir(QCoreApplication::applicationDirPath() + "/../share/keepassx")) { + else if (isDataDirAbsolute && testSetDir(KEEPASSX_DATA_DIR)) { } - else if (testSetDir(KEEPASSX_DATA_DIR)) { + else if (!isDataDirAbsolute && testSetDir(QString("%1/../%2").arg(appDirPath, KEEPASSX_DATA_DIR))) { + } + else if (!isDataDirAbsolute && testSetDir(QString("%1/%2").arg(KEEPASSX_PREFIX_DIR, KEEPASSX_DATA_DIR))) { } #endif #ifdef Q_OS_MAC - else if (testSetDir(QCoreApplication::applicationDirPath() + "/../Resources")) { + else if (testSetDir(appDirPath + "/../Resources")) { } #endif #ifdef Q_OS_WIN - else if (testSetDir(QCoreApplication::applicationDirPath() + "/share")) { + else if (testSetDir(appDirPath + "/share")) { } #endif From c9d007fcdf8fb81336f1667d76e964516c40707b Mon Sep 17 00:00:00 2001 From: Amir Pakdel Date: Tue, 12 May 2015 16:31:14 -0400 Subject: [PATCH 12/20] Always clearing ChangeMasterKeyWidget.m_key --- src/gui/ChangeMasterKeyWidget.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/gui/ChangeMasterKeyWidget.cpp b/src/gui/ChangeMasterKeyWidget.cpp index ee0c8b106..3e346bc10 100644 --- a/src/gui/ChangeMasterKeyWidget.cpp +++ b/src/gui/ChangeMasterKeyWidget.cpp @@ -96,7 +96,7 @@ QLabel* ChangeMasterKeyWidget::headlineLabel() void ChangeMasterKeyWidget::generateKey() { - bool cleared = false; + m_key.clear(); if (m_ui->passwordGroup->isChecked()) { if (m_ui->enterPasswordEdit->text() == m_ui->repeatPasswordEdit->text()) { @@ -107,8 +107,6 @@ void ChangeMasterKeyWidget::generateKey() return; } } - m_key.clear(); - cleared = true; m_key.addKey(PasswordKey(m_ui->enterPasswordEdit->text())); } else { @@ -127,7 +125,6 @@ void ChangeMasterKeyWidget::generateKey() .arg(m_ui->keyFileCombo->currentText(), errorMsg)); return; } - if (!cleared) m_key.clear(); m_key.addKey(fileKey); } From 5d9039ea894d6435b0491a287b9e44584ce39808 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Tue, 12 May 2015 23:40:02 +0200 Subject: [PATCH 13/20] Silence compiler warning about an unused variable. --- src/autotype/AutoTypeAction.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/autotype/AutoTypeAction.cpp b/src/autotype/AutoTypeAction.cpp index cc751abe0..090ca8234 100644 --- a/src/autotype/AutoTypeAction.cpp +++ b/src/autotype/AutoTypeAction.cpp @@ -89,5 +89,7 @@ void AutoTypeExecutor::execDelay(AutoTypeDelay* action) void AutoTypeExecutor::execClearField(AutoTypeClearField* action) { + Q_UNUSED(action); + // TODO: implement } From 68373730bfb3f563f88e66432600f7671dc70ddd Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Tue, 12 May 2015 23:57:36 +0200 Subject: [PATCH 14/20] Fix compiler warnings where keysyms are printed. %lX expectes unsigned long which KeySym is an alias for. --- src/autotype/x11/AutoTypeX11.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autotype/x11/AutoTypeX11.cpp b/src/autotype/x11/AutoTypeX11.cpp index 47ab9127a..ddcf809ee 100644 --- a/src/autotype/x11/AutoTypeX11.cpp +++ b/src/autotype/x11/AutoTypeX11.cpp @@ -655,7 +655,7 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym) int keycode; if (keysym == NoSymbol) { - qWarning("No such key: keysym=0x%lX", static_cast(keysym)); + qWarning("No such key: keysym=0x%lX", keysym); return; } @@ -682,7 +682,7 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym) /* determine keycode and mask for the given keysym */ keycode = GetKeycode(keysym, &wanted_mask); if (keycode < 8 || keycode > 255) { - qWarning("Unable to get valid keycode for key: keysym=0x%lX", static_cast(keysym)); + qWarning("Unable to get valid keycode for key: keysym=0x%lX", keysym); return; } From a8bf6a97824ac7a7e69cbdb4c1caaa24b2e64cd8 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Thu, 14 May 2015 12:58:00 +0200 Subject: [PATCH 15/20] Refactor Tools::disableCoreDumps(). - Use all available methods. - Don't print a warning when no method is implmeneted on the platform. --- src/core/Tools.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp index 01ec316e2..8034417f0 100644 --- a/src/core/Tools.cpp +++ b/src/core/Tools.cpp @@ -39,10 +39,12 @@ #include "config-keepassx.h" +#if defined(HAVE_RLIMIT_CORE) +#include +#endif + #if defined(HAVE_PR_SET_DUMPABLE) #include -#elif defined(HAVE_RLIMIT_CORE) -#include #endif #ifdef HAVE_PT_DENY_ATTACH @@ -222,21 +224,23 @@ QString platform() void disableCoreDumps() { - bool success = false; + // default to true + // there is no point in printing a warning if this is not implemented on the platform + bool success = true; - // prefer PR_SET_DUMPABLE since that also prevents ptrace -#if defined(HAVE_PR_SET_DUMPABLE) - success = (prctl(PR_SET_DUMPABLE, 0) == 0); -#elif defined(HAVE_RLIMIT_CORE) +#if defined(HAVE_RLIMIT_CORE) struct rlimit limit; limit.rlim_cur = 0; limit.rlim_max = 0; - success = (setrlimit(RLIMIT_CORE, &limit) == 0); + success = success && (setrlimit(RLIMIT_CORE, &limit) == 0); +#endif + +#if defined(HAVE_PR_SET_DUMPABLE) + success = success && (prctl(PR_SET_DUMPABLE, 0) == 0); #endif // Mac OS X #ifdef HAVE_PT_DENY_ATTACH - // make sure setrlimit() and ptrace() succeeded success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0); #endif From c53573685366e47ad1c6e748b1c3038f9ce1eee6 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Thu, 14 May 2015 12:59:36 +0200 Subject: [PATCH 16/20] Add GUI for changing default group auto-type sequence. Closes #175 --- src/gui/group/EditGroupWidget.cpp | 16 ++++++++ src/gui/group/EditGroupWidgetMain.ui | 57 ++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp index 092af4a06..ebe5cb970 100644 --- a/src/gui/group/EditGroupWidget.cpp +++ b/src/gui/group/EditGroupWidget.cpp @@ -38,6 +38,8 @@ EditGroupWidget::EditGroupWidget(QWidget* parent) add(tr("Properties"), m_editWidgetProperties); connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool))); + connect(m_mainUi->autoTypeSequenceCustomRadio, SIGNAL(toggled(bool)), + m_mainUi->autoTypeSequenceCustomEdit, SLOT(setEnabled(bool))); connect(this, SIGNAL(accepted()), SLOT(save())); connect(this, SIGNAL(rejected()), SLOT(cancel())); @@ -74,6 +76,13 @@ void EditGroupWidget::loadGroup(Group* group, bool create, Database* database) m_mainUi->expireDatePicker->setDateTime(group->timeInfo().expiryTime().toLocalTime()); m_mainUi->searchComboBox->setCurrentIndex(indexFromTriState(group->searchingEnabled())); m_mainUi->autotypeComboBox->setCurrentIndex(indexFromTriState(group->autoTypeEnabled())); + if (group->defaultAutoTypeSequence().isEmpty()) { + m_mainUi->autoTypeSequenceInherit->setChecked(true); + } + else { + m_mainUi->autoTypeSequenceCustomRadio->setChecked(true); + } + m_mainUi->autoTypeSequenceCustomEdit->setText(group->defaultAutoTypeSequence()); IconStruct iconStruct; iconStruct.uuid = group->iconUuid(); @@ -97,6 +106,13 @@ void EditGroupWidget::save() m_group->setSearchingEnabled(triStateFromIndex(m_mainUi->searchComboBox->currentIndex())); m_group->setAutoTypeEnabled(triStateFromIndex(m_mainUi->autotypeComboBox->currentIndex())); + if (m_mainUi->autoTypeSequenceInherit->isChecked()) { + m_group->setDefaultAutoTypeSequence(QString()); + } + else { + m_group->setDefaultAutoTypeSequence(m_mainUi->autoTypeSequenceCustomEdit->text()); + } + IconStruct iconStruct = m_editGroupWidgetIcons->save(); if (iconStruct.number < 0) { diff --git a/src/gui/group/EditGroupWidgetMain.ui b/src/gui/group/EditGroupWidgetMain.ui index fdbf054d0..b8abf762c 100644 --- a/src/gui/group/EditGroupWidgetMain.ui +++ b/src/gui/group/EditGroupWidgetMain.ui @@ -7,7 +7,7 @@ 0 0 676 - 334 + 356 @@ -36,6 +36,13 @@ + + + + Expires + + + @@ -46,13 +53,6 @@ - - - - Expires - - - @@ -73,6 +73,47 @@ + + + + Use default auto-type sequence of parent group + + + + + + + Set default auto-type sequence + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 1 + + + + + + + + false + + + + + From 7a2c02f0df874e92ae8928008d7520c44a7a8674 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Thu, 14 May 2015 16:58:53 +0200 Subject: [PATCH 17/20] Initialize some instance variables in ctor. Discovered by Coverity. Most likely doesn't fix any actual bug but better be safe. --- src/format/KeePass1Reader.cpp | 7 ++++++- src/format/KeePass2Reader.cpp | 6 +++++- src/format/KeePass2XmlReader.cpp | 1 + src/streams/SymmetricCipherStream.cpp | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp index a15c832b3..929632bf0 100644 --- a/src/format/KeePass1Reader.cpp +++ b/src/format/KeePass1Reader.cpp @@ -49,7 +49,12 @@ private: KeePass1Reader::KeePass1Reader() - : m_error(false) + : m_db(Q_NULLPTR) + , m_tmpParent(Q_NULLPTR) + , m_device(Q_NULLPTR) + , m_encryptionFlags(0) + , m_transformRounds(0) + , m_error(false) { } diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 20d20d862..2a25001c6 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -33,8 +33,12 @@ #include "streams/SymmetricCipherStream.h" KeePass2Reader::KeePass2Reader() - : m_error(false) + : m_device(Q_NULLPTR) + , m_headerStream(Q_NULLPTR) + , m_error(false) + , m_headerEnd(false) , m_saveXml(false) + , m_db(Q_NULLPTR) { } diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp index ed5715cbe..9af89db77 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/KeePass2XmlReader.cpp @@ -34,6 +34,7 @@ KeePass2XmlReader::KeePass2XmlReader() : m_randomStream(Q_NULLPTR) , m_db(Q_NULLPTR) , m_meta(Q_NULLPTR) + , m_tmpParent(Q_NULLPTR) , m_error(false) , m_strictMode(false) { diff --git a/src/streams/SymmetricCipherStream.cpp b/src/streams/SymmetricCipherStream.cpp index 28525b39f..c713e5220 100644 --- a/src/streams/SymmetricCipherStream.cpp +++ b/src/streams/SymmetricCipherStream.cpp @@ -24,6 +24,7 @@ SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice, SymmetricCip , m_bufferPos(0) , m_bufferFilling(false) , m_error(false) + , m_isInitalized(false) { } From 7db9c788551ecc8fc9087ca1161f9d7625762187 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Thu, 14 May 2015 20:44:17 +0200 Subject: [PATCH 18/20] Ignore libgcrypt errors in CryptoHash::CryptoHash(). Postponed until after 2.0 when I'll use excpetions. Should be safe as we check basic functioning in Crypto::testSha256(). --- src/crypto/CryptoHash.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp index f175b7754..d116451fc 100644 --- a/src/crypto/CryptoHash.cpp +++ b/src/crypto/CryptoHash.cpp @@ -49,6 +49,7 @@ CryptoHash::CryptoHash(CryptoHash::Algorithm algo) gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, 0); Q_ASSERT(error == 0); // TODO: error handling + Q_UNUSED(error); d->hashLen = gcry_md_get_algo_dlen(algoGcrypt); } From d553698b20a420e34d7eb2a0536c2ed749f0d290 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Thu, 14 May 2015 20:46:59 +0200 Subject: [PATCH 19/20] Travis CI: Pass --output-on-failure to ctest. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 15af85bf8..6e26860f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,6 @@ before_script: mkdir build && pushd build script: - cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_GUI_TESTS=ON .. - make - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui"; fi - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui"; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui --output-on-failure"; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui --output-on-failure"; fi - if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test; fi From fc43aa17174b5626a82f643d2dec49f71b4fc7f3 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Fri, 15 May 2015 00:20:23 +0200 Subject: [PATCH 20/20] Update translations. --- share/translations/keepassx_cs.ts | 1205 ++++++++++++++++++++++++++ share/translations/keepassx_da.ts | 1202 +++++++++++++++++++++++++ share/translations/keepassx_de.ts | 35 +- share/translations/keepassx_en.ts | 86 +- share/translations/keepassx_es.ts | 1205 ++++++++++++++++++++++++++ share/translations/keepassx_fr.ts | 1205 ++++++++++++++++++++++++++ share/translations/keepassx_id.ts | 1205 ++++++++++++++++++++++++++ share/translations/keepassx_nl_NL.ts | 31 +- share/translations/keepassx_ru.ts | 1205 ++++++++++++++++++++++++++ share/translations/keepassx_sv.ts | 31 +- share/translations/keepassx_zh_TW.ts | 1203 +++++++++++++++++++++++++ share/translations/update.sh | 4 + 12 files changed, 8593 insertions(+), 24 deletions(-) create mode 100644 share/translations/keepassx_cs.ts create mode 100644 share/translations/keepassx_da.ts create mode 100644 share/translations/keepassx_es.ts create mode 100644 share/translations/keepassx_fr.ts create mode 100644 share/translations/keepassx_id.ts create mode 100644 share/translations/keepassx_ru.ts create mode 100644 share/translations/keepassx_zh_TW.ts diff --git a/share/translations/keepassx_cs.ts b/share/translations/keepassx_cs.ts new file mode 100644 index 000000000..0ac580fe7 --- /dev/null +++ b/share/translations/keepassx_cs.ts @@ -0,0 +1,1205 @@ + + + AboutDialog + + About KeePassX + O aplikaci KeePassX + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + KeePassX je šířeno za podmínek licence GNU General Public License (GPL) verze 2 a (případně) 3. + + + + AutoType + + Auto-Type - KeePassX + Samočinné vyplňování – KeePassX + + + Couldn't find an entry that matches the window title: + Nelze nalézt položku, která by se shodovala s titulkem okna: + + + + AutoTypeAssociationsModel + + Window + Okno + + + Sequence + Posloupnost + + + Default sequence + Výchozí posloupnost + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + Samočinné vyplňování – KeePassX + + + Select entry to Auto-Type: + Vyberte položku pro samočinné vyplňování: + + + + ChangeMasterKeyWidget + + Password + Heslo + + + Enter password: + Zadejte heslo: + + + Repeat password: + Zopakujte heslo: + + + Key file + Soubor s klíčem + + + Browse + Procházet + + + Create + Vytvořit + + + Key files + Soubory s klíči + + + All files + Veškeré soubory + + + Create Key File... + Vytvořit soubor s klíčem… + + + Error + Chyba + + + Unable to create Key File : + Nelze vytvořit soubor s klíčem : + + + Select a key file + Vyberte soubor s klíčem + + + Question + Otázka + + + Do you really want to use an empty string as password? + Opravdu chcete heslo ponechat nevyplněné? + + + Different passwords supplied. + Zadání hesla nejsou shodná. + + + + DatabaseOpenWidget + + Enter master key + Zadejte hlavní klíč + + + Key File: + Soubor s klíčem: + + + Password: + Heslo: + + + Browse + Procházet + + + Error + Chyba + + + Unable to open the database. + Databázi nebylo možné otevřít. + + + Can't open key file + Soubor s klíčem nelze otevřít. + + + All files + Veškeré soubory + + + Key files + Soubory s klíči + + + Select key file + Vyberte soubor s klíčem + + + + DatabaseSettingsWidget + + Database name: + Název databáze: + + + Database description: + Popis databáze: + + + Transform rounds: + Počet průchodů: + + + Default username: + Výchozí uživatelské jméno: + + + Use recycle bin: + Použít Koš: + + + MiB + + + + Benchmark + Test výkonu + + + Max. history items: + Nejvyšší umožněný položek historie: + + + Max. history size: + Nejvyšší umožněná velikost historie: + + + + DatabaseTabWidget + + Root + Kořen + + + KeePass 2 Database + Databáze aplikace KeePass 2 + + + All files + Veškeré soubory + + + Open database + Otevřít databázi + + + Warning + Varování + + + File not found! + Soubor nebyl nalezen! + + + Open KeePass 1 database + Otevřít databázi aplikace KeePass 1 + + + KeePass 1 database + Databáze aplikace KeePass 1 + + + All files (*) + Veškeré soubory (*) + + + Close? + Zavřít? + + + "%1" is in edit mode. +Close anyway? + %1 je upravováno. +Přesto zavřít? + + + Save changes? + Uložit změny? + + + "%1" was modified. +Save changes? + %1 bylo změněno. +Uložit změny? + + + Error + Chyba + + + Writing the database failed. + Zápis do databáze se nezdařil. + + + Save database as + Uložit databázi jako + + + New database + Nová databáze + + + locked + zamčeno + + + + DatabaseWidget + + Change master key + Změnit hlavní klíč + + + Delete entry? + Smazat položku? + + + Do you really want to delete the entry "%1" for good? + Opravdu chcete smazat položku %1? + + + Delete entries? + Smazat položky? + + + Do you really want to delete %1 entries for good? + Opravdu chcete smazat %1 položek? + + + Move entries to recycle bin? + Přesunout položky do Koše? + + + Do you really want to move %n entry(s) to the recycle bin? + + + + Delete group? + Smazat skupinu? + + + Do you really want to delete the group "%1" for good? + Opravdu chcete smazat skupinu %1? + + + Current group + Stávající skupina + + + + EditEntryWidget + + Entry + Položka + + + Advanced + Pokročilé + + + Icon + Ikona + + + Auto-Type + Samočinné vyplňování + + + Properties + Vlastnosti + + + History + Historie + + + Entry history + Historie položky + + + Add entry + Přidat položku + + + Edit entry + Upravit položku + + + Error + Chyba + + + Different passwords supplied. + Zadání hesla se neshodují. + + + New attribute + Nový atribut + + + Select file + Vyberte soubor + + + Unable to open file + Soubor nelze otevřít + + + Save attachment + Uložit přílohu + + + Unable to save the attachment: + + Přílohu nelze uložit: + + + + Tomorrow + Zítra + + + %n week(s) + + + + %n month(s) + + + + 1 year + 1 rok + + + + EditEntryWidgetAdvanced + + Additional attributes + Další atributy + + + Add + Přidat + + + Edit + Upravit + + + Remove + Odebrat + + + Attachments + Přílohy + + + Save + Uložit + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + Zapnout samočinné vyplňování u této položky + + + Inherit default Auto-Type sequence from the group + Převzít výchozí posloupnost samočinného vyplňování ze skupiny + + + Use custom Auto-Type sequence: + Použít vlastní posloupnost samočinného vyplňování: + + + + + + + + - + + + + Window title: + Titulek okna: + + + Use default sequence + Použít výchozí posloupnost + + + Set custom sequence: + Nastavit vlastní posloupnost: + + + + EditEntryWidgetHistory + + Show + Zobrazit + + + Restore + Obnovit + + + Delete + Smazat + + + Delete all + Smazat vše + + + + EditEntryWidgetMain + + Title: + Titulek: + + + Username: + Uživatelské jméno: + + + Password: + Heslo: + + + Repeat: + Zopakovat: + + + Gen. + Obec. + + + URL: + URL adresa: + + + Expires + Platnost skončí: + + + Presets + Přednastavené + + + Notes: + Poznámky: + + + + EditGroupWidget + + Group + Skupina + + + Icon + Ikona + + + Properties + Vlastnosti + + + Add group + Přidat skupinu + + + Edit group + Upravit skupinu + + + Enable + Zapnout + + + Disable + Vypnout + + + Inherit from parent group (%1) + Převzít z nadřazené skupiny (%1) + + + + EditGroupWidgetMain + + Name + Název + + + Notes + Poznámky + + + Expires + Platnost skončí + + + Search + Hledat + + + Auto-type + Samočinné vyplňování + + + + EditWidgetIcons + + Use default icon + Použít výchozí ikonu + + + Use custom icon + Použít vlastní ikonu + + + Add custom icon + Přidat vlastní ikonu + + + Delete custom icon + Smazat vlastní ikonu + + + Images + Obrázky + + + All files + Veškeré soubory + + + Select Image + Vyberte obrázek + + + Can't delete icon! + Ikonu nelze smazat! + + + Can't delete icon. Still used by %n item(s). + + + + + EditWidgetProperties + + Created: + Vytvořeno: + + + Modified: + Čas poslední změny: + + + Accessed: + Čas posledního přístupu: + + + Uuid: + Uuid identifikátor: + + + + EntryAttributesModel + + Name + Název + + + + EntryHistoryModel + + Last modified + Čas poslední změny + + + Title + Titulek + + + Username + Uživatelské jméno + + + URL + URL adresa + + + + EntryModel + + Group + Skupina + + + Title + Titulek + + + Username + Uživatelské jméno + + + URL + URL adresa + + + + Group + + Recycle Bin + Koš + + + + KeePass1OpenWidget + + Import KeePass1 database + Importovat databázi aplikace KeePass 1 + + + Error + Chyba + + + Unable to open the database. + Databázi nelze otevřít. + + + + KeePass1Reader + + Unable to read keyfile. + Soubor s klíčem nebylo možné načíst. + + + Not a KeePass database. + Nejedná se o databázi Keepass. + + + Unsupported encryption algorithm. + Nepodporovaný šifrovací algoritmus. + + + Unsupported KeePass database version. + Nepodporovaná verze databáze KeePass. + + + Root + Kořen + + + + KeePass2Reader + + Not a KeePass database. + Nejedná se o databázi KeePass. + + + Unsupported KeePass database version. + Nepodporovaná verze databáze KeePass. + + + Wrong key or database file is corrupt. + Chybný klíč nebo je databáze poškozená. + + + + Main + + Fatal error while testing the cryptographic functions. + Při testování šifrovacích funkcí došlo k fatální chybě. + + + KeePassX - Error + KeePassX – chyba + + + + MainWindow + + Database + Databáze + + + Recent databases + Nedávno otevřené databáze + + + Help + Nápověda + + + Entries + Položky + + + Copy attribute to clipboard + Zkopírovat atribut do schránky + + + Groups + Skupiny + + + Extras + Doplňující + + + View + Zobrazit + + + Quit + Ukončit + + + About + O aplikaci + + + Open database + Otevřít databázi + + + Save database + Uložit databázi + + + Close database + Zavřít databázi + + + New database + Nová databáze + + + Add new entry + Přidat novou položku + + + View/Edit entry + Zobrazit/upravit položku + + + Delete entry + Smazat položku + + + Add new group + Přidat novou skupinu + + + Edit group + Upravit skupinu + + + Delete group + Smazat skupinu + + + Save database as + Uložit databázi jako + + + Change master key + Změnit hlavní klíč + + + Database settings + Nastavení databáze + + + Import KeePass 1 database + Importovat databázi aplikace KeePass 1 + + + Clone entry + Klonovat položku + + + Find + Najít + + + Username + Uživatelské jméno + + + Copy username to clipboard + Zkopírovat uživatelské jméno do schránky + + + Password + Heslo + + + Copy password to clipboard + Zkopírovat heslo do schránky + + + Settings + Nastavení + + + Perform Auto-Type + Provést samočinné vyplnění + + + Open URL + Otevřít URL adresu + + + Lock databases + Uzamknout databáze + + + Title + Titulek + + + URL + URL adresa + + + Notes + Poznámky + + + Show toolbar + Zobrazit lištu nástrojů + + + read-only + pouze pro čtení + + + Toggle window + Přepnout okno + + + + PasswordGeneratorWidget + + Password: + Heslo: + + + Length: + Délka: + + + Character Types + Typy znaků + + + Upper Case Letters + Velká písmena + + + Lower Case Letters + Malá písmena + + + Numbers + Čísla + + + Special Characters + Speciální znaky + + + Exclude look-alike characters + Vynechat podobně vypadající znaky + + + Ensure that the password contains characters from every group + Zajistit, aby heslo obsahovalo znaky ze všech zvolených skupin + + + Accept + Přijmout + + + + QCommandLineParser + + Displays version information. + Zobrazí informace o verzi. + + + Displays this help. + Zobrazí tuto nápovědu. + + + Unknown option '%1'. + Neznámá volba %1. + + + Unknown options: %1. + Neznámé volby: %1. + + + Missing value after '%1'. + Chybějící hodnota, následující za %1. + + + Unexpected value after '%1'. + Neočekávaná hodnota, následující za %1. + + + [options] + [volby] + + + Usage: %1 + Použití: %1 + + + Options: + Volby: + + + Arguments: + Argumenty: + + + + QSaveFile + + Existing file %1 is not writable + Existující soubor %1 není zapisovatelný + + + Writing canceled by application + Zápis byl zrušen aplikací + + + Partial write. Partition full? + Zápis nebylo možné dokončit zcela. Nedostatek volného místa? + + + + QtIOCompressor + + Internal zlib error when compressing: + Během komprimace se vyskytla vnitřní chyba v knihovně zlib: + + + Error writing to underlying device: + Chyba při zápisu na zařízení: + + + Error opening underlying device: + Chyba při otevírání zařízení: + + + Error reading data from underlying device: + Chyba při čtení dat ze zařízení: + + + Internal zlib error when decompressing: + Vnitřní chyba knihovny zlib při rozbalování: + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + Formát tohoto gzip archivu není podporován touto verzí knihovny zlib. + + + Internal zlib error: + Vnitřní chyba knihovny zlib: + + + + SearchWidget + + Find: + Najít: + + + Case sensitive + Rozlišovat velikost písmen + + + Current group + Stávající skupina + + + Root group + Kořenová skupina + + + + SettingsWidget + + Application Settings + Nastavení aplikace + + + General + Obecné + + + Security + Zabezpečení + + + + SettingsWidgetGeneral + + Remember last databases + Pamatovat si nedávno otevírané databáze + + + Open previous databases on startup + Při startu otevřít minule otevřenou databázi + + + Mark as modified on expanded state changes + Při změnách rozšířeného stavu označit jako změněné + + + Automatically save on exit + Při ukončování samočinně uložit + + + Automatically save after every change + Po každé změně okamžitě samočinně uložit + + + Minimize when copying to clipboard + Po zkopírování do schránky samočinně zminimalizovat aplikaci + + + Use group icon on entry creation + Při vytváření položky použít ikonu skupiny + + + Global Auto-Type shortcut + Všeobecná klávesová zkratka pro samočinné vyplňování + + + Use entry title to match windows for global auto-type + Použít titulek položky pro porovnání s okny pro všeobecné samočinné vyplňování + + + Language + Jazyk + + + Show a system tray icon + Zobrazit ikonu v oznamovací oblasti + + + Hide window to system tray when minimized + Minimalizovat do oznamovací oblasti + + + + SettingsWidgetSecurity + + Clear clipboard after + Vyčistit schránku po uplynutí + + + sec + sek + + + Lock databases after inactivity of + Uzamknout databázi při nečinnosti delší než + + + Show passwords in cleartext by default + Vždy zobrazovat hesla + + + Always ask before performing auto-type + Před provedením samočinného vyplnění se vždy dotázat + + + + UnlockDatabaseWidget + + Unlock database + Odemknout databázi + + + Error + Chyba + + + Wrong key. + Chybný klíč. + + + + WelcomeWidget + + Welcome! + Vítejte! + + + + main + + KeePassX - cross-platform password manager + KeePassX – multiplatformní správce hesel + + + filename of the password database to open (*.kdbx) + Soubor s databází hesel (*.kdbx), který otevřít + + + path to a custom config file + umístění souboru s vlastními nastaveními + + + password of the database (DANGEROUS!) + heslo databáze (NEBEZPEČNÉ!) + + + key file of the database + soubor s klíčem k databázi + + + \ No newline at end of file diff --git a/share/translations/keepassx_da.ts b/share/translations/keepassx_da.ts new file mode 100644 index 000000000..25bdd2c1c --- /dev/null +++ b/share/translations/keepassx_da.ts @@ -0,0 +1,1202 @@ + + + AboutDialog + + About KeePassX + Om KeePassX + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + KeePassX distribueres under betingelserne i GNU General Public License (GPL) version 2 eller (efter eget valg) version 3. + + + + AutoType + + Auto-Type - KeePassX + + + + Couldn't find an entry that matches the window title: + Kunne ikke finde en post, der matcher vinduets titel: + + + + AutoTypeAssociationsModel + + Window + Vindue + + + Sequence + Sekvens + + + Default sequence + Standardsekvens + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + + + + Select entry to Auto-Type: + + + + + ChangeMasterKeyWidget + + Password + Adgangskode + + + Enter password: + Indtast adgangskode + + + Repeat password: + Gentag adgangskode + + + Key file + Nøglefil + + + Browse + Gennemse + + + Create + Opret + + + Key files + Nøglefiler + + + All files + Alle filer + + + Create Key File... + Opret Nøglefil... + + + Error + Fejl + + + Unable to create Key File : + Kan ikke oprette Nøglefil : + + + Select a key file + Vælg en nøglefil + + + Question + Spørgsmål + + + Do you really want to use an empty string as password? + Vil du virkelig bruge en tom streng som adgangskode? + + + Different passwords supplied. + Andre adgangskoder leveret. + + + + DatabaseOpenWidget + + Enter master key + Indtast primærnøgle + + + Key File: + Nøglefil: + + + Password: + Adgangskode: + + + Browse + Gennemse + + + Error + Fejl + + + Unable to open the database. + Kan ikke åbne databasen. + + + Can't open key file + Kan ikke åbne nøglefil + + + All files + Alle filer + + + Key files + Nøglefiler + + + Select key file + Vælg nøglefil + + + + DatabaseSettingsWidget + + Database name: + Databasenavn: + + + Database description: + Beskrivelse af database: + + + Transform rounds: + + + + Default username: + Standard brugernavn: + + + Use recycle bin: + Brug skraldespand: + + + MiB + MB + + + Benchmark + Benchmark + + + Max. history items: + + + + Max. history size: + + + + + DatabaseTabWidget + + Root + + + + KeePass 2 Database + KeePass 2 Database + + + All files + Alle filer + + + Open database + Åben database + + + Warning + Advarsel + + + File not found! + Filen blev ikke fundet! + + + Open KeePass 1 database + Åben KeePass 1 database + + + KeePass 1 database + KeePass 1 database + + + All files (*) + Alle filer (*) + + + Close? + Luk? + + + "%1" is in edit mode. +Close anyway? + + + + Save changes? + Gem ændringer? + + + "%1" was modified. +Save changes? + + + + Error + Fejl + + + Writing the database failed. + Kan ikke skrive til databasen. + + + Save database as + Gem database som + + + New database + Ny database + + + locked + låst + + + + DatabaseWidget + + Change master key + Skift primærnøgle + + + Delete entry? + Slet post? + + + Do you really want to delete the entry "%1" for good? + + + + Delete entries? + Slet poster? + + + Do you really want to delete %1 entries for good? + + + + Move entries to recycle bin? + Flyt poster til skraldespanden? + + + Do you really want to move %n entry(s) to the recycle bin? + + + + Delete group? + Slet gruppe? + + + Do you really want to delete the group "%1" for good? + + + + Current group + Nuværende gruppe + + + + EditEntryWidget + + Entry + Post + + + Advanced + Avanceret + + + Icon + Ikon + + + Auto-Type + + + + Properties + Egenskaber + + + History + Historik + + + Entry history + + + + Add entry + Tilføj post + + + Edit entry + Rediger post + + + Error + Fejl + + + Different passwords supplied. + Andre adgangskoder leveret. + + + New attribute + Ny attribut + + + Select file + Vælg fil + + + Unable to open file + Filen kan ikke åbnes + + + Save attachment + Gem vedhæftning + + + Unable to save the attachment: + + + + + Tomorrow + I morgen + + + %n week(s) + %n uge%n uger + + + %n month(s) + %n måned%n måneder + + + 1 year + Et år + + + + EditEntryWidgetAdvanced + + Additional attributes + Yderligere attributter + + + Add + Tilføj + + + Edit + Rediger + + + Remove + Fjern + + + Attachments + Vedhæftninger + + + Save + Gem + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + + + + Inherit default Auto-Type sequence from the group + + + + Use custom Auto-Type sequence: + + + + + + + + + + - + - + + + Window title: + Vinduestitel: + + + Use default sequence + + + + Set custom sequence: + + + + + EditEntryWidgetHistory + + Show + Vis + + + Restore + Gendan + + + Delete + Slet + + + Delete all + Slet alle + + + + EditEntryWidgetMain + + Title: + Titel: + + + Username: + Brugernavn: + + + Password: + Adgangskode: + + + Repeat: + Gentag: + + + Gen. + + + + URL: + URL: + + + Expires + Udløber + + + Presets + + + + Notes: + Noter: + + + + EditGroupWidget + + Group + Gruppe + + + Icon + Ikon + + + Properties + Egenskaber + + + Add group + Tilføj gruppe + + + Edit group + Rediger gruppe + + + Enable + Aktiver + + + Disable + Deaktivér + + + Inherit from parent group (%1) + Arv fra forældregruppe (%1) + + + + EditGroupWidgetMain + + Name + Navn + + + Notes + Noter + + + Expires + Udløber + + + Search + Søg + + + Auto-type + + + + + EditWidgetIcons + + Use default icon + Brug standardikon + + + Use custom icon + Brug brugerbestemt ikon + + + Add custom icon + Tilføj brugerbestemt ikon + + + Delete custom icon + Slet brugerbestemt ikon + + + Images + Billeder + + + All files + Alle filer + + + Select Image + Vælg Billede + + + Can't delete icon! + Kan ikke slette ikon! + + + Can't delete icon. Still used by %n item(s). + + + + + EditWidgetProperties + + Created: + Oprettet: + + + Modified: + Ændret: + + + Accessed: + Tilgået: + + + Uuid: + Uuid: + + + + EntryAttributesModel + + Name + Navn + + + + EntryHistoryModel + + Last modified + Sidst ændret + + + Title + Titel + + + Username + Brugernavn + + + URL + URL + + + + EntryModel + + Group + Gruppe + + + Title + Titel + + + Username + Brugernavn + + + URL + URL + + + + Group + + Recycle Bin + Skraldespand + + + + KeePass1OpenWidget + + Import KeePass1 database + Importér KeePass1 database + + + Error + Fejl + + + Unable to open the database. + Kan ikke åbne databasen. + + + + KeePass1Reader + + Unable to read keyfile. + Kan ikke indlæse nøglefil. + + + Not a KeePass database. + Ikke en KeePass database. + + + Unsupported encryption algorithm. + Ikke understøttet krypteringsalgoritme + + + Unsupported KeePass database version. + Ikke understøttet KeePass database version + + + Root + + + + + KeePass2Reader + + Not a KeePass database. + Det er ikke en KeePass database. + + + Unsupported KeePass database version. + KeePass database version ikke understøttet. + + + Wrong key or database file is corrupt. + Forkert nøgle eller databasefil er korrupt. + + + + Main + + Fatal error while testing the cryptographic functions. + Fatal fejl ved test af kryptografiske funktioner. + + + KeePassX - Error + KeePassX - Fejl + + + + MainWindow + + Database + Database + + + Recent databases + Seneste databaser + + + Help + Hjælp + + + Entries + Poster + + + Copy attribute to clipboard + Kopiér attribut til udklipsholder + + + Groups + Grupper + + + Extras + + + + View + Vis + + + Quit + Afslut + + + About + Om + + + Open database + Åben database + + + Save database + Gem database + + + Close database + Luk databasen + + + New database + Ny database + + + Add new entry + Tilføj ny post + + + View/Edit entry + Vis/Rediger post + + + Delete entry + Slet post + + + Add new group + Tilføj ny gruppe + + + Edit group + Rediger gruppe + + + Delete group + Slet gruppe + + + Save database as + Gem database som + + + Change master key + Skift primærnøgle + + + Database settings + Databaseindstillinger + + + Import KeePass 1 database + Importér KeePass 1 database + + + Clone entry + Klon post + + + Find + Find + + + Username + Brugernavn + + + Copy username to clipboard + Kopiér brugernavn til udklipsholder + + + Password + Adgangskode + + + Copy password to clipboard + Kopiér adgangskode til udklipsholder + + + Settings + Indstillinger + + + Perform Auto-Type + + + + Open URL + Åben URL + + + Lock databases + Lås databaser + + + Title + Titel + + + URL + URL + + + Notes + Noter + + + Show toolbar + Vis værktøjslinie + + + read-only + skrivebeskyttet + + + Toggle window + Skift vindue + + + + PasswordGeneratorWidget + + Password: + Adgangskode: + + + Length: + Længde: + + + Character Types + + + + Upper Case Letters + Store Bogstaver + + + Lower Case Letters + + + + Numbers + Numre + + + Special Characters + + + + Exclude look-alike characters + + + + Ensure that the password contains characters from every group + + + + Accept + Acceptér + + + + QCommandLineParser + + Displays version information. + Vis versionsinformation + + + Displays this help. + Vis denne hjælp. + + + Unknown option '%1'. + Ukendt valgmulighed '%1'. + + + Unknown options: %1. + Ukendt valgmuligheder '%1'. + + + Missing value after '%1'. + Manglende værdi efter '%1'. + + + Unexpected value after '%1'. + Uventet værdi efter '%1'. + + + [options] + [Valgmuligheder] + + + Usage: %1 + Brug: %1 + + + Options: + Muligheder: + + + Arguments: + Argumenter: + + + + QSaveFile + + Existing file %1 is not writable + Eksisterende fil %1 er ikke skrivbar + + + Writing canceled by application + Skrivning afbrudt af programmet + + + Partial write. Partition full? + Delvis gemt. Diskafsnit fyldt op? + + + + QtIOCompressor + + Internal zlib error when compressing: + Intern zlib-fejl ved komprimering: + + + Error writing to underlying device: + + + + Error opening underlying device: + + + + Error reading data from underlying device: + Fejl ved læsning af data fra underliggende enhed: + + + Internal zlib error when decompressing: + + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + Gzip-format understøttes ikke i denne version af zlib. + + + Internal zlib error: + Intern zlib fejl: + + + + SearchWidget + + Find: + Find: + + + Case sensitive + + + + Current group + Nuværende gruppe + + + Root group + + + + + SettingsWidget + + Application Settings + Programindstillinger + + + General + + + + Security + Sikkerhed + + + + SettingsWidgetGeneral + + Remember last databases + + + + Open previous databases on startup + + + + Mark as modified on expanded state changes + + + + Automatically save on exit + Gem automatisk ved afslutning + + + Automatically save after every change + Gem automatisk ved ændringer + + + Minimize when copying to clipboard + Minimér ved kopiering til udklipsholder + + + Use group icon on entry creation + + + + Global Auto-Type shortcut + + + + Use entry title to match windows for global auto-type + + + + Language + Sprog + + + Show a system tray icon + Vis et ikon i systembakken + + + Hide window to system tray when minimized + Skjul vindue i systembakken når det er minimeret + + + + SettingsWidgetSecurity + + Clear clipboard after + Ryd udklipsholder efter + + + sec + sek + + + Lock databases after inactivity of + + + + Show passwords in cleartext by default + + + + Always ask before performing auto-type + + + + + UnlockDatabaseWidget + + Unlock database + Lås database op + + + Error + Fejl + + + Wrong key. + Forkert nøgle. + + + + WelcomeWidget + + Welcome! + Velkommen! + + + + main + + KeePassX - cross-platform password manager + + + + filename of the password database to open (*.kdbx) + filnavn på databasen der skal åbnes (* .kdbx) + + + path to a custom config file + + + + password of the database (DANGEROUS!) + adgangskode til databasen (FAARLIGT!) + + + key file of the database + databasens nøglefil + + + \ No newline at end of file diff --git a/share/translations/keepassx_de.ts b/share/translations/keepassx_de.ts index 5433c3ced..59ea1947f 100644 --- a/share/translations/keepassx_de.ts +++ b/share/translations/keepassx_de.ts @@ -17,8 +17,8 @@ Auto-Type - KeePassX - Couldn't find an entry that matches the window title. - Konnte dem Fenstertitel keinen passenden Eintrag zuordnen. + Couldn't find an entry that matches the window title: + Konnte keinen Eintrag finden, welcher mit dem Fenstertitel übereinstimmt: @@ -138,7 +138,7 @@ Can't open key file - Schlüsseldatein kann nicht geöffnet werden + Schlüsseldatei kann nicht geöffnet werden All files @@ -185,7 +185,7 @@ Max. history items: - Max Einträge im Verlauf: + Max. Einträge im Verlauf: Max. history size: @@ -739,6 +739,17 @@ Save changes? Falscher Schlüssel oder die Datei ist beschädigt. + + Main + + Fatal error while testing the cryptographic functions. + Fataler Fehler beim Testen der kryptografischen Funktionen. + + + KeePassX - Error + KeePassX - Fehler + + MainWindow @@ -897,6 +908,10 @@ Save changes? read-only Nur Lesezugriff + + Toggle window + Fenster zeigen/verstecken + PasswordGeneratorWidget @@ -1105,6 +1120,18 @@ Save changes? Use entry title to match windows for global auto-type Verwende den Eintragstitel für entsprechende Fenster für den globale Auto-Typ + + Language + Sprache + + + Show a system tray icon + Taskleistensymbol anzeigen + + + Hide window to system tray when minimized + Fenster verstecken wenn minimiert + SettingsWidgetSecurity diff --git a/share/translations/keepassx_en.ts b/share/translations/keepassx_en.ts index a7b5b109d..af21c20cb 100644 --- a/share/translations/keepassx_en.ts +++ b/share/translations/keepassx_en.ts @@ -111,6 +111,15 @@ Different passwords supplied. + + Failed to set key file + + + + Failed to set %1 as the Key file: +%2 + + DatabaseOpenWidget @@ -270,6 +279,31 @@ Save changes? locked + + The database you are trying to open is locked by another instance of KeePassX. +Do you want to open it anyway? Alternatively the database is opened read-only. + + + + Lock database + + + + Can't lock the database as you are currently editing it. +Please press cancel to finish your changes or discard them. + + + + This database has never been saved. +You can save the database or stop locking it. + + + + This database has been modified. +Do you want to save the database before locking it? +Otherwise your changes are lost. + + DatabaseWidget @@ -316,6 +350,14 @@ Save changes? Current group + + Error + + + + Unable to calculate master key + + EditEntryWidget @@ -433,6 +475,10 @@ Save changes? Save + + Open + + EditEntryWidgetAutoType @@ -584,6 +630,14 @@ Save changes? Auto-type + + Use default auto-type sequence of parent group + + + + Set default auto-type sequence + + EditWidgetIcons @@ -735,6 +789,10 @@ Save changes? Root + + Unable to calculate master key + + KeePass2Reader @@ -750,6 +808,10 @@ Save changes? Wrong key or database file is corrupt. + + Unable to calculate master key + + Main @@ -788,10 +850,6 @@ Save changes? Groups - - Extras - - View @@ -924,6 +982,10 @@ Save changes? Toggle window + + Tools + + PasswordGeneratorWidget @@ -1104,10 +1166,6 @@ Save changes? Open previous databases on startup - - Mark as modified on expanded state changes - - Automatically save on exit @@ -1144,6 +1202,10 @@ Save changes? Hide window to system tray when minimized + + Remember last key files + + SettingsWidgetSecurity @@ -1174,14 +1236,6 @@ Save changes? Unlock database - - Error - - - - Wrong key. - - WelcomeWidget diff --git a/share/translations/keepassx_es.ts b/share/translations/keepassx_es.ts new file mode 100644 index 000000000..b3f6b6ffe --- /dev/null +++ b/share/translations/keepassx_es.ts @@ -0,0 +1,1205 @@ + + + AboutDialog + + About KeePassX + Acerca de KeePassX + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + KeePassX es distribuido bajo los términos de la versión 2 de la Licencia Pública GNU (GPL) o por la versión 3 (si así lo prefiere). + + + + AutoType + + Auto-Type - KeePassX + Auto-Escritura - KeePassX + + + Couldn't find an entry that matches the window title: + No se puede encontrar una entrada que corresponda al título de la ventana: + + + + AutoTypeAssociationsModel + + Window + Ventana + + + Sequence + Secuencia + + + Default sequence + Secuencia por defecto + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + Auto-Escritura - KeePassX + + + Select entry to Auto-Type: + Seleccionar entrada para Auto-Escritura: + + + + ChangeMasterKeyWidget + + Password + Contraseña + + + Enter password: + Ingrese la contraseña + + + Repeat password: + Repita la contraseña: + + + Key file + Archivo llave + + + Browse + Abrir archivo + + + Create + Crear + + + Key files + Archivos de llaves + + + All files + Todos los archivos + + + Create Key File... + Crear un Archivo Llave .... + + + Error + Error + + + Unable to create Key File : + No se puede crear el Archivo Llave: + + + Select a key file + Seleccione un archivo llave + + + Question + Pregunta + + + Do you really want to use an empty string as password? + ¿Realmente desea usar una cadena vacía como contraseña? + + + Different passwords supplied. + Las contraseñas ingresadas son distintas. + + + + DatabaseOpenWidget + + Enter master key + Ingrese la clave maestra + + + Key File: + Archivo clave: + + + Password: + Contraseña: + + + Browse + Navegar + + + Error + Error + + + Unable to open the database. + Incapaz de abrir la base de datos. + + + Can't open key file + No se puede abrir el archivo llave + + + All files + Todos los archivos + + + Key files + Archivos llave + + + Select key file + Seleccionar archivo llave + + + + DatabaseSettingsWidget + + Database name: + Nombre de la base de datos: + + + Database description: + Descripción de la base de datos: + + + Transform rounds: + Rondas de transformación: + + + Default username: + Nombre de usuario por defecto: + + + Use recycle bin: + Usar papelera de reciclaje: + + + MiB + MiB + + + Benchmark + Prueba de rendimiento + + + Max. history items: + Elementos máximos del historial: + + + Max. history size: + Tamaño máximo del historial: + + + + DatabaseTabWidget + + Root + + + + KeePass 2 Database + Base de datos KeePass 2 + + + All files + Todos los archivos + + + Open database + Abrir base de datos + + + Warning + Advertencia + + + File not found! + ¡Archivo no encontrado! + + + Open KeePass 1 database + Abrir base de datos KeePass 1 + + + KeePass 1 database + Base de datos KeePass 1 + + + All files (*) + Todos los archivos (*) + + + Close? + ¿Cerrar? + + + "%1" is in edit mode. +Close anyway? + "%1" está en modo de edición. +¿Cerrar de todas formas? + + + Save changes? + ¿Guardar cambios? + + + "%1" was modified. +Save changes? + "%1" ha sido modificado. +¿Guardar cambios? + + + Error + Error + + + Writing the database failed. + La escritura de la base de datos falló. + + + Save database as + Guardar base de datos como + + + New database + Nueva base de datos + + + locked + bloqueado + + + + DatabaseWidget + + Change master key + Cambiar la llave maestra + + + Delete entry? + ¿Eliminar la entrada? + + + Do you really want to delete the entry "%1" for good? + ¿Realmente quiere eliminar la entrada "%1" de forma definitiva? + + + Delete entries? + ¿Eliminar entradas? + + + Do you really want to delete %1 entries for good? + ¿Realmente quiere eliminar las entradas "%1" de forma definitiva? + + + Move entries to recycle bin? + ¿Mover entradas a la papelera de reciclaje? + + + Do you really want to move %n entry(s) to the recycle bin? + ¿Realmente quiere mover la entrada "%1" a la papelera de reciclaje?¿Realmente quiere mover las entradas "%1" a la papelera de reciclaje? + + + Delete group? + ¿Eliminar grupo? + + + Do you really want to delete the group "%1" for good? + ¿Realmente quiere eliminar el grupo "%1" de forma definitiva? + + + Current group + Grupo actual + + + + EditEntryWidget + + Entry + Entrada + + + Advanced + Avanzado + + + Icon + Icono + + + Auto-Type + Auto-Escritura + + + Properties + Propiedades + + + History + Historial + + + Entry history + Historial de entradas + + + Add entry + Añadir entrada + + + Edit entry + Editar entrada + + + Error + Error + + + Different passwords supplied. + Las contraseñas ingresadas son distintas. + + + New attribute + Nuevo atributo + + + Select file + Seleccionar archivo llave + + + Unable to open file + Incapaz de abrir el archivo + + + Save attachment + Guardar adjunto + + + Unable to save the attachment: + + Incapaz de guardar el adjunto: + + + + Tomorrow + Mañana + + + %n week(s) + %n semana%n semanas + + + %n month(s) + %n mes%n meses + + + 1 year + 1 año + + + + EditEntryWidgetAdvanced + + Additional attributes + Atributos adicionales + + + Add + Añadir + + + Edit + Editar + + + Remove + Eliminar + + + Attachments + Adjuntos + + + Save + Guardar + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + Activar Auto-Escritura para esta entrada + + + Inherit default Auto-Type sequence from the group + Heredar Auto-Escritura por defecto del grupo + + + Use custom Auto-Type sequence: + Utilizar secuencia de Auto-Escritura personalizada: + + + + + + + + + - + - + + + Window title: + Título de la ventana: + + + Use default sequence + Utilizar secuencia por defecto + + + Set custom sequence: + Definir secuencia personalizada: + + + + EditEntryWidgetHistory + + Show + Mostrar + + + Restore + Recuperar + + + Delete + Eliminar + + + Delete all + Eliminar todo + + + + EditEntryWidgetMain + + Title: + Título: + + + Username: + Nombre de usuario: + + + Password: + Contraseña: + + + Repeat: + Repetir: + + + Gen. + Gen. + + + URL: + URL: + + + Expires + Expira + + + Presets + Predeterminado + + + Notes: + Notas: + + + + EditGroupWidget + + Group + Grupo + + + Icon + Icono + + + Properties + Propiedades + + + Add group + Añadir grupo + + + Edit group + Editar grupo + + + Enable + Habilitar + + + Disable + Deshabilitar + + + Inherit from parent group (%1) + Heredar del grupo padre (%1) + + + + EditGroupWidgetMain + + Name + Nombre + + + Notes + Notas + + + Expires + Expira + + + Search + Buscar + + + Auto-type + Auto-escritura + + + + EditWidgetIcons + + Use default icon + Usar icono por defecto + + + Use custom icon + Usar icono personalizado + + + Add custom icon + Añadir icono personalizado + + + Delete custom icon + Eliminar icono personalizado + + + Images + Imágenes + + + All files + Todos los archivos + + + Select Image + Seleccionar imagen + + + Can't delete icon! + ¡No se puede eliminar el icono! + + + Can't delete icon. Still used by %n item(s). + No se puede eliminar el icono. Utilizado aún en %n elementoNo se puede eliminar el icono. Utilizado aún en %n elementos + + + + EditWidgetProperties + + Created: + Creado: + + + Modified: + Modificado: + + + Accessed: + Accedido: + + + Uuid: + Uuid: + + + + EntryAttributesModel + + Name + Nombre + + + + EntryHistoryModel + + Last modified + Última modificación + + + Title + Título + + + Username + Nombre de usuario + + + URL + URL + + + + EntryModel + + Group + Grupo + + + Title + Título + + + Username + Nombre de usuario: + + + URL + URL + + + + Group + + Recycle Bin + Papelera de reciclaje + + + + KeePass1OpenWidget + + Import KeePass1 database + Importar base de datos KeePass1 + + + Error + Error + + + Unable to open the database. + Incapaz de abrir la base de datos. + + + + KeePass1Reader + + Unable to read keyfile. + Incapaz de leer el archivo + + + Not a KeePass database. + No es una base de datos KeePass. + + + Unsupported encryption algorithm. + Algoritmo de cifrado no soportado. + + + Unsupported KeePass database version. + Versión de la base de datos KeePass no soportada. + + + Root + + + + + KeePass2Reader + + Not a KeePass database. + No es una base de datos KeePass. + + + Unsupported KeePass database version. + Versión de la base de datos KeePass no soportada. + + + Wrong key or database file is corrupt. + La contraseña es incorrecta o el archivo está dañado + + + + Main + + Fatal error while testing the cryptographic functions. + Error fatal comprobando las funciones criptográficas. + + + KeePassX - Error + KeePassX - Error + + + + MainWindow + + Database + Base de datos + + + Recent databases + Bases de datos recientes + + + Help + Ayuda + + + Entries + Entradas + + + Copy attribute to clipboard + Copiar atributo al portapapeles + + + Groups + Grupos + + + Extras + Extras + + + View + Ver + + + Quit + Salir + + + About + Acerca de + + + Open database + Abrir base de datos + + + Save database + Guardar base de datos + + + Close database + Cerrar base de datos + + + New database + Nueva base de datos + + + Add new entry + Añadir nueva entrada + + + View/Edit entry + Ver/Editar entrada + + + Delete entry + Eliminar entrada + + + Add new group + Añadir nuevo grupo + + + Edit group + Editar grupo + + + Delete group + Eliminar grupo + + + Save database as + Guardar base de datos como + + + Change master key + Cambiar la llave maestra + + + Database settings + Configuración de la base de datos + + + Import KeePass 1 database + Importar base de datos KeePass 1 + + + Clone entry + Clonar entrada + + + Find + Buscar + + + Username + Usuario + + + Copy username to clipboard + Copiar nombre de usuario al portapapeles + + + Password + Contraseña + + + Copy password to clipboard + Copiar contraseña al portapapeles + + + Settings + Configuración + + + Perform Auto-Type + Relizar Auto-Escritura + + + Open URL + Abrir URL + + + Lock databases + Bloquear base de datos + + + Title + Título + + + URL + URL + + + Notes + Notas + + + Show toolbar + Mostrar barra de herramientas + + + read-only + sólo lectura + + + Toggle window + Cambiar a ventana + + + + PasswordGeneratorWidget + + Password: + Contraseña: + + + Length: + Longitud: + + + Character Types + Tipos de caracteres + + + Upper Case Letters + Letras mayúsculas + + + Lower Case Letters + Letras minúsculas + + + Numbers + Números + + + Special Characters + Caracteres especiales: + + + Exclude look-alike characters + Excluir caracteres similares + + + Ensure that the password contains characters from every group + Asegurar que la contraseña contiene caracteres de todos los grupos + + + Accept + Aceptar + + + + QCommandLineParser + + Displays version information. + Muestra información de versión. + + + Displays this help. + Muestra esta ayuda. + + + Unknown option '%1'. + Opción desconocida '%1'. + + + Unknown options: %1. + Opciones desconocidas '%1'. + + + Missing value after '%1'. + Falta valor después de '%1', + + + Unexpected value after '%1'. + Valor inesperado después de '%1'. + + + [options] + [opciones] + + + Usage: %1 + Uso: %1 + + + Options: + Opciones: + + + Arguments: + Argumentos: + + + + QSaveFile + + Existing file %1 is not writable + El archivo existente %1 no se puede sobrescribir + + + Writing canceled by application + escritura cancelada por la aplicación + + + Partial write. Partition full? + Escritura parcial. ¿La partición está llena? + + + + QtIOCompressor + + Internal zlib error when compressing: + Error interno de zlib comprimiendo: + + + Error writing to underlying device: + Error al escribir en el dispositivo subyacente: + + + Error opening underlying device: + Error al abrir el dispositivo subyacente: + + + Error reading data from underlying device: + Error al leer el dispositivo subyacente: + + + Internal zlib error when decompressing: + Error interno de zlib descomprimiendo: + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + El formato gzip no está soportado en esta versión de zlib. + + + Internal zlib error: + Error interno de zlib: + + + + SearchWidget + + Find: + Buscar: + + + Case sensitive + Distinguir mayúsculas/minúsculas + + + Current group + Grupo actual + + + Root group + + + + + SettingsWidget + + Application Settings + Configuración de la aplicación + + + General + General + + + Security + Seguridad + + + + SettingsWidgetGeneral + + Remember last databases + Recordar última base de datos + + + Open previous databases on startup + Abrir base de datos anterior al inicio + + + Mark as modified on expanded state changes + Marcar como modificado en los cambios de estado ampliados + + + Automatically save on exit + Guardar automáticamente al salir + + + Automatically save after every change + Guardar automáticamente después de cada cambio + + + Minimize when copying to clipboard + Minimizar al copiar al portapapeles + + + Use group icon on entry creation + Usar icono del grupo en la creación de entrada + + + Global Auto-Type shortcut + Atajo global de Auto-Escritura + + + Use entry title to match windows for global auto-type + Usar el título de la entrada para coincidir con la ventana para la auto-escritura global + + + Language + Idioma + + + Show a system tray icon + Mostrar icono en la bandeja de del sistema + + + Hide window to system tray when minimized + Ocultar la ventana a la bandeja del sistema cuando se minimiza + + + + SettingsWidgetSecurity + + Clear clipboard after + Limpiar el portapapeles después de + + + sec + segundos + + + Lock databases after inactivity of + Bloquear base de datos tras un periodo de inactividad de + + + Show passwords in cleartext by default + Mostrar contraseñas en texto claro por defecto + + + Always ask before performing auto-type + Preguntar siempre antes de realizar auto-escritura + + + + UnlockDatabaseWidget + + Unlock database + Desbloquear base de datos + + + Error + Error + + + Wrong key. + Clave incorrecta. + + + + WelcomeWidget + + Welcome! + Bienvenid@! + + + + main + + KeePassX - cross-platform password manager + KeePassX - gestor de claves multiplataforma + + + filename of the password database to open (*.kdbx) + nombre de archivo de la base de datos de contraseñas a abrir (*.kdbx) + + + path to a custom config file + ruta a un archivo de configuración personalizado + + + password of the database (DANGEROUS!) + contraseña de la base de datos (¡PELIGROSO!) + + + key file of the database + archivo llave de la base de datos + + + \ No newline at end of file diff --git a/share/translations/keepassx_fr.ts b/share/translations/keepassx_fr.ts new file mode 100644 index 000000000..cf575025b --- /dev/null +++ b/share/translations/keepassx_fr.ts @@ -0,0 +1,1205 @@ + + + AboutDialog + + About KeePassX + À propos de KeePassX + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + KeePassX est distribué selon les conditions de la GNU General Public License (GPL) version 2 ou (à votre choix) version 3. + + + + AutoType + + Auto-Type - KeePassX + Auto-Type - KeePassX + + + Couldn't find an entry that matches the window title: + Impossible de trouver une entrée qui corresponde au titre de la fenêtre : + + + + AutoTypeAssociationsModel + + Window + Fenêtre + + + Sequence + Séquence + + + Default sequence + Séquence par défaut + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + Auto-Type - KeePassX + + + Select entry to Auto-Type: + Choisissez un champ pour Auto-Type : + + + + ChangeMasterKeyWidget + + Password + Mot de passe + + + Enter password: + Entrez un mot de passe : + + + Repeat password: + Confirmez le mot de passe : + + + Key file + Fichier de clé + + + Browse + Naviguer + + + Create + Créer + + + Key files + Fichiers de clé + + + All files + Tous les fichiers + + + Create Key File... + Créer un fichier de clé... + + + Error + Erreur + + + Unable to create Key File : + Incapable de créer un fichier de clé : + + + Select a key file + Choisir un fichier de clé + + + Question + Question + + + Do you really want to use an empty string as password? + Voulez-vous vraiment utiliser une chaîne vide comme mot de passe ? + + + Different passwords supplied. + Les mots de passe ne sont pas identiques. + + + + DatabaseOpenWidget + + Enter master key + Entrez la clé maîtresse + + + Key File: + Fichier de clé : + + + Password: + Mot de passe : + + + Browse + Naviguer + + + Error + Erreur + + + Unable to open the database. + Impossible d'ouvrir la base de données. + + + Can't open key file + Impossible d'ouvrir le fichier de clé + + + All files + Tous les fichiers + + + Key files + Fichiers de clé + + + Select key file + Choisissez un fichier de clé + + + + DatabaseSettingsWidget + + Database name: + Nom de la base de données : + + + Database description: + Description de la base de données : + + + Transform rounds: + Passes de transformation : + + + Default username: + Nom d'utilisateur par défaut : + + + Use recycle bin: + Utiliser la corbeille : + + + MiB + MiB + + + Benchmark + Base de référence + + + Max. history items: + Nombre max. d'éléments dans l'historique : + + + Max. history size: + Taille max. de l'historique : + + + + DatabaseTabWidget + + Root + Racine + + + KeePass 2 Database + Base de données Keepass 2 + + + All files + Tous les fichiers + + + Open database + Ovrire la base de données + + + Warning + Attention + + + File not found! + Fichier introuvable! + + + Open KeePass 1 database + Ouvrir une base de données KeePass 1 + + + KeePass 1 database + Base de données Keepass 1 + + + All files (*) + Tous les fichiers (*) + + + Close? + Fermer? + + + "%1" is in edit mode. +Close anyway? + "%1" est en cours de modification. +Fermer quand même ? + + + Save changes? + Enregistrer les modifications ? + + + "%1" was modified. +Save changes? + "%1" a été modifié. +Enregistrer les modifications ? + + + Error + Erreur + + + Writing the database failed. + Une erreur s'est produite lors de l'écriture de la base de données. + + + Save database as + Enregistrer comme nouvelle base de données + + + New database + Nouvelle base de données + + + locked + verrouillée + + + + DatabaseWidget + + Change master key + Changer la clé maîtresse + + + Delete entry? + Supprimer l'entrée ? + + + Do you really want to delete the entry "%1" for good? + Voulez-vous supprimer l'entrée "%1" définitivement ? + + + Delete entries? + Supprimer les entrées ? + + + Do you really want to delete %1 entries for good? + Voulez-vous supprimer "%1" entrées définitivement ? + + + Move entries to recycle bin? + Déplacer les entrées vers la corbeille ? + + + Do you really want to move %n entry(s) to the recycle bin? + Voulez-vous déplacer %n entrée(s) vers la corbeille ?Voulez-vous déplacer %n entrée(s) vers la corbeille ? + + + Delete group? + Supprimer le groupe ? + + + Do you really want to delete the group "%1" for good? + Voulez-vous supprimer le groupe "%1" définitivement ? + + + Current group + Group actif + + + + EditEntryWidget + + Entry + Entrée + + + Advanced + Avancées + + + Icon + Icône + + + Auto-Type + Auto-Type + + + Properties + Propriétés + + + History + Historique + + + Entry history + Historique de l'entrée + + + Add entry + Ajouter une entrée + + + Edit entry + Modifier l'entrée + + + Error + Erreur + + + Different passwords supplied. + Les mots de passe ne sont pas identiques. + + + New attribute + Nouvel attribut + + + Select file + Choisissez un fichier + + + Unable to open file + Impossible d'ouvrir le fichier + + + Save attachment + Enregistrer le fichier attaché + + + Unable to save the attachment: + + Impossible de enregistrer le fichier attaché: + + + + Tomorrow + Demain + + + %n week(s) + %n semaine(s)%n semaine(s) + + + %n month(s) + %n mois%n mois + + + 1 year + Une année + + + + EditEntryWidgetAdvanced + + Additional attributes + Attributs additionnel + + + Add + Ajouter + + + Edit + Modifier + + + Remove + Supprimer + + + Attachments + Affichage + + + Save + Enregistrer le fichier + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + Activer l'Auto-Type pour cette entrée + + + Inherit default Auto-Type sequence from the group + + + + Use custom Auto-Type sequence: + Utiliser une séquence d'Auto-Type personnalisée : + + + + + + + + + - + - + + + Window title: + Titre de la fenêtre: + + + Use default sequence + Utiliser la séquence par défaut + + + Set custom sequence: + + + + + EditEntryWidgetHistory + + Show + Exposer + + + Restore + Rétablir + + + Delete + Supprimer + + + Delete all + Supprimer tout + + + + EditEntryWidgetMain + + Title: + Titre: + + + Username: + Non d'utilisateur: + + + Password: + Mot de passe: + + + Repeat: + Confirme: + + + Gen. + Gen. + + + URL: + URL: + + + Expires + Expiration + + + Presets + Valeurs par défaut + + + Notes: + Notes: + + + + EditGroupWidget + + Group + Groupe + + + Icon + Icône + + + Properties + Propriétés + + + Add group + Ajouter un groupe. + + + Edit group + Modifie le groupe + + + Enable + Activer + + + Disable + Désactiver + + + Inherit from parent group (%1) + Hériter du groupe parent (%1) + + + + EditGroupWidgetMain + + Name + Nom + + + Notes + Notes + + + Expires + Expires + + + Search + Chercher + + + Auto-type + Auto-type + + + + EditWidgetIcons + + Use default icon + Utiliser l'icône par défaut + + + Use custom icon + Utiliser une icône personnalisée + + + Add custom icon + Ajouter une icône personnalisée + + + Delete custom icon + Supprimer une icône personnalisée + + + Images + Images + + + All files + Tous les dossiers + + + Select Image + Choisis un image. + + + Can't delete icon! + Impossible de supprimer l'icône ! + + + Can't delete icon. Still used by %n item(s). + Impossible de supprimer l'icône. Toujours utilisée par %n objet(s).Impossible de supprimer l'icône. Toujours utilisée par %n objet(s). + + + + EditWidgetProperties + + Created: + Créé le : + + + Modified: + Modifié le : + + + Accessed: + Accédé le : + + + Uuid: + Uuid: + + + + EntryAttributesModel + + Name + Nom + + + + EntryHistoryModel + + Last modified + Dernière modification + + + Title + Titre + + + Username + Nom d'utilisateur + + + URL + URL + + + + EntryModel + + Group + Groupe + + + Title + Titre + + + Username + Non d'utilisateur + + + URL + URL + + + + Group + + Recycle Bin + Bac de Recyclage + + + + KeePass1OpenWidget + + Import KeePass1 database + Importe un KeePass1 bas de données + + + Error + Erreur + + + Unable to open the database. + Impossible d'ouvrir la bas de données. + + + + KeePass1Reader + + Unable to read keyfile. + Impossible de lire le fichier de clé. + + + Not a KeePass database. + Ce n'est pas une base de donnée KeePass. + + + Unsupported encryption algorithm. + Algorithme de chiffrement non supporté. + + + Unsupported KeePass database version. + Version de base de donnée KeePass non supportée. + + + Root + Racine + + + + KeePass2Reader + + Not a KeePass database. + Ce n'est pas une base de donnée KeePass. + + + Unsupported KeePass database version. + Version de base de donnée KeePass non supportée. + + + Wrong key or database file is corrupt. + Mauvaise clé ou fichier de base de donnée corrompu. + + + + Main + + Fatal error while testing the cryptographic functions. + Erreur fatale lors des tests des fonctions cryptographiques. + + + KeePassX - Error + KeePassX - Erreur + + + + MainWindow + + Database + Base de données + + + Recent databases + Bases de données récentes + + + Help + Aide + + + Entries + Entrées + + + Copy attribute to clipboard + Copier l'attribut dans le presse-papiers + + + Groups + Groupes + + + Extras + Extras + + + View + Vue + + + Quit + Quitter + + + About + À propos + + + Open database + Ouvrir une base de donnée + + + Save database + Enregistrer la base de donnée + + + Close database + Fermer la base de donnée + + + New database + Nouvelle base de donnée + + + Add new entry + Ajouter une entrée + + + View/Edit entry + Voir/Modifier l'entrée + + + Delete entry + Supprimer l'entrée + + + Add new group + Ajouter un groupe + + + Edit group + Modifier le groupe + + + Delete group + Supprimer le groupe + + + Save database as + Enregistrer la base de donnée sous + + + Change master key + Modifier la clé maître + + + Database settings + Paramètre de la base de donnée + + + Import KeePass 1 database + Importer une base de donnée KeePass 1 + + + Clone entry + Dupliquer l'entrée + + + Find + Chercher + + + Username + Nom d'utilisateur + + + Copy username to clipboard + Copier le nom d'utilisateur dans le presse-papiers + + + Password + Mot de passe + + + Copy password to clipboard + Copier le mot de passe dans le presse-papiers + + + Settings + Paramètres + + + Perform Auto-Type + Effectuer un Auto-Type + + + Open URL + Ouvrir l'URL + + + Lock databases + Verrouiller les bases de données + + + Title + Titre + + + URL + URL + + + Notes + Notes + + + Show toolbar + Afficher la barre d'outils + + + read-only + Lecture seulement + + + Toggle window + Basculer de fenêtre + + + + PasswordGeneratorWidget + + Password: + Mot de passe: + + + Length: + Longueur : + + + Character Types + Types de caractère + + + Upper Case Letters + Lettres majuscules + + + Lower Case Letters + Lettres minuscules + + + Numbers + Nombres + + + Special Characters + Caractères spéciaux + + + Exclude look-alike characters + Exclure les caractères se ressemblant + + + Ensure that the password contains characters from every group + S'assurer que le mot de passe possède un caractère de chaque groupe + + + Accept + Accepter + + + + QCommandLineParser + + Displays version information. + Afficher les informations de version + + + Displays this help. + Afficher cette aide. + + + Unknown option '%1'. + Option inconnue '%1'. + + + Unknown options: %1. + Options inconnues : %1. + + + Missing value after '%1'. + Valeur manquante après '%1'. + + + Unexpected value after '%1'. + Valeur inattendue après '%1'. + + + [options] + [options] + + + Usage: %1 + Utilisation : %1 + + + Options: + Options : + + + Arguments: + Arguments : + + + + QSaveFile + + Existing file %1 is not writable + Le fichier existant %1 n'est pas accessible en écriture + + + Writing canceled by application + Écriture annulée par l'application + + + Partial write. Partition full? + Écriture partielle. Partition pleine ? + + + + QtIOCompressor + + Internal zlib error when compressing: + Erreur interne zlib lors de la compression : + + + Error writing to underlying device: + + + + Error opening underlying device: + + + + Error reading data from underlying device: + + + + Internal zlib error when decompressing: + Erreur interne zlib lors de la décompression : + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + Le format gzip n'est pas supporté dans cette version de zlib. + + + Internal zlib error: + Erreur interne zlib : + + + + SearchWidget + + Find: + Chercher : + + + Case sensitive + Sensible à la casse + + + Current group + Groupe actif + + + Root group + Groupe racine + + + + SettingsWidget + + Application Settings + Paramètre de l'application + + + General + Général + + + Security + Sécurité + + + + SettingsWidgetGeneral + + Remember last databases + Se souvenir des dernières bases de données + + + Open previous databases on startup + Ouvrir les base de données précédentes au démarrage + + + Mark as modified on expanded state changes + + + + Automatically save on exit + Sauvegarde automatiquement à la sortie + + + Automatically save after every change + Sauvegarde automatiquement après chaque modification + + + Minimize when copying to clipboard + + + + Use group icon on entry creation + Utiliser l'icône de groupe à la création d'une entrée + + + Global Auto-Type shortcut + Raccourci d'Auto-Type global + + + Use entry title to match windows for global auto-type + Utiliser la correspondance entre le titre de l'entrée et de la fenêtre pour l'Auto-Type global + + + Language + Langue + + + Show a system tray icon + + + + Hide window to system tray when minimized + + + + + SettingsWidgetSecurity + + Clear clipboard after + Vider le presse-papiers après + + + sec + s + + + Lock databases after inactivity of + Verrouiller les bases de donnée après une inactivité de + + + Show passwords in cleartext by default + Afficher les mots de passe en clair par défaut + + + Always ask before performing auto-type + Toujours demander avant d'effectuer un auto-type + + + + UnlockDatabaseWidget + + Unlock database + Déverrouiller la base de donnée + + + Error + Erreur + + + Wrong key. + Mauvaise clé. + + + + WelcomeWidget + + Welcome! + Bienvenue ! + + + + main + + KeePassX - cross-platform password manager + KeePassX - Gestionnaire de mot de passe multi-plateforme + + + filename of the password database to open (*.kdbx) + Nom de fichier de la base de donnée de mot de pass à ouvrir (*.kdbx) + + + path to a custom config file + Chemin vers un fichier de configuration personnalisé + + + password of the database (DANGEROUS!) + Mot de passe de la base de donnée (DANGEREUX !) + + + key file of the database + Fichier de clé de la base de donnée + + + \ No newline at end of file diff --git a/share/translations/keepassx_id.ts b/share/translations/keepassx_id.ts new file mode 100644 index 000000000..d71e981e1 --- /dev/null +++ b/share/translations/keepassx_id.ts @@ -0,0 +1,1205 @@ + + + AboutDialog + + About KeePassX + Tentang KeePassX + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + + + + + AutoType + + Auto-Type - KeePassX + Ketik-Otomatis - KeePassX + + + Couldn't find an entry that matches the window title: + Tidak dapat menemukan entri yang cocok dengan judul jendela + + + + AutoTypeAssociationsModel + + Window + Jendela + + + Sequence + Urutan + + + Default sequence + Urutan baku + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + Ketik-Otomatis - KeePassX + + + Select entry to Auto-Type: + Pilih entri untuk Ketik-Otomatis: + + + + ChangeMasterKeyWidget + + Password + Kata Sandi + + + Enter password: + Masukan kata sandi: + + + Repeat password: + Ulangi kata sandi: + + + Key file + Berkas kunci + + + Browse + Telusuri + + + Create + Buat + + + Key files + Berkas Kunci + + + All files + Semua berkas + + + Create Key File... + Buat Berkas Kunci... + + + Error + Galat + + + Unable to create Key File : + Tidak dapat membuat Berkas Kunci : + + + Select a key file + Pilih sebuah berkas kunci + + + Question + Pertanyaan + + + Do you really want to use an empty string as password? + Apakah anda ingin menggunakan string kosong sebagai kata sandi? + + + Different passwords supplied. + Kata sandi yang berbeda diberikan. + + + + DatabaseOpenWidget + + Enter master key + Masukan kunci utama + + + Key File: + Berkas Kunci: + + + Password: + Kata sandi: + + + Browse + Telusuri + + + Error + Galat + + + Unable to open the database. + Tidak dapat membuka basis data + + + Can't open key file + Tidak dapat membuka berkas kunci + + + All files + Semua berkas + + + Key files + Berkas kunci + + + Select key file + Pilih berkas kunci + + + + DatabaseSettingsWidget + + Database name: + Nama basis data: + + + Database description: + Deskripsi basis data: + + + Transform rounds: + + + + Default username: + Nama pengguna baku: + + + Use recycle bin: + + + + MiB + MiB + + + Benchmark + + + + Max. history items: + Maks. item riwayat: + + + Max. history size: + Maks. ukuran riwayat: + + + + DatabaseTabWidget + + Root + + + + KeePass 2 Database + Basis data KeePass 2 + + + All files + Semua berkas + + + Open database + Buka basis data + + + Warning + Peringatan + + + File not found! + Berkas tidak ditemukan! + + + Open KeePass 1 database + Buka basis data KeePass 1 + + + KeePass 1 database + Basis data KeePass 1 + + + All files (*) + Semua berkas (*) + + + Close? + Tutup? + + + "%1" is in edit mode. +Close anyway? + "%1" dalam berada mode sunting. +Tetap tutup? + + + Save changes? + Simpan perubahan? + + + "%1" was modified. +Save changes? + "%1" telah dimodifikasi. +Simpan perubahan? + + + Error + Galat + + + Writing the database failed. + Menulis basis data gagal. + + + Save database as + Simpan basis data sebagai + + + New database + Basis data baru + + + locked + terkunci + + + + DatabaseWidget + + Change master key + Ubah kunci utama + + + Delete entry? + Hapus entri? + + + Do you really want to delete the entry "%1" for good? + Apakah anda ingin menghapus entri "%1" untuk selamanya? + + + Delete entries? + Hapus entri? + + + Do you really want to delete %1 entries for good? + Apakah anda ingin menghapus entri %1 untuk selamanya? + + + Move entries to recycle bin? + + + + Do you really want to move %n entry(s) to the recycle bin? + + + + Delete group? + Hapus grup? + + + Do you really want to delete the group "%1" for good? + Apakah anda ingin menghapus grup "%1" untuk selamanya? + + + Current group + Grup saat ini + + + + EditEntryWidget + + Entry + Entri + + + Advanced + Tingkat Lanjut + + + Icon + Ikon + + + Auto-Type + Ketik-Otomatis + + + Properties + Properti + + + History + Riwayat + + + Entry history + Entri riwayat + + + Add entry + Tambah entri + + + Edit entry + Sunting entri + + + Error + Galat + + + Different passwords supplied. + Kata sandi yang berbeda diberikan. + + + New attribute + Atribut baru + + + Select file + Pilih berkas + + + Unable to open file + Tidak dapat membuka berkas + + + Save attachment + Simpan lampiran + + + Unable to save the attachment: + + Tidak dapat menyimpan lampiran: + + + + Tomorrow + Besok + + + %n week(s) + %n minggu(s) + + + %n month(s) + %n bulan(s) + + + 1 year + 1 tahun + + + + EditEntryWidgetAdvanced + + Additional attributes + Atribut tambahan + + + Add + Tambah + + + Edit + Sunting + + + Remove + Hapus + + + Attachments + Lampiran + + + Save + Simpan + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + Aktifkan Ketik-Otomatis untuk entri ini + + + Inherit default Auto-Type sequence from the group + + + + Use custom Auto-Type sequence: + + + + + + + + + + - + - + + + Window title: + Judul jendela: + + + Use default sequence + Gunakan urutan baku + + + Set custom sequence: + Tetapkan urutan kustom: + + + + EditEntryWidgetHistory + + Show + Tampilkan + + + Restore + Kembalikan + + + Delete + Hapus + + + Delete all + Hapus semua + + + + EditEntryWidgetMain + + Title: + Judul: + + + Username: + Nama pengguna: + + + Password: + Kata sandi: + + + Repeat: + Ulangi: + + + Gen. + Gen. + + + URL: + URL: + + + Expires + Kadaluarsa + + + Presets + + + + Notes: + Catatan: + + + + EditGroupWidget + + Group + Grup + + + Icon + Ikon + + + Properties + Properti + + + Add group + Tambah grup + + + Edit group + Sunting grup + + + Enable + Aktifkan + + + Disable + Nonaktifkan + + + Inherit from parent group (%1) + + + + + EditGroupWidgetMain + + Name + Nama + + + Notes + Catatan + + + Expires + Kadaluarsa + + + Search + Cari + + + Auto-type + Ketik-otomatis + + + + EditWidgetIcons + + Use default icon + Gunakan ikon baku + + + Use custom icon + Gunakan ikon kustom + + + Add custom icon + Tambah ikon kustom + + + Delete custom icon + Hapus ikon kustom + + + Images + Gambar + + + All files + Semua berkas + + + Select Image + Pilih gambar + + + Can't delete icon! + Tidak dapat menghapus ikon! + + + Can't delete icon. Still used by %n item(s). + Can't delete icon. Still used by %n item(s). + + + + EditWidgetProperties + + Created: + Dibuat: + + + Modified: + Dimodifikasi: + + + Accessed: + Diakses: + + + Uuid: + Uuid: + + + + EntryAttributesModel + + Name + Nama + + + + EntryHistoryModel + + Last modified + Terakhir dimodifikasi + + + Title + Judul + + + Username + Nama pengguna + + + URL + URL + + + + EntryModel + + Group + Grup + + + Title + Judul + + + Username + Nama pengguna + + + URL + URL + + + + Group + + Recycle Bin + + + + + KeePass1OpenWidget + + Import KeePass1 database + Impor basis data KeePass1 + + + Error + Galat + + + Unable to open the database. + Tidak dapat membuka basis data + + + + KeePass1Reader + + Unable to read keyfile. + Tidak dapat membaca berkas kunci. + + + Not a KeePass database. + Bukan basis data KeePass + + + Unsupported encryption algorithm. + Algoritma enkripsi tidak didukung + + + Unsupported KeePass database version. + Versi Basis data KeePass tidak didukung + + + Root + + + + + KeePass2Reader + + Not a KeePass database. + Bukan basis data KeePass + + + Unsupported KeePass database version. + Versi basis data KeePass tidak didukung + + + Wrong key or database file is corrupt. + Kunci salah atau berkas basis data korup. + + + + Main + + Fatal error while testing the cryptographic functions. + + + + KeePassX - Error + KeePassX - Galat + + + + MainWindow + + Database + Basis data + + + Recent databases + + + + Help + Bantuan + + + Entries + Entri + + + Copy attribute to clipboard + Salin atribut ke papan klip + + + Groups + Grup + + + Extras + Ekstra + + + View + + + + Quit + Keluar + + + About + Tentang + + + Open database + Buka basis data + + + Save database + Simpan basis data + + + Close database + Tutup basis data + + + New database + Basis data baru + + + Add new entry + Tambah entri baru + + + View/Edit entry + Tampil/Sunting entri + + + Delete entry + Hapus entri + + + Add new group + Tambah grup baru + + + Edit group + Sunting grup + + + Delete group + Hapus grup + + + Save database as + Simpan basis data sebagai + + + Change master key + Ubah kunci utama + + + Database settings + Pengaturan basis data + + + Import KeePass 1 database + Impor basis data KeePass 1 + + + Clone entry + + + + Find + Temukan + + + Username + Nama pengguna + + + Copy username to clipboard + Salin nama pengguna ke papan klip + + + Password + Kata sandi + + + Copy password to clipboard + Salin kata sandi ke papan klip + + + Settings + Pengaturan + + + Perform Auto-Type + Melakukan Ketik-Otomatis + + + Open URL + Buka URL + + + Lock databases + Kunci basis data + + + Title + Judul + + + URL + URL + + + Notes + Catatan + + + Show toolbar + Tampilkan bilah alat + + + read-only + hanya-baca + + + Toggle window + + + + + PasswordGeneratorWidget + + Password: + Kata sandi: + + + Length: + Panjang: + + + Character Types + Tipe karakter + + + Upper Case Letters + + + + Lower Case Letters + + + + Numbers + Angka + + + Special Characters + Karakter Spesial + + + Exclude look-alike characters + + + + Ensure that the password contains characters from every group + Pastikan kata sandi berisi karakter dari setiap grup + + + Accept + Terima + + + + QCommandLineParser + + Displays version information. + Tampilkan informasi versi + + + Displays this help. + Tampilkan bantuan ini + + + Unknown option '%1'. + Opsi tidak diketahui '%1'. + + + Unknown options: %1. + Opsi tidak diketahui: %1. + + + Missing value after '%1'. + Nilai hilang setelah '%1'. + + + Unexpected value after '%1'. + Nilai tidak terduga setelah '%1'. + + + [options] + [opsi] + + + Usage: %1 + Penggunaan: %1 + + + Options: + Opsi: + + + Arguments: + Argumen + + + + QSaveFile + + Existing file %1 is not writable + Berkas yang ada %1 tidak dapat ditulis + + + Writing canceled by application + Menulis dibatalkan oleh aplikasi + + + Partial write. Partition full? + + + + + QtIOCompressor + + Internal zlib error when compressing: + Galat zlib internal ketika mengkompress: + + + Error writing to underlying device: + + + + Error opening underlying device: + + + + Error reading data from underlying device: + + + + Internal zlib error when decompressing: + Galat zlib internal ketika dekompress + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + Format gzip tidak didukung pada versi zlib ini. + + + Internal zlib error: + Galat zlib internal: + + + + SearchWidget + + Find: + Temukan: + + + Case sensitive + + + + Current group + + + + Root group + + + + + SettingsWidget + + Application Settings + Pengaturan Aplikasi + + + General + Umum + + + Security + Keamanan + + + + SettingsWidgetGeneral + + Remember last databases + Ingat basis data terakhir + + + Open previous databases on startup + Buka basis data sebelumnya saat mulai + + + Mark as modified on expanded state changes + + + + Automatically save on exit + Otomatis simpan ketika keluar + + + Automatically save after every change + Otomatis simpan setelah setiap perubahan + + + Minimize when copying to clipboard + Kecilkan ketika menyalin ke papan klip + + + Use group icon on entry creation + Gunakan ikon grup pada pembuatan entri + + + Global Auto-Type shortcut + Jalan pintas global Ketik-Otomatis + + + Use entry title to match windows for global auto-type + + + + Language + Bahasa + + + Show a system tray icon + Tampilkan sebuah ikon baki sistem + + + Hide window to system tray when minimized + Sembunyikan jendela ke baki sistem ketika dikecilkan + + + + SettingsWidgetSecurity + + Clear clipboard after + Bersihkan papan klip setelaj + + + sec + det + + + Lock databases after inactivity of + + + + Show passwords in cleartext by default + + + + Always ask before performing auto-type + + + + + UnlockDatabaseWidget + + Unlock database + + + + Error + Galat + + + Wrong key. + Kunci salah. + + + + WelcomeWidget + + Welcome! + Selamat Datang. + + + + main + + KeePassX - cross-platform password manager + KeePassX - manajer kata sandi cross-platform + + + filename of the password database to open (*.kdbx) + nama berkasi dari basis data kata sandi untuk dibuka (*.kdbx) + + + path to a custom config file + + + + password of the database (DANGEROUS!) + kata sandi dari basis data (BERBAHAYA!) + + + key file of the database + berkas kunci dari basis data + + + \ No newline at end of file diff --git a/share/translations/keepassx_nl_NL.ts b/share/translations/keepassx_nl_NL.ts index aa6320eee..da3769216 100644 --- a/share/translations/keepassx_nl_NL.ts +++ b/share/translations/keepassx_nl_NL.ts @@ -17,8 +17,8 @@ Auto-typen - KeePassX - Couldn't find an entry that matches the window title. - Kon geen element vinden dat overeenkomt met de venstertitel. + Couldn't find an entry that matches the window title: + Kon geen element vinden dat overeenkomt met de venstertitel: @@ -740,6 +740,17 @@ Opslaan? Verkeerde sleutel of corrupte database. + + Main + + Fatal error while testing the cryptographic functions. + Fatale fout bij het testen van de cryptografische functies. + + + KeePassX - Error + KeePassX - Fout + + MainWindow @@ -898,6 +909,10 @@ Opslaan? read-only alleen-lezen + + Toggle window + Wissel venster + PasswordGeneratorWidget @@ -1106,6 +1121,18 @@ Opslaan? Use entry title to match windows for global auto-type Gebruik naam van element als vensternaam voor auto-typen + + Language + Taal + + + Show a system tray icon + Toon een icoon in de systray + + + Hide window to system tray when minimized + Bij minimaliseren enkel icoon in systray tonen + SettingsWidgetSecurity diff --git a/share/translations/keepassx_ru.ts b/share/translations/keepassx_ru.ts new file mode 100644 index 000000000..6b8092efc --- /dev/null +++ b/share/translations/keepassx_ru.ts @@ -0,0 +1,1205 @@ + + + AboutDialog + + About KeePassX + О KeePassX + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + KeePassX распространяется на условиях Стандартной общественной лицензии GNU (GPL) версии 2 или (на ваше усмотрение) версии 3. + + + + AutoType + + Auto-Type - KeePassX + Автоввод — KeePassX + + + Couldn't find an entry that matches the window title: + Невозможно найти запись, соответствующую заголовку окна: + + + + AutoTypeAssociationsModel + + Window + Окно + + + Sequence + Последовательность + + + Default sequence + Стандартная последовательность + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + Автоввод — KeePassX + + + Select entry to Auto-Type: + Выберете запись для автоввода: + + + + ChangeMasterKeyWidget + + Password + Пароль + + + Enter password: + Введите пароль: + + + Repeat password: + Повторите пароль: + + + Key file + Файл-ключ + + + Browse + Обзор + + + Create + Создать + + + Key files + Файлы-ключи + + + All files + Все файлы + + + Create Key File... + Создать файл-ключ... + + + Error + Ошибка + + + Unable to create Key File : + Невозможно создать файл-ключ: + + + Select a key file + Выбрать файл-ключ + + + Question + Вопрос + + + Do you really want to use an empty string as password? + Вы действительно хотите использовать пустую строку как пароль? + + + Different passwords supplied. + Пароли не совпадают. + + + + DatabaseOpenWidget + + Enter master key + Введите мастер-пароль + + + Key File: + Файл-ключ: + + + Password: + Пароль: + + + Browse + Обзор + + + Error + Ошибка + + + Unable to open the database. + Невозможно открыть хранилище. + + + Can't open key file + Не могу открыть файл-ключ + + + All files + Все файлы + + + Key files + Файлы-ключи + + + Select key file + Выберете файл-ключ + + + + DatabaseSettingsWidget + + Database name: + Имя хранилища: + + + Database description: + Описание хранилища: + + + Transform rounds: + Раундов преобразований: + + + Default username: + Имя пользователя по-умолчанию: + + + Use recycle bin: + Использовать корзину: + + + MiB + МиБ + + + Benchmark + Проверка + + + Max. history items: + Максимум записей истории: + + + Max. history size: + Максимальный размер истории: + + + + DatabaseTabWidget + + Root + Корень + + + KeePass 2 Database + Хранилище KeePass 2 + + + All files + Все файлы + + + Open database + Открыть хранилище + + + Warning + Внимание + + + File not found! + Файл не найден! + + + Open KeePass 1 database + Открыть хранилище KeePass 1 + + + KeePass 1 database + Хранилище KeePass 1 + + + All files (*) + Все файлы (*) + + + Close? + Закрыть? + + + "%1" is in edit mode. +Close anyway? + «%1» в режиме редактирования. +Всё равно закрыть? + + + Save changes? + Сохранить изменения? + + + "%1" was modified. +Save changes? + «%1» изменён. +Сохранить изменения? + + + Error + Ошибка + + + Writing the database failed. + Не удалось записать хранилище. + + + Save database as + Сохранить хранилище как + + + New database + Новое хранилище + + + locked + заблокированно + + + + DatabaseWidget + + Change master key + Сменить мастер-пароль + + + Delete entry? + Удалить запись? + + + Do you really want to delete the entry "%1" for good? + Вы действительно хотите навсегда удалить запись «%1»? + + + Delete entries? + Удалить записи? + + + Do you really want to delete %1 entries for good? + Вы действительно хотите навсегда удалить %1 записей? + + + Move entries to recycle bin? + Поместить записи в корзину? + + + Do you really want to move %n entry(s) to the recycle bin? + Вы действительно хотите поместить %n запись в корзину?Вы действительно хотите поместить %n записи в корзину?Вы действительно хотите поместить %n записей в корзину?Вы действительно хотите поместить %n записей в корзину? + + + Delete group? + Удалить группу? + + + Do you really want to delete the group "%1" for good? + Вы действительно хотите навсегда удалить группу «%1»? + + + Current group + Текущая группа + + + + EditEntryWidget + + Entry + Запись + + + Advanced + Расширенные + + + Icon + Иконка + + + Auto-Type + Автоввод + + + Properties + Параметры + + + History + История + + + Entry history + История записи + + + Add entry + Добавить запись + + + Edit entry + Редактировать запись + + + Error + Ошибка + + + Different passwords supplied. + Пароли не совпадают. + + + New attribute + Новый атрибут + + + Select file + Выбрать файл + + + Unable to open file + Невозможно открыть файл + + + Save attachment + Сохранить вложение + + + Unable to save the attachment: + + Невозможно сохранить вложение: + + + + Tomorrow + Завтра + + + %n week(s) + %n неделя%n недели%n недель%n недель + + + %n month(s) + %n месяц%n месяца%n месяцев%n месяцев + + + 1 year + 1 год + + + + EditEntryWidgetAdvanced + + Additional attributes + Дополнительные атрибуты + + + Add + Добавить + + + Edit + Изменить + + + Remove + Удалить + + + Attachments + Вложения + + + Save + Сохранить + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + Включить автоввод для этой записи + + + Inherit default Auto-Type sequence from the group + Унаследовать стандартную последовательность автоввода от группы + + + Use custom Auto-Type sequence: + Использовать свою последовательность автоввода: + + + + + + + + + - + - + + + Window title: + Заголовок окна: + + + Use default sequence + Использовать стандартную последовательность + + + Set custom sequence: + Установить свою последовательность: + + + + EditEntryWidgetHistory + + Show + Показать + + + Restore + Восстановить + + + Delete + Удалить + + + Delete all + Удалить всё + + + + EditEntryWidgetMain + + Title: + Заголовок: + + + Username: + Имя пользователя: + + + Password: + Пароль: + + + Repeat: + Пароль ещё раз: + + + Gen. + + + + URL: + URL: + + + Expires + Истекает + + + Presets + Предустановки + + + Notes: + Примечания: + + + + EditGroupWidget + + Group + Группа + + + Icon + Иконка + + + Properties + Параметры + + + Add group + Добавить группу + + + Edit group + Редактировать группу + + + Enable + Включено + + + Disable + Выключено + + + Inherit from parent group (%1) + Наследовать у родительской группы (%1) + + + + EditGroupWidgetMain + + Name + Имя + + + Notes + Примечания + + + Expires + Истекает + + + Search + Поиск + + + Auto-type + автоввод + + + + EditWidgetIcons + + Use default icon + Использовать стандартную иконку + + + Use custom icon + Использовать свою иконку + + + Add custom icon + Добавить свою иконку + + + Delete custom icon + Удалить свою иконку + + + Images + Изображения + + + All files + Все файлы + + + Select Image + Выбор изображения + + + Can't delete icon! + Не могу удалить иконку! + + + Can't delete icon. Still used by %n item(s). + Не могу удалить иконку, %n запись всё ещё использует её.Не могу удалить иконку, %n записи всё ещё использует её.Не могу удалить иконку, %n записей всё ещё использует её.Не могу удалить иконку, %n записей всё ещё использует её. + + + + EditWidgetProperties + + Created: + Создано: + + + Modified: + Изменено: + + + Accessed: + Доступ: + + + Uuid: + Uuid: + + + + EntryAttributesModel + + Name + Имя + + + + EntryHistoryModel + + Last modified + Последнее изменение + + + Title + Заголовок + + + Username + Имя пользователя + + + URL + URL + + + + EntryModel + + Group + Группа + + + Title + Заголовок + + + Username + Имя пользователя + + + URL + URL + + + + Group + + Recycle Bin + Корзина + + + + KeePass1OpenWidget + + Import KeePass1 database + Импортировать хранилище KeePass 1 + + + Error + Ошибка + + + Unable to open the database. + Невозможно открыть хранилище. + + + + KeePass1Reader + + Unable to read keyfile. + Невозможно прочесть файл-ключ. + + + Not a KeePass database. + Не хранилище KeePass. + + + Unsupported encryption algorithm. + Алгоритм шифрования не поддерживается. + + + Unsupported KeePass database version. + Версия хранилища KeePass не поддерживается. + + + Root + Корень + + + + KeePass2Reader + + Not a KeePass database. + Не хранилище KeePass. + + + Unsupported KeePass database version. + Версия хранилища KeePass не поддерживается. + + + Wrong key or database file is corrupt. + Неверный ключ или файл хранилища повреждён. + + + + Main + + Fatal error while testing the cryptographic functions. + Неисправимая ошибка в процессе тестирования криптографических функций. + + + KeePassX - Error + KeePassX — Ошибка + + + + MainWindow + + Database + Хранилище + + + Recent databases + Недавние хранилища + + + Help + Помощь + + + Entries + Записи + + + Copy attribute to clipboard + Скопировать атрибут в буфер обмена + + + Groups + Группы + + + Extras + Дополнительно + + + View + Вид + + + Quit + Выход + + + About + О программе + + + Open database + Открыть хранилище + + + Save database + Сохранить хранилище + + + Close database + Закрыть хранилище + + + New database + Новое хранилище + + + Add new entry + Добавить новую запись + + + View/Edit entry + Посмотреть/редактировать запись + + + Delete entry + Удалить запись + + + Add new group + Добавить новую группу + + + Edit group + Редактировать группу + + + Delete group + Удалить группу + + + Save database as + Сохранить хранилище как + + + Change master key + Сменить мастер-пароль + + + Database settings + Параметры хранилища + + + Import KeePass 1 database + Импортировать хранилище KeePass 1 + + + Clone entry + Клонировать запись + + + Find + Найти + + + Username + Имя пользователя + + + Copy username to clipboard + Скопировать имя пользователя в буфер обмена + + + Password + Пароль + + + Copy password to clipboard + Скопировать пароль в буфер обмена + + + Settings + Настройки + + + Perform Auto-Type + Произвести автоввод + + + Open URL + Открыть URL + + + Lock databases + Заблокировать хранилище + + + Title + Заголовок + + + URL + URL + + + Notes + Примечания + + + Show toolbar + Показать панель инструментов + + + read-only + только для чтения + + + Toggle window + Переключить окно + + + + PasswordGeneratorWidget + + Password: + Пароль: + + + Length: + Длина: + + + Character Types + Виды символов + + + Upper Case Letters + Заглавные буквы + + + Lower Case Letters + Строчные буквы + + + Numbers + Цифры + + + Special Characters + Особые символы + + + Exclude look-alike characters + Исключить похожие символы + + + Ensure that the password contains characters from every group + Пожалуйста, пусть пароль будет содержать символы всех видов + + + Accept + Принять + + + + QCommandLineParser + + Displays version information. + Показывает информацию о версии. + + + Displays this help. + Показывает эту справку. + + + Unknown option '%1'. + Неизвестная опция «%1». + + + Unknown options: %1. + Неизвестные опции %1. + + + Missing value after '%1'. + Пропущено значение после «%1». + + + Unexpected value after '%1'. + Непредвиденное значение после «%1». + + + [options] + [опции] + + + Usage: %1 + Использование: %1 + + + Options: + Опции: + + + Arguments: + Аргументы: + + + + QSaveFile + + Existing file %1 is not writable + Существующий файл %1 непригоден для записи + + + Writing canceled by application + Запись отменена приложением + + + Partial write. Partition full? + Частичная запись. Раздел переполнен? + + + + QtIOCompressor + + Internal zlib error when compressing: + Внутренняя ошибка zlib при сжатии: + + + Error writing to underlying device: + Ошибка записи на нижлежащее устройство: + + + Error opening underlying device: + Ошибка открытия нижлежащего устройства: + + + Error reading data from underlying device: + Ошибка чтения с нижлежащего устройства: + + + Internal zlib error when decompressing: + Внутренняя ошибка zlib при расжатии: + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + Формат gzip не поддерживается в этой версии zlib. + + + Internal zlib error: + Внутренняя ошибка zlib: + + + + SearchWidget + + Find: + Найти: + + + Case sensitive + Чувствительно к регистру + + + Current group + Текущая группа + + + Root group + Корневая группа + + + + SettingsWidget + + Application Settings + Параметры приложения + + + General + Общие + + + Security + Безопасность + + + + SettingsWidgetGeneral + + Remember last databases + Помнить последнее хранилище + + + Open previous databases on startup + Открывать предыдущее хранилище при запуске + + + Mark as modified on expanded state changes + + + + Automatically save on exit + Автоматически сохранять при выходе + + + Automatically save after every change + Автоматически сохранять после каждого изменения + + + Minimize when copying to clipboard + Сворачивать при копировании в буфер обмена + + + Use group icon on entry creation + Использовать значок группы для новых записей + + + Global Auto-Type shortcut + Глобальное сочетание клавиш для автоввода + + + Use entry title to match windows for global auto-type + Использовать заголовок записи для подбора окон для глобального автоввода + + + Language + Язык + + + Show a system tray icon + Показывать иконку в трее + + + Hide window to system tray when minimized + При сворачивании прятать окно в область системных уведомлений + + + + SettingsWidgetSecurity + + Clear clipboard after + Потом почтистить буфер обмена + + + sec + сек + + + Lock databases after inactivity of + Заблокировать хранилище после неактивности длительностью + + + Show passwords in cleartext by default + Показывать пароль в открытую по-умолчанию + + + Always ask before performing auto-type + Всегда спрашивать перед тем, как производить автоввод + + + + UnlockDatabaseWidget + + Unlock database + Разблокировать хранилище + + + Error + Ошибка + + + Wrong key. + Неверный ключ. + + + + WelcomeWidget + + Welcome! + Добро пожаловать! + + + + main + + KeePassX - cross-platform password manager + KeePassX — кросс-платформенный менеджер паролей + + + filename of the password database to open (*.kdbx) + имя файла открываемого хранилища паролей (*.kdbx) + + + path to a custom config file + путь к своему файлу настроек + + + password of the database (DANGEROUS!) + пароль от хранилища (ОПАСНО!) + + + key file of the database + файл-ключ хранилища + + + \ No newline at end of file diff --git a/share/translations/keepassx_sv.ts b/share/translations/keepassx_sv.ts index 2a3ba790c..c18a31020 100644 --- a/share/translations/keepassx_sv.ts +++ b/share/translations/keepassx_sv.ts @@ -17,8 +17,8 @@ Auto-skriv - KeePassX - Couldn't find an entry that matches the window title. - Kunde inte hitta en post som matchar fönstertiteln. + Couldn't find an entry that matches the window title: + Kunde inte hitta en post som matchar fönstertiteln: @@ -740,6 +740,17 @@ Spara ändringarna? Fel lösenord eller korrupt databas-fil + + Main + + Fatal error while testing the cryptographic functions. + Allvarligt fel vid testning av kryptografiska funktioner. + + + KeePassX - Error + KeePassX - Fel + + MainWindow @@ -898,6 +909,10 @@ Spara ändringarna? read-only läs bara + + Toggle window + Visa/dölj fönster + PasswordGeneratorWidget @@ -1106,6 +1121,18 @@ Spara ändringarna? Use entry title to match windows for global auto-type Använda postens titel till matchning med fönster för globalt auto-skriv + + Language + Språk + + + Show a system tray icon + Visa statusfält ikon + + + Hide window to system tray when minimized + Vid minimering, minimera fönstret till systemfältet + SettingsWidgetSecurity diff --git a/share/translations/keepassx_zh_TW.ts b/share/translations/keepassx_zh_TW.ts new file mode 100644 index 000000000..76307744b --- /dev/null +++ b/share/translations/keepassx_zh_TW.ts @@ -0,0 +1,1203 @@ + + + AboutDialog + + About KeePassX + 關於 KeePassX + + + KeePassX is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3. + KeePassX 是使用第 2 版 GNU 通用公共授權條款所發佈的 (或者,可根據你的選擇選用第 3 版) + + + + AutoType + + Auto-Type - KeePassX + KeePassX - 自動輸入 + + + Couldn't find an entry that matches the window title: + 無法找到符合視窗標題的項目 + + + + AutoTypeAssociationsModel + + Window + 視窗 + + + Sequence + 序列 + + + Default sequence + 預設的序列 + + + + AutoTypeSelectDialog + + Auto-Type - KeePassX + KeePassX - 自動輸入 + + + Select entry to Auto-Type: + 選擇自動輸入的項目 + + + + ChangeMasterKeyWidget + + Password + 密碼 + + + Enter password: + 輸入密碼 + + + Repeat password: + 再次輸入密碼 + + + Key file + 金鑰檔案 + + + Browse + 瀏覽 + + + Create + 建立 + + + Key files + 金鑰檔案 + + + All files + 所有的檔案 + + + Create Key File... + 建立一個金鑰檔案 + + + Error + 錯誤 + + + Unable to create Key File : + 無法建立金鑰檔案: + + + Select a key file + 選擇一個金鑰檔案 + + + Question + 問題 + + + Do you really want to use an empty string as password? + 你真的想使用空白密碼嗎? + + + Different passwords supplied. + 提供了不同的密碼 + + + + DatabaseOpenWidget + + Enter master key + 輸入主金鑰 + + + Key File: + 金鑰檔案: + + + Password: + 密碼: + + + Browse + 瀏覽 + + + Error + 錯誤 + + + Unable to open the database. + 無法打開這個資料庫 + + + Can't open key file + 無法打開金鑰檔案 + + + All files + 所有的檔案 + + + Key files + 金鑰檔案 + + + Select key file + 選擇金鑰檔案 + + + + DatabaseSettingsWidget + + Database name: + 資料庫名稱: + + + Database description: + 資料庫描述: + + + Transform rounds: + 加密轉換次數 + + + Default username: + 預設的使用者名稱: + + + Use recycle bin: + 使用垃圾桶: + + + MiB + MiB + + + Benchmark + 效能測試 + + + Max. history items: + 最大的歷史筆數: + + + Max. history size: + 最大的歷史大小: + + + + DatabaseTabWidget + + Root + + + + KeePass 2 Database + KeePass 2 資料庫 + + + All files + 所有的檔案 + + + Open database + 打開資料庫 + + + Warning + 警告 + + + File not found! + 找不到檔案! + + + Open KeePass 1 database + 打開 KeePass 1 資料庫 + + + KeePass 1 database + KeePass 1 資料庫 + + + All files (*) + 所有的檔案 (*) + + + Close? + 關閉? + + + "%1" is in edit mode. +Close anyway? + "%1" 正在編輯中。仍然要關閉嗎? + + + Save changes? + 儲存修改? + + + "%1" was modified. +Save changes? + "%1" 已被修改。要儲存嗎? + + + Error + 錯誤 + + + Writing the database failed. + 寫入資料庫失敗 + + + Save database as + 將資料庫儲存為 + + + New database + 新的資料庫 + + + locked + 已鎖住 + + + + DatabaseWidget + + Change master key + 變更主金鑰 + + + Delete entry? + 刪除項目 + + + Do you really want to delete the entry "%1" for good? + 你真的想永遠的刪除 "%1" 項目嗎 + + + Delete entries? + 刪除項目? + + + Do you really want to delete %1 entries for good? + 你真的想永遠刪除 "%1 " 項目嗎? + + + Move entries to recycle bin? + 移動項目到垃圾桶? + + + Do you really want to move %n entry(s) to the recycle bin? + 你真的想將 %n 個項目移到垃圾桶? + + + Delete group? + 刪除群組? + + + Do you really want to delete the group "%1" for good? + 你真的想永遠刪除 "%1 " 群組嗎? + + + Current group + 目前的群組 + + + + EditEntryWidget + + Entry + 項目 + + + Advanced + 進階的 + + + Icon + 圖示 + + + Auto-Type + 自動輸入 + + + Properties + 性質 + + + History + 歷史記錄 + + + Entry history + 項目歷史 + + + Add entry + 增加項目 + + + Edit entry + 編輯項目 + + + Error + 錯誤 + + + Different passwords supplied. + 提供了不同的密碼 + + + New attribute + 新的屬性 + + + Select file + 選擇檔案 + + + Unable to open file + 無法打開檔案 + + + Save attachment + 儲存附件 + + + Unable to save the attachment: + + 無法儲存這個附件: + + + + Tomorrow + 明天 + + + %n week(s) + %n 個禮拜 + + + %n month(s) + %n 個月 + + + 1 year + 1 年 + + + + EditEntryWidgetAdvanced + + Additional attributes + 額外的屬性 + + + Add + 加入 + + + Edit + 編輯 + + + Remove + 移除 + + + Attachments + 附件 + + + Save + 儲存 + + + + EditEntryWidgetAutoType + + Enable Auto-Type for this entry + 打開此項目的自動輸入 + + + Inherit default Auto-Type sequence from the group + 從父群組繼承預設的自動輸入序列 + + + Use custom Auto-Type sequence: + 使用自訂的自動輸入序列 + + + + + + + + + - + - + + + Window title: + 視窗標題: + + + Use default sequence + 使用預設序列 + + + Set custom sequence: + 設定自訂的序列 + + + + EditEntryWidgetHistory + + Show + 顯示 + + + Restore + 還原 + + + Delete + 刪除 + + + Delete all + 刪除全部 + + + + EditEntryWidgetMain + + Title: + 標題: + + + Username: + 使用者名稱: + + + Password: + 密碼: + + + Repeat: + 重複: + + + Gen. + 產生 + + + URL: + 網址: + + + Expires + 過期 + + + Presets + 預設 + + + Notes: + 附註: + + + + EditGroupWidget + + Group + 群組 + + + Icon + 圖示 + + + Properties + 性質 + + + Add group + 加入群組 + + + Edit group + 編輯群組 + + + Enable + 啟用 + + + Disable + 關閉 + + + Inherit from parent group (%1) + 繼承自父群組 (%1) + + + + EditGroupWidgetMain + + Name + 名稱 + + + Notes + 附註 + + + Expires + 過期 + + + Search + 搜尋 + + + Auto-type + 自動輸入 + + + + EditWidgetIcons + + Use default icon + 使用預設的圖示 + + + Use custom icon + 使用自訂的圖示 + + + Add custom icon + 加入自訂的圖示 + + + Delete custom icon + 刪除自訂的圖示 + + + Images + 圖片 + + + All files + 所有的檔案 + + + Select Image + 選擇圖片 + + + Can't delete icon! + 不能刪除圖示! + + + Can't delete icon. Still used by %n item(s). + 不能刪除圖示。仍在被 %n 個使用 + + + + EditWidgetProperties + + Created: + 已建立: + + + Modified: + 已修改: + + + Accessed: + 已存取 + + + Uuid: + Uuid (通用唯一識別碼) + + + + EntryAttributesModel + + Name + 名稱 + + + + EntryHistoryModel + + Last modified + 最後修改 + + + Title + 標題 + + + Username + 使用者名稱 + + + URL + 網址 + + + + EntryModel + + Group + 群組 + + + Title + 標題 + + + Username + 使用者名稱 + + + URL + 網址 + + + + Group + + Recycle Bin + 回收桶 + + + + KeePass1OpenWidget + + Import KeePass1 database + 匯入 KeePass 1 資料庫 + + + Error + 錯誤 + + + Unable to open the database. + 無法開啟這個資料庫 + + + + KeePass1Reader + + Unable to read keyfile. + 無法讀取金鑰檔案 + + + Not a KeePass database. + 不是 KeePass 資料庫 + + + Unsupported encryption algorithm. + 不支援的加密演算法 + + + Unsupported KeePass database version. + 不支援的 KeePass 資料庫版本 + + + Root + + + + + KeePass2Reader + + Not a KeePass database. + 不是 KeePass 的資料庫 + + + Unsupported KeePass database version. + 不支援的 KeePass 資料庫版本 + + + Wrong key or database file is corrupt. + 無法的金鑰或資料庫損壞 + + + + Main + + Fatal error while testing the cryptographic functions. + 重大錯誤,在測試加密函數時 + + + KeePassX - Error + KeePassX - 錯誤 + + + + MainWindow + + Database + 資料庫 + + + Recent databases + 近期的資料庫 + + + Help + 幫助 + + + Entries + 項目 + + + Copy attribute to clipboard + 將屬性複製到剪貼簿 + + + Groups + 群組 + + + Extras + 其它 + + + View + 顯示 + + + Quit + 關閉 + + + About + 關於 + + + Open database + 打開資料庫 + + + Save database + 儲存資料庫 + + + Close database + 關閉資料庫 + + + New database + 新增資料庫 + + + Add new entry + 加入項目 + + + View/Edit entry + 瀏覽/編輯項目 + + + Delete entry + 刪除項目 + + + Add new group + 增加新的群組 + + + Edit group + 編輯群組 + + + Delete group + 刪除群組 + + + Save database as + 儲存資料庫為 + + + Change master key + 變更主金鑰 + + + Database settings + 資料庫設定 + + + Import KeePass 1 database + 匯入 KeePass 1 資料庫 + + + Clone entry + 拷貝項目 + + + Find + 尋找 + + + Username + 使用者名稱 + + + Copy username to clipboard + 將使用者名稱複製到剪貼簿 + + + Password + 密碼 + + + Copy password to clipboard + 將密碼複製到剪貼簿 + + + Settings + 設定 + + + Perform Auto-Type + 執行自動輸入 + + + Open URL + 打開網址 + + + Lock databases + 鎖住資料庫 + + + Title + 標題 + + + URL + 網址 + + + Notes + 附註 + + + Show toolbar + 顯示工具列 + + + read-only + 唯讀 + + + Toggle window + 切換視窗 + + + + PasswordGeneratorWidget + + Password: + 密碼: + + + Length: + 長度: + + + Character Types + 字元類型 + + + Upper Case Letters + 大寫英文字母 + + + Lower Case Letters + 小寫英文字母 + + + Numbers + 數字 + + + Special Characters + 特殊字元 + + + Exclude look-alike characters + 去除相似的字元 + + + Ensure that the password contains characters from every group + 確定密碼包含每一組的字元 + + + Accept + 接受 + + + + QCommandLineParser + + Displays version information. + 顯示版本資訊 + + + Displays this help. + 顯示這個幫助訊息 + + + Unknown option '%1'. + 不知的選項 '%1' + + + Unknown options: %1. + 不知的選項 '%1' + + + Missing value after '%1'. + 在 "%1" 後缺少值 + + + Unexpected value after '%1'. + "%1" 後有不預期的值 + + + [options] + [選項] + + + Usage: %1 + 使用方式:%1 + + + Options: + 選項: + + + Arguments: + 參數 + + + + QSaveFile + + Existing file %1 is not writable + 現有的檔案 %1 不可寫入 + + + Writing canceled by application + 應用程式取消寫入 + + + Partial write. Partition full? + 部分寫入。分區滿了嗎? + + + + QtIOCompressor + + Internal zlib error when compressing: + 進行壓縮時,函式庫 zlib 出錯 + + + Error writing to underlying device: + 寫入底層裝置時出錯 + + + Error opening underlying device: + 開啟底層裝置時出錯 + + + Error reading data from underlying device: + 讀取底層裝置時出錯 + + + Internal zlib error when decompressing: + 在解壓縮時,內部的 zlib 函式庫發生錯誤: + + + + QtIOCompressor::open + + The gzip format not supported in this version of zlib. + 這個版本的壓縮函式庫 zlib 不支援 gzip + + + Internal zlib error: + 內部函式庫 zlib 發生錯誤: + + + + SearchWidget + + Find: + 尋找: + + + Case sensitive + 區分大小寫 + + + Current group + 目前的群組 + + + Root group + 根群組 + + + + SettingsWidget + + Application Settings + 應用程式設定 + + + General + 一般 + + + Security + 安全性 + + + + SettingsWidgetGeneral + + Remember last databases + 記住最近的資料庫 + + + Open previous databases on startup + 在啟動時開啟最近的資料庫 + + + Mark as modified on expanded state changes + 擴展狀態發生變化時,標記為已修改 + + + Automatically save on exit + 離開時,自動儲存 + + + Automatically save after every change + 修改後,自動儲存 + + + Minimize when copying to clipboard + 在複製到剪貼簿時最小化 + + + Use group icon on entry creation + 新增項目時使用群組圖示 + + + Global Auto-Type shortcut + 全域自動輸入快捷鍵 + + + Use entry title to match windows for global auto-type + 使用項目標題來找尋自動輸入的目標視窗 + + + Language + 語言 + + + Show a system tray icon + 顯示系統夾圖示 + + + Hide window to system tray when minimized + 將視窗最小化至工作列 + + + + SettingsWidgetSecurity + + Clear clipboard after + 在多久後清除剪貼簿 + + + sec + + + + Lock databases after inactivity of + 在多久沒有動作之後鎖住資料庫 + + + Show passwords in cleartext by default + 預設以明碼顯示密碼 + + + Always ask before performing auto-type + 在執行自動輸入前通常要詢問 + + + + UnlockDatabaseWidget + + Unlock database + 解鎖資料庫 + + + Error + 錯誤 + + + Wrong key. + 錯誤的金鑰 + + + + WelcomeWidget + + Welcome! + 歡迎! + + + + main + + KeePassX - cross-platform password manager + KeePassX - 跨平台密碼管理軟體 + + + filename of the password database to open (*.kdbx) + 要開啟的密碼資料庫檔案名稱 (*.kdbx) + + + path to a custom config file + 自定設定檔的路徑 + + + password of the database (DANGEROUS!) + 資料庫的密碼(危險!) + + + key file of the database + 資料庫的金鑰 + + + \ No newline at end of file diff --git a/share/translations/update.sh b/share/translations/update.sh index 6828dc820..c6296b54c 100755 --- a/share/translations/update.sh +++ b/share/translations/update.sh @@ -4,5 +4,9 @@ BASEDIR=$(dirname $0) cd $BASEDIR/../.. +echo Updating source file lupdate -no-ui-lines -disable-heuristic similartext -locations none -no-obsolete src -ts share/translations/keepassx_en.ts lupdate -no-ui-lines -disable-heuristic similartext -locations none -pluralonly src -ts share/translations/keepassx_en_plurals.ts + +echo Pulling translations from Transifex +tx pull -a --minimum-perc=80