Line data Source code
1 : // SPDX-FileCopyrightText: 2016 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 <utility>
18 : #else
19 : #define WIN32_LEAN_AND_MEAN /*_KILLING_MACHINE*/
20 : #define WIN32_EXTRA_LEAN
21 : #include <windows.h>
22 : #include <winnetwk.h>
23 : #undef DELETE
24 : #endif
25 :
26 : #ifdef QT_DEBUG
27 : #include "debughelper.h"
28 : #endif
29 :
30 0 : QtPass::QtPass(MainWindow *mainWindow)
31 0 : : m_mainWindow(mainWindow), freshStart(true) {
32 0 : setClipboardTimer();
33 0 : clearClipboardTimer.setSingleShot(true);
34 0 : connect(&clearClipboardTimer, &QTimer::timeout, this,
35 0 : &QtPass::clearClipboard);
36 :
37 : #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
38 : #pragma GCC diagnostic push
39 : #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
40 : #endif
41 0 : QObject::connect(qApp, &QApplication::aboutToQuit, this,
42 0 : &QtPass::clearClipboard);
43 : #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
44 : #pragma GCC diagnostic pop
45 : #endif
46 :
47 0 : setMainWindow();
48 0 : }
49 :
50 : /**
51 : * @brief QtPass::~QtPass destroy!
52 : */
53 0 : QtPass::~QtPass() {
54 : #ifdef Q_OS_WIN
55 : if (QtPassSettings::isUseWebDav())
56 : WNetCancelConnection2A(QtPassSettings::getPassStore().toUtf8().constData(),
57 : 0, 1);
58 : #else
59 0 : if (fusedav.state() == QProcess::Running) {
60 0 : fusedav.terminate();
61 0 : fusedav.waitForFinished(2000);
62 : }
63 : #endif
64 0 : }
65 :
66 : /**
67 : * @brief QtPass::init make sure we are ready to go as soon as
68 : * possible
69 : */
70 0 : auto QtPass::init() -> bool {
71 0 : QString passStore = QtPassSettings::getPassStore(Util::findPasswordStore());
72 0 : QtPassSettings::setPassStore(passStore);
73 :
74 0 : QtPassSettings::initExecutables();
75 :
76 0 : QString version = QtPassSettings::getVersion();
77 : // dbg()<< version;
78 :
79 : // Config updates
80 0 : if (version.isEmpty()) {
81 : #ifdef QT_DEBUG
82 : dbg() << "assuming fresh install";
83 : #endif
84 :
85 0 : if (QtPassSettings::getAutoclearSeconds() < 5) {
86 0 : QtPassSettings::setAutoclearSeconds(10);
87 : }
88 0 : if (QtPassSettings::getAutoclearPanelSeconds() < 5) {
89 0 : QtPassSettings::setAutoclearPanelSeconds(10);
90 : }
91 0 : if (!QtPassSettings::getPwgenExecutable().isEmpty()) {
92 0 : QtPassSettings::setUsePwgen(true);
93 : } else {
94 0 : QtPassSettings::setUsePwgen(false);
95 : }
96 0 : QtPassSettings::setPassTemplate("login\nurl");
97 : } else {
98 : // QStringList ver = version.split(".");
99 : // dbg()<< ver;
100 : // if (ver[0] == "0" && ver[1] == "8") {
101 : //// upgrade to 0.9
102 : // }
103 0 : if (QtPassSettings::getPassTemplate().isEmpty()) {
104 0 : QtPassSettings::setPassTemplate("login\nurl");
105 : }
106 : }
107 :
108 0 : QtPassSettings::setVersion(VERSION);
109 :
110 0 : if (Util::checkConfig()) {
111 0 : m_mainWindow->config();
112 0 : if (freshStart && Util::checkConfig()) {
113 : return false;
114 : }
115 : }
116 :
117 : // TODO(annejan): this needs to be before we try to access the store,
118 : // but it would be better to do it after the Window is shown,
119 : // as the long delay it can cause is irritating otherwise.
120 0 : if (QtPassSettings::isUseWebDav()) {
121 0 : mountWebDav();
122 : }
123 :
124 0 : freshStart = false;
125 : // startupPhase = false;
126 0 : return true;
127 : }
128 :
129 0 : void QtPass::setMainWindow() {
130 0 : m_mainWindow->restoreWindow();
131 :
132 0 : fusedav.setParent(m_mainWindow);
133 :
134 : // TODO(bezet): this should be reconnected dynamically when pass changes
135 0 : connectPassSignalHandlers(QtPassSettings::getRealPass());
136 0 : connectPassSignalHandlers(QtPassSettings::getImitatePass());
137 :
138 0 : connect(m_mainWindow, &MainWindow::passShowHandlerFinished, this,
139 0 : &QtPass::passShowHandlerFinished);
140 :
141 : // only for ipass
142 0 : connect(QtPassSettings::getImitatePass(), &ImitatePass::startReencryptPath,
143 0 : m_mainWindow, &MainWindow::startReencryptPath);
144 0 : connect(QtPassSettings::getImitatePass(), &ImitatePass::endReencryptPath,
145 0 : m_mainWindow, &MainWindow::endReencryptPath);
146 :
147 0 : connect(m_mainWindow, &MainWindow::passGitInitNeeded, [=]() -> void {
148 : #ifdef QT_DEBUG
149 : dbg() << "Pass git init called";
150 : #endif
151 0 : QtPassSettings::getPass()->GitInit();
152 0 : });
153 :
154 0 : connect(m_mainWindow, &MainWindow::generateGPGKeyPair, m_mainWindow,
155 0 : [=](const QString &batch) -> void {
156 0 : QtPassSettings::getPass()->GenerateGPGKeys(batch);
157 0 : m_mainWindow->showStatusMessage(tr("Generating GPG key pair"),
158 : 60000);
159 0 : });
160 0 : }
161 :
162 0 : void QtPass::connectPassSignalHandlers(Pass *pass) {
163 0 : connect(pass, &Pass::error, this, &QtPass::processError);
164 0 : connect(pass, &Pass::processErrorExit, this, &QtPass::processErrorExit);
165 0 : connect(pass, &Pass::critical, m_mainWindow, &MainWindow::critical);
166 0 : connect(pass, &Pass::startingExecuteWrapper, m_mainWindow,
167 0 : &MainWindow::executeWrapperStarted);
168 0 : connect(pass, &Pass::statusMsg, m_mainWindow, &MainWindow::showStatusMessage);
169 0 : connect(pass, &Pass::finishedShow, m_mainWindow,
170 0 : &MainWindow::passShowHandler);
171 0 : connect(pass, &Pass::finishedOtpGenerate, m_mainWindow,
172 0 : &MainWindow::passOtpHandler);
173 :
174 0 : connect(pass, &Pass::finishedGitInit, this, &QtPass::passStoreChanged);
175 0 : connect(pass, &Pass::finishedGitPull, this, &QtPass::processFinished);
176 0 : connect(pass, &Pass::finishedGitPush, this, &QtPass::processFinished);
177 0 : connect(pass, &Pass::finishedInsert, this, &QtPass::finishedInsert);
178 0 : connect(pass, &Pass::finishedRemove, this, &QtPass::passStoreChanged);
179 0 : connect(pass, &Pass::finishedInit, this, &QtPass::passStoreChanged);
180 0 : connect(pass, &Pass::finishedMove, this, &QtPass::passStoreChanged);
181 0 : connect(pass, &Pass::finishedCopy, this, &QtPass::passStoreChanged);
182 0 : connect(pass, &Pass::finishedGenerateGPGKeys, this,
183 0 : &QtPass::onKeyGenerationComplete);
184 0 : }
185 :
186 : /**
187 : * @brief QtPass::mountWebDav is some scary voodoo magic
188 : */
189 0 : void QtPass::mountWebDav() {
190 : #ifdef Q_OS_WIN
191 : char dst[20] = {0};
192 : NETRESOURCEA netres;
193 : memset(&netres, 0, sizeof(netres));
194 : netres.dwType = RESOURCETYPE_DISK;
195 : netres.lpLocalName = 0;
196 : netres.lpRemoteName = QtPassSettings::getWebDavUrl().toUtf8().data();
197 : DWORD size = sizeof(dst);
198 : DWORD r = WNetUseConnectionA(
199 : reinterpret_cast<HWND>(m_mainWindow->effectiveWinId()), &netres,
200 : QtPassSettings::getWebDavPassword().toUtf8().constData(),
201 : QtPassSettings::getWebDavUser().toUtf8().constData(),
202 : CONNECT_TEMPORARY | CONNECT_INTERACTIVE | CONNECT_REDIRECT, dst, &size,
203 : 0);
204 : if (r == NO_ERROR) {
205 : QtPassSettings::setPassStore(dst);
206 : } else {
207 : char message[256] = {0};
208 : FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, r, 0, message,
209 : sizeof(message), 0);
210 : m_mainWindow->flashText(tr("Failed to connect WebDAV:\n") + message +
211 : " (0x" + QString::number(r, 16) + ")",
212 : true);
213 : }
214 : #else
215 0 : fusedav.start("fusedav", QStringList()
216 0 : << "-o"
217 0 : << "nonempty"
218 0 : << "-u"
219 0 : << "\"" + QtPassSettings::getWebDavUser() + "\""
220 0 : << QtPassSettings::getWebDavUrl()
221 0 : << "\"" + QtPassSettings::getPassStore() + "\"");
222 0 : fusedav.waitForStarted();
223 0 : if (fusedav.state() == QProcess::Running) {
224 0 : QString pwd = QtPassSettings::getWebDavPassword();
225 0 : bool ok = true;
226 0 : if (pwd.isEmpty()) {
227 0 : pwd = QInputDialog::getText(m_mainWindow, tr("QtPass WebDAV password"),
228 0 : tr("Enter password to connect to WebDAV:"),
229 0 : QLineEdit::Password, "", &ok);
230 : }
231 0 : if (ok && !pwd.isEmpty()) {
232 0 : fusedav.write(pwd.toUtf8() + '\n');
233 0 : fusedav.closeWriteChannel();
234 0 : fusedav.waitForFinished(2000);
235 : } else {
236 0 : fusedav.terminate();
237 : }
238 : }
239 0 : QString error = fusedav.readAllStandardError();
240 0 : int prompt = error.indexOf("Password:");
241 0 : if (prompt >= 0) {
242 0 : error.remove(0, prompt + 10);
243 : }
244 0 : if (fusedav.state() != QProcess::Running) {
245 0 : error = tr("fusedav exited unexpectedly\n") + error;
246 : }
247 0 : if (error.size() > 0) {
248 0 : m_mainWindow->flashText(
249 0 : tr("Failed to start fusedav to connect WebDAV:\n") + error, true);
250 : }
251 : #endif
252 0 : }
253 :
254 : /**
255 : * @brief QtPass::processError something went wrong
256 : * @param error
257 : */
258 0 : void QtPass::processError(QProcess::ProcessError error) {
259 0 : QString errorString;
260 0 : switch (error) {
261 0 : case QProcess::FailedToStart:
262 0 : errorString = tr("QProcess::FailedToStart");
263 0 : break;
264 0 : case QProcess::Crashed:
265 0 : errorString = tr("QProcess::Crashed");
266 0 : break;
267 0 : case QProcess::Timedout:
268 0 : errorString = tr("QProcess::Timedout");
269 0 : break;
270 0 : case QProcess::ReadError:
271 0 : errorString = tr("QProcess::ReadError");
272 0 : break;
273 0 : case QProcess::WriteError:
274 0 : errorString = tr("QProcess::WriteError");
275 0 : break;
276 0 : case QProcess::UnknownError:
277 0 : errorString = tr("QProcess::UnknownError");
278 0 : break;
279 : }
280 :
281 0 : m_mainWindow->flashText(errorString, true);
282 0 : m_mainWindow->setUiElementsEnabled(true);
283 0 : }
284 :
285 0 : void QtPass::processErrorExit(int exitCode, const QString &p_error) {
286 0 : if (!p_error.isEmpty()) {
287 0 : QString output;
288 0 : QString error = p_error.toHtmlEscaped();
289 0 : if (exitCode == 0) {
290 : // https://github.com/IJHack/qtpass/issues/111
291 0 : output = "<span style=\"color: darkgray;\">" + error + "</span><br />";
292 : } else {
293 0 : output = "<span style=\"color: red;\">" + error + "</span><br />";
294 : }
295 :
296 0 : output.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
297 0 : output.replace(QStringLiteral("\n"), "<br />");
298 :
299 0 : m_mainWindow->flashText(output, false, true);
300 : }
301 :
302 0 : m_mainWindow->setUiElementsEnabled(true);
303 0 : }
304 :
305 : /**
306 : * @brief QtPass::processFinished background process has finished
307 : * @param exitCode
308 : * @param exitStatus
309 : * @param output stdout from a process
310 : * @param errout stderr from a process
311 : */
312 0 : void QtPass::processFinished(const QString &p_output, const QString &p_errout) {
313 0 : showInTextBrowser(p_output);
314 : // Sometimes there is error output even with 0 exit code, which is
315 : // assumed in this function
316 0 : processErrorExit(0, p_errout);
317 :
318 0 : m_mainWindow->setUiElementsEnabled(true);
319 0 : }
320 :
321 0 : void QtPass::passStoreChanged(const QString &p_out, const QString &p_err) {
322 0 : processFinished(p_out, p_err);
323 0 : doGitPush();
324 0 : }
325 :
326 0 : void QtPass::finishedInsert(const QString &p_output, const QString &p_errout) {
327 0 : processFinished(p_output, p_errout);
328 0 : doGitPush();
329 0 : m_mainWindow->on_treeView_clicked(m_mainWindow->getCurrentTreeViewIndex());
330 0 : }
331 :
332 0 : void QtPass::onKeyGenerationComplete(const QString &p_output,
333 : const QString &p_errout) {
334 0 : if (nullptr != m_mainWindow->getKeygenDialog()) {
335 : #ifdef QT_DEBUG
336 : qDebug() << "Keygen Done";
337 : #endif
338 :
339 0 : m_mainWindow->cleanKeygenDialog();
340 : // TODO(annejan): some sanity checking ?
341 : }
342 :
343 0 : processFinished(p_output, p_errout);
344 0 : }
345 :
346 0 : void QtPass::passShowHandlerFinished(QString output) {
347 0 : showInTextBrowser(std::move(output));
348 0 : }
349 :
350 0 : void QtPass::showInTextBrowser(QString output, const QString &prefix,
351 : const QString &postfix) {
352 0 : output = output.toHtmlEscaped();
353 :
354 0 : output.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
355 0 : output.replace(QStringLiteral("\n"), "<br />");
356 0 : output = prefix + output + postfix;
357 :
358 0 : m_mainWindow->flashText(output, false, true);
359 0 : }
360 :
361 0 : void QtPass::doGitPush() {
362 0 : if (QtPassSettings::isAutoPush()) {
363 0 : m_mainWindow->onPush();
364 : }
365 0 : }
366 :
367 0 : void QtPass::setClippedText(const QString &password, const QString &p_output) {
368 0 : if (QtPassSettings::getClipBoardType() != Enums::CLIPBOARD_NEVER &&
369 : !p_output.isEmpty()) {
370 0 : clippedText = password;
371 0 : if (QtPassSettings::getClipBoardType() == Enums::CLIPBOARD_ALWAYS) {
372 0 : copyTextToClipboard(password);
373 : }
374 : }
375 0 : }
376 0 : void QtPass::clearClippedText() { clippedText = ""; }
377 :
378 0 : void QtPass::setClipboardTimer() {
379 0 : clearClipboardTimer.setInterval(MS_PER_SECOND *
380 0 : QtPassSettings::getAutoclearSeconds());
381 0 : }
382 :
383 : /**
384 : * @brief MainWindow::clearClipboard remove clipboard contents.
385 : */
386 0 : void QtPass::clearClipboard() {
387 0 : QClipboard *clipboard = QApplication::clipboard();
388 : bool cleared = false;
389 0 : if (this->clippedText == clipboard->text(QClipboard::Selection)) {
390 0 : clipboard->clear(QClipboard::Selection);
391 0 : clipboard->setText(QString(""), QClipboard::Selection);
392 : cleared = true;
393 : }
394 0 : if (this->clippedText == clipboard->text(QClipboard::Clipboard)) {
395 0 : clipboard->clear(QClipboard::Clipboard);
396 : cleared = true;
397 : }
398 0 : if (cleared) {
399 0 : m_mainWindow->showStatusMessage(tr("Clipboard cleared"));
400 : } else {
401 0 : m_mainWindow->showStatusMessage(tr("Clipboard not cleared"));
402 : }
403 :
404 0 : clippedText.clear();
405 0 : }
406 :
407 : /**
408 : * @brief MainWindow::copyTextToClipboard copies text to your clipboard
409 : * @param text
410 : */
411 0 : void QtPass::copyTextToClipboard(const QString &text) {
412 0 : QClipboard *clip = QApplication::clipboard();
413 0 : if (!QtPassSettings::isUseSelection()) {
414 0 : clip->setText(text, QClipboard::Clipboard);
415 : } else {
416 0 : clip->setText(text, QClipboard::Selection);
417 : }
418 :
419 0 : clippedText = text;
420 0 : m_mainWindow->showStatusMessage(tr("Copied to clipboard"));
421 0 : if (QtPassSettings::isUseAutoclear()) {
422 0 : clearClipboardTimer.start();
423 : }
424 0 : }
425 :
426 : /**
427 : * @brief displays the text as qrcode
428 : * @param text
429 : */
430 0 : void QtPass::showTextAsQRCode(const QString &text) {
431 0 : QProcess qrencode;
432 0 : qrencode.start(QtPassSettings::getQrencodeExecutable("/usr/bin/qrencode"),
433 0 : QStringList() << "-o-"
434 0 : << "-tPNG");
435 0 : qrencode.write(text.toUtf8());
436 0 : qrencode.closeWriteChannel();
437 0 : qrencode.waitForFinished();
438 0 : QByteArray output(qrencode.readAllStandardOutput());
439 :
440 0 : if (qrencode.exitStatus() || qrencode.exitCode()) {
441 0 : QString error(qrencode.readAllStandardError());
442 0 : m_mainWindow->showStatusMessage(error);
443 : } else {
444 0 : QPixmap image;
445 0 : image.loadFromData(output, "PNG");
446 :
447 0 : auto *popup = new QDialog(nullptr, Qt::Popup | Qt::FramelessWindowHint);
448 0 : auto *layout = new QVBoxLayout;
449 0 : auto *popupLabel = new QLabel();
450 0 : layout->addWidget(popupLabel);
451 0 : popupLabel->setPixmap(image);
452 0 : popupLabel->setScaledContents(true);
453 0 : popupLabel->show();
454 0 : popup->setLayout(layout);
455 0 : popup->move(QCursor::pos());
456 0 : popup->exec();
457 0 : }
458 0 : }
|