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