Skip to content

Commit

Permalink
CLI: Add Import XML command (keepassxreboot#3572)
Browse files Browse the repository at this point in the history
The CLI now contains an "import" command that creates a new database from the specified XML export. The new database is in kdbx 4 format, and does not currently accept a keyfile in database creation.

This change is required to create new databases from XML backups.

Fixes keepassxreboot#2458
  • Loading branch information
jsachs authored and scoroi committed Nov 10, 2019
1 parent 6324f95 commit a13addf
Show file tree
Hide file tree
Showing 14 changed files with 2,127 additions and 1,904 deletions.
3 changes: 1 addition & 2 deletions src/browser/BrowserService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,7 @@ QList<Entry*> BrowserService::searchEntries(const QString& url, const StringPair
// Check if database is connected with KeePassXC-Browser
auto databaseConnected = [&](const QSharedPointer<Database>& db) {
for (const StringPair& keyPair : keyList) {
QString key =
db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first);
QString key = db->metadata()->customData()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first);
if (!key.isEmpty() && keyPair.second == key) {
return true;
}
Expand Down
1 change: 1 addition & 0 deletions src/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ set(cli_SOURCES
Export.cpp
Generate.cpp
Help.cpp
Import.cpp
List.cpp
Locate.cpp
Merge.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/cli/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "Export.h"
#include "Generate.h"
#include "Help.h"
#include "Import.h"
#include "List.h"
#include "Locate.h"
#include "Merge.h"
Expand Down Expand Up @@ -180,6 +181,7 @@ namespace Commands
s_commands.insert(QStringLiteral("quit"), QSharedPointer<Command>(new Exit("quit")));
} else {
s_commands.insert(QStringLiteral("export"), QSharedPointer<Command>(new Export()));
s_commands.insert(QStringLiteral("import"), QSharedPointer<Command>(new Import()));
}
}

Expand Down
24 changes: 1 addition & 23 deletions src/cli/Create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ int Create::execute(const QStringList& arguments)

auto key = QSharedPointer<CompositeKey>::create();

auto password = getPasswordFromStdin();
auto password = Utils::getPasswordFromStdin();
if (!password.isNull()) {
key->addKey(password);
}
Expand Down Expand Up @@ -107,28 +107,6 @@ int Create::execute(const QStringList& arguments)
return EXIT_SUCCESS;
}

/**
* Read optional password from stdin.
*
* @return Pointer to the PasswordKey or null if passwordkey is skipped
* by user
*/
QSharedPointer<PasswordKey> Create::getPasswordFromStdin()
{
QSharedPointer<PasswordKey> passwordKey;
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);

out << QObject::tr("Insert password to encrypt database (Press enter to leave blank): ");
out.flush();
QString password = Utils::getPassword();

if (!password.isEmpty()) {
passwordKey = QSharedPointer<PasswordKey>(new PasswordKey(password));
}

return passwordKey;
}

/**
* Load a key file from disk. When the path specified does not exist a
* new file will be generated. No folders will be generated so the parent
Expand Down
2 changes: 0 additions & 2 deletions src/cli/Create.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
#include "Command.h"

#include "keys/FileKey.h"
#include "keys/PasswordKey.h"

class Create : public Command
{
Expand All @@ -30,7 +29,6 @@ class Create : public Command
int execute(const QStringList& arguments) override;

private:
QSharedPointer<PasswordKey> getPasswordFromStdin();
bool loadFileKey(const QString& path, QSharedPointer<FileKey>& fileKey);
};

Expand Down
100 changes: 100 additions & 0 deletions src/cli/Import.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright (C) 2019 KeePassXC Team <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <cstdlib>
#include <stdio.h>

#include <QFileInfo>
#include <QString>
#include <QTextStream>

#include "Import.h"

#include "cli/TextStream.h"
#include "cli/Utils.h"
#include "core/Database.h"
#include "keys/CompositeKey.h"
#include "keys/Key.h"

/**
* Create a database file from an XML export of another database.
* A password can be specified to encrypt the database.
* If none is specified the function will fail.
*
* If the database is being saved in a non existant directory, the
* function will fail.
*
* @return EXIT_SUCCESS on success, or EXIT_FAILURE on failure
*/
Import::Import()
{
name = QString("import");
description = QObject::tr("Import the contents of an XML database.");
positionalArguments.append({QString("xml"), QObject::tr("Path of the XML database export."), QString("")});
positionalArguments.append({QString("database"), QObject::tr("Path of the new database."), QString("")});
}

int Import::execute(const QStringList& arguments)
{
QSharedPointer<QCommandLineParser> parser = getCommandLineParser(arguments);
if (parser.isNull()) {
return EXIT_FAILURE;
}

TextStream outputTextStream(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);

const QStringList args = parser->positionalArguments();
const QString xmlExportPath = args.at(0);
const QString dbPath = args.at(1);

if (QFileInfo::exists(dbPath)) {
errorTextStream << QObject::tr("File %1 already exists.").arg(dbPath) << endl;
return EXIT_FAILURE;
}

auto key = QSharedPointer<CompositeKey>::create();

auto password = Utils::getPasswordFromStdin();
if (!password.isNull()) {
key->addKey(password);
}

if (key->isEmpty()) {
errorTextStream << QObject::tr("No key is set. Aborting database creation.") << endl;
return EXIT_FAILURE;
}

QString errorMessage;
Database db;
db.setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2));
db.setKey(key);

if (!db.import(xmlExportPath, &errorMessage)) {
errorTextStream << QObject::tr("Unable to import XML database export %1").arg(errorMessage) << endl;
return EXIT_FAILURE;
}

if (!db.save(dbPath, &errorMessage, true, false)) {
errorTextStream << QObject::tr("Failed to save the database: %1.").arg(errorMessage) << endl;
return EXIT_FAILURE;
}

outputTextStream << QObject::tr("Successfully imported database.") << endl;
return EXIT_SUCCESS;
}
30 changes: 30 additions & 0 deletions src/cli/Import.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2019 KeePassXC Team <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef KEEPASSXC_IMPORT_H
#define KEEPASSXC_IMPORT_H

#include "Command.h"

class Import : public Command
{
public:
Import();
int execute(const QStringList& arguments) override;
};

#endif // KEEPASSXC_IMPORT_H
24 changes: 23 additions & 1 deletion src/cli/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ namespace Utils
}

if (isPasswordProtected) {
out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename) << flush;
out << QObject::tr("Enter password to unlock %1: ").arg(databaseFilename) << flush;
QString line = Utils::getPassword(outputDescriptor);
auto passwordKey = QSharedPointer<PasswordKey>::create();
passwordKey->setPassword(line);
Expand Down Expand Up @@ -217,6 +217,28 @@ namespace Utils
return line;
}

/**
* Read optional password from stdin.
*
* @return Pointer to the PasswordKey or null if passwordkey is skipped
* by user
*/
QSharedPointer<PasswordKey> getPasswordFromStdin()
{
QSharedPointer<PasswordKey> passwordKey;
QTextStream out(Utils::STDOUT, QIODevice::WriteOnly);

out << QObject::tr("Enter password to encrypt database (optional): ");
out.flush();
QString password = Utils::getPassword();

if (!password.isEmpty()) {
passwordKey = QSharedPointer<PasswordKey>(new PasswordKey(password));
}

return passwordKey;
}

/**
* A valid and running event loop is needed to use the global QClipboard,
* so we need to use this from the CLI.
Expand Down
1 change: 1 addition & 0 deletions src/cli/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace Utils

void setStdinEcho(bool enable);
QString getPassword(FILE* outputDescriptor = STDOUT);
QSharedPointer<PasswordKey> getPasswordFromStdin();
int clipText(const QString& text);
QSharedPointer<Database> unlockDatabase(const QString& databaseFilename,
const bool isPasswordProtected = true,
Expand Down
3 changes: 3 additions & 0 deletions src/cli/keepassxc-cli.1
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ Generates a random password.
.IP "help [command]"
Displays a list of available commands, or detailed information about the specified command.

.IP "import [options] <xml> <database>"
Imports the contents of an XML database to the target database.

.IP "locate [options] <database> <term>"
Locates all the entries that match a specific search term in a database.

Expand Down
19 changes: 19 additions & 0 deletions src/core/Database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "core/Group.h"
#include "core/Merger.h"
#include "core/Metadata.h"
#include "format/KdbxXmlReader.h"
#include "format/KeePass2Reader.h"
#include "format/KeePass2Writer.h"
#include "keys/FileKey.h"
Expand Down Expand Up @@ -332,6 +333,24 @@ bool Database::extract(QByteArray& xmlOutput, QString* error)
return true;
}

bool Database::import(const QString& xmlExportPath, QString* error)
{
KdbxXmlReader reader(KeePass2::FILE_VERSION_4);
QFile file(xmlExportPath);
file.open(QIODevice::ReadOnly);

reader.readDatabase(&file, this);

if (reader.hasError()) {
if (error) {
*error = reader.errorString();
}
return false;
}

return true;
}

/**
* Remove the old backup and replace it with a new one
* backups are named <filename>.old.<extension>
Expand Down
1 change: 1 addition & 0 deletions src/core/Database.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class Database : public QObject
bool save(QString* error = nullptr, bool atomic = true, bool backup = false);
bool save(const QString& filePath, QString* error = nullptr, bool atomic = true, bool backup = false);
bool extract(QByteArray&, QString* error = nullptr);
bool import(const QString& xmlExportPath, QString* error = nullptr);

bool isInitialized() const;
void setInitialized(bool initialized);
Expand Down
Loading

0 comments on commit a13addf

Please sign in to comment.