File indexing completed on 2024-03-24 17:13:50
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 // copy kscreen config 0162 if (!args[QStringLiteral("kscreen-config")].isNull()) { 0163 const QString destinationDir = sddmHomeDirPath + "/.local/share/kscreen/"; 0164 QSet<QString> done; 0165 copyDirectoryRecursively(args[QStringLiteral("kscreen-config")].toString(), destinationDir, done); 0166 } 0167 0168 // write cursor theme, NumLock preference, and scaling DPI to config file 0169 ActionReply reply = ActionReply::HelperErrorReply(); 0170 QSharedPointer<KConfig> sddmConfig = openConfig(args[QStringLiteral("kde_settings.conf")].toString()); 0171 QSharedPointer<KConfig> sddmOldConfig = openConfig(args[QStringLiteral("sddm.conf")].toString()); 0172 0173 QMap<QString, QVariant>::const_iterator iterator; 0174 0175 for (iterator = args.constBegin(); iterator != args.constEnd(); ++iterator) { 0176 if (iterator.key() == QLatin1String("kde_settings.conf")) { 0177 continue; 0178 } 0179 0180 QStringList configFields = iterator.key().split(QLatin1Char('/')); 0181 if (configFields.size() != 3) { 0182 continue; 0183 } 0184 0185 QSharedPointer<KConfig> config; 0186 QString fileName = configFields[0]; 0187 QString groupName = configFields[1]; 0188 QString keyName = configFields[2]; 0189 0190 if (fileName == QLatin1String("kde_settings.conf") && iterator.value().isValid()) { 0191 sddmConfig->group(groupName).writeEntry(keyName, iterator.value()); 0192 sddmOldConfig->group(groupName).deleteEntry(keyName); 0193 } 0194 } 0195 0196 sddmOldConfig->sync(); 0197 sddmConfig->sync(); 0198 0199 return ActionReply::SuccessReply(); 0200 } 0201 0202 ActionReply SddmAuthHelper::reset(const QVariantMap &args) 0203 { 0204 // initial check for sddm user; abort if user not present 0205 // we have to check with QString and isEmpty() instead of QDir and exists() because 0206 // QDir returns "." and true for exists() in the case of a non-existent user; 0207 QString sddmHomeDirPath = KUser("sddm").homeDir(); 0208 if (sddmHomeDirPath.isEmpty()) { 0209 qDebug() << "Cannot proceed, user 'sddm' does not exist. Please check your SDDM install."; 0210 return ActionReply::HelperErrorReply(); 0211 } 0212 0213 QDir sddmConfigLocation(sddmHomeDirPath + QStringLiteral("/.config")); 0214 QDir fontconfigDir(args[QStringLiteral("sddmUserConfig")].toString() + QStringLiteral("/fontconfig")); 0215 0216 fontconfigDir.removeRecursively(); 0217 QFile::remove(sddmConfigLocation.path() + QStringLiteral("/kdeglobals")); 0218 QFile::remove(sddmConfigLocation.path() + QStringLiteral("/plasmarc")); 0219 0220 QDir(sddmHomeDirPath + "/.local/share/kscreen/").removeRecursively(); 0221 0222 // remove cursor theme, NumLock preference, and scaling DPI from config file 0223 ActionReply reply = ActionReply::HelperErrorReply(); 0224 QSharedPointer<KConfig> sddmConfig = openConfig(args[QStringLiteral("kde_settings.conf")].toString()); 0225 QSharedPointer<KConfig> sddmOldConfig = openConfig(args[QStringLiteral("sddm.conf")].toString()); 0226 0227 QMap<QString, QVariant>::const_iterator iterator; 0228 0229 for (iterator = args.constBegin(); iterator != args.constEnd(); ++iterator) { 0230 if (iterator.key() == QLatin1String("kde_settings.conf")) { 0231 continue; 0232 } 0233 0234 QStringList configFields = iterator.key().split(QLatin1Char('/')); 0235 if (configFields.size() != 3) { 0236 continue; 0237 } 0238 0239 QSharedPointer<KConfig> config; 0240 QString fileName = configFields[0]; 0241 QString groupName = configFields[1]; 0242 QString keyName = configFields[2]; 0243 0244 if (fileName == QLatin1String("kde_settings.conf")) { 0245 sddmConfig->group(groupName).deleteEntry(keyName); 0246 sddmOldConfig->group(groupName).deleteEntry(keyName); 0247 } 0248 } 0249 0250 sddmOldConfig->sync(); 0251 sddmConfig->sync(); 0252 0253 return ActionReply::SuccessReply(); 0254 } 0255 0256 ActionReply SddmAuthHelper::save(const QVariantMap &args) 0257 { 0258 ActionReply reply = ActionReply::HelperErrorReply(); 0259 QSharedPointer<KConfig> sddmConfig = openConfig(QString{QLatin1String(SDDM_CONFIG_DIR "/") + QStringLiteral("kde_settings.conf")}); 0260 QSharedPointer<KConfig> sddmOldConfig = openConfig(QStringLiteral(SDDM_CONFIG_FILE)); 0261 QSharedPointer<KConfig> themeConfig; 0262 QString themeConfigFile = args[QStringLiteral("theme.conf.user")].toString(); 0263 0264 if (!themeConfigFile.isEmpty()) { 0265 themeConfig = openConfig(themeConfigFile); 0266 } 0267 0268 QMap<QString, QVariant>::const_iterator iterator; 0269 0270 for (iterator = args.constBegin(); iterator != args.constEnd(); ++iterator) { 0271 if (iterator.key() == QLatin1String("kde_settings.conf") || iterator.key() == QLatin1String("theme.conf.user")) { 0272 continue; 0273 } 0274 0275 QStringList configFields = iterator.key().split(QLatin1Char('/')); 0276 if (configFields.size() != 3) { 0277 continue; 0278 } 0279 0280 QString fileName = configFields[0]; 0281 QString groupName = configFields[1]; 0282 QString keyName = configFields[2]; 0283 0284 // if there is an identical keyName in "sddm.conf" we want to delete it so SDDM doesn't read from the old file 0285 // hierarchically SDDM prefers "etc/sddm.conf" to "/etc/sddm.conf.d/some_file.conf" 0286 0287 if (fileName == QLatin1String("kde_settings.conf")) { 0288 sddmConfig->group(groupName).writeEntry(keyName, iterator.value()); 0289 sddmOldConfig->group(groupName).deleteEntry(keyName); 0290 } else if (fileName == QLatin1String("theme.conf.user") && !themeConfig.isNull()) { 0291 QFileInfo themeConfigFileInfo(themeConfigFile); 0292 QDir configRootDirectory = themeConfigFileInfo.absoluteDir(); 0293 0294 if (keyName == QLatin1String("background")) { 0295 QFileInfo newBackgroundFileInfo(iterator.value().toString()); 0296 QString previousBackground = themeConfig->group(groupName).readEntry(keyName); 0297 0298 bool backgroundChanged = newBackgroundFileInfo.fileName() != previousBackground; 0299 if (backgroundChanged) { 0300 if (!previousBackground.isEmpty()) { 0301 QString previousBackgroundPath = configRootDirectory.filePath(previousBackground); 0302 if (QFile::remove(previousBackgroundPath)) { 0303 qDebug() << "Removed previous background " << previousBackgroundPath; 0304 } 0305 } 0306 0307 if (newBackgroundFileInfo.exists()) { 0308 QString newBackgroundPath = configRootDirectory.filePath(newBackgroundFileInfo.fileName()); 0309 qDebug() << "Copying background from " << newBackgroundFileInfo.absoluteFilePath() << " to " << newBackgroundPath; 0310 if (QFile::copy(newBackgroundFileInfo.absoluteFilePath(), newBackgroundPath)) { 0311 QFile::setPermissions(newBackgroundPath, QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::ReadOther); 0312 themeConfig->group(groupName).writeEntry(keyName, newBackgroundFileInfo.fileName()); 0313 } 0314 } else { 0315 themeConfig->group(groupName).deleteEntry(keyName); 0316 } 0317 } 0318 } else { 0319 themeConfig->group(groupName).writeEntry(keyName, iterator.value()); 0320 } 0321 } 0322 } 0323 0324 sddmOldConfig->sync(); 0325 sddmConfig->sync(); 0326 0327 if (!themeConfig.isNull()) { 0328 themeConfig->sync(); 0329 } 0330 0331 return ActionReply::SuccessReply(); 0332 } 0333 0334 ActionReply SddmAuthHelper::installtheme(const QVariantMap &args) 0335 { 0336 const QString filePath = args[QStringLiteral("filePath")].toString(); 0337 if (filePath.isEmpty()) { 0338 return ActionReply::HelperErrorReply(); 0339 } 0340 0341 const QString themesBaseDir = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sddm/themes"), QStandardPaths::LocateDirectory); 0342 QDir dir(themesBaseDir); 0343 if (!dir.exists()) { 0344 return ActionReply::HelperErrorReply(); 0345 } 0346 0347 qDebug() << "Installing " << filePath << " into " << themesBaseDir; 0348 0349 if (!QFile::exists(filePath)) { 0350 return ActionReply::HelperErrorReply(); 0351 } 0352 0353 QMimeDatabase db; 0354 QMimeType mimeType = db.mimeTypeForFile(filePath); 0355 qWarning() << "Postinstallation: uncompress the file"; 0356 0357 QScopedPointer<KArchive> archive; 0358 0359 // there must be a better way to do this? If not, make a static bool KZip::supportsMimeType(const QMimeType &type); ? 0360 // or even a factory class in KArchive 0361 0362 if (mimeType.inherits(QStringLiteral("application/zip"))) { 0363 archive.reset(new KZip(filePath)); 0364 } else if (mimeType.inherits(QStringLiteral("application/tar")) || mimeType.inherits(QStringLiteral("application/x-gzip")) 0365 || mimeType.inherits(QStringLiteral("application/x-bzip")) || mimeType.inherits(QStringLiteral("application/x-lzma")) 0366 || mimeType.inherits(QStringLiteral("application/x-xz")) || mimeType.inherits(QStringLiteral("application/x-bzip-compressed-tar")) 0367 || mimeType.inherits(QStringLiteral("application/x-compressed-tar"))) { 0368 archive.reset(new KTar(filePath)); 0369 } else { 0370 auto e = ActionReply::HelperErrorReply(); 0371 e.setErrorDescription(kli18n("Invalid theme package").untranslatedText()); 0372 return e; 0373 } 0374 0375 if (!archive->open(QIODevice::ReadOnly)) { 0376 auto e = ActionReply::HelperErrorReply(); 0377 e.setErrorDescription(kli18n("Could not open file").untranslatedText()); 0378 return e; 0379 } 0380 0381 auto directory = archive->directory(); 0382 0383 QStringList installedPaths; 0384 0385 // some basic validation 0386 // the top level should only have folders, and those folders should contain a valid metadata.desktop file 0387 // if we get anything else, abort everything before copying 0388 const auto entries = directory->entries(); 0389 for (const QString &name : entries) { 0390 auto entry = directory->entry(name); 0391 if (!entry->isDirectory()) { 0392 auto e = ActionReply::HelperErrorReply(); 0393 e.setErrorDescription(kli18n("Invalid theme package").untranslatedText()); 0394 return e; 0395 } 0396 auto subDirectory = static_cast<const KArchiveDirectory *>(entry); 0397 auto metadataFile = subDirectory->file(QStringLiteral("metadata.desktop")); 0398 if (!metadataFile || !metadataFile->data().contains("[SddmGreeterTheme]")) { 0399 auto e = ActionReply::HelperErrorReply(); 0400 e.setErrorDescription(kli18n("Invalid theme package").untranslatedText()); 0401 return e; 0402 } 0403 installedPaths.append(themesBaseDir + QLatin1Char('/') + name); 0404 } 0405 0406 if (!directory->copyTo(themesBaseDir)) { 0407 auto e = ActionReply::HelperErrorReply(); 0408 e.setErrorDescription(kli18n("Could not decompress archive").untranslatedText()); 0409 return e; 0410 } 0411 0412 auto rc = ActionReply::SuccessReply(); 0413 rc.addData(QStringLiteral("installedPaths"), installedPaths); 0414 return rc; 0415 } 0416 0417 ActionReply SddmAuthHelper::uninstalltheme(const QVariantMap &args) 0418 { 0419 const QString themePath = args[QStringLiteral("filePath")].toString(); 0420 const QString themesBaseDir = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sddm/themes"), QStandardPaths::LocateDirectory); 0421 0422 QDir dir(themePath); 0423 if (!dir.exists()) { 0424 return ActionReply::HelperErrorReply(); 0425 } 0426 0427 // validate the themePath is directly inside the themesBaseDir 0428 QDir baseDir(themesBaseDir); 0429 if (baseDir.absoluteFilePath(dir.dirName()) != dir.absolutePath()) { 0430 return ActionReply::HelperErrorReply(); 0431 } 0432 0433 if (!dir.removeRecursively()) { 0434 return ActionReply::HelperErrorReply(); 0435 } 0436 0437 return ActionReply::SuccessReply(); 0438 } 0439 0440 KAUTH_HELPER_MAIN("org.kde.kcontrol.kcmsddm", SddmAuthHelper) 0441 0442 #include "moc_sddmauthhelper.cpp"