1/* This file is part of the KDE libraries
2 SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
3 SPDX-FileCopyrightText: 1998, 1999, 2000 Waldo Bastian <bastian@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kauthorized.h"
9
10#include <QDebug>
11#include <QDir>
12#include <QList>
13#include <QUrl>
14
15#include "kconfig_core_log_settings.h"
16#include <QCoreApplication>
17#include <ksharedconfig.h>
18#include <stdlib.h> // srand(), rand()
19#ifndef Q_OS_WIN
20#include <netdb.h>
21#include <unistd.h>
22#endif
23
24#include <kconfiggroup.h>
25
26#include <QMutexLocker>
27#include <QRecursiveMutex>
28
29extern bool kde_kiosk_exception;
30
31class URLActionRule
32{
33public:
34#define checkExactMatch(s, b) \
35 if (s.isEmpty()) \
36 b = true; \
37 else if (s[s.length() - 1] == QLatin1Char('!')) { \
38 b = false; \
39 s.chop(1); \
40 } else \
41 b = true;
42#define checkStartWildCard(s, b) \
43 if (s.isEmpty()) \
44 b = true; \
45 else if (s[0] == QLatin1Char('*')) { \
46 b = true; \
47 s.remove(0, 1); \
48 } else \
49 b = false;
50#define checkEqual(s, b) b = (s == QLatin1String("="));
51
52 URLActionRule(const QByteArray &act,
53 const QString &bProt,
54 const QString &bHost,
55 const QString &bPath,
56 const QString &dProt,
57 const QString &dHost,
58 const QString &dPath,
59 bool perm)
60 : action(act)
61 , baseProt(bProt)
62 , baseHost(bHost)
63 , basePath(bPath)
64 , destProt(dProt)
65 , destHost(dHost)
66 , destPath(dPath)
67 , permission(perm)
68 {
69 checkExactMatch(baseProt, baseProtWildCard);
70 checkStartWildCard(baseHost, baseHostWildCard);
71 checkExactMatch(basePath, basePathWildCard);
72 checkExactMatch(destProt, destProtWildCard);
73 checkStartWildCard(destHost, destHostWildCard);
74 checkExactMatch(destPath, destPathWildCard);
75 checkEqual(destProt, destProtEqual);
76 checkEqual(destHost, destHostEqual);
77 }
78
79 bool baseMatch(const QUrl &url, const QString &protClass) const
80 {
81 if (baseProtWildCard) {
82 if (!baseProt.isEmpty() //
83 && !url.scheme().startsWith(s: baseProt) //
84 && (protClass.isEmpty() || (protClass != baseProt))) {
85 return false;
86 }
87 } else {
88 if (url.scheme() != baseProt //
89 && (protClass.isEmpty() || (protClass != baseProt))) {
90 return false;
91 }
92 }
93 if (baseHostWildCard) {
94 if (!baseHost.isEmpty() && !url.host().endsWith(s: baseHost)) {
95 return false;
96 }
97 } else {
98 if (url.host() != baseHost) {
99 return false;
100 }
101 }
102 if (basePathWildCard) {
103 if (!basePath.isEmpty() && !url.path().startsWith(s: basePath)) {
104 return false;
105 }
106 } else {
107 if (url.path() != basePath) {
108 return false;
109 }
110 }
111 return true;
112 }
113
114 bool destMatch(const QUrl &url, const QString &protClass, const QUrl &base, const QString &baseClass) const
115 {
116 if (destProtEqual) {
117 if (url.scheme() != base.scheme() //
118 && (protClass.isEmpty() || baseClass.isEmpty() || protClass != baseClass)) {
119 return false;
120 }
121 } else if (destProtWildCard) {
122 if (!destProt.isEmpty() //
123 && !url.scheme().startsWith(s: destProt) //
124 && (protClass.isEmpty() || (protClass != destProt))) {
125 return false;
126 }
127 } else {
128 if (url.scheme() != destProt //
129 && (protClass.isEmpty() || (protClass != destProt))) {
130 return false;
131 }
132 }
133 if (destHostWildCard) {
134 if (!destHost.isEmpty() && !url.host().endsWith(s: destHost)) {
135 return false;
136 }
137 } else if (destHostEqual) {
138 if (url.host() != base.host()) {
139 return false;
140 }
141 } else {
142 if (url.host() != destHost) {
143 return false;
144 }
145 }
146 if (destPathWildCard) {
147 if (!destPath.isEmpty() && !url.path().startsWith(s: destPath)) {
148 return false;
149 }
150 } else {
151 if (url.path() != destPath) {
152 return false;
153 }
154 }
155 return true;
156 }
157
158 QByteArray action;
159 QString baseProt;
160 QString baseHost;
161 QString basePath;
162 QString destProt;
163 QString destHost;
164 QString destPath;
165 bool baseProtWildCard : 1;
166 bool baseHostWildCard : 1;
167 bool basePathWildCard : 1;
168 bool destProtWildCard : 1;
169 bool destHostWildCard : 1;
170 bool destPathWildCard : 1;
171 bool destProtEqual : 1;
172 bool destHostEqual : 1;
173 bool permission;
174};
175
176Q_DECLARE_TYPEINFO(URLActionRule, Q_RELOCATABLE_TYPE);
177
178class KAuthorizedPrivate
179{
180public:
181 KAuthorizedPrivate()
182 : actionRestrictions(false)
183 , blockEverything(false)
184 {
185 Q_ASSERT_X(QCoreApplication::instance(), "KAuthorizedPrivate()", "There has to be an existing QCoreApplication::instance() pointer");
186
187 KSharedConfig::Ptr config = KSharedConfig::openConfig();
188
189 Q_ASSERT_X(config, "KAuthorizedPrivate()", "There has to be an existing KSharedConfig::openConfig() pointer");
190 if (!config) {
191 blockEverything = true;
192 return;
193 }
194 actionRestrictions = config->hasGroup(QStringLiteral("KDE Action Restrictions")) && !kde_kiosk_exception;
195 }
196
197 ~KAuthorizedPrivate()
198 {
199 }
200
201 bool actionRestrictions : 1;
202 bool blockEverything : 1;
203 QList<URLActionRule> urlActionRestrictions;
204 QRecursiveMutex mutex;
205};
206
207Q_GLOBAL_STATIC(KAuthorizedPrivate, authPrivate)
208#define KAUTHORIZED_D KAuthorizedPrivate *d = authPrivate()
209
210KAuthorized::KAuthorized()
211 : QObject(nullptr)
212{
213}
214
215bool KAuthorized::authorize(const QString &genericAction)
216{
217 KAUTHORIZED_D;
218 if (d->blockEverything) {
219 return false;
220 }
221
222 if (!d->actionRestrictions) {
223 return true;
224 }
225
226 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KDE Action Restrictions"));
227 return cg.readEntry(key: genericAction, aDefault: true);
228}
229
230bool KAuthorized::authorize(KAuthorized::GenericRestriction action)
231{
232 const QMetaEnum metaEnum = QMetaEnum::fromType<KAuthorized::GenericRestriction>();
233
234 if (metaEnum.isValid() && action != 0) {
235 return KAuthorized::authorize(genericAction: QString::fromLatin1(ba: metaEnum.valueToKey(value: action)).toLower());
236 }
237 qCWarning(KCONFIG_CORE_LOG) << "Invalid GenericRestriction requested" << action;
238 return false;
239}
240
241bool KAuthorized::authorizeAction(const QString &action)
242{
243 KAUTHORIZED_D;
244 if (d->blockEverything) {
245 return false;
246 }
247 if (!d->actionRestrictions || action.isEmpty()) {
248 return true;
249 }
250
251 return authorize(genericAction: QLatin1String("action/") + action);
252}
253
254bool KAuthorized::authorizeAction(KAuthorized::GenericAction action)
255{
256 const QMetaEnum metaEnum = QMetaEnum::fromType<KAuthorized::GenericAction>();
257 if (metaEnum.isValid() && action != 0) {
258 return KAuthorized::authorizeAction(action: QString::fromLatin1(ba: metaEnum.valueToKey(value: action)).toLower());
259 }
260 qCWarning(KCONFIG_CORE_LOG) << "Invalid GenericAction requested" << action;
261 return false;
262}
263
264bool KAuthorized::authorizeControlModule(const QString &menuId)
265{
266 if (menuId.isEmpty() || kde_kiosk_exception) {
267 return true;
268 }
269 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KDE Control Module Restrictions"));
270 return cg.readEntry(key: menuId, aDefault: true);
271}
272
273// Exported for unittests (e.g. in KIO, we're missing tests for this in kconfig)
274KCONFIGCORE_EXPORT void loadUrlActionRestrictions(const KConfigGroup &cg)
275{
276 KAUTHORIZED_D;
277 const QString Any;
278
279 d->urlActionRestrictions.clear();
280 d->urlActionRestrictions.append(t: URLActionRule("open", Any, Any, Any, Any, Any, Any, true));
281 d->urlActionRestrictions.append(t: URLActionRule("list", Any, Any, Any, Any, Any, Any, true));
282 // TEST:
283 // d->urlActionRestrictions.append(
284 // URLActionRule("list", Any, Any, Any, Any, Any, Any, false));
285 // d->urlActionRestrictions.append(
286 // URLActionRule("list", Any, Any, Any, "file", Any, QDir::homePath(), true));
287 d->urlActionRestrictions.append(t: URLActionRule("link", Any, Any, Any, QStringLiteral(":internet"), Any, Any, true));
288 d->urlActionRestrictions.append(t: URLActionRule("redirect", Any, Any, Any, QStringLiteral(":internet"), Any, Any, true));
289
290 // We allow redirections to file: but not from internet protocols, redirecting to file:
291 // is very popular among KIO workers and we don't want to break them
292 d->urlActionRestrictions.append(t: URLActionRule("redirect", Any, Any, Any, QStringLiteral("file"), Any, Any, true));
293 d->urlActionRestrictions.append(t: URLActionRule("redirect", QStringLiteral(":internet"), Any, Any, QStringLiteral("file"), Any, Any, false));
294
295 // local protocols may redirect everywhere
296 d->urlActionRestrictions.append(t: URLActionRule("redirect", QStringLiteral(":local"), Any, Any, Any, Any, Any, true));
297
298 // Anyone may redirect to about:
299 d->urlActionRestrictions.append(t: URLActionRule("redirect", Any, Any, Any, QStringLiteral("about"), Any, Any, true));
300
301 // Anyone may redirect to mailto:
302 d->urlActionRestrictions.append(t: URLActionRule("redirect", Any, Any, Any, QStringLiteral("mailto"), Any, Any, true));
303
304 // Anyone may redirect to itself, cq. within it's own group
305 d->urlActionRestrictions.append(t: URLActionRule("redirect", Any, Any, Any, QStringLiteral("="), Any, Any, true));
306
307 d->urlActionRestrictions.append(t: URLActionRule("redirect", QStringLiteral("about"), Any, Any, Any, Any, Any, true));
308
309 int count = cg.readEntry(key: "rule_count", defaultValue: 0);
310 QString keyFormat = QStringLiteral("rule_%1");
311 for (int i = 1; i <= count; i++) {
312 QString key = keyFormat.arg(a: i);
313 const QStringList rule = cg.readEntry(key, aDefault: QStringList());
314 if (rule.count() != 8) {
315 continue;
316 }
317 const QByteArray action = rule[0].toLatin1();
318 const QString refProt = rule[1];
319 const QString refHost = rule[2];
320 QString refPath = rule[3];
321 const QString urlProt = rule[4];
322 const QString urlHost = rule[5];
323 QString urlPath = rule[6];
324 const bool bEnabled = (rule[7].compare(other: QLatin1String("true"), cs: Qt::CaseInsensitive) == 0);
325
326 if (refPath.startsWith(s: QLatin1String("$HOME"))) {
327 refPath.replace(i: 0, len: 5, after: QDir::homePath());
328 } else if (refPath.startsWith(c: QLatin1Char('~'))) {
329 refPath.replace(i: 0, len: 1, after: QDir::homePath());
330 }
331 if (urlPath.startsWith(s: QLatin1String("$HOME"))) {
332 urlPath.replace(i: 0, len: 5, after: QDir::homePath());
333 } else if (urlPath.startsWith(c: QLatin1Char('~'))) {
334 urlPath.replace(i: 0, len: 1, after: QDir::homePath());
335 }
336
337 if (refPath.startsWith(s: QLatin1String("$TMP"))) {
338 refPath.replace(i: 0, len: 4, after: QDir::tempPath());
339 }
340 if (urlPath.startsWith(s: QLatin1String("$TMP"))) {
341 urlPath.replace(i: 0, len: 4, after: QDir::tempPath());
342 }
343
344 d->urlActionRestrictions.append(t: URLActionRule(action, refProt, refHost, refPath, urlProt, urlHost, urlPath, bEnabled));
345 }
346}
347
348namespace KAuthorizedInternal
349{
350/**
351 * Helper for KAuthorized::allowUrlAction in KIO
352 * @private
353 */
354KCONFIGCORE_EXPORT void allowUrlAction(const QString &action, const QUrl &_baseURL, const QUrl &_destURL)
355{
356 KAUTHORIZED_D;
357 QMutexLocker locker((&d->mutex));
358
359 const QString basePath = _baseURL.adjusted(options: QUrl::StripTrailingSlash).path();
360 const QString destPath = _destURL.adjusted(options: QUrl::StripTrailingSlash).path();
361
362 d->urlActionRestrictions.append(
363 t: URLActionRule(action.toLatin1(), _baseURL.scheme(), _baseURL.host(), basePath, _destURL.scheme(), _destURL.host(), destPath, true));
364}
365
366/**
367 * Helper for KAuthorized::authorizeUrlAction in KIO
368 * @private
369 */
370KCONFIGCORE_EXPORT bool
371authorizeUrlAction(const QString &action, const QUrl &_baseURL, const QUrl &_destURL, const QString &baseClass, const QString &destClass)
372{
373 KAUTHORIZED_D;
374 QMutexLocker locker(&(d->mutex));
375 if (d->blockEverything) {
376 return false;
377 }
378
379 if (_destURL.isEmpty()) {
380 return true;
381 }
382
383 bool result = false;
384 if (d->urlActionRestrictions.isEmpty()) {
385 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KDE URL Restrictions"));
386 loadUrlActionRestrictions(cg);
387 }
388
389 QUrl baseURL(_baseURL);
390 baseURL.setPath(path: QDir::cleanPath(path: baseURL.path()));
391
392 QUrl destURL(_destURL);
393 destURL.setPath(path: QDir::cleanPath(path: destURL.path()));
394
395 for (const URLActionRule &rule : std::as_const(t&: d->urlActionRestrictions)) {
396 if ((result != rule.permission) && // No need to check if it doesn't make a difference
397 (action == QLatin1String(rule.action.constData())) && rule.baseMatch(url: baseURL, protClass: baseClass)
398 && rule.destMatch(url: destURL, protClass: destClass, base: baseURL, baseClass)) {
399 result = rule.permission;
400 }
401 }
402 return result;
403}
404} // namespace
405
406#include "moc_kauthorized.cpp"
407

source code of kconfig/src/core/kauthorized.cpp