File indexing completed on 2024-05-12 03:54:23

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_RELOCATABLE_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(QStringLiteral("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 KAUTHORIZED_D KAuthorizedPrivate *d = authPrivate()
0209 
0210 KAuthorized::KAuthorized()
0211     : QObject(nullptr)
0212 {
0213 }
0214 
0215 bool KAuthorized::authorize(const QString &genericAction)
0216 {
0217     KAUTHORIZED_D;
0218     if (d->blockEverything) {
0219         return false;
0220     }
0221 
0222     if (!d->actionRestrictions) {
0223         return true;
0224     }
0225 
0226     KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KDE Action Restrictions"));
0227     return cg.readEntry(genericAction, true);
0228 }
0229 
0230 bool KAuthorized::authorize(KAuthorized::GenericRestriction action)
0231 {
0232     const QMetaEnum metaEnum = QMetaEnum::fromType<KAuthorized::GenericRestriction>();
0233 
0234     if (metaEnum.isValid() && action != 0) {
0235         return KAuthorized::authorize(QString::fromLatin1(metaEnum.valueToKey(action)).toLower());
0236     }
0237     qCWarning(KCONFIG_CORE_LOG) << "Invalid GenericRestriction requested" << action;
0238     return false;
0239 }
0240 
0241 bool KAuthorized::authorizeAction(const QString &action)
0242 {
0243     KAUTHORIZED_D;
0244     if (d->blockEverything) {
0245         return false;
0246     }
0247     if (!d->actionRestrictions || action.isEmpty()) {
0248         return true;
0249     }
0250 
0251     return authorize(QLatin1String("action/") + action);
0252 }
0253 
0254 bool KAuthorized::authorizeAction(KAuthorized::GenericAction action)
0255 {
0256     const QMetaEnum metaEnum = QMetaEnum::fromType<KAuthorized::GenericAction>();
0257     if (metaEnum.isValid() && action != 0) {
0258         return KAuthorized::authorizeAction(QString::fromLatin1(metaEnum.valueToKey(action)).toLower());
0259     }
0260     qCWarning(KCONFIG_CORE_LOG) << "Invalid GenericAction requested" << action;
0261     return false;
0262 }
0263 
0264 bool KAuthorized::authorizeControlModule(const QString &menuId)
0265 {
0266     if (menuId.isEmpty() || kde_kiosk_exception) {
0267         return true;
0268     }
0269     KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KDE Control Module Restrictions"));
0270     return cg.readEntry(menuId, true);
0271 }
0272 
0273 // Exported for unittests (e.g. in KIO, we're missing tests for this in kconfig)
0274 KCONFIGCORE_EXPORT void loadUrlActionRestrictions(const KConfigGroup &cg)
0275 {
0276     KAUTHORIZED_D;
0277     const QString Any;
0278 
0279     d->urlActionRestrictions.clear();
0280     d->urlActionRestrictions.append(URLActionRule("open", Any, Any, Any, Any, Any, Any, true));
0281     d->urlActionRestrictions.append(URLActionRule("list", Any, Any, Any, Any, Any, Any, true));
0282     // TEST:
0283     //  d->urlActionRestrictions.append(
0284     //  URLActionRule("list", Any, Any, Any, Any, Any, Any, false));
0285     //  d->urlActionRestrictions.append(
0286     //  URLActionRule("list", Any, Any, Any, "file", Any, QDir::homePath(), true));
0287     d->urlActionRestrictions.append(URLActionRule("link", Any, Any, Any, QStringLiteral(":internet"), Any, Any, true));
0288     d->urlActionRestrictions.append(URLActionRule("redirect", Any, Any, Any, QStringLiteral(":internet"), Any, Any, true));
0289 
0290     // We allow redirections to file: but not from internet protocols, redirecting to file:
0291     // is very popular among KIO workers and we don't want to break them
0292     d->urlActionRestrictions.append(URLActionRule("redirect", Any, Any, Any, QStringLiteral("file"), Any, Any, true));
0293     d->urlActionRestrictions.append(URLActionRule("redirect", QStringLiteral(":internet"), Any, Any, QStringLiteral("file"), Any, Any, false));
0294 
0295     // local protocols may redirect everywhere
0296     d->urlActionRestrictions.append(URLActionRule("redirect", QStringLiteral(":local"), Any, Any, Any, Any, Any, true));
0297 
0298     // Anyone may redirect to about:
0299     d->urlActionRestrictions.append(URLActionRule("redirect", Any, Any, Any, QStringLiteral("about"), Any, Any, true));
0300 
0301     // Anyone may redirect to mailto:
0302     d->urlActionRestrictions.append(URLActionRule("redirect", Any, Any, Any, QStringLiteral("mailto"), Any, Any, true));
0303 
0304     // Anyone may redirect to itself, cq. within it's own group
0305     d->urlActionRestrictions.append(URLActionRule("redirect", Any, Any, Any, QStringLiteral("="), Any, Any, true));
0306 
0307     d->urlActionRestrictions.append(URLActionRule("redirect", QStringLiteral("about"), Any, Any, Any, Any, Any, true));
0308 
0309     int count = cg.readEntry("rule_count", 0);
0310     QString keyFormat = QStringLiteral("rule_%1");
0311     for (int i = 1; i <= count; i++) {
0312         QString key = keyFormat.arg(i);
0313         const QStringList rule = cg.readEntry(key, QStringList());
0314         if (rule.count() != 8) {
0315             continue;
0316         }
0317         const QByteArray action = rule[0].toLatin1();
0318         const QString refProt = rule[1];
0319         const QString refHost = rule[2];
0320         QString refPath = rule[3];
0321         const QString urlProt = rule[4];
0322         const QString urlHost = rule[5];
0323         QString urlPath = rule[6];
0324         const bool bEnabled = (rule[7].compare(QLatin1String("true"), Qt::CaseInsensitive) == 0);
0325 
0326         if (refPath.startsWith(QLatin1String("$HOME"))) {
0327             refPath.replace(0, 5, QDir::homePath());
0328         } else if (refPath.startsWith(QLatin1Char('~'))) {
0329             refPath.replace(0, 1, QDir::homePath());
0330         }
0331         if (urlPath.startsWith(QLatin1String("$HOME"))) {
0332             urlPath.replace(0, 5, QDir::homePath());
0333         } else if (urlPath.startsWith(QLatin1Char('~'))) {
0334             urlPath.replace(0, 1, QDir::homePath());
0335         }
0336 
0337         if (refPath.startsWith(QLatin1String("$TMP"))) {
0338             refPath.replace(0, 4, QDir::tempPath());
0339         }
0340         if (urlPath.startsWith(QLatin1String("$TMP"))) {
0341             urlPath.replace(0, 4, QDir::tempPath());
0342         }
0343 
0344         d->urlActionRestrictions.append(URLActionRule(action, refProt, refHost, refPath, urlProt, urlHost, urlPath, bEnabled));
0345     }
0346 }
0347 
0348 namespace KAuthorizedInternal
0349 {
0350 /**
0351  * Helper for KAuthorized::allowUrlAction in KIO
0352  * @private
0353  */
0354 KCONFIGCORE_EXPORT void allowUrlAction(const QString &action, const QUrl &_baseURL, const QUrl &_destURL)
0355 {
0356     KAUTHORIZED_D;
0357     QMutexLocker locker((&d->mutex));
0358 
0359     const QString basePath = _baseURL.adjusted(QUrl::StripTrailingSlash).path();
0360     const QString destPath = _destURL.adjusted(QUrl::StripTrailingSlash).path();
0361 
0362     d->urlActionRestrictions.append(
0363         URLActionRule(action.toLatin1(), _baseURL.scheme(), _baseURL.host(), basePath, _destURL.scheme(), _destURL.host(), destPath, true));
0364 }
0365 
0366 /**
0367  * Helper for KAuthorized::authorizeUrlAction in KIO
0368  * @private
0369  */
0370 KCONFIGCORE_EXPORT bool
0371 authorizeUrlAction(const QString &action, const QUrl &_baseURL, const QUrl &_destURL, const QString &baseClass, const QString &destClass)
0372 {
0373     KAUTHORIZED_D;
0374     QMutexLocker locker(&(d->mutex));
0375     if (d->blockEverything) {
0376         return false;
0377     }
0378 
0379     if (_destURL.isEmpty()) {
0380         return true;
0381     }
0382 
0383     bool result = false;
0384     if (d->urlActionRestrictions.isEmpty()) {
0385         KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KDE URL Restrictions"));
0386         loadUrlActionRestrictions(cg);
0387     }
0388 
0389     QUrl baseURL(_baseURL);
0390     baseURL.setPath(QDir::cleanPath(baseURL.path()));
0391 
0392     QUrl destURL(_destURL);
0393     destURL.setPath(QDir::cleanPath(destURL.path()));
0394 
0395     for (const URLActionRule &rule : std::as_const(d->urlActionRestrictions)) {
0396         if ((result != rule.permission) && // No need to check if it doesn't make a difference
0397             (action == QLatin1String(rule.action.constData())) && rule.baseMatch(baseURL, baseClass)
0398             && rule.destMatch(destURL, destClass, baseURL, baseClass)) {
0399             result = rule.permission;
0400         }
0401     }
0402     return result;
0403 }
0404 } // namespace
0405 
0406 #include "moc_kauthorized.cpp"