sailfish-safe

Sailfish frontend for safe(1)
git clone git://git.z3bra.org/sailfish-safe.git
Log | Files | Refs | README | LICENSE

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 }