safe.cpp (5685B)
1 /* 2 * Copyright (C) 2021 Willy Goiffon <contact@z3bra.org> 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18 #include "safe.h" 19 20 #include <QStandardPaths> 21 #include <QProcess> 22 #include <QIODevice> 23 #include <QRegularExpression> 24 #include <QRegularExpressionMatch> 25 #include <QTimer> 26 #include <QtConcurrent> 27 #include <QFutureWatcher> 28 29 #define SAFE_DIR "SAFE_DIR" 30 #define SAFE_PID "SAFE_PID" 31 #define SAFE_SOCK "SAFE_SOCK" 32 #define SAFE_ASKPASS "SAFE_ASKPASS" 33 34 namespace { 35 } // namespace 36 37 Safe::LockTask *Safe::lock() 38 { 39 return new LockTask(); 40 } 41 42 Safe::UnlockTask *Safe::unlock(const QString &passphrase) 43 { 44 return new UnlockTask(passphrase); 45 } 46 47 Safe::EncryptTask *Safe::encrypt(const QString &file, const QString &content) 48 { 49 return new EncryptTask(file, content); 50 } 51 52 Safe::DecryptTask *Safe::decrypt(const QString &file) 53 { 54 return new DecryptTask(file); 55 } 56 57 58 Safe::Task::Task(QObject *parent) 59 : QObject(parent) 60 { 61 QTimer::singleShot(0, this, &Task::start); 62 } 63 64 bool Safe::Task::error() const 65 { 66 return !mError.isNull(); 67 } 68 69 QString Safe::Task::errorString() const 70 { 71 return mError; 72 } 73 74 void Safe::Task::setError(const QString &error) 75 { 76 mError = error; 77 } 78 79 void Safe::Task::start() 80 { 81 qDebug() << "Starting task" << this; 82 auto future = QtConcurrent::run(this, &Task::run); 83 auto *watcher = new QFutureWatcher<void>; 84 connect(watcher, &QFutureWatcher<void>::finished, watcher, &QObject::deleteLater); 85 connect(watcher, &QFutureWatcher<void>::finished, this, &Safe::Task::finished); 86 connect(watcher, &QFutureWatcher<void>::finished, this, &QObject::deleteLater); 87 watcher->setFuture(future); 88 } 89 90 Safe::LockTask::LockTask() 91 {} 92 93 void Safe::LockTask::run() 94 { 95 if (qEnvironmentVariableIsSet(SAFE_PID)) 96 return; 97 98 const auto kill = QStandardPaths::findExecutable(QStringLiteral("kill")); 99 const auto agent = QString::fromUtf8(qgetenv(SAFE_PID)); 100 101 QProcess process; 102 process.setProgram(kill); 103 process.setArguments({ 104 QStringLiteral("-USR1"), 105 QStringLiteral("%1").arg(agent) 106 }); 107 process.start(); 108 process.waitForStarted(); 109 process.waitForFinished(); 110 if (process.exitCode() != 0) { 111 const auto err = process.readAllStandardError(); 112 qWarning() << "Failed to forget key:" << err; 113 setError(QString::fromUtf8(err)); 114 } 115 } 116 117 Safe::UnlockTask::UnlockTask(const QString &passphrase) 118 : mPassphrase(passphrase) 119 {} 120 121 void Safe::UnlockTask::run() 122 { 123 const QString safe = QStandardPaths::findExecutable(QStringLiteral("safe")); 124 125 if (!qEnvironmentVariableIsSet(SAFE_PID)) { 126 qWarning() << "Agent is not running"; 127 return; 128 } 129 130 if (!qEnvironmentVariableIsSet(SAFE_SOCK)) { 131 qWarning() << "Agent is not reachable"; 132 return; 133 } 134 135 // Temporary hack until we can properly pass the passphrase to safe(1) 136 // Expect the "askpass" command to be available and reading from stdin 137 // ex. `read pass; echo $pass` 138 qputenv(SAFE_ASKPASS, "/usr/local/bin/askpass"); 139 140 QProcess process; 141 process.setProgram(safe); 142 process.setArguments({ 143 QStringLiteral("-r"), 144 QStringLiteral("-k") 145 }); 146 process.start(); 147 process.waitForStarted(); 148 QThread::currentThread()->sleep(1); 149 process.write(mPassphrase.toUtf8()); 150 process.write("\n"); 151 process.waitForFinished(); 152 if (process.exitCode() != 0) { 153 const auto err = process.readAllStandardError(); 154 qWarning() << "Failed to unlock safe:" << err; 155 setError(QString::fromUtf8(err)); 156 } 157 } 158 159 Safe::EncryptTask::EncryptTask(const QString &file, const QString &content) 160 : mFile(file), mContent(content) 161 {} 162 163 void Safe::EncryptTask::run() 164 { 165 const QString safe = QStandardPaths::findExecutable(QStringLiteral("safe")); 166 QProcess process; 167 process.setProgram(safe); 168 process.setArguments({ 169 QStringLiteral("-a"), 170 QStringLiteral("%1").arg(mFile) 171 }); 172 process.start(); 173 process.waitForStarted(); 174 process.write(mContent.toUtf8()); 175 process.closeWriteChannel(); 176 process.waitForFinished(); 177 if (process.exitCode() != 0) { 178 const auto err = process.readAllStandardError(); 179 qWarning() << "Failed to encrypt data:" << err; 180 setError(QString::fromUtf8(err)); 181 } 182 } 183 184 Safe::DecryptTask::DecryptTask(const QString &file) 185 : Task(), mFile(file) 186 {} 187 188 QString Safe::DecryptTask::content() const 189 { 190 return mContent; 191 } 192 193 void Safe::DecryptTask::run() 194 { 195 const QString safe = QStandardPaths::findExecutable(QStringLiteral("safe")); 196 QProcess process; 197 process.setProgram(safe); 198 process.setArguments({QStringLiteral("%1").arg(mFile)}); 199 process.start(); 200 process.waitForStarted(); 201 process.closeWriteChannel(); 202 process.waitForFinished(); 203 if (process.exitCode() != 0) { 204 const auto err = process.readAllStandardError(); 205 qWarning() << "Failed to decrypt data:" << err; 206 setError(QString::fromUtf8(err)); 207 } else { 208 mContent = QString::fromUtf8(process.readAllStandardOutput()); 209 } 210 }