File indexing completed on 2024-07-21 03:42:55

0001 /*
0002     SPDX-FileCopyrightText: 2017 KDE Developers
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "katesecuretextbuffer_p.h"
0008 
0009 #include "config.h"
0010 
0011 #include <KAuth/HelperSupport>
0012 
0013 #ifndef Q_OS_WIN
0014 #include <cerrno>
0015 #include <unistd.h>
0016 #endif
0017 
0018 #include <QDir>
0019 #include <QFile>
0020 #include <QFileInfo>
0021 #include <QString>
0022 #include <QTemporaryFile>
0023 
0024 KAUTH_HELPER_MAIN("org.kde.ktexteditor6.katetextbuffer", SecureTextBuffer)
0025 
0026 ActionReply SecureTextBuffer::savefile(const QVariantMap &args)
0027 {
0028     const QString sourceFile = args[QStringLiteral("sourceFile")].toString();
0029     const QString targetFile = args[QStringLiteral("targetFile")].toString();
0030     const QByteArray checksum = args[QStringLiteral("checksum")].toByteArray();
0031     const uint ownerId = (uint)args[QStringLiteral("ownerId")].toInt();
0032     const uint groupId = (uint)args[QStringLiteral("groupId")].toInt();
0033 
0034     if (saveFileInternal(sourceFile, targetFile, checksum, ownerId, groupId)) {
0035         return ActionReply::SuccessReply();
0036     }
0037 
0038     return ActionReply::HelperErrorReply();
0039 }
0040 
0041 bool SecureTextBuffer::saveFileInternal(const QString &sourceFile,
0042                                         const QString &targetFile,
0043                                         const QByteArray &checksum,
0044                                         const uint ownerId,
0045                                         const uint groupId)
0046 {
0047     // open source file for reading
0048     // if not possible, signal error
0049     QFile readFile(sourceFile);
0050     if (!readFile.open(QIODevice::ReadOnly)) {
0051         return false;
0052     }
0053 
0054     // construct file info for target file
0055     // we need to know things like path/exists/permissions
0056     const QFileInfo targetFileInfo(targetFile);
0057 
0058     // create temporary file in current directory to be able to later do an atomic rename
0059     // we need to pass full path, else QTemporaryFile uses the temporary directory
0060     // if not possible, signal error, this catches e.g. a non-existing target directory, too
0061     QTemporaryFile tempFile(targetFileInfo.absolutePath() + QLatin1String("/secureXXXXXX"));
0062     if (!tempFile.open()) {
0063         return false;
0064     }
0065 
0066     // copy contents + do checksumming
0067     // if not possible, signal error
0068     QCryptographicHash cryptographicHash(checksumAlgorithm);
0069     const qint64 bufferLength = 4096;
0070     char buffer[bufferLength];
0071     qint64 read = -1;
0072     while ((read = readFile.read(buffer, bufferLength)) > 0) {
0073         cryptographicHash.addData(QByteArrayView(buffer, read));
0074         if (tempFile.write(buffer, read) == -1) {
0075             return false;
0076         }
0077     }
0078 
0079     // check that copying was successful and checksum matched
0080     // we need to flush the file, as QTemporaryFile keeps the handle open
0081     // and we later do things like renaming of the file!
0082     // if not possible, signal error
0083     if ((read == -1) || (cryptographicHash.result() != checksum) || !tempFile.flush()) {
0084         return false;
0085     }
0086 
0087     // try to preserve the permissions
0088     if (!targetFileInfo.exists()) {
0089         // ensure new file is readable by anyone
0090         tempFile.setPermissions(tempFile.permissions() | QFile::Permission::ReadGroup | QFile::Permission::ReadOther);
0091     } else {
0092         // ensure the same file permissions
0093         tempFile.setPermissions(targetFileInfo.permissions());
0094 
0095         // ensure file has the same owner and group as before
0096         setOwner(tempFile.handle(), ownerId, groupId);
0097     }
0098 
0099     // try to (atomic) rename temporary file to the target file
0100     if (moveFile(tempFile.fileName(), targetFileInfo.filePath())) {
0101         // temporary file was renamed, there is nothing to remove anymore
0102         tempFile.setAutoRemove(false);
0103         return true;
0104     }
0105 
0106     // we failed
0107     // QTemporaryFile will handle cleanup
0108     return false;
0109 }
0110 
0111 void SecureTextBuffer::setOwner(const int filedes, const uint ownerId, const uint groupId)
0112 {
0113 #ifndef Q_OS_WIN
0114     if (ownerId != (uint)-2 && groupId != (uint)-2) {
0115         int result = fchown(filedes, ownerId, groupId);
0116         // set at least correct group if owner cannot be changed
0117         if (result != 0 && errno == EPERM) {
0118             result = fchown(filedes, getuid(), groupId);
0119             (void)result; // ignore if we can't do a thing
0120         }
0121     }
0122 #else
0123     // no-op for windows
0124 #endif
0125 }
0126 
0127 bool SecureTextBuffer::moveFile(const QString &sourceFile, const QString &targetFile)
0128 {
0129 #if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID)
0130     const int result = std::rename(QFile::encodeName(sourceFile).constData(), QFile::encodeName(targetFile).constData());
0131     return (result == 0);
0132 #else
0133     // use racy fallback for windows
0134     QFile::remove(targetFile);
0135     return QFile::rename(sourceFile, targetFile);
0136 #endif
0137 }