1/*
2 SPDX-FileCopyrightText: 2017 KDE Developers
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "katesecuretextbuffer_p.h"
8
9#include "config.h"
10
11#include <KAuth/HelperSupport>
12
13#ifndef Q_OS_WIN
14#include <cerrno>
15#include <unistd.h>
16#endif
17
18#include <QDir>
19#include <QFile>
20#include <QFileInfo>
21#include <QString>
22#include <QTemporaryFile>
23
24KAUTH_HELPER_MAIN("org.kde.ktexteditor6.katetextbuffer", SecureTextBuffer)
25
26ActionReply SecureTextBuffer::savefile(const QVariantMap &args)
27{
28 const QString sourceFile = args[QStringLiteral("sourceFile")].toString();
29 const QString targetFile = args[QStringLiteral("targetFile")].toString();
30 const QByteArray checksum = args[QStringLiteral("checksum")].toByteArray();
31 const uint ownerId = (uint)args[QStringLiteral("ownerId")].toInt();
32 const uint groupId = (uint)args[QStringLiteral("groupId")].toInt();
33
34 if (saveFileInternal(sourceFile, targetFile, checksum, ownerId, groupId)) {
35 return ActionReply::SuccessReply();
36 }
37
38 return ActionReply::HelperErrorReply();
39}
40
41bool SecureTextBuffer::saveFileInternal(const QString &sourceFile,
42 const QString &targetFile,
43 const QByteArray &checksum,
44 const uint ownerId,
45 const uint groupId)
46{
47 // open source file for reading
48 // if not possible, signal error
49 QFile readFile(sourceFile);
50 if (!readFile.open(flags: QIODevice::ReadOnly)) {
51 return false;
52 }
53
54 // construct file info for target file
55 // we need to know things like path/exists/permissions
56 const QFileInfo targetFileInfo(targetFile);
57
58 // create temporary file in current directory to be able to later do an atomic rename
59 // we need to pass full path, else QTemporaryFile uses the temporary directory
60 // if not possible, signal error, this catches e.g. a non-existing target directory, too
61 QTemporaryFile tempFile(targetFileInfo.absolutePath() + QLatin1String("/secureXXXXXX"));
62 if (!tempFile.open()) {
63 return false;
64 }
65
66 // copy contents + do checksumming
67 // if not possible, signal error
68 QCryptographicHash cryptographicHash(checksumAlgorithm);
69 const qint64 bufferLength = 4096;
70 char buffer[bufferLength];
71 qint64 read = -1;
72 while ((read = readFile.read(data: buffer, maxlen: bufferLength)) > 0) {
73 cryptographicHash.addData(data: QByteArrayView(buffer, read));
74 if (tempFile.write(data: buffer, len: read) == -1) {
75 return false;
76 }
77 }
78
79 // check that copying was successful and checksum matched
80 // we need to flush the file, as QTemporaryFile keeps the handle open
81 // and we later do things like renaming of the file!
82 // if not possible, signal error
83 if ((read == -1) || (cryptographicHash.result() != checksum) || !tempFile.flush()) {
84 return false;
85 }
86
87 // try to preserve the permissions
88 if (!targetFileInfo.exists()) {
89 // ensure new file is readable by anyone
90 tempFile.setPermissions(tempFile.permissions() | QFile::Permission::ReadGroup | QFile::Permission::ReadOther);
91 } else {
92 // ensure the same file permissions
93 tempFile.setPermissions(targetFileInfo.permissions());
94
95 // ensure file has the same owner and group as before
96 setOwner(filedes: tempFile.handle(), ownerId, groupId);
97 }
98
99 // try to (atomic) rename temporary file to the target file
100 if (moveFile(sourceFile: tempFile.fileName(), targetFile: targetFileInfo.filePath())) {
101 // temporary file was renamed, there is nothing to remove anymore
102 tempFile.setAutoRemove(false);
103 return true;
104 }
105
106 // we failed
107 // QTemporaryFile will handle cleanup
108 return false;
109}
110
111void SecureTextBuffer::setOwner(const int filedes, const uint ownerId, const uint groupId)
112{
113#ifndef Q_OS_WIN
114 if (ownerId != (uint)-2 && groupId != (uint)-2) {
115 int result = fchown(fd: filedes, owner: ownerId, group: groupId);
116 // set at least correct group if owner cannot be changed
117 if (result != 0 && errno == EPERM) {
118 result = fchown(fd: filedes, owner: getuid(), group: groupId);
119 (void)result; // ignore if we can't do a thing
120 }
121 }
122#else
123 // no-op for windows
124#endif
125}
126
127bool SecureTextBuffer::moveFile(const QString &sourceFile, const QString &targetFile)
128{
129#if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID)
130 const int result = std::rename(old: QFile::encodeName(fileName: sourceFile).constData(), new: QFile::encodeName(fileName: targetFile).constData());
131 return (result == 0);
132#else
133 // use racy fallback for windows
134 QFile::remove(targetFile);
135 return QFile::rename(sourceFile, targetFile);
136#endif
137}
138

source code of ktexteditor/src/buffer/katesecuretextbuffer.cpp