Warning, file /plasma/sddm-kcm/sddmauthhelper.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2019 Filip Fila <filipfila.kde@gmail.com>
0003     SPDX-FileCopyrightText: 2013 Reza Fatahilah Shah <rshah0385@kireihana.com>
0004     SPDX-FileCopyrightText: 2011, 2012 David Edmundson <kde@davidedmundson.co.uk>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 #include "sddmauthhelper.h"
0009 #include "src/config.h"
0010 
0011 #include <unistd.h>
0012 
0013 #include <QDebug>
0014 #include <QDir>
0015 #include <QFile>
0016 #include <QFileInfo>
0017 #include <QMimeDatabase>
0018 #include <QMimeType>
0019 #include <QSharedPointer>
0020 
0021 #include <KArchive>
0022 #include <KConfig>
0023 #include <KConfigGroup>
0024 #include <KLazyLocalizedString>
0025 #include <KLocalizedString>
0026 #include <KTar>
0027 #include <KUser>
0028 #include <KZip>
0029 
0030 static QSharedPointer<KConfig> openConfig(const QString &filePath)
0031 {
0032     // if the sddm.conf.d folder doesn't exist we fail to set the right permissions for kde_settings.conf
0033     QFileInfo fileLocation(filePath);
0034     QDir dir(fileLocation.absolutePath());
0035     if (!dir.exists()) {
0036         QDir().mkpath(dir.path());
0037     }
0038     QFile file(filePath);
0039     if (!file.exists()) {
0040         // If we are creating the config file, ensure it is world-readable: if
0041         // we don't do that, KConfig will create a file which is only readable
0042         // by root
0043         file.open(QIODevice::WriteOnly);
0044         file.close();
0045         file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::ReadOther);
0046     }
0047     // in case the file has already been created with wrong permissions
0048     else if (!(file.permissions() & QFile::ReadOwner & QFile::WriteOwner & QFile::ReadGroup & QFile::ReadOther)) {
0049         file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::ReadOther);
0050     }
0051 
0052     return QSharedPointer<KConfig>(new KConfig(file.fileName(), KConfig::SimpleConfig));
0053 }
0054 
0055 void SddmAuthHelper::copyDirectoryRecursively(const QString &source, const QString &destination, QSet<QString> &done)
0056 {
0057     if (done.contains(source)) {
0058         return;
0059     }
0060     done.insert(source);
0061 
0062     const QDir sourceDir(source);
0063     const auto entries = sourceDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files);
0064     for (const auto &entry : entries) {
0065         const auto destinationPath = destination + '/' + entry.fileName();
0066         if (entry.isFile()) {
0067             copyFile(entry.absoluteFilePath(), destinationPath);
0068         } else {
0069             QDir().mkpath(destinationPath);
0070             copyDirectoryRecursively(entry.absoluteFilePath(), destinationPath, done);
0071         }
0072     }
0073 }
0074 
0075 void SddmAuthHelper::copyFile(const QString &source, const QString &destination)
0076 {
0077     KUser sddmUser(QStringLiteral("sddm"));
0078 
0079     if (QFile::exists(destination)) {
0080         QFile::remove(destination);
0081     }
0082 
0083     if (!QFile::copy(source, destination)) {
0084         qWarning() << "Could not copy" << source << "to" << destination;
0085     }
0086     const char *destinationConverted = destination.toLocal8Bit().data();
0087     if (chown(destinationConverted, sddmUser.userId().nativeId(), sddmUser.groupId().nativeId())) {
0088         return;
0089     }
0090 }
0091 
0092 ActionReply SddmAuthHelper::sync(const QVariantMap &args)
0093 {
0094     // initial check for sddm user; abort if user not present
0095     // we have to check with QString and isEmpty() instead of QDir and exists() because
0096     // QDir returns "." and true for exists() in the case of a non-existent user;
0097     QString sddmHomeDirPath = KUser("sddm").homeDir();
0098     if (sddmHomeDirPath.isEmpty()) {
0099         qDebug() << "Cannot proceed, user 'sddm' does not exist. Please check your SDDM install.";
0100         return ActionReply::HelperErrorReply();
0101     }
0102 
0103     // In plasma-framework, ThemePrivate::useCache documents the requirement to
0104     // clear the cache when colors change while the app that uses them isn't running;
0105     // that condition applies to the SDDM greeter here, so clear the cache if it
0106     // exists to make sure SDDM has a fresh state
0107     QDir sddmCacheLocation(sddmHomeDirPath + QStringLiteral("/.cache"));
0108     if (sddmCacheLocation.exists()) {
0109         sddmCacheLocation.removeRecursively();
0110     }
0111 
0112     // create SDDM config directory if it does not exist
0113     QDir sddmConfigLocation(sddmHomeDirPath + QStringLiteral("/.config"));
0114     if (!sddmConfigLocation.exists()) {
0115         QDir().mkpath(sddmConfigLocation.path());
0116     }
0117 
0118     // copy fontconfig (font, font rendering)
0119     if (!args[QStringLiteral("fontconfig")].isNull()) {
0120         QDir fontconfigSource(args[QStringLiteral("fontconfig")].toString());
0121         QStringList sourceFileEntries = fontconfigSource.entryList(QDir::Files);
0122         QStringList sourceDirEntries = fontconfigSource.entryList(QDir::AllDirs);
0123         QDir fontconfigDestination(sddmConfigLocation.path() + QStringLiteral("/fontconfig"));
0124 
0125         if (!fontconfigDestination.exists()) {
0126             QDir().mkpath(fontconfigDestination.path());
0127         }
0128 
0129         if (sourceDirEntries.count() != 0) {
0130             for (int i = 0; i < sourceDirEntries.count(); i++) {
0131                 QString directoriesSource = fontconfigSource.path() + QDir::separator() + sourceDirEntries[i];
0132                 QString directoriesDestination = fontconfigDestination.path() + QDir::separator() + sourceDirEntries[i];
0133                 fontconfigSource.mkpath(directoriesDestination);
0134                 copyFile(directoriesSource, directoriesDestination);
0135             }
0136         }
0137 
0138         if (sourceFileEntries.count() != 0) {
0139             for (int i = 0; i < sourceFileEntries.count(); i++) {
0140                 QString filesSource = fontconfigSource.path() + QDir::separator() + sourceFileEntries[i];
0141                 QString filesDestination = fontconfigDestination.path() + QDir::separator() + sourceFileEntries[i];
0142                 copyFile(filesSource, filesDestination);
0143             }
0144         }
0145     }
0146 
0147     // copy kdeglobals (color scheme)
0148     if (!args[QStringLiteral("kdeglobals")].isNull()) {
0149         QDir kdeglobalsSource(args[QStringLiteral("kdeglobals")].toString());
0150         QDir kdeglobalsDestination(sddmConfigLocation.path() + QStringLiteral("/kdeglobals"));
0151         copyFile(kdeglobalsSource.path(), kdeglobalsDestination.path());
0152     }
0153 
0154     // copy plasmarc (icons, UI style)
0155     if (!args[QStringLiteral("plasmarc")].isNull()) {
0156         QDir plasmarcSource(args[QStringLiteral("plasmarc")].toString());
0157         QDir plasmarcDestination(sddmConfigLocation.path() + QStringLiteral("/plasmarc"));
0158         copyFile(plasmarcSource.path(), plasmarcDestination.path());
0159     }
0160 
0161     // If SDDM is set up to use Wayland with KWin, NumLock is controlled by this
0162     if (!args[QStringLiteral("kcminputrc")].isNull()) {
0163         QDir kcminputrcSource(args[QStringLiteral("kcminputrc")].toString());
0164         QDir kcminputrcDestination(sddmConfigLocation.path() + QStringLiteral("/kcminputrc"));
0165         copyFile(kcminputrcSource.path(), kcminputrcDestination.path());
0166     }
0167 
0168     // copy kscreen config
0169     if (!args[QStringLiteral("kscreen-config")].isNull()) {
0170         const QString destinationDir = sddmHomeDirPath + "/.local/share/kscreen/";
0171         QSet<QString> done;
0172         copyDirectoryRecursively(args[QStringLiteral("kscreen-config")].toString(), destinationDir, done);
0173     }
0174 
0175     // write cursor theme, NumLock preference, and scaling DPI to config file
0176     ActionReply reply = ActionReply::HelperErrorReply();
0177     QSharedPointer<KConfig> sddmConfig = openConfig(args[QStringLiteral("kde_settings.conf")].toString());
0178     QSharedPointer<KConfig> sddmOldConfig = openConfig(args[QStringLiteral("sddm.conf")].toString());
0179 
0180     QMap<QString, QVariant>::const_iterator iterator;
0181 
0182     for (iterator = args.constBegin(); iterator != args.constEnd(); ++iterator) {
0183         if (iterator.key() == QLatin1String("kde_settings.conf")) {
0184             continue;
0185         }
0186 
0187         QStringList configFields = iterator.key().split(QLatin1Char('/'));
0188         if (configFields.size() != 3) {
0189             continue;
0190         }
0191 
0192         QSharedPointer<KConfig> config;
0193         QString fileName = configFields[0];
0194         QString groupName = configFields[1];
0195         QString keyName = configFields[2];
0196 
0197         if (fileName == QLatin1String("kde_settings.conf") && iterator.value().isValid()) {
0198             sddmConfig->group(groupName).writeEntry(keyName, iterator.value());
0199             sddmOldConfig->group(groupName).deleteEntry(keyName);
0200         }
0201     }
0202 
0203     sddmOldConfig->sync();
0204     sddmConfig->sync();
0205 
0206     return ActionReply::SuccessReply();
0207 }
0208 
0209 ActionReply SddmAuthHelper::reset(const QVariantMap &args)
0210 {
0211     // initial check for sddm user; abort if user not present
0212     // we have to check with QString and isEmpty() instead of QDir and exists() because
0213     // QDir returns "." and true for exists() in the case of a non-existent user;
0214     QString sddmHomeDirPath = KUser("sddm").homeDir();
0215     if (sddmHomeDirPath.isEmpty()) {
0216         qDebug() << "Cannot proceed, user 'sddm' does not exist. Please check your SDDM install.";
0217         return ActionReply::HelperErrorReply();
0218     }
0219 
0220     QDir sddmConfigLocation(sddmHomeDirPath + QStringLiteral("/.config"));
0221     QDir fontconfigDir(args[QStringLiteral("sddmUserConfig")].toString() + QStringLiteral("/fontconfig"));
0222 
0223     fontconfigDir.removeRecursively();
0224     QFile::remove(sddmConfigLocation.path() + QStringLiteral("/kdeglobals"));
0225     QFile::remove(sddmConfigLocation.path() + QStringLiteral("/plasmarc"));
0226 
0227     QDir(sddmHomeDirPath + "/.local/share/kscreen/").removeRecursively();
0228 
0229     // remove cursor theme, NumLock preference, and scaling DPI from config file
0230     ActionReply reply = ActionReply::HelperErrorReply();
0231     QSharedPointer<KConfig> sddmConfig = openConfig(args[QStringLiteral("kde_settings.conf")].toString());
0232     QSharedPointer<KConfig> sddmOldConfig = openConfig(args[QStringLiteral("sddm.conf")].toString());
0233 
0234     QMap<QString, QVariant>::const_iterator iterator;
0235 
0236     for (iterator = args.constBegin(); iterator != args.constEnd(); ++iterator) {
0237         if (iterator.key() == QLatin1String("kde_settings.conf")) {
0238             continue;
0239         }
0240 
0241         QStringList configFields = iterator.key().split(QLatin1Char('/'));
0242         if (configFields.size() != 3) {
0243             continue;
0244         }
0245 
0246         QSharedPointer<KConfig> config;
0247         QString fileName = configFields[0];
0248         QString groupName = configFields[1];
0249         QString keyName = configFields[2];
0250 
0251         if (fileName == QLatin1String("kde_settings.conf")) {
0252             sddmConfig->group(groupName).deleteEntry(keyName);
0253             sddmOldConfig->group(groupName).deleteEntry(keyName);
0254         }
0255     }
0256 
0257     sddmOldConfig->sync();
0258     sddmConfig->sync();
0259 
0260     return ActionReply::SuccessReply();
0261 }
0262 
0263 ActionReply SddmAuthHelper::save(const QVariantMap &args)
0264 {
0265     ActionReply reply = ActionReply::HelperErrorReply();
0266     QSharedPointer<KConfig> sddmConfig = openConfig(QString{QLatin1String(SDDM_CONFIG_DIR "/") + QStringLiteral("kde_settings.conf")});
0267     QSharedPointer<KConfig> sddmOldConfig = openConfig(QStringLiteral(SDDM_CONFIG_FILE));
0268     QSharedPointer<KConfig> themeConfig;
0269     QString themeConfigFile = args[QStringLiteral("theme.conf.user")].toString();
0270 
0271     if (!themeConfigFile.isEmpty()) {
0272         themeConfig = openConfig(themeConfigFile);
0273     }
0274 
0275     QMap<QString, QVariant>::const_iterator iterator;
0276 
0277     for (iterator = args.constBegin(); iterator != args.constEnd(); ++iterator) {
0278         if (iterator.key() == QLatin1String("kde_settings.conf") || iterator.key() == QLatin1String("theme.conf.user")) {
0279             continue;
0280         }
0281 
0282         QStringList configFields = iterator.key().split(QLatin1Char('/'));
0283         if (configFields.size() != 3) {
0284             continue;
0285         }
0286 
0287         QString fileName = configFields[0];
0288         QString groupName = configFields[1];
0289         QString keyName = configFields[2];
0290 
0291         // if there is an identical keyName in "sddm.conf" we want to delete it so SDDM doesn't read from the old file
0292         // hierarchically SDDM prefers "etc/sddm.conf" to "/etc/sddm.conf.d/some_file.conf"
0293 
0294         if (fileName == QLatin1String("kde_settings.conf")) {
0295             sddmConfig->group(groupName).writeEntry(keyName, iterator.value());
0296             sddmOldConfig->group(groupName).deleteEntry(keyName);
0297         } else if (fileName == QLatin1String("theme.conf.user") && !themeConfig.isNull()) {
0298             QFileInfo themeConfigFileInfo(themeConfigFile);
0299             QDir configRootDirectory = themeConfigFileInfo.absoluteDir();
0300 
0301             if (keyName == QLatin1String("background")) {
0302                 QFileInfo newBackgroundFileInfo(iterator.value().toString());
0303                 QString previousBackground = themeConfig->group(groupName).readEntry(keyName);
0304 
0305                 bool backgroundChanged = newBackgroundFileInfo.fileName() != previousBackground;
0306                 if (backgroundChanged) {
0307                     if (!previousBackground.isEmpty()) {
0308                         QString previousBackgroundPath = configRootDirectory.filePath(previousBackground);
0309                         if (QFile::remove(previousBackgroundPath)) {
0310                             qDebug() << "Removed previous background " << previousBackgroundPath;
0311                         }
0312                     }
0313 
0314                     if (newBackgroundFileInfo.exists()) {
0315                         QString newBackgroundPath = configRootDirectory.filePath(newBackgroundFileInfo.fileName());
0316                         qDebug() << "Copying background from " << newBackgroundFileInfo.absoluteFilePath() << " to " << newBackgroundPath;
0317                         if (QFile::copy(newBackgroundFileInfo.absoluteFilePath(), newBackgroundPath)) {
0318                             QFile::setPermissions(newBackgroundPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::ReadOther);
0319                             themeConfig->group(groupName).writeEntry(keyName, newBackgroundFileInfo.fileName());
0320                         }
0321                     } else {
0322                         themeConfig->group(groupName).deleteEntry(keyName);
0323                     }
0324                 }
0325             } else {
0326                 themeConfig->group(groupName).writeEntry(keyName, iterator.value());
0327             }
0328         }
0329     }
0330 
0331     sddmOldConfig->sync();
0332     sddmConfig->sync();
0333 
0334     if (!themeConfig.isNull()) {
0335         themeConfig->sync();
0336     }
0337 
0338     return ActionReply::SuccessReply();
0339 }
0340 
0341 ActionReply SddmAuthHelper::installtheme(const QVariantMap &args)
0342 {
0343     const QString filePath = args[QStringLiteral("filePath")].toString();
0344     if (filePath.isEmpty()) {
0345         return ActionReply::HelperErrorReply();
0346     }
0347 
0348     const QString themesBaseDir = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sddm/themes"), QStandardPaths::LocateDirectory);
0349     QDir dir(themesBaseDir);
0350     if (!dir.exists()) {
0351         return ActionReply::HelperErrorReply();
0352     }
0353 
0354     qDebug() << "Installing " << filePath << " into " << themesBaseDir;
0355 
0356     if (!QFile::exists(filePath)) {
0357         return ActionReply::HelperErrorReply();
0358     }
0359 
0360     QMimeDatabase db;
0361     QMimeType mimeType = db.mimeTypeForFile(filePath);
0362     qWarning() << "Postinstallation: uncompress the file";
0363 
0364     QScopedPointer<KArchive> archive;
0365 
0366     // there must be a better way to do this? If not, make a static bool KZip::supportsMimeType(const QMimeType &type); ?
0367     // or even a factory class in KArchive
0368 
0369     if (mimeType.inherits(QStringLiteral("application/zip"))) {
0370         archive.reset(new KZip(filePath));
0371     } else if (mimeType.inherits(QStringLiteral("application/tar")) || mimeType.inherits(QStringLiteral("application/x-gzip"))
0372                || mimeType.inherits(QStringLiteral("application/x-bzip")) || mimeType.inherits(QStringLiteral("application/x-lzma"))
0373                || mimeType.inherits(QStringLiteral("application/x-xz")) || mimeType.inherits(QStringLiteral("application/x-bzip-compressed-tar"))
0374                || mimeType.inherits(QStringLiteral("application/x-compressed-tar"))) {
0375         archive.reset(new KTar(filePath));
0376     } else {
0377         auto e = ActionReply::HelperErrorReply();
0378         e.setErrorDescription(kli18n("Invalid theme package").untranslatedText());
0379         return e;
0380     }
0381 
0382     if (!archive->open(QIODevice::ReadOnly)) {
0383         auto e = ActionReply::HelperErrorReply();
0384         e.setErrorDescription(kli18n("Could not open file").untranslatedText());
0385         return e;
0386     }
0387 
0388     auto directory = archive->directory();
0389 
0390     QStringList installedPaths;
0391 
0392     // some basic validation
0393     // the top level should only have folders, and those folders should contain a valid metadata.desktop file
0394     // if we get anything else, abort everything before copying
0395     const auto entries = directory->entries();
0396     for (const QString &name : entries) {
0397         auto entry = directory->entry(name);
0398         if (!entry->isDirectory()) {
0399             auto e = ActionReply::HelperErrorReply();
0400             e.setErrorDescription(kli18n("Invalid theme package").untranslatedText());
0401             return e;
0402         }
0403         auto subDirectory = static_cast<const KArchiveDirectory *>(entry);
0404         auto metadataFile = subDirectory->file(QStringLiteral("metadata.desktop"));
0405         if (!metadataFile || !metadataFile->data().contains("[SddmGreeterTheme]")) {
0406             auto e = ActionReply::HelperErrorReply();
0407             e.setErrorDescription(kli18n("Invalid theme package").untranslatedText());
0408             return e;
0409         }
0410         installedPaths.append(themesBaseDir + QLatin1Char('/') + name);
0411     }
0412 
0413     if (!directory->copyTo(themesBaseDir)) {
0414         auto e = ActionReply::HelperErrorReply();
0415         e.setErrorDescription(kli18n("Could not decompress archive").untranslatedText());
0416         return e;
0417     }
0418 
0419     auto rc = ActionReply::SuccessReply();
0420     rc.addData(QStringLiteral("installedPaths"), installedPaths);
0421     return rc;
0422 }
0423 
0424 ActionReply SddmAuthHelper::uninstalltheme(const QVariantMap &args)
0425 {
0426     const QString themePath = args[QStringLiteral("filePath")].toString();
0427     const QString themesBaseDir = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sddm/themes"), QStandardPaths::LocateDirectory);
0428 
0429     QDir dir(themePath);
0430     if (!dir.exists()) {
0431         return ActionReply::HelperErrorReply();
0432     }
0433 
0434     // validate the themePath is directly inside the themesBaseDir
0435     QDir baseDir(themesBaseDir);
0436     if (baseDir.absoluteFilePath(dir.dirName()) != dir.absolutePath()) {
0437         return ActionReply::HelperErrorReply();
0438     }
0439 
0440     if (!dir.removeRecursively()) {
0441         return ActionReply::HelperErrorReply();
0442     }
0443 
0444     return ActionReply::SuccessReply();
0445 }
0446 
0447 KAUTH_HELPER_MAIN("org.kde.kcontrol.kcmsddm", SddmAuthHelper)
0448 
0449 #include "moc_sddmauthhelper.cpp"