QtPass 1.6.0
Multi-platform GUI for pass, the standard unix password manager.
Loading...
Searching...
No Matches
qtpass.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2018 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
3#include "qtpass.h"
4#include "mainwindow.h"
5#include "qtpasssettings.h"
6#include "util.h"
7#include <QApplication>
8#include <QClipboard>
9#include <QDialog>
10#include <QLabel>
11#include <QPixmap>
12#include <QVBoxLayout>
13
14#ifndef Q_OS_WIN
15#include <QInputDialog>
16#include <QLineEdit>
17#include <QMimeData>
18#include <utility>
19#else
20#define WIN32_LEAN_AND_MEAN /*_KILLING_MACHINE*/
21#define WIN32_EXTRA_LEAN
22#include <windows.h>
23#include <winnetwk.h>
24#undef DELETE
25#include <QMimeData>
26#endif
27
28#ifdef QT_DEBUG
29#include "debughelper.h"
30#endif
31
37 : m_mainWindow(mainWindow), freshStart(true) {
39 clearClipboardTimer.setSingleShot(true);
40 connect(&clearClipboardTimer, &QTimer::timeout, this,
42
43#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
44#pragma GCC diagnostic push
45#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
46#endif
47 QObject::connect(qApp, &QApplication::aboutToQuit, this,
49#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
50#pragma GCC diagnostic pop
51#endif
52
53 setMainWindow();
54}
55
60#ifdef Q_OS_WIN
62 WNetCancelConnection2A(QtPassSettings::getPassStore().toUtf8().constData(),
63 0, 1);
64#else
65 if (fusedav.state() == QProcess::Running) {
66 fusedav.terminate();
67 fusedav.waitForFinished(2000);
68 }
69#endif
70}
71
76auto QtPass::init() -> bool {
79
81
82 QString version = QtPassSettings::getVersion();
83
84 // Config updates
85 if (version.isEmpty()) {
86#ifdef QT_DEBUG
87 dbg() << "assuming fresh install";
88#endif
89
92 }
95 }
96 if (!QtPassSettings::getPwgenExecutable().isEmpty()) {
98 } else {
100 }
102 } else {
103 if (QtPassSettings::getPassTemplate().isEmpty()) {
105 }
106 }
107
109
110 if (!Util::configIsValid()) {
111 m_mainWindow->config();
112 if (freshStart && !Util::configIsValid()) {
113 return false;
114 }
115 }
116
117 // Note: WebDAV mount needs to happen before accessing the store,
118 // but ideally should be done after Window is shown to avoid long delay.
120 mountWebDav();
121 }
122
123 freshStart = false;
124 return true;
125}
126
130void QtPass::setMainWindow() {
131 m_mainWindow->restoreWindow();
132
133 fusedav.setParent(m_mainWindow);
134
135 // Signal handlers are connected for both pass implementations
136 // Note: When pass binary changes, QtPass restart is required to reconnect
137 // This is acceptable as pass binary change is infrequent
138 connectPassSignalHandlers(QtPassSettings::getRealPass());
139 connectPassSignalHandlers(QtPassSettings::getImitatePass());
140
141 connect(m_mainWindow, &MainWindow::passShowHandlerFinished, this,
142 &QtPass::passShowHandlerFinished);
143
144 // only for ipass
146 m_mainWindow, &MainWindow::startReencryptPath);
148 m_mainWindow, &MainWindow::endReencryptPath);
149
150 connect(m_mainWindow, &MainWindow::passGitInitNeeded, []() {
151#ifdef QT_DEBUG
152 dbg() << "Pass git init called";
153#endif
154 QtPassSettings::getPass()->GitInit();
155 });
156
157 connect(m_mainWindow, &MainWindow::generateGPGKeyPair, m_mainWindow,
158 [this](const QString &batch) {
159 QtPassSettings::getPass()->GenerateGPGKeys(batch);
160 m_mainWindow->showStatusMessage(tr("Generating GPG key pair"),
161 60000);
162 });
163}
164
169void QtPass::connectPassSignalHandlers(Pass *pass) {
170 connect(pass, &Pass::error, this, &QtPass::processError);
171 connect(pass, &Pass::processErrorExit, this, &QtPass::processErrorExit);
172 connect(pass, &Pass::critical, m_mainWindow, &MainWindow::critical);
173 connect(pass, &Pass::startingExecuteWrapper, m_mainWindow,
175 connect(pass, &Pass::statusMsg, m_mainWindow, &MainWindow::showStatusMessage);
176 connect(pass, &Pass::finishedShow, m_mainWindow,
178 connect(pass, &Pass::finishedOtpGenerate, m_mainWindow,
180
181 connect(pass, &Pass::finishedGitInit, this, &QtPass::passStoreChanged);
182 connect(pass, &Pass::finishedGitPull, this, &QtPass::processFinished);
183 connect(pass, &Pass::finishedGitPush, this, &QtPass::processFinished);
184 connect(pass, &Pass::finishedInsert, this, &QtPass::finishedInsert);
185 connect(pass, &Pass::finishedRemove, this, &QtPass::passStoreChanged);
186 connect(pass, &Pass::finishedInit, this, &QtPass::passStoreChanged);
187 connect(pass, &Pass::finishedMove, this, &QtPass::passStoreChanged);
188 connect(pass, &Pass::finishedCopy, this, &QtPass::passStoreChanged);
189 connect(pass, &Pass::finishedGenerateGPGKeys, this,
190 &QtPass::onKeyGenerationComplete);
191 connect(pass, &Pass::finishedGrep, m_mainWindow, &MainWindow::onGrepFinished);
192}
193
197void QtPass::mountWebDav() {
198#ifdef Q_OS_WIN
199 char dst[20] = {0};
200 NETRESOURCEA netres;
201 memset(&netres, 0, sizeof(netres));
202 netres.dwType = RESOURCETYPE_DISK;
203 netres.lpLocalName = nullptr;
204 // Store QByteArray in variables to ensure lifetime during WNetUseConnectionA
205 // call
206 QByteArray webDavUrlUtf8 = QtPassSettings::getWebDavUrl().toUtf8();
207 QByteArray webDavPasswordUtf8 = QtPassSettings::getWebDavPassword().toUtf8();
208 QByteArray webDavUserUtf8 = QtPassSettings::getWebDavUser().toUtf8();
209 netres.lpRemoteName = const_cast<char *>(webDavUrlUtf8.constData());
210 DWORD size = sizeof(dst);
211 DWORD r = WNetUseConnectionA(
212 reinterpret_cast<HWND>(m_mainWindow->effectiveWinId()), &netres,
213 const_cast<char *>(webDavPasswordUtf8.constData()),
214 const_cast<char *>(webDavUserUtf8.constData()),
215 CONNECT_TEMPORARY | CONNECT_INTERACTIVE | CONNECT_REDIRECT, dst, &size,
216 0);
217 if (r == NO_ERROR) {
219 } else {
220 char message[256] = {0};
221 FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, r, 0, message,
222 sizeof(message), 0);
223 m_mainWindow->flashText(tr("Failed to connect WebDAV:\n") + message +
224 " (0x" + QString::number(r, 16) + ")",
225 true);
226 }
227#else
228 fusedav.start("fusedav", QStringList()
229 << "-o"
230 << "nonempty"
231 << "-u"
232 << "\"" + QtPassSettings::getWebDavUser() + "\""
234 << "\"" + QtPassSettings::getPassStore() + "\"");
235 fusedav.waitForStarted();
236 if (fusedav.state() == QProcess::Running) {
237 QString pwd = QtPassSettings::getWebDavPassword();
238 bool ok = true;
239 if (pwd.isEmpty()) {
240 pwd = QInputDialog::getText(m_mainWindow, tr("QtPass WebDAV password"),
241 tr("Enter password to connect to WebDAV:"),
242 QLineEdit::Password, "", &ok);
243 }
244 if (ok && !pwd.isEmpty()) {
245 fusedav.write(pwd.toUtf8() + '\n');
246 fusedav.closeWriteChannel();
247 fusedav.waitForFinished(2000);
248 } else {
249 fusedav.terminate();
250 }
251 }
252 QString error = fusedav.readAllStandardError();
253 int prompt = error.indexOf("Password:");
254 if (prompt >= 0) {
255 error.remove(0, prompt + 10);
256 }
257 if (fusedav.state() != QProcess::Running) {
258 error = tr("fusedav exited unexpectedly\n") + error;
259 }
260 if (error.size() > 0) {
261 m_mainWindow->flashText(
262 tr("Failed to start fusedav to connect WebDAV:\n") + error, true);
263 }
264#endif
265}
266
271void QtPass::processError(QProcess::ProcessError error) {
272 QString errorString;
273 switch (error) {
274 case QProcess::FailedToStart:
275 errorString = tr("QProcess::FailedToStart");
276 break;
277 case QProcess::Crashed:
278 errorString = tr("QProcess::Crashed");
279 break;
280 case QProcess::Timedout:
281 errorString = tr("QProcess::Timedout");
282 break;
283 case QProcess::ReadError:
284 errorString = tr("QProcess::ReadError");
285 break;
286 case QProcess::WriteError:
287 errorString = tr("QProcess::WriteError");
288 break;
289 case QProcess::UnknownError:
290 errorString = tr("QProcess::UnknownError");
291 break;
292 }
293 m_mainWindow->flashText(errorString, true);
294 m_mainWindow->setUiElementsEnabled(true);
295}
296
302void QtPass::processErrorExit(int exitCode, const QString &p_error) {
303 if (nullptr != m_mainWindow->getKeygenDialog()) {
304 m_mainWindow->cleanKeygenDialog();
305 if (exitCode != 0) {
306 m_mainWindow->showStatusMessage(tr("GPG key pair generation failed"),
307 10000);
308 }
309 }
310
311 if (!p_error.isEmpty()) {
312 QString output;
313 QString error = p_error.toHtmlEscaped();
314 if (exitCode == 0) {
315 // https://github.com/IJHack/qtpass/issues/111
316 output = "<span style=\"color: darkgray;\">" + error + "</span><br />";
317 } else {
318 output = "<span style=\"color: red;\">" + error + "</span><br />";
319 }
320
321 output.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
322 output.replace(QStringLiteral("\n"), "<br />");
323
324 m_mainWindow->flashText(output, false, true);
325 }
326
327 m_mainWindow->setUiElementsEnabled(true);
328}
329
337void QtPass::processFinished(const QString &p_output, const QString &p_errout) {
338 showInTextBrowser(p_output);
339 // Sometimes there is error output even with 0 exit code, which is
340 // assumed in this function
341 processErrorExit(0, p_errout);
342
343 m_mainWindow->setUiElementsEnabled(true);
344}
345
351void QtPass::passStoreChanged(const QString &p_out, const QString &p_err) {
352 processFinished(p_out, p_err);
353 doGitPush();
354}
355
361void QtPass::finishedInsert(const QString &p_output, const QString &p_errout) {
362 processFinished(p_output, p_errout);
363 doGitPush();
364 m_mainWindow->on_treeView_clicked(m_mainWindow->getCurrentTreeViewIndex());
365}
366
372void QtPass::onKeyGenerationComplete(const QString &p_output,
373 const QString &p_errout) {
374 if (nullptr != m_mainWindow->getKeygenDialog()) {
375#ifdef QT_DEBUG
376 qDebug() << "Keygen Done";
377#endif
378
379 m_mainWindow->cleanKeygenDialog();
380 m_mainWindow->showStatusMessage(tr("GPG key pair generated successfully"),
381 10000);
382 }
383
384 processFinished(p_output, p_errout);
385}
386
391void QtPass::passShowHandlerFinished(QString output) {
392 showInTextBrowser(std::move(output));
393}
394
401void QtPass::showInTextBrowser(QString output, const QString &prefix,
402 const QString &postfix) {
403 output = output.toHtmlEscaped();
404
405 output.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
406 output.replace(QStringLiteral("\n"), "<br />");
407 output = prefix + output + postfix;
408
409 m_mainWindow->flashText(output, false, true);
410}
411
415void QtPass::doGitPush() {
417 m_mainWindow->onPush();
418 }
419}
420
427void QtPass::setClippedText(const QString &password, const QString &p_output) {
429 !p_output.isEmpty()) {
430 clippedText = password;
432 copyTextToClipboard(password);
433 }
434 }
435}
436
439void QtPass::clearClippedText() { clippedText = ""; }
440
445 clearClipboardTimer.setInterval(MS_PER_SECOND *
447}
448
453 QClipboard *clipboard = QApplication::clipboard();
454 bool cleared = false;
455 if (this->clippedText == clipboard->text(QClipboard::Selection)) {
456 clipboard->clear(QClipboard::Selection);
457 clipboard->setText(QString(""), QClipboard::Selection);
458 cleared = true;
459 }
460 if (this->clippedText == clipboard->text(QClipboard::Clipboard)) {
461 clipboard->clear(QClipboard::Clipboard);
462 cleared = true;
463 }
464 if (cleared) {
465 m_mainWindow->showStatusMessage(tr("Clipboard cleared"));
466 } else {
467 m_mainWindow->showStatusMessage(tr("Clipboard not cleared"));
468 }
469
470 clippedText.clear();
471}
472
478auto buildClipboardMimeData(const QString &text) -> QMimeData * {
479 auto *mimeData = new QMimeData();
480 mimeData->setText(text);
481#ifdef Q_OS_LINUX
482 mimeData->setData("x-kde-passwordManagerHint", QByteArray("secret"));
483#endif
484#ifdef Q_OS_MAC
485 mimeData->setData("application/x-nspasteboard-concealed-type", QByteArray());
486#endif
487#ifdef Q_OS_WIN
488 mimeData->setData("ExcludeClipboardContentFromMonitorProcessing",
489 dwordBytes(1));
490 mimeData->setData("CanIncludeInClipboardHistory", dwordBytes(0));
491 mimeData->setData("CanUploadToCloudClipboard", dwordBytes(0));
492#endif
493 return mimeData;
494}
495
500void QtPass::copyTextToClipboard(const QString &text) {
501 QClipboard *clip = QApplication::clipboard();
502
503 QClipboard::Mode mode = QClipboard::Clipboard;
504 if (QtPassSettings::isUseSelection() && clip->supportsSelection()) {
505 mode = QClipboard::Selection;
506 }
507
508 auto *mimeData = buildClipboardMimeData(text);
509 clip->setMimeData(mimeData, mode);
510
511 clippedText = text;
512 m_mainWindow->showStatusMessage(tr("Copied to clipboard"));
514 clearClipboardTimer.start();
515 }
516}
517
522void QtPass::showTextAsQRCode(const QString &text) {
523 QProcess qrencode;
524 qrencode.start(QtPassSettings::getQrencodeExecutable("/usr/bin/qrencode"),
525 QStringList() << "-o-"
526 << "-tPNG");
527 qrencode.write(text.toUtf8());
528 qrencode.closeWriteChannel();
529 qrencode.waitForFinished();
530 QByteArray output(qrencode.readAllStandardOutput());
531
532 if (qrencode.exitStatus() || qrencode.exitCode()) {
533 QString error(qrencode.readAllStandardError());
534 m_mainWindow->showStatusMessage(error);
535 } else {
536 QPixmap image;
537 image.loadFromData(output, "PNG");
538 QDialog *popup = createQRCodePopup(image);
539 popup->exec();
540 }
541}
542
550QDialog *QtPass::createQRCodePopup(const QPixmap &image) {
551 auto *popup = new QDialog(nullptr, Qt::Popup | Qt::FramelessWindowHint);
552 popup->setAttribute(Qt::WA_DeleteOnClose);
553 auto *layout = new QVBoxLayout;
554 auto *popupLabel = new QLabel();
555 layout->addWidget(popupLabel);
556 popupLabel->setPixmap(image);
557 popupLabel->setScaledContents(true);
558 popupLabel->show();
559 popup->setLayout(layout);
560 popup->move(QCursor::pos());
561 return popup;
562}
void endReencryptPath()
Emitted after finishing re-encryption.
void startReencryptPath()
Emitted before starting re-encryption.
The MainWindow class does way too much, not only is it a switchboard, configuration handler and more,...
Definition mainwindow.h:52
void startReencryptPath()
MainWindow::startReencryptPath disable ui elements and treeview.
void passShowHandler(const QString &)
void endReencryptPath()
MainWindow::endReencryptPath re-enable ui elements.
void onGrepFinished(const QList< QPair< QString, QStringList > > &results)
Display grep results in grepResultsList.
void executeWrapperStarted()
void passGitInitNeeded()
void critical(const QString &, const QString &)
MainWindow::critical critical message popup wrapper.
void showStatusMessage(const QString &msg, int timeout=2000)
Displays message in status bar.
void passOtpHandler(const QString &)
void passShowHandlerFinished(const QString &output)
void generateGPGKeyPair(const QString &batch)
void restoreWindow()
Abstract base class for password store operations.
Definition pass.h:35
void startingExecuteWrapper()
Emitted before executing a command.
void critical(const QString &, const QString &)
Emit critical error.
void finishedCopy(const QString &, const QString &)
Emitted when copy finishes.
void finishedShow(const QString &)
Emitted when show finishes.
void finishedRemove(const QString &, const QString &)
Emitted when remove finishes.
void finishedMove(const QString &, const QString &)
Emitted when move finishes.
void statusMsg(const QString &, int)
Emit status message.
void finishedGitInit(const QString &, const QString &)
Emitted when Git init finishes.
void finishedInsert(const QString &, const QString &)
Emitted when insert finishes.
void finishedGrep(const QList< QPair< QString, QStringList > > &results)
Emitted when grep finishes with matching results.
void finishedInit(const QString &, const QString &)
Emitted when init finishes.
void finishedOtpGenerate(const QString &)
Emitted when OTP generation finishes.
void processErrorExit(int exitCode, const QString &err)
Emitted on process error exit.
void finishedGitPull(const QString &, const QString &)
Emitted when Git pull finishes.
void finishedGitPush(const QString &, const QString &)
Emitted when Git push finishes.
void error(QProcess::ProcessError)
Emitted when a process error occurs.
void finishedGenerateGPGKeys(const QString &, const QString &)
Emitted when GPG key generation finishes.
void clearClippedText()
Clears the stored clipped text.
Definition qtpass.cpp:439
void setClipboardTimer()
Sets the clipboard clear timer based on autoclear settings.
Definition qtpass.cpp:444
void clearClipboard()
MainWindow::clearClipboard remove clipboard contents.
Definition qtpass.cpp:452
~QtPass()
QtPass::~QtPass destroy!
Definition qtpass.cpp:59
void setClippedText(const QString &, const QString &p_output=QString())
Sets the text to be stored in clipboard and handles clipboard operations.
Definition qtpass.cpp:427
QtPass(MainWindow *mainWindow)
Constructs a QtPass instance.
Definition qtpass.cpp:36
static QDialog * createQRCodePopup(const QPixmap &image)
QtPass::createQRCodePopup creates a popup dialog with the given QR code image. This is extracted for ...
Definition qtpass.cpp:550
void copyTextToClipboard(const QString &text)
MainWindow::copyTextToClipboard copies text to your clipboard.
Definition qtpass.cpp:500
auto init() -> bool
QtPass::init make sure we are ready to go as soon as possible.
Definition qtpass.cpp:76
void showTextAsQRCode(const QString &text)
displays the text as qrcode
Definition qtpass.cpp:522
static auto isUseSelection(const bool &defaultValue=QVariant().toBool()) -> bool
Get whether to use selection (X11) for clipboard.
static auto isUseAutoclear(const bool &defaultValue=QVariant().toBool()) -> bool
Get whether to use autoclear for clipboard.
static void setAutoclearPanelSeconds(const int &autoClearPanelSeconds)
Save panel autoclear seconds.
static auto getWebDavPassword(const QString &defaultValue=QVariant().toString()) -> QString
Get WebDAV password.
static auto getPwgenExecutable(const QString &defaultValue=QVariant().toString()) -> QString
Get pwgen executable path.
static auto getWebDavUser(const QString &defaultValue=QVariant().toString()) -> QString
Get WebDAV username.
static void setPassStore(const QString &passStore)
Save password store path.
static auto getPass() -> Pass *
Get currently active pass backend instance.
static auto getClipBoardType(const Enums::clipBoardType &defaultValue=Enums::CLIPBOARD_NEVER) -> Enums::clipBoardType
Get clipboard type as enum.
static void setUsePwgen(const bool &usePwgen)
Save pwgen support flag.
static auto getPassStore(const QString &defaultValue=QVariant().toString()) -> QString
Get password store directory path.
static auto getVersion(const QString &defaultValue=QVariant().toString()) -> QString
Get saved application version.
static void initExecutables()
Initialize executable paths by auto-detecting them.
static void setPassTemplate(const QString &passTemplate)
Save pass entry template.
static auto getQrencodeExecutable(const QString &defaultValue=QVariant().toString()) -> QString
Get qrencode executable path.
static void setVersion(const QString &version)
Save application version.
static auto getRealPass() -> RealPass *
Get real pass backend instance.
static auto getPassTemplate(const QString &defaultValue=QVariant().toString()) -> QString
Get pass entry template.
static auto getWebDavUrl(const QString &defaultValue=QVariant().toString()) -> QString
Get WebDAV URL.
static auto isUseWebDav(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether WebDAV integration is enabled.
static auto getAutoclearPanelSeconds(const int &defaultValue=QVariant().toInt()) -> int
Get panel autoclear delay in seconds.
static void setAutoclearSeconds(const int &autoClearSeconds)
Save autoclear seconds.
static auto getImitatePass() -> ImitatePass *
Get imitate pass backend instance.
static auto getAutoclearSeconds(const int &defaultValue=QVariant().toInt()) -> int
Get autoclear delay in seconds.
static auto isAutoPush(const bool &defaultValue=QVariant().toBool()) -> bool
Check whether automatic push is enabled.
static auto protocolRegex() -> const QRegularExpression &
Returns a regex to match URL protocols.
Definition util.cpp:269
static auto findPasswordStore() -> QString
Locate the password store directory.
Definition util.cpp:77
static auto configIsValid() -> bool
Verify that the required configuration is complete.
Definition util.cpp:191
Debug utilities for QtPass.
#define dbg()
Simple debug macro that includes file and line number.
Definition debughelper.h:21
@ CLIPBOARD_ALWAYS
Definition enums.h:18
@ CLIPBOARD_NEVER
Definition enums.h:17
auto buildClipboardMimeData(const QString &text) -> QMimeData *
Build clipboard MIME data with platform-specific security hints.
Definition qtpass.cpp:478
auto buildClipboardMimeData(const QString &text) -> QMimeData *
Build clipboard MIME data with platform-specific security hints.
Definition qtpass.cpp:478
constexpr int MS_PER_SECOND
Definition util.h:12