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"