File indexing completed on 2024-06-02 05:43:03

0001 /*
0002  *   SPDX-FileCopyrightText: 2017 Ivan Cukic <ivan.cukic (at) kde.org>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "cryfsbackend.h"
0008 
0009 #include <QDir>
0010 #include <QMessageBox>
0011 #include <QProcess>
0012 #include <QRegularExpression>
0013 
0014 #include <singleton_p.h>
0015 
0016 #include <KConfigGroup>
0017 #include <KLocalizedString>
0018 #include <KMountPoint>
0019 #include <KSharedConfig>
0020 
0021 #include <algorithm>
0022 
0023 #include <asynqt/basic/all.h>
0024 #include <asynqt/operations/collect.h>
0025 #include <asynqt/operations/transform.h>
0026 #include <asynqt/wrappers/process.h>
0027 
0028 using namespace AsynQt;
0029 
0030 namespace PlasmaVault
0031 {
0032 // see: https://github.com/cryfs/cryfs/blob/develop/src/cryfs/ErrorCodes.h
0033 enum class ExitCode : int {
0034     Success = 0,
0035 
0036     // An error happened that doesn't have an error code associated with it
0037     UnspecifiedError = 1,
0038 
0039     // The command line arguments are invalid.
0040     InvalidArguments = 10,
0041 
0042     // Couldn't load config file. Probably the password is wrong
0043     WrongPassword = 11,
0044 
0045     // Password cannot be empty
0046     EmptyPassword = 12,
0047 
0048     // The file system format is too new for this CryFS version. Please update your CryFS version.
0049     TooNewFilesystemFormat = 13,
0050 
0051     // The file system format is too old for this CryFS version. Run with --allow-filesystem-upgrade to upgrade it.
0052     TooOldFilesystemFormat = 14,
0053 
0054     // The file system uses a different cipher than the one specified on the command line using the --cipher argument.
0055     WrongCipher = 15,
0056 
0057     // Base directory doesn't exist or is inaccessible (i.e. not read or writable or not a directory)
0058     InaccessibleBaseDir = 16,
0059 
0060     // Mount directory doesn't exist or is inaccessible (i.e. not read or writable or not a directory)
0061     InaccessibleMountDir = 17,
0062 
0063     // Base directory can't be a subdirectory of the mount directory
0064     BaseDirInsideMountDir = 18,
0065 
0066     // Something's wrong with the file system.
0067     InvalidFilesystem = 19,
0068 };
0069 
0070 CryFsBackend::CryFsBackend()
0071 {
0072 }
0073 
0074 CryFsBackend::~CryFsBackend()
0075 {
0076 }
0077 
0078 Backend::Ptr CryFsBackend::instance()
0079 {
0080     return singleton::instance<CryFsBackend>();
0081 }
0082 
0083 FutureResult<> CryFsBackend::mount(const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload)
0084 {
0085     QDir dir;
0086 
0087     const auto password = payload[KEY_PASSWORD].toString();
0088     const auto cypher = payload["cryfs-cipher"].toString();
0089     const auto shouldUpgrade = payload["cryfs-fs-upgrade"].toBool();
0090 
0091     if (!dir.mkpath(device.data()) || !dir.mkpath(mountPoint.data())) {
0092         return errorResult(Error::BackendError, i18n("Failed to create directories, check your permissions"));
0093     }
0094     removeDotDirectory(mountPoint);
0095 
0096     auto process =
0097         // Cypher is specified, use it to create the device
0098         (!cypher.isEmpty()) ? cryfs({
0099             "--cipher",
0100             cypher,
0101             device.data(), // source directory to initialize cryfs in
0102             mountPoint.data() // where to mount the file system
0103         })
0104 
0105         // Cypher is not specified, use the default, whatever it is
0106         : shouldUpgrade ? cryfs({device.data(), // source directory to initialize cryfs in
0107                                  mountPoint.data(), // where to mount the file system
0108                                  "--allow-filesystem-upgrade"})
0109 
0110                         : cryfs({
0111                             device.data(), // source directory to initialize cryfs in
0112                             mountPoint.data() // where to mount the file system
0113                         })
0114 
0115         ;
0116 
0117     auto result = makeFuture(process, [this, device, mountPoint, payload](QProcess *process) {
0118         const auto out = process->readAllStandardOutput();
0119         const auto err = process->readAllStandardError();
0120 
0121         qDebug() << "OUT: " << out;
0122         qDebug() << "ERR: " << err;
0123 
0124         const auto exitCode = (ExitCode)process->exitCode();
0125 
0126         auto upgradeFileSystem = [this, device, mountPoint, payload] {
0127             const auto upgrade =
0128                 QMessageBox::Yes
0129                 == QMessageBox::question(
0130                     nullptr,
0131                     i18n("Upgrade the vault?"),
0132                     i18n("This vault was created with an older version of cryfs and needs to be upgraded.\n\nMind that this process is irreversible and the "
0133                          "vault will no longer work with older versions of cryfs.\n\nDo you want to perform the upgrade now?"));
0134 
0135             if (!upgrade) {
0136                 return Result<>::error(Error::BackendError, i18n("The vault needs to be upgraded before it can be opened with this version of cryfs"));
0137             }
0138 
0139             auto new_payload = payload;
0140             new_payload["cryfs-fs-upgrade"] = true;
0141 
0142             return AsynQt::await(mount(device, mountPoint, new_payload));
0143         };
0144 
0145         // clang-format off
0146         return
0147             // If we tried to mount into a non-empty location, report
0148             err.contains("'nonempty'") ?
0149                 Result<>::error(Error::CommandError,
0150                                 i18n("The mount point directory is not empty, refusing to open the vault")) :
0151 
0152             // If all went well, just return success
0153             (process->exitStatus() == QProcess::NormalExit && exitCode == ExitCode::Success) ?
0154                 Result<>::success() :
0155 
0156             exitCode == ExitCode::WrongPassword ?
0157                 Result<>::error(Error::BackendError,
0158                                 i18n("You entered the wrong password")) :
0159 
0160             exitCode == ExitCode::TooNewFilesystemFormat ?
0161                 Result<>::error(Error::BackendError,
0162                                 i18n("The installed version of cryfs is too old to open this vault.")) :
0163 
0164             exitCode == ExitCode::TooOldFilesystemFormat ?
0165                 upgradeFileSystem() :
0166 
0167             // otherwise just report that we failed
0168                 Result<>::error(Error::CommandError,
0169                                 i18n("Unable to perform the operation (error code %1).", QString::number((int)exitCode)),
0170                                 out, err);
0171 
0172 
0173         });
0174     // clang-format on
0175 
0176     // Writing the password
0177     process->write(password.toUtf8());
0178     process->write("\n");
0179 
0180     return result;
0181 }
0182 
0183 FutureResult<> CryFsBackend::validateBackend()
0184 {
0185     using namespace AsynQt::operators;
0186 
0187     // We need to check whether all the commands are installed
0188     // and whether the user has permissions to run them
0189     return collect(checkVersion(cryfs({"--version"}), std::make_tuple(0, 9, 9)), checkVersion(fusermount({"--version"}), std::make_tuple(2, 9, 7)))
0190 
0191         | transform([this](const QPair<bool, QString> &cryfs, const QPair<bool, QString> &fusermount) {
0192                bool success = cryfs.first && fusermount.first;
0193                QString message = formatMessageLine("cryfs", cryfs) + formatMessageLine("fusermount", fusermount);
0194 
0195                return success ? Result<>::success() : Result<>::error(Error::BackendError, message);
0196            });
0197 }
0198 
0199 bool CryFsBackend::isInitialized(const Device &device) const
0200 {
0201     QFile cryFsConfig(device.data() + QStringLiteral("/cryfs.config"));
0202     return cryFsConfig.exists();
0203 }
0204 
0205 QProcess *CryFsBackend::cryfs(const QStringList &arguments) const
0206 {
0207     auto config = KSharedConfig::openConfig(PLASMAVAULT_CONFIG_FILE);
0208     KConfigGroup backendConfig(config, "CryfsBackend");
0209 
0210     return process("cryfs", arguments + backendConfig.readEntry("extraMountOptions", QStringList{}), {{"CRYFS_FRONTEND", "noninteractive"}});
0211 }
0212 
0213 } // namespace PlasmaVault