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 | |
29 | extern bool kde_kiosk_exception; |
30 | |
31 | class URLActionRule |
32 | { |
33 | public: |
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 | |
176 | Q_DECLARE_TYPEINFO(URLActionRule, Q_RELOCATABLE_TYPE); |
177 | |
178 | class KAuthorizedPrivate |
179 | { |
180 | public: |
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 | |
207 | Q_GLOBAL_STATIC(KAuthorizedPrivate, authPrivate) |
208 | #define KAUTHORIZED_D KAuthorizedPrivate *d = authPrivate() |
209 | |
210 | KAuthorized::KAuthorized() |
211 | : QObject(nullptr) |
212 | { |
213 | } |
214 | |
215 | bool 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 | |
230 | bool 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 | |
241 | bool 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 | |
254 | bool 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 | |
264 | bool KAuthorized::authorizeControlModule(const QString &) |
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) |
274 | KCONFIGCORE_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 | |
348 | namespace KAuthorizedInternal |
349 | { |
350 | /** |
351 | * Helper for KAuthorized::allowUrlAction in KIO |
352 | * @private |
353 | */ |
354 | KCONFIGCORE_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 | */ |
370 | KCONFIGCORE_EXPORT bool |
371 | authorizeUrlAction(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 | |