keepassxc/src/cli/Utils.cpp
Carlo Teubner 94430c300b CLI: fix missing check for correct credentials (#2629)
* CLI: fix missing check for correct credentials

Before this fix, most/all CLI commands had incorrect behaviour when bad
credentials were supplied: they would carry on regardless, with
potentially catastrophic results.  In particular, the "add" subcommand
seemed to corrupt the database.  "ls" would always report an empty
database.  Haven't tested any others.

Also fixed a related missing check specific to the "merge" subcommand.
2019-01-21 14:24:29 -05:00

227 lines
6 KiB
C++

/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* 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 "Utils.h"
#ifdef Q_OS_WIN
#include <windows.h>
#else
#include <termios.h>
#include <unistd.h>
#endif
#include <QProcess>
namespace Utils
{
/**
* STDOUT file handle for the CLI.
*/
FILE* STDOUT = stdout;
/**
* STDERR file handle for the CLI.
*/
FILE* STDERR = stderr;
/**
* STDIN file handle for the CLI.
*/
FILE* STDIN = stdin;
/**
* DEVNULL file handle for the CLI.
*/
#ifdef Q_OS_WIN
FILE* DEVNULL = fopen("nul", "w");
#else
FILE* DEVNULL = fopen("/dev/null", "w");
#endif
void setStdinEcho(bool enable = true)
{
#ifdef Q_OS_WIN
HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode;
GetConsoleMode(hIn, &mode);
if (enable) {
mode |= ENABLE_ECHO_INPUT;
} else {
mode &= ~ENABLE_ECHO_INPUT;
}
SetConsoleMode(hIn, mode);
#else
struct termios t;
tcgetattr(STDIN_FILENO, &t);
if (enable) {
t.c_lflag |= ECHO;
} else {
t.c_lflag &= ~ECHO;
}
tcsetattr(STDIN_FILENO, TCSANOW, &t);
#endif
}
namespace Test
{
QStringList nextPasswords = {};
/**
* Set the next password returned by \link getPassword() instead of reading it from STDIN.
* Multiple calls to this method will fill a queue of passwords.
* This function is intended for testing purposes.
*
* @param password password to return next
*/
void setNextPassword(const QString& password)
{
nextPasswords.append(password);
}
} // namespace Test
QSharedPointer<Database> unlockDatabase(const QString& databaseFilename,
const QString& keyFilename,
FILE* outputDescriptor,
FILE* errorDescriptor)
{
auto compositeKey = QSharedPointer<CompositeKey>::create();
TextStream out(outputDescriptor);
TextStream err(errorDescriptor);
out << QObject::tr("Insert password to unlock %1: ").arg(databaseFilename) << flush;
QString line = Utils::getPassword(outputDescriptor);
auto passwordKey = QSharedPointer<PasswordKey>::create();
passwordKey->setPassword(line);
compositeKey->addKey(passwordKey);
if (!keyFilename.isEmpty()) {
auto fileKey = QSharedPointer<FileKey>::create();
QString errorMessage;
// LCOV_EXCL_START
if (!fileKey->load(keyFilename, &errorMessage)) {
err << QObject::tr("Failed to load key file %1: %2").arg(keyFilename, errorMessage) << endl;
return {};
}
if (fileKey->type() != FileKey::Hashed) {
err << QObject::tr("WARNING: You are using a legacy key file format which may become\n"
"unsupported in the future.\n\n"
"Please consider generating a new key file.")
<< endl;
}
// LCOV_EXCL_STOP
compositeKey->addKey(fileKey);
}
auto db = QSharedPointer<Database>::create();
QString error;
if (db->open(databaseFilename, compositeKey, &error, false)) {
return db;
} else {
err << error << endl;
return {};
}
}
/**
* Read a user password from STDIN or return a password previously
* set by \link setNextPassword().
*
* @return the password
*/
QString getPassword(FILE* outputDescriptor)
{
TextStream out(outputDescriptor, QIODevice::WriteOnly);
// return preset password if one is set
if (!Test::nextPasswords.isEmpty()) {
auto password = Test::nextPasswords.takeFirst();
// simulate user entering newline
out << endl;
return password;
}
TextStream in(STDIN, QIODevice::ReadOnly);
setStdinEcho(false);
QString line = in.readLine();
setStdinEcho(true);
out << endl;
return line;
}
/**
* A valid and running event loop is needed to use the global QClipboard,
* so we need to use this from the CLI.
*/
int clipText(const QString& text)
{
TextStream err(Utils::STDERR);
QString programName = "";
QStringList arguments;
#ifdef Q_OS_UNIX
programName = "xclip";
arguments << "-i"
<< "-selection"
<< "clipboard";
#endif
#ifdef Q_OS_MACOS
programName = "pbcopy";
#endif
#ifdef Q_OS_WIN
programName = "clip";
#endif
if (programName.isEmpty()) {
err << QObject::tr("No program defined for clipboard manipulation");
err.flush();
return EXIT_FAILURE;
}
auto* clipProcess = new QProcess(nullptr);
clipProcess->start(programName, arguments);
clipProcess->waitForStarted();
if (clipProcess->state() != QProcess::Running) {
err << QObject::tr("Unable to start program %1").arg(programName);
err.flush();
return EXIT_FAILURE;
}
if (clipProcess->write(text.toLatin1()) == -1) {
qDebug("Unable to write to process : %s", qPrintable(clipProcess->errorString()));
}
clipProcess->waitForBytesWritten();
clipProcess->closeWriteChannel();
clipProcess->waitForFinished();
return clipProcess->exitCode();
}
} // namespace Utils