File indexing completed on 2024-06-23 05:13:47
0001 /* -*- mode: c++; c-basic-offset:4 -*- 0002 conf/dirservconfigpage.cpp 0003 0004 This file is part of Kleopatra, the KDE keymanager 0005 SPDX-FileCopyrightText: 2004, 2008 Klarälvdalens Datakonsult AB 0006 0007 SPDX-FileCopyrightText: 2022 g10 Code GmbH 0008 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> 0009 0010 SPDX-License-Identifier: GPL-2.0-or-later 0011 */ 0012 0013 #include <config-kleopatra.h> 0014 0015 #include "dirservconfigpage.h" 0016 0017 #include "labelledwidget.h" 0018 0019 #include <settings.h> 0020 0021 #include <Libkleo/Compat> 0022 #include <Libkleo/DirectoryServicesWidget> 0023 #include <Libkleo/GnuPG> 0024 #include <Libkleo/KeyserverConfig> 0025 0026 #include <QGpgME/CryptoConfig> 0027 #include <QGpgME/Protocol> 0028 0029 #include "kleopatra_debug.h" 0030 #include <KConfig> 0031 #include <KLocalizedString> 0032 #include <KMessageBox> 0033 #include <QSpinBox> 0034 0035 #include <QCheckBox> 0036 #include <QGroupBox> 0037 #include <QLabel> 0038 #include <QLayout> 0039 #include <QLineEdit> 0040 #include <QTimeEdit> 0041 #include <QVBoxLayout> 0042 0043 #include <gpgme++/engineinfo.h> 0044 #include <gpgme.h> 0045 0046 using namespace Kleo; 0047 using namespace QGpgME; 0048 0049 // Option for configuring X.509 servers (available via gpgconf since GnuPG 2.3.5 and 2.2.34) 0050 static const char s_x509services_componentName[] = "dirmngr"; 0051 static const char s_x509services_entryName[] = "ldapserver"; 0052 0053 // Legacy option for configuring X.509 servers (deprecated with GnuPG 2.2.28 and 2.3.2) 0054 static const char s_x509services_legacy_componentName[] = "gpgsm"; 0055 static const char s_x509services_legacy_entryName[] = "keyserver"; 0056 0057 static const char s_pgpservice_componentName[] = "dirmngr"; 0058 static const char s_pgpservice_entryName[] = "keyserver"; 0059 0060 // legacy config entry used until GnuPG 2.2 0061 static const char s_pgpservice_legacy_componentName[] = "gpg"; 0062 static const char s_pgpservice_legacy_entryName[] = "keyserver"; 0063 0064 static const char s_timeout_componentName[] = "dirmngr"; 0065 static const char s_timeout_entryName[] = "ldaptimeout"; 0066 0067 static const char s_maxitems_componentName[] = "dirmngr"; 0068 static const char s_maxitems_entryName[] = "max-replies"; 0069 0070 class DirectoryServicesConfigurationPage::Private 0071 { 0072 DirectoryServicesConfigurationPage *q = nullptr; 0073 0074 public: 0075 Private(DirectoryServicesConfigurationPage *q); 0076 0077 void load(); 0078 void save(); 0079 void defaults(); 0080 0081 private: 0082 enum EntryMultiplicity { 0083 SingleValue, 0084 ListValue, 0085 }; 0086 enum ShowError { 0087 DoNotShowError, 0088 DoShowError, 0089 }; 0090 0091 void setX509ServerEntry(const std::vector<KeyserverConfig> &servers); 0092 void load(const Kleo::Settings &settings); 0093 0094 QGpgME::CryptoConfigEntry *configEntry(const char *componentName, 0095 const char *entryName, 0096 QGpgME::CryptoConfigEntry::ArgType argType, 0097 EntryMultiplicity multiplicity, 0098 ShowError showError); 0099 0100 Kleo::LabelledWidget<QLineEdit> mOpenPGPKeyserverEdit; 0101 Kleo::DirectoryServicesWidget *mDirectoryServices = nullptr; 0102 Kleo::LabelledWidget<QTimeEdit> mTimeout; 0103 Kleo::LabelledWidget<QSpinBox> mMaxItems; 0104 QCheckBox *mFetchMissingSignerKeysCB = nullptr; 0105 QCheckBox *mQueryWKDsForAllUserIDsCB = nullptr; 0106 0107 QGpgME::CryptoConfigEntry *mOpenPGPServiceEntry = nullptr; 0108 QGpgME::CryptoConfigEntry *mTimeoutConfigEntry = nullptr; 0109 QGpgME::CryptoConfigEntry *mMaxItemsConfigEntry = nullptr; 0110 0111 QGpgME::CryptoConfig *mConfig = nullptr; 0112 }; 0113 0114 DirectoryServicesConfigurationPage::Private::Private(DirectoryServicesConfigurationPage *q) 0115 { 0116 mConfig = QGpgME::cryptoConfig(); 0117 auto glay = new QGridLayout(q->widget()); 0118 0119 // OpenPGP keyserver 0120 int row = 0; 0121 { 0122 auto l = new QHBoxLayout{}; 0123 l->setContentsMargins(0, 0, 0, 0); 0124 0125 mOpenPGPKeyserverEdit.createWidgets(q->widget()); 0126 mOpenPGPKeyserverEdit.label()->setText(i18n("OpenPGP keyserver:")); 0127 if (engineIsVersion(2, 4, 4) // 0128 || (engineIsVersion(2, 2, 42) && !engineIsVersion(2, 3, 0))) { 0129 mOpenPGPKeyserverEdit.widget()->setToolTip( // 0130 xi18nc("@info:tooltip", 0131 "Enter the address of the keyserver to use when searching for OpenPGP certificates and " 0132 "when uploading OpenPGP certificates. If you do not enter an address then an internal " 0133 "default will be used. To disable the use of an OpenPGP keyserver enter the special value <emphasis>none</emphasis>.")); 0134 } 0135 l->addWidget(mOpenPGPKeyserverEdit.label()); 0136 l->addWidget(mOpenPGPKeyserverEdit.widget()); 0137 0138 glay->addLayout(l, row, 0, 1, 3); 0139 connect(mOpenPGPKeyserverEdit.widget(), &QLineEdit::textEdited, q, &DirectoryServicesConfigurationPage::markAsChanged); 0140 } 0141 0142 // X.509 servers 0143 if (Settings{}.cmsEnabled()) { 0144 ++row; 0145 auto groupBox = new QGroupBox{i18n("X.509 Directory Services"), q->widget()}; 0146 groupBox->setFlat(true); 0147 auto groupBoxLayout = new QVBoxLayout{groupBox}; 0148 groupBoxLayout->setContentsMargins({}); 0149 0150 if (gpgme_check_version("1.16.0")) { 0151 mDirectoryServices = new Kleo::DirectoryServicesWidget(q->widget()); 0152 if (QLayout *l = mDirectoryServices->layout()) { 0153 l->setContentsMargins(0, 0, 0, 0); 0154 } 0155 groupBoxLayout->addWidget(mDirectoryServices); 0156 connect(mDirectoryServices, &DirectoryServicesWidget::changed, q, &DirectoryServicesConfigurationPage::markAsChanged); 0157 } else { 0158 // QGpgME does not properly support keyserver flags for X.509 keyservers (added in GnuPG 2.2.28); 0159 // disable the configuration to prevent the configuration from being corrupted 0160 groupBoxLayout->addWidget(new QLabel{i18n("Configuration of directory services is not possible " 0161 "because the used gpgme libraries are too old."), 0162 q->widget()}); 0163 } 0164 0165 glay->addWidget(groupBox, row, 0, 1, 3); 0166 } 0167 0168 // LDAP timeout 0169 ++row; 0170 mTimeout.createWidgets(q->widget()); 0171 mTimeout.label()->setText(i18n("LDAP &timeout (minutes:seconds):")); 0172 mTimeout.widget()->setDisplayFormat(QStringLiteral("mm:ss")); 0173 connect(mTimeout.widget(), &QTimeEdit::timeChanged, q, &DirectoryServicesConfigurationPage::markAsChanged); 0174 glay->addWidget(mTimeout.label(), row, 0); 0175 glay->addWidget(mTimeout.widget(), row, 1); 0176 0177 // Max number of items returned by queries 0178 ++row; 0179 mMaxItems.createWidgets(q->widget()); 0180 mMaxItems.label()->setText(i18n("&Maximum number of items returned by query:")); 0181 mMaxItems.widget()->setMinimum(0); 0182 connect(mMaxItems.widget(), &QSpinBox::valueChanged, q, &DirectoryServicesConfigurationPage::markAsChanged); 0183 glay->addWidget(mMaxItems.label(), row, 0); 0184 glay->addWidget(mMaxItems.widget(), row, 1); 0185 0186 ++row; 0187 mFetchMissingSignerKeysCB = new QCheckBox{q->widget()}; 0188 mFetchMissingSignerKeysCB->setText(i18nc("@option:check", "Retrieve missing certification keys when importing new keys")); 0189 mFetchMissingSignerKeysCB->setToolTip(xi18nc("@info:tooltip", 0190 "If enabled, then Kleopatra will automatically try to retrieve the keys " 0191 "that were used to certify the user IDs of newly imported OpenPGP keys.")); 0192 connect(mFetchMissingSignerKeysCB, &QCheckBox::toggled, q, &DirectoryServicesConfigurationPage::markAsChanged); 0193 glay->addWidget(mFetchMissingSignerKeysCB, row, 0, 1, 3); 0194 0195 ++row; 0196 mQueryWKDsForAllUserIDsCB = new QCheckBox{q->widget()}; 0197 mQueryWKDsForAllUserIDsCB->setText(i18nc("@option:check", "Query certificate directories of providers for all user IDs")); 0198 mQueryWKDsForAllUserIDsCB->setToolTip(xi18nc("@info:tooltip", 0199 "By default, Kleopatra only queries the certificate directories of providers (WKD) " 0200 "for user IDs that were originally retrieved from a WKD when you update an OpenPGP " 0201 "certificate. If this option is enabled, then Kleopatra will query WKDs for all user IDs.")); 0202 connect(mQueryWKDsForAllUserIDsCB, &QCheckBox::toggled, q, &DirectoryServicesConfigurationPage::markAsChanged); 0203 glay->addWidget(mQueryWKDsForAllUserIDsCB, row, 0, 1, 3); 0204 0205 glay->setRowStretch(++row, 1); 0206 glay->setColumnStretch(2, 1); 0207 } 0208 0209 static auto readKeyserverConfigs(const CryptoConfigEntry *configEntry) 0210 { 0211 std::vector<KeyserverConfig> servers; 0212 if (configEntry) { 0213 const auto urls = configEntry->urlValueList(); 0214 servers.reserve(urls.size()); 0215 std::transform(std::begin(urls), std::end(urls), std::back_inserter(servers), &KeyserverConfig::fromUrl); 0216 } 0217 return servers; 0218 } 0219 0220 void DirectoryServicesConfigurationPage::Private::load(const Kleo::Settings &settings) 0221 { 0222 if (mDirectoryServices) { 0223 mDirectoryServices->clear(); 0224 0225 // gpgsm uses the deprecated keyserver option in gpgsm.conf additionally to the ldapserver option in dirmngr.conf; 0226 // we (try to) read servers from both entries, but always write to the newest existing entry 0227 const auto *const newEntry = 0228 configEntry(s_x509services_componentName, s_x509services_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); 0229 const auto *const legacyEntry = 0230 configEntry(s_x509services_legacy_componentName, s_x509services_legacy_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); 0231 auto entry = newEntry ? newEntry : legacyEntry; 0232 if (entry) { 0233 const auto additionalServers = readKeyserverConfigs(legacyEntry); 0234 auto servers = readKeyserverConfigs(newEntry); 0235 std::copy(std::begin(additionalServers), std::end(additionalServers), std::back_inserter(servers)); 0236 mDirectoryServices->setKeyservers(servers); 0237 mDirectoryServices->setReadOnly(entry->isReadOnly()); 0238 } else { 0239 qCWarning(KLEOPATRA_LOG) << "Unknown or wrong typed config entries" << s_x509services_componentName << "/" << s_x509services_entryName << "and" 0240 << s_x509services_legacy_componentName << "/" << s_x509services_legacy_entryName; 0241 0242 mDirectoryServices->setDisabled(true); 0243 } 0244 } 0245 0246 { 0247 // gpg prefers the deprecated keyserver option in gpg.conf over the keyserver option in dirmngr.conf; 0248 // therefore, we use the deprecated keyserver option if it is set or if the new option doesn't exist (gpg < 2.1.9) 0249 auto const newEntry = configEntry(s_pgpservice_componentName, s_pgpservice_entryName, CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError); 0250 auto const legacyEntry = 0251 configEntry(s_pgpservice_legacy_componentName, s_pgpservice_legacy_entryName, CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError); 0252 mOpenPGPServiceEntry = ((legacyEntry && legacyEntry->isSet()) || !newEntry) ? legacyEntry : newEntry; 0253 0254 if (!mOpenPGPServiceEntry) { 0255 qCWarning(KLEOPATRA_LOG) << "Unknown or wrong typed config entries" << s_pgpservice_componentName << "/" << s_pgpservice_entryName << "and" 0256 << s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName; 0257 } else if (mOpenPGPServiceEntry == legacyEntry) { 0258 qCDebug(KLEOPATRA_LOG) << "Using config entry" << s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName; 0259 } else { 0260 qCDebug(KLEOPATRA_LOG) << "Using config entry" << s_pgpservice_componentName << "/" << s_pgpservice_entryName; 0261 } 0262 0263 mOpenPGPKeyserverEdit.widget()->setText(mOpenPGPServiceEntry && mOpenPGPServiceEntry->isSet() ? mOpenPGPServiceEntry->stringValue() : QString()); 0264 mOpenPGPKeyserverEdit.setEnabled(mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly()); 0265 if (newEntry && !newEntry->defaultValue().isNull()) { 0266 mOpenPGPKeyserverEdit.widget()->setPlaceholderText(newEntry->defaultValue().toString()); 0267 } else { 0268 if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.16") { 0269 mOpenPGPKeyserverEdit.widget()->setPlaceholderText(QStringLiteral("hkp://keys.gnupg.net")); 0270 } else { 0271 mOpenPGPKeyserverEdit.widget()->setPlaceholderText(QStringLiteral("hkps://hkps.pool.sks-keyservers.net")); 0272 } 0273 } 0274 } 0275 0276 // read LDAP timeout 0277 // first try to read the config entry as int (GnuPG 2.3) 0278 mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError); 0279 if (!mTimeoutConfigEntry) { 0280 // if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2) 0281 mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError); 0282 } 0283 if (mTimeoutConfigEntry) { 0284 const int ldapTimeout = mTimeoutConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ? mTimeoutConfigEntry->intValue() 0285 : static_cast<int>(mTimeoutConfigEntry->uintValue()); 0286 const QTime time = QTime(0, 0, 0, 0).addSecs(ldapTimeout); 0287 // qCDebug(KLEOPATRA_LOG) <<"timeout:" << mTimeoutConfigEntry->uintValue() <<" ->" << time; 0288 mTimeout.widget()->setTime(time); 0289 } 0290 mTimeout.setEnabled(mTimeoutConfigEntry && !mTimeoutConfigEntry->isReadOnly()); 0291 0292 // read max-replies config entry 0293 // first try to read the config entry as int (GnuPG 2.3) 0294 mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError); 0295 if (!mMaxItemsConfigEntry) { 0296 // if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2) 0297 mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError); 0298 } 0299 if (mMaxItemsConfigEntry) { 0300 const int value = mMaxItemsConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ? mMaxItemsConfigEntry->intValue() 0301 : static_cast<int>(mMaxItemsConfigEntry->uintValue()); 0302 mMaxItems.widget()->blockSignals(true); // KNumInput emits valueChanged from setValue! 0303 mMaxItems.widget()->setValue(value); 0304 mMaxItems.widget()->blockSignals(false); 0305 } 0306 mMaxItems.setEnabled(mMaxItemsConfigEntry && !mMaxItemsConfigEntry->isReadOnly()); 0307 0308 mFetchMissingSignerKeysCB->setChecked(settings.retrieveSignerKeysAfterImport()); 0309 mFetchMissingSignerKeysCB->setEnabled(!settings.isImmutable(QStringLiteral("RetrieveSignerKeysAfterImport"))); 0310 mQueryWKDsForAllUserIDsCB->setChecked(settings.queryWKDsForAllUserIDs()); 0311 mQueryWKDsForAllUserIDsCB->setEnabled(!settings.isImmutable(QStringLiteral("QueryWKDsForAllUserIDs"))); 0312 } 0313 0314 void DirectoryServicesConfigurationPage::Private::load() 0315 { 0316 load(Settings{}); 0317 } 0318 0319 namespace 0320 { 0321 void updateIntegerConfigEntry(QGpgME::CryptoConfigEntry *configEntry, int value) 0322 { 0323 if (!configEntry) { 0324 return; 0325 } 0326 if (configEntry->argType() == CryptoConfigEntry::ArgType_Int) { 0327 if (configEntry->intValue() != value) { 0328 configEntry->setIntValue(value); 0329 } 0330 } else { 0331 const auto newValue = static_cast<unsigned>(value); 0332 if (configEntry->uintValue() != newValue) { 0333 configEntry->setUIntValue(newValue); 0334 } 0335 } 0336 } 0337 } 0338 0339 void DirectoryServicesConfigurationPage::Private::setX509ServerEntry(const std::vector<KeyserverConfig> &servers) 0340 { 0341 const auto newEntry = configEntry(s_x509services_componentName, s_x509services_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); 0342 const auto legacyEntry = 0343 configEntry(s_x509services_legacy_componentName, s_x509services_legacy_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); 0344 0345 if ((newEntry && newEntry->isReadOnly()) || (legacyEntry && legacyEntry->isReadOnly())) { 0346 // do not change the config entries if either config entry is read-only 0347 return; 0348 } 0349 QList<QUrl> urls; 0350 urls.reserve(servers.size()); 0351 std::transform(std::begin(servers), std::end(servers), std::back_inserter(urls), std::mem_fn(&KeyserverConfig::toUrl)); 0352 if (newEntry) { 0353 // write all servers to the new config entry 0354 newEntry->setURLValueList(urls); 0355 // and clear the legacy config entry 0356 if (legacyEntry) { 0357 legacyEntry->setURLValueList({}); 0358 } 0359 } else if (legacyEntry) { 0360 // write all servers to the legacy config entry if the new entry is not available 0361 legacyEntry->setURLValueList(urls); 0362 } else { 0363 qCWarning(KLEOPATRA_LOG) << "Could not store the X.509 servers. Unknown or wrong typed config entries" << s_x509services_componentName << "/" 0364 << s_x509services_entryName << "and" << s_x509services_legacy_componentName << "/" << s_x509services_legacy_entryName; 0365 } 0366 } 0367 0368 void DirectoryServicesConfigurationPage::Private::save() 0369 { 0370 if (mDirectoryServices && mDirectoryServices->isEnabled()) { 0371 setX509ServerEntry(mDirectoryServices->keyservers()); 0372 } 0373 0374 if (mOpenPGPServiceEntry) { 0375 const auto keyserver = mOpenPGPKeyserverEdit.widget()->text().trimmed(); 0376 if (keyserver.isEmpty()) { 0377 mOpenPGPServiceEntry->resetToDefault(); 0378 } else if (keyserver == QLatin1StringView{"none"}) { 0379 mOpenPGPServiceEntry->setStringValue(keyserver); 0380 } else { 0381 const auto keyserverUrl = keyserver.contains(QLatin1StringView{"://"}) ? keyserver : (QLatin1String{"hkps://"} + keyserver); 0382 mOpenPGPServiceEntry->setStringValue(keyserverUrl); 0383 } 0384 } 0385 0386 const QTime time{mTimeout.widget()->time()}; 0387 updateIntegerConfigEntry(mTimeoutConfigEntry, time.minute() * 60 + time.second()); 0388 0389 updateIntegerConfigEntry(mMaxItemsConfigEntry, mMaxItems.widget()->value()); 0390 0391 mConfig->sync(true); 0392 0393 Settings settings; 0394 settings.setRetrieveSignerKeysAfterImport(mFetchMissingSignerKeysCB->isChecked()); 0395 settings.setQueryWKDsForAllUserIDs(mQueryWKDsForAllUserIDsCB->isChecked()); 0396 settings.save(); 0397 } 0398 0399 void DirectoryServicesConfigurationPage::Private::defaults() 0400 { 0401 // these guys don't have a default, to clear them: 0402 if (mDirectoryServices && mDirectoryServices->isEnabled()) { 0403 setX509ServerEntry({}); 0404 } 0405 if (mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly()) { 0406 mOpenPGPServiceEntry->setStringValue(QString()); 0407 } 0408 // these presumably have a default, use that one: 0409 if (mTimeoutConfigEntry && !mTimeoutConfigEntry->isReadOnly()) { 0410 mTimeoutConfigEntry->resetToDefault(); 0411 } 0412 if (mMaxItemsConfigEntry && !mMaxItemsConfigEntry->isReadOnly()) { 0413 mMaxItemsConfigEntry->resetToDefault(); 0414 } 0415 0416 Settings settings; 0417 settings.setRetrieveSignerKeysAfterImport(settings.findItem(QStringLiteral("RetrieveSignerKeysAfterImport"))->getDefault().toBool()); 0418 settings.setQueryWKDsForAllUserIDs(settings.findItem(QStringLiteral("QueryWKDsForAllUserIDs"))->getDefault().toBool()); 0419 0420 load(settings); 0421 } 0422 0423 // Find config entry for ldap servers. Implements runtime checks on the configuration option. 0424 CryptoConfigEntry *DirectoryServicesConfigurationPage::Private::configEntry(const char *componentName, 0425 const char *entryName, 0426 CryptoConfigEntry::ArgType argType, 0427 EntryMultiplicity multiplicity, 0428 ShowError showError) 0429 { 0430 CryptoConfigEntry *const entry = Kleo::getCryptoConfigEntry(mConfig, componentName, entryName); 0431 if (!entry) { 0432 if (showError == DoShowError) { 0433 KMessageBox::error( 0434 q->widget(), 0435 i18n("Backend error: gpgconf does not seem to know the entry for %1/%2", QLatin1StringView(componentName), QLatin1String(entryName))); 0436 } 0437 return nullptr; 0438 } 0439 if (entry->argType() != argType || entry->isList() != bool(multiplicity)) { 0440 if (showError == DoShowError) { 0441 KMessageBox::error(q->widget(), 0442 i18n("Backend error: gpgconf has wrong type for %1/%2: %3 %4", 0443 QLatin1StringView(componentName), 0444 QLatin1StringView(entryName), 0445 entry->argType(), 0446 entry->isList())); 0447 } 0448 return nullptr; 0449 } 0450 return entry; 0451 } 0452 0453 DirectoryServicesConfigurationPage::DirectoryServicesConfigurationPage(QObject *parent, const KPluginMetaData &data) 0454 : KCModule(parent, data) 0455 , d{new Private{this}} 0456 { 0457 } 0458 0459 DirectoryServicesConfigurationPage::~DirectoryServicesConfigurationPage() = default; 0460 0461 void DirectoryServicesConfigurationPage::load() 0462 { 0463 d->load(); 0464 } 0465 0466 void DirectoryServicesConfigurationPage::save() 0467 { 0468 d->save(); 0469 } 0470 0471 void DirectoryServicesConfigurationPage::defaults() 0472 { 0473 d->defaults(); 0474 } 0475 0476 #include "moc_dirservconfigpage.cpp"