File indexing completed on 2024-03-24 05:39:28
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"