File indexing completed on 2024-05-12 15:34:08
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org> 0003 SPDX-FileCopyrightText: 1998, 1999, 2000 Waldo Bastian <bastian@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "kauthorized.h" 0009 0010 #include <QDebug> 0011 #include <QDir> 0012 #include <QList> 0013 #include <QUrl> 0014 0015 #include "kconfig_core_log_settings.h" 0016 #include <QCoreApplication> 0017 #include <ksharedconfig.h> 0018 #include <stdlib.h> // srand(), rand() 0019 #ifndef Q_OS_WIN 0020 #include <netdb.h> 0021 #include <unistd.h> 0022 #endif 0023 0024 #include <kconfiggroup.h> 0025 0026 #include <QMutexLocker> 0027 #include <QRecursiveMutex> 0028 0029 extern bool kde_kiosk_exception; 0030 0031 class URLActionRule 0032 { 0033 public: 0034 #define checkExactMatch(s, b) \ 0035 if (s.isEmpty()) \ 0036 b = true; \ 0037 else if (s[s.length() - 1] == QLatin1Char('!')) { \ 0038 b = false; \ 0039 s.chop(1); \ 0040 } else \ 0041 b = true; 0042 #define checkStartWildCard(s, b) \ 0043 if (s.isEmpty()) \ 0044 b = true; \ 0045 else if (s[0] == QLatin1Char('*')) { \ 0046 b = true; \ 0047 s.remove(0, 1); \ 0048 } else \ 0049 b = false; 0050 #define checkEqual(s, b) b = (s == QLatin1String("=")); 0051 0052 URLActionRule(const QByteArray &act, 0053 const QString &bProt, 0054 const QString &bHost, 0055 const QString &bPath, 0056 const QString &dProt, 0057 const QString &dHost, 0058 const QString &dPath, 0059 bool perm) 0060 : action(act) 0061 , baseProt(bProt) 0062 , baseHost(bHost) 0063 , basePath(bPath) 0064 , destProt(dProt) 0065 , destHost(dHost) 0066 , destPath(dPath) 0067 , permission(perm) 0068 { 0069 checkExactMatch(baseProt, baseProtWildCard); 0070 checkStartWildCard(baseHost, baseHostWildCard); 0071 checkExactMatch(basePath, basePathWildCard); 0072 checkExactMatch(destProt, destProtWildCard); 0073 checkStartWildCard(destHost, destHostWildCard); 0074 checkExactMatch(destPath, destPathWildCard); 0075 checkEqual(destProt, destProtEqual); 0076 checkEqual(destHost, destHostEqual); 0077 } 0078 0079 bool baseMatch(const QUrl &url, const QString &protClass) const 0080 { 0081 if (baseProtWildCard) { 0082 if (!baseProt.isEmpty() // 0083 && !url.scheme().startsWith(baseProt) // 0084 && (protClass.isEmpty() || (protClass != baseProt))) { 0085 return false; 0086 } 0087 } else { 0088 if (url.scheme() != baseProt // 0089 && (protClass.isEmpty() || (protClass != baseProt))) { 0090 return false; 0091 } 0092 } 0093 if (baseHostWildCard) { 0094 if (!baseHost.isEmpty() && !url.host().endsWith(baseHost)) { 0095 return false; 0096 } 0097 } else { 0098 if (url.host() != baseHost) { 0099 return false; 0100 } 0101 } 0102 if (basePathWildCard) { 0103 if (!basePath.isEmpty() && !url.path().startsWith(basePath)) { 0104 return false; 0105 } 0106 } else { 0107 if (url.path() != basePath) { 0108 return false; 0109 } 0110 } 0111 return true; 0112 } 0113 0114 bool destMatch(const QUrl &url, const QString &protClass, const QUrl &base, const QString &baseClass) const 0115 { 0116 if (destProtEqual) { 0117 if (url.scheme() != base.scheme() // 0118 && (protClass.isEmpty() || baseClass.isEmpty() || protClass != baseClass)) { 0119 return false; 0120 } 0121 } else if (destProtWildCard) { 0122 if (!destProt.isEmpty() // 0123 && !url.scheme().startsWith(destProt) // 0124 && (protClass.isEmpty() || (protClass != destProt))) { 0125 return false; 0126 } 0127 } else { 0128 if (url.scheme() != destProt // 0129 && (protClass.isEmpty() || (protClass != destProt))) { 0130 return false; 0131 } 0132 } 0133 if (destHostWildCard) { 0134 if (!destHost.isEmpty() && !url.host().endsWith(destHost)) { 0135 return false; 0136 } 0137 } else if (destHostEqual) { 0138 if (url.host() != base.host()) { 0139 return false; 0140 } 0141 } else { 0142 if (url.host() != destHost) { 0143 return false; 0144 } 0145 } 0146 if (destPathWildCard) { 0147 if (!destPath.isEmpty() && !url.path().startsWith(destPath)) { 0148 return false; 0149 } 0150 } else { 0151 if (url.path() != destPath) { 0152 return false; 0153 } 0154 } 0155 return true; 0156 } 0157 0158 QByteArray action; 0159 QString baseProt; 0160 QString baseHost; 0161 QString basePath; 0162 QString destProt; 0163 QString destHost; 0164 QString destPath; 0165 bool baseProtWildCard : 1; 0166 bool baseHostWildCard : 1; 0167 bool basePathWildCard : 1; 0168 bool destProtWildCard : 1; 0169 bool destHostWildCard : 1; 0170 bool destPathWildCard : 1; 0171 bool destProtEqual : 1; 0172 bool destHostEqual : 1; 0173 bool permission; 0174 }; 0175 0176 Q_DECLARE_TYPEINFO(URLActionRule, Q_MOVABLE_TYPE); 0177 0178 class KAuthorizedPrivate 0179 { 0180 public: 0181 KAuthorizedPrivate() 0182 : actionRestrictions(false) 0183 , blockEverything(false) 0184 { 0185 Q_ASSERT_X(QCoreApplication::instance(), "KAuthorizedPrivate()", "There has to be an existing QCoreApplication::instance() pointer"); 0186 0187 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0188 0189 Q_ASSERT_X(config, "KAuthorizedPrivate()", "There has to be an existing KSharedConfig::openConfig() pointer"); 0190 if (!config) { 0191 blockEverything = true; 0192 return; 0193 } 0194 actionRestrictions = config->hasGroup("KDE Action Restrictions") && !kde_kiosk_exception; 0195 } 0196 0197 ~KAuthorizedPrivate() 0198 { 0199 } 0200 0201 bool actionRestrictions : 1; 0202 bool blockEverything : 1; 0203 QList<URLActionRule> urlActionRestrictions; 0204 QRecursiveMutex mutex; 0205 }; 0206 0207 Q_GLOBAL_STATIC(KAuthorizedPrivate, authPrivate) 0208 #define MY_D KAuthorizedPrivate *d = authPrivate(); 0209 0210 bool KAuthorized::authorize(const QString &genericAction) 0211 { 0212 MY_D if (d->blockEverything) 0213 { 0214 return false; 0215 } 0216 0217 if (!d->actionRestrictions) { 0218 return true; 0219 } 0220 0221 KConfigGroup cg(KSharedConfig::openConfig(), "KDE Action Restrictions"); 0222 return cg.readEntry(genericAction, true); 0223 } 0224 0225 bool KAuthorized::authorize(KAuthorized::GenericRestriction action) 0226 { 0227 const QMetaEnum metaEnum = QMetaEnum::fromType<KAuthorized::GenericRestriction>(); 0228 0229 if (metaEnum.isValid() && action != 0) { 0230 return KAuthorized::authorize(QString::fromLatin1(metaEnum.valueToKey(action)).toLower()); 0231 } 0232 qCWarning(KCONFIG_CORE_LOG) << "Invalid GenericRestriction requested" << action; 0233 return false; 0234 } 0235 0236 bool KAuthorized::authorizeAction(const QString &action) 0237 { 0238 MY_D if (d->blockEverything) 0239 { 0240 return false; 0241 } 0242 if (!d->actionRestrictions || action.isEmpty()) { 0243 return true; 0244 } 0245 0246 return authorize(QLatin1String("action/") + action); 0247 } 0248 0249 bool KAuthorized::authorizeAction(KAuthorized::GenericAction action) 0250 { 0251 const QMetaEnum metaEnum = QMetaEnum::fromType<KAuthorized::GenericAction>(); 0252 if (metaEnum.isValid() && action != 0) { 0253 return KAuthorized::authorizeAction(QString::fromLatin1(metaEnum.valueToKey(action)).toLower()); 0254 } 0255 qCWarning(KCONFIG_CORE_LOG) << "Invalid GenericAction requested" << action; 0256 return false; 0257 } 0258 0259 #if KCONFIGCORE_BUILD_DEPRECATED_SINCE(5, 24) 0260 bool KAuthorized::authorizeKAction(const QString &action) 0261 { 0262 return authorizeAction(action); 0263 } 0264 #endif 0265 0266 bool KAuthorized::authorizeControlModule(const QString &menuId) 0267 { 0268 if (menuId.isEmpty() || kde_kiosk_exception) { 0269 return true; 0270 } 0271 KConfigGroup cg(KSharedConfig::openConfig(), "KDE Control Module Restrictions"); 0272 return cg.readEntry(menuId, true); 0273 } 0274 0275 QStringList KAuthorized::authorizeControlModules(const QStringList &menuIds) 0276 { 0277 KConfigGroup cg(KSharedConfig::openConfig(), "KDE Control Module Restrictions"); 0278 QStringList result; 0279 for (const auto &id : menuIds) { 0280 if (cg.readEntry(id, true)) { 0281 result.append(id); 0282 } 0283 } 0284 return result; 0285 } 0286 0287 // Exported for unittests (e.g. in KIO, we're missing tests for this in kconfig) 0288 KCONFIGCORE_EXPORT void loadUrlActionRestrictions(const KConfigGroup &cg) 0289 { 0290 MY_D const QString Any; 0291 0292 d->urlActionRestrictions.clear(); 0293 d->urlActionRestrictions.append(URLActionRule("open", Any, Any, Any, Any, Any, Any, true)); 0294 d->urlActionRestrictions.append(URLActionRule("list", Any, Any, Any, Any, Any, Any, true)); 0295 // TEST: 0296 // d->urlActionRestrictions.append( 0297 // URLActionRule("list", Any, Any, Any, Any, Any, Any, false)); 0298 // d->urlActionRestrictions.append( 0299 // URLActionRule("list", Any, Any, Any, "file", Any, QDir::homePath(), true)); 0300 d->urlActionRestrictions.append(URLActionRule("link", Any, Any, Any, QStringLiteral(":internet"), Any, Any, true)); 0301 d->urlActionRestrictions.append(URLActionRule("redirect", Any, Any, Any, QStringLiteral(":internet"), Any, Any, true)); 0302 0303 // We allow redirections to file: but not from internet protocols, redirecting to file: 0304 // is very popular among KIO workers and we don't want to break them 0305 d->urlActionRestrictions.append(URLActionRule("redirect", Any, Any, Any, QStringLiteral("file"), Any, Any, true)); 0306 d->urlActionRestrictions.append(URLActionRule("redirect", QStringLiteral(":internet"), Any, Any, QStringLiteral("file"), Any, Any, false)); 0307 0308 // local protocols may redirect everywhere 0309 d->urlActionRestrictions.append(URLActionRule("redirect", QStringLiteral(":local"), Any, Any, Any, Any, Any, true)); 0310 0311 // Anyone may redirect to about: 0312 d->urlActionRestrictions.append(URLActionRule("redirect", Any, Any, Any, QStringLiteral("about"), Any, Any, true)); 0313 0314 // Anyone may redirect to mailto: 0315 d->urlActionRestrictions.append(URLActionRule("redirect", Any, Any, Any, QStringLiteral("mailto"), Any, Any, true)); 0316 0317 // Anyone may redirect to itself, cq. within it's own group 0318 d->urlActionRestrictions.append(URLActionRule("redirect", Any, Any, Any, QStringLiteral("="), Any, Any, true)); 0319 0320 d->urlActionRestrictions.append(URLActionRule("redirect", QStringLiteral("about"), Any, Any, Any, Any, Any, true)); 0321 0322 int count = cg.readEntry("rule_count", 0); 0323 QString keyFormat = QStringLiteral("rule_%1"); 0324 for (int i = 1; i <= count; i++) { 0325 QString key = keyFormat.arg(i); 0326 const QStringList rule = cg.readEntry(key, QStringList()); 0327 if (rule.count() != 8) { 0328 continue; 0329 } 0330 const QByteArray action = rule[0].toLatin1(); 0331 const QString refProt = rule[1]; 0332 const QString refHost = rule[2]; 0333 QString refPath = rule[3]; 0334 const QString urlProt = rule[4]; 0335 const QString urlHost = rule[5]; 0336 QString urlPath = rule[6]; 0337 const bool bEnabled = (rule[7].compare(QLatin1String("true"), Qt::CaseInsensitive) == 0); 0338 0339 if (refPath.startsWith(QLatin1String("$HOME"))) { 0340 refPath.replace(0, 5, QDir::homePath()); 0341 } else if (refPath.startsWith(QLatin1Char('~'))) { 0342 refPath.replace(0, 1, QDir::homePath()); 0343 } 0344 if (urlPath.startsWith(QLatin1String("$HOME"))) { 0345 urlPath.replace(0, 5, QDir::homePath()); 0346 } else if (urlPath.startsWith(QLatin1Char('~'))) { 0347 urlPath.replace(0, 1, QDir::homePath()); 0348 } 0349 0350 if (refPath.startsWith(QLatin1String("$TMP"))) { 0351 refPath.replace(0, 4, QDir::tempPath()); 0352 } 0353 if (urlPath.startsWith(QLatin1String("$TMP"))) { 0354 urlPath.replace(0, 4, QDir::tempPath()); 0355 } 0356 0357 d->urlActionRestrictions.append(URLActionRule(action, refProt, refHost, refPath, urlProt, urlHost, urlPath, bEnabled)); 0358 } 0359 } 0360 0361 namespace KAuthorized 0362 { 0363 /** 0364 * Helper for KAuthorized::allowUrlAction in KIO 0365 * @private 0366 */ 0367 KCONFIGCORE_EXPORT void allowUrlActionInternal(const QString &action, const QUrl &_baseURL, const QUrl &_destURL) 0368 { 0369 MY_D QMutexLocker locker((&d->mutex)); 0370 0371 const QString basePath = _baseURL.adjusted(QUrl::StripTrailingSlash).path(); 0372 const QString destPath = _destURL.adjusted(QUrl::StripTrailingSlash).path(); 0373 0374 d->urlActionRestrictions.append( 0375 URLActionRule(action.toLatin1(), _baseURL.scheme(), _baseURL.host(), basePath, _destURL.scheme(), _destURL.host(), destPath, true)); 0376 } 0377 0378 /** 0379 * Helper for KAuthorized::authorizeUrlAction in KIO 0380 * @private 0381 */ 0382 KCONFIGCORE_EXPORT bool 0383 authorizeUrlActionInternal(const QString &action, const QUrl &_baseURL, const QUrl &_destURL, const QString &baseClass, const QString &destClass) 0384 { 0385 MY_D QMutexLocker locker(&(d->mutex)); 0386 if (d->blockEverything) { 0387 return false; 0388 } 0389 0390 if (_destURL.isEmpty()) { 0391 return true; 0392 } 0393 0394 bool result = false; 0395 if (d->urlActionRestrictions.isEmpty()) { 0396 KConfigGroup cg(KSharedConfig::openConfig(), "KDE URL Restrictions"); 0397 loadUrlActionRestrictions(cg); 0398 } 0399 0400 QUrl baseURL(_baseURL); 0401 baseURL.setPath(QDir::cleanPath(baseURL.path())); 0402 0403 QUrl destURL(_destURL); 0404 destURL.setPath(QDir::cleanPath(destURL.path())); 0405 0406 for (const URLActionRule &rule : std::as_const(d->urlActionRestrictions)) { 0407 if ((result != rule.permission) && // No need to check if it doesn't make a difference 0408 (action == QLatin1String(rule.action.constData())) && rule.baseMatch(baseURL, baseClass) 0409 && rule.destMatch(destURL, destClass, baseURL, baseClass)) { 0410 result = rule.permission; 0411 } 0412 } 0413 return result; 0414 } 0415 0416 } // namespace 0417 0418 #include "moc_kauthorized.cpp"