Add CLI tests and improve coding style and i18n

The CLI module was lacking unit test coverage and showed some severe
coding style violations, which this patch addresses.

In addition, all uses of qCritical() with untranslatble raw char*
sequences were removed in favor of proper locale strings. These are
written to STDERR through QTextStreams and support output
redirection for testing purposes. With this change, error messages don't
depend on the global Qt logging settings and targets anymore and go
directly to the terminal or into a file if needed.

This patch also fixes a bug discovered during unit test development,
where the extract command would just dump the raw XML contents without
decrypting embedded Salsa20-protected values first, making the XML
export mostly useless, since passwords are scrambled.

Lastly, all CLI commands received a dedicated -h/--help option.
This commit is contained in:
Janek Bevendorff 2018-09-29 19:00:47 +02:00
parent 18b22834c1
commit 113c8eb702
67 changed files with 2259 additions and 1250 deletions

236
src/core/Bootstrap.cpp Normal file
View file

@ -0,0 +1,236 @@
/*
* Copyright (C) 2018 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 "Bootstrap.h"
#include "core/Config.h"
#include "core/Translator.h"
#ifdef Q_OS_WIN
#include <aclapi.h> // for createWindowsDACL()
#include <windows.h> // for Sleep(), SetDllDirectoryA(), SetSearchPathMode(), ...
#endif
namespace Bootstrap
{
/**
* When QNetworkAccessManager is instantiated it regularly starts polling
* all network interfaces to see if anything changes and if so, what. This
* creates a latency spike every 10 seconds on Mac OS 10.12+ and Windows 7 >=
* when on a wifi connection.
* So here we disable it for lack of better measure.
* This will also cause this message: QObject::startTimer: Timers cannot
* have negative intervals
* For more info see:
* - https://bugreports.qt.io/browse/QTBUG-40332
* - https://bugreports.qt.io/browse/QTBUG-46015
*/
static inline void applyEarlyQNetworkAccessManagerWorkaround()
{
qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
}
/**
* Perform early application bootstrapping such as setting up search paths,
* configuration OS security properties, and loading translators.
* A QApplication object has to be instantiated before calling this function.
*/
void bootstrapApplication()
{
#ifdef QT_NO_DEBUG
disableCoreDumps();
#endif
setupSearchPaths();
applyEarlyQNetworkAccessManagerWorkaround();
Translator::installTranslators();
#ifdef Q_OS_MAC
// Don't show menu icons on OSX
QApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
#endif
}
/**
* Restore the main window's state after launch
*
* @param mainWindow the main window whose state to restore
*/
void restoreMainWindowState(MainWindow& mainWindow)
{
// start minimized if configured
bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool();
bool minimizeToTray = config()->get("GUI/MinimizeToTray").toBool();
#ifndef Q_OS_LINUX
if (minimizeOnStartup) {
#else
// On some Linux systems, the window should NOT be minimized and hidden (i.e. not shown), at
// the same time (which would happen if both minimize on startup and minimize to tray are set)
// since otherwise it causes problems on restore as seen on issue #1595. Hiding it is enough.
if (minimizeOnStartup && !minimizeToTray) {
#endif
mainWindow.setWindowState(Qt::WindowMinimized);
}
if (!(minimizeOnStartup && minimizeToTray)) {
mainWindow.show();
}
if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) {
const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList();
for (const QString& filename : fileNames) {
if (!filename.isEmpty() && QFile::exists(filename)) {
mainWindow.openDatabase(filename);
}
}
}
}
// LCOV_EXCL_START
void disableCoreDumps()
{
// default to true
// there is no point in printing a warning if this is not implemented on the platform
bool success = true;
#if defined(HAVE_RLIMIT_CORE)
struct rlimit limit;
limit.rlim_cur = 0;
limit.rlim_max = 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
success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0);
#endif
#ifdef Q_OS_WIN
success = success && createWindowsDACL();
#endif
if (!success) {
qWarning("Unable to disable core dumps.");
}
}
//
// This function grants the user associated with the process token minimal access rights and
// denies everything else on Windows. This includes PROCESS_QUERY_INFORMATION and
// PROCESS_VM_READ access rights that are required for MiniDumpWriteDump() or ReadProcessMemory().
// We do this using a discretionary access control list (DACL). Effectively this prevents
// crash dumps and disallows other processes from accessing our memory. This works as long
// as you do not have admin privileges, since then you are able to grant yourself the
// SeDebugPrivilege or SeTakeOwnershipPrivilege and circumvent the DACL.
//
bool createWindowsDACL()
{
bool bSuccess = false;
#ifdef Q_OS_WIN
// Process token and user
HANDLE hToken = nullptr;
PTOKEN_USER pTokenUser = nullptr;
DWORD cbBufferSize = 0;
// Access control list
PACL pACL = nullptr;
DWORD cbACL = 0;
// Open the access token associated with the calling process
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
goto Cleanup;
}
// Retrieve the token information in a TOKEN_USER structure
GetTokenInformation(hToken, TokenUser, nullptr, 0, &cbBufferSize);
pTokenUser = static_cast<PTOKEN_USER>(HeapAlloc(GetProcessHeap(), 0, cbBufferSize));
if (pTokenUser == nullptr) {
goto Cleanup;
}
if (!GetTokenInformation(hToken, TokenUser, pTokenUser, cbBufferSize, &cbBufferSize)) {
goto Cleanup;
}
if (!IsValidSid(pTokenUser->User.Sid)) {
goto Cleanup;
}
// Calculate the amount of memory that must be allocated for the DACL
cbACL = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pTokenUser->User.Sid);
// Create and initialize an ACL
pACL = static_cast<PACL>(HeapAlloc(GetProcessHeap(), 0, cbACL));
if (pACL == nullptr) {
goto Cleanup;
}
if (!InitializeAcl(pACL, cbACL, ACL_REVISION)) {
goto Cleanup;
}
// Add allowed access control entries, everything else is denied
if (!AddAccessAllowedAce(
pACL,
ACL_REVISION,
SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE, // same as protected process
pTokenUser->User.Sid // pointer to the trustee's SID
)) {
goto Cleanup;
}
// Set discretionary access control list
bSuccess = ERROR_SUCCESS
== SetSecurityInfo(GetCurrentProcess(), // object handle
SE_KERNEL_OBJECT, // type of object
DACL_SECURITY_INFORMATION, // change only the objects DACL
nullptr,
nullptr, // do not change owner or group
pACL, // DACL specified
nullptr // do not change SACL
);
Cleanup:
if (pACL != nullptr) {
HeapFree(GetProcessHeap(), 0, pACL);
}
if (pTokenUser != nullptr) {
HeapFree(GetProcessHeap(), 0, pTokenUser);
}
if (hToken != nullptr) {
CloseHandle(hToken);
}
#endif
return bSuccess;
}
// LCOV_EXCL_STOP
void setupSearchPaths()
{
#ifdef Q_OS_WIN
// Make sure Windows doesn't load DLLs from the current working directory
SetDllDirectoryA("");
SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE);
#endif
}
} // namespace Bootstrap