File indexing completed on 2024-06-23 05:30:39
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Martino Pilia <martino.pilia (at) gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "gocryptfsbackend.h" 0008 0009 #include <QDir> 0010 #include <QProcess> 0011 0012 #include <KConfigGroup> 0013 #include <KLocalizedString> 0014 #include <KMountPoint> 0015 #include <KSharedConfig> 0016 0017 #include <algorithm> 0018 0019 #include <asynqt/basic/all.h> 0020 #include <asynqt/operations/collect.h> 0021 #include <asynqt/operations/transform.h> 0022 #include <asynqt/wrappers/process.h> 0023 0024 #include <qregularexpression.h> 0025 #include <singleton_p.h> 0026 0027 using namespace AsynQt; 0028 0029 namespace PlasmaVault 0030 { 0031 // See `man gocryptfs`, section EXIT CODES. 0032 enum class ExitCode : int { 0033 Success = 0, 0034 0035 // CIPHERDIR is not an emtpy directory (on "-init") 0036 NonEmptyCipherDir = 6, 0037 0038 // MOUNTPOINT is not an empty directory 0039 NonEmptyMountPoint = 10, 0040 0041 // Password incorrect 0042 WrongPassword = 12, 0043 0044 // Password is empty (on "-init") 0045 EmptyPassword = 22, 0046 0047 // Could not read gocryptfs.conf 0048 CannotReadConfig = 23, 0049 0050 // Could not write gocryptfs.conf (on "-init" or "-password") 0051 CannotWriteConfig = 24, 0052 0053 // fsck found errors 0054 FsckError = 26, 0055 }; 0056 0057 GocryptfsBackend::GocryptfsBackend() 0058 { 0059 } 0060 0061 GocryptfsBackend::~GocryptfsBackend() 0062 { 0063 } 0064 0065 Backend::Ptr GocryptfsBackend::instance() 0066 { 0067 return singleton::instance<GocryptfsBackend>(); 0068 } 0069 0070 FutureResult<> GocryptfsBackend::mount(const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload) 0071 { 0072 QDir dir; 0073 0074 const auto password = payload[KEY_PASSWORD].toString(); 0075 0076 if (!dir.mkpath(device.data()) || !dir.mkpath(mountPoint.data())) { 0077 return errorResult(Error::BackendError, i18n("Failed to create directories, check your permissions")); 0078 } 0079 removeDotDirectory(mountPoint); 0080 0081 if (isInitialized(device)) { 0082 auto mountProcess = gocryptfs({ 0083 device.data(), // cypher data directory 0084 mountPoint.data() // mount point 0085 }); 0086 0087 auto mountResult = makeFuture(mountProcess, hasProcessFinishedSuccessfully); 0088 0089 // Write password 0090 mountProcess->write(password.toUtf8() + "\n"); 0091 0092 return mountResult; 0093 } else { 0094 // Initialise cipherdir 0095 auto initProcess = gocryptfs({ 0096 "-init", 0097 device.data(), 0098 }); 0099 0100 auto initResult = makeFuture(initProcess, [this, device, mountPoint, payload](QProcess *process) { 0101 auto const exitCode = static_cast<ExitCode>(process->exitCode()); 0102 0103 switch (exitCode) { 0104 case ExitCode::Success: 0105 return AsynQt::await(mount(device, mountPoint, payload)); 0106 0107 case ExitCode::NonEmptyCipherDir: 0108 return Result<>::error(Error::BackendError, i18n("The cipher directory is not empty, cannot initialise the vault.")); 0109 0110 case ExitCode::EmptyPassword: 0111 return Result<>::error(Error::BackendError, i18n("The password is empty, cannot initialise the vault.")); 0112 0113 case ExitCode::CannotWriteConfig: 0114 return Result<>::error(Error::BackendError, i18n("Cannot write gocryptfs.conf inside cipher directory, check your permissions.")); 0115 0116 default: 0117 return Result<>::error(Error::CommandError, 0118 i18n("Unable to perform the operation (error code %1).", QString::number((int)exitCode)), 0119 process->readAllStandardOutput(), 0120 process->readAllStandardError()); 0121 } 0122 }); 0123 0124 // Write password twice (insert and confirm) 0125 for (int i = 0; i < 2; ++i) { 0126 initProcess->write(password.toUtf8() + "\n"); 0127 } 0128 0129 return initResult; 0130 } 0131 } 0132 0133 FutureResult<> GocryptfsBackend::validateBackend() 0134 { 0135 using namespace AsynQt::operators; 0136 0137 auto customCheckVersion = [](QProcess *process, const std::tuple<int, int> &requiredVersion) { 0138 using namespace AsynQt::operators; 0139 0140 return makeFuture(process, [=](QProcess *process) { 0141 if (process->exitStatus() != QProcess::NormalExit) { 0142 return qMakePair(false, i18n("Failed to execute")); 0143 } 0144 0145 // We don't care about the minor version for gocryptfs 0146 QRegularExpression versionMatcher("([0-9]+)[.]([0-9]+)"); 0147 0148 const auto out = process->readAllStandardOutput(); 0149 const auto err = process->readAllStandardError(); 0150 0151 if (out.isEmpty() && err.isEmpty()) { 0152 return qMakePair(false, i18n("Unable to detect the version")); 0153 } 0154 0155 // gocryptfs prints out several versions separated by semicolons 0156 // -- of itself, of go-fuse and of go 0157 // We just need the first 0158 const auto gocryptfsVersionString = (out + err).split(';').at(0); 0159 0160 if (!gocryptfsVersionString.startsWith("gocryptfs")) { 0161 return qMakePair(false, i18n("Unable to detect the version, the version string is invalid")); 0162 } 0163 0164 const auto matches = versionMatcher.match(gocryptfsVersionString); 0165 0166 if (!matches.hasMatch()) { 0167 return qMakePair(false, i18n("Unable to detect the version")); 0168 } 0169 0170 const auto matchedVersion = std::make_tuple(matches.captured(1).toInt(), matches.captured(2).toInt()); 0171 0172 if (matchedVersion < requiredVersion) { 0173 // Bad version, we need to notify the world 0174 return qMakePair(false, 0175 i18n("Wrong version installed. The required version is %1.%2", std::get<0>(requiredVersion), std::get<1>(requiredVersion))); 0176 } 0177 0178 return qMakePair(true, i18n("Correct version found")); 0179 }); 0180 }; 0181 0182 // We need to check whether all the commands are installed 0183 // and whether the user has permissions to run them 0184 return collect(customCheckVersion(gocryptfs({"--version"}), std::make_tuple(1, 8)), checkVersion(fusermount({"--version"}), std::make_tuple(2, 9, 7))) 0185 0186 | transform([this](const QPair<bool, QString> &gocryptfs, const QPair<bool, QString> &fusermount) { 0187 bool success = gocryptfs.first && fusermount.first; 0188 QString message = formatMessageLine("gocryptfs", gocryptfs) + formatMessageLine("fusermount", fusermount); 0189 0190 return success ? Result<>::success() : Result<>::error(Error::BackendError, message); 0191 }); 0192 } 0193 0194 bool GocryptfsBackend::isInitialized(const Device &device) const 0195 { 0196 QFile gocryptfsConfig(getConfigFilePath(device)); 0197 return gocryptfsConfig.exists(); 0198 } 0199 0200 QProcess *GocryptfsBackend::gocryptfs(const QStringList &arguments) const 0201 { 0202 auto config = KSharedConfig::openConfig(PLASMAVAULT_CONFIG_FILE); 0203 KConfigGroup backendConfig(config, "GocryptfsBackend"); 0204 0205 return process("gocryptfs", arguments + backendConfig.readEntry("extraMountOptions", QStringList{}), {}); 0206 } 0207 0208 QString GocryptfsBackend::getConfigFilePath(const Device &device) const 0209 { 0210 return device.data() + QStringLiteral("/gocryptfs.conf"); 0211 } 0212 0213 } // namespace PlasmaVault