dirservconfigpage.cpp 15.7 KB
Newer Older
1
2
/* -*- mode: c++; c-basic-offset:4 -*-
    conf/dirservconfigpage.cpp
3

4
    This file is part of Kleopatra, the KDE keymanager
5
    SPDX-FileCopyrightText: 2004, 2008 Klarälvdalens Datakonsult AB
6

7
    SPDX-License-Identifier: GPL-2.0-or-later
8
9
*/

Marc Mutz's avatar
Marc Mutz committed
10
11
#include <config-kleopatra.h>

12
#include "dirservconfigpage.h"
13
14

#include <Libkleo/Compat>
15
#include <Libkleo/DirectoryServicesWidget>
16
#include <Libkleo/KeyserverConfig>
17
18

#include <QGpgME/Protocol>
19

20
#include <KMessageBox>
Laurent Montel's avatar
Laurent Montel committed
21
#include <KLocalizedString>
Laurent Montel's avatar
Laurent Montel committed
22
#include "kleopatra_debug.h"
23
#include <KConfig>
24
#include <QSpinBox>
25

26
27
#include <QLabel>
#include <QCheckBox>
28
#include <QGroupBox>
29
#include <QLayout>
30
#include <QLineEdit>
Laurent Montel's avatar
Laurent Montel committed
31
#include <QTimeEdit>
32
#include <QVBoxLayout>
33

34
35
#include <gpgme++/engineinfo.h>

36
using namespace Kleo;
Ingo Klöcker's avatar
Ingo Klöcker committed
37
using namespace QGpgME;
38

Till Adam's avatar
Till Adam committed
39
#if 0 // disabled, since it is apparently confusing
40
41
42
43
// For sync'ing kabldaprc
class KABSynchronizer
{
public:
Laurent Montel's avatar
Laurent Montel committed
44
45
46
47
    KABSynchronizer()
        : mConfig("kabldaprc")
    {
        mConfig.setGroup("LDAP");
48
    }
Laurent Montel's avatar
Laurent Montel committed
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

    KUrl::List readCurrentList() const
    {

        KUrl::List lst;
        // stolen from kabc/ldapclient.cpp
        const uint numHosts = mConfig.readEntry("NumSelectedHosts");
        for (uint j = 0; j < numHosts; j++) {
            const QString num = QString::number(j);

            KUrl url;
            url.setProtocol("ldap");
            url.setPath("/");   // workaround KUrl parsing bug
            const QString host = mConfig.readEntry(QString("SelectedHost") + num).trimmed();
            url.setHost(host);

            const int port = mConfig.readEntry(QString("SelectedPort") + num);
            if (port != 0) {
                url.setPort(port);
            }

            const QString base = mConfig.readEntry(QString("SelectedBase") + num).trimmed();
            url.setQuery(base);

            const QString bindDN = mConfig.readEntry(QString("SelectedBind") + num).trimmed();
            url.setUser(bindDN);

            const QString pwdBindDN = mConfig.readEntry(QString("SelectedPwdBind") + num).trimmed();
            url.setPass(pwdBindDN);
            lst.append(url);
        }
        return lst;
    }

    void writeList(const KUrl::List &lst)
    {

        mConfig.writeEntry("NumSelectedHosts", lst.count());

        KUrl::List::const_iterator it = lst.begin();
        KUrl::List::const_iterator end = lst.end();
        unsigned j = 0;
        for (; it != end; ++it, ++j) {
            const QString num = QString::number(j);
            KUrl url = *it;

Laurent Montel's avatar
Laurent Montel committed
95
            Q_ASSERT(url.scheme() == "ldap");
Laurent Montel's avatar
Laurent Montel committed
96
97
98
99
100
101
102
103
104
105
106
            mConfig.writeEntry(QString("SelectedHost") + num, url.host());
            mConfig.writeEntry(QString("SelectedPort") + num, url.port());

            // KUrl automatically encoded the query (e.g. for spaces inside it),
            // so decode it before writing it out
            const QString base = KUrl::decode_string(url.query().mid(1));
            mConfig.writeEntry(QString("SelectedBase") + num, base);
            mConfig.writeEntry(QString("SelectedBind") + num, url.user());
            mConfig.writeEntry(QString("SelectedPwdBind") + num, url.pass());
        }
        mConfig.sync();
107
108
109
    }

private:
Laurent Montel's avatar
Laurent Montel committed
110
    KConfig mConfig;
111
112
};

Till Adam's avatar
Till Adam committed
113
114
#endif

115
116
static const char s_x509services_componentName[] = "gpgsm";
static const char s_x509services_entryName[] = "keyserver";
117

118
119
static const char s_x509services_legacy_componentName[] = "dirmngr";
static const char s_x509services_legacy_entryName[] = "LDAP Server";
120

121
static const char s_pgpservice_componentName[] = "dirmngr";
122
static const char s_pgpservice_entryName[] = "keyserver";
123

124
// legacy config entry used until GnuPG 2.2
125
126
127
static const char s_pgpservice_legacy_componentName[] = "gpg";
static const char s_pgpservice_legacy_entryName[] = "keyserver";

128
129
130
131
132
133
static const char s_timeout_componentName[] = "dirmngr";
static const char s_timeout_entryName[] = "ldaptimeout";

static const char s_maxitems_componentName[] = "dirmngr";
static const char s_maxitems_entryName[] = "max-replies";

Sergio Martins's avatar
Sergio Martins committed
134
#ifdef NOT_USEFUL_CURRENTLY
135
136
static const char s_addnewservers_componentName[] = "dirmngr";
static const char s_addnewservers_entryName[] = "add-servers";
Sergio Martins's avatar
Sergio Martins committed
137
#endif
138

Laurent Montel's avatar
Laurent Montel committed
139
140
DirectoryServicesConfigurationPage::DirectoryServicesConfigurationPage(QWidget *parent, const QVariantList &args)
    : KCModule(parent, args)
141
{
142
    mConfig = QGpgME::cryptoConfig();
Laurent Montel's avatar
Laurent Montel committed
143
    auto glay = new QGridLayout(this);
Laurent Montel's avatar
Laurent Montel committed
144
    glay->setContentsMargins(0, 0, 0, 0);
145

146
    // OpenPGP keyserver
Laurent Montel's avatar
Laurent Montel committed
147
    int row = 0;
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
    {
        auto l = new QHBoxLayout{};
        l->setContentsMargins(0, 0, 0, 0);

        l->addWidget(new QLabel{i18n("OpenPGP keyserver:"), this});
        mOpenPGPKeyserverEdit = new QLineEdit{this};
        if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.16") {
            mOpenPGPKeyserverEdit->setPlaceholderText(QStringLiteral("hkp://keys.gnupg.net"));
        } else {
            mOpenPGPKeyserverEdit->setPlaceholderText(QStringLiteral("hkps://hkps.pool.sks-keyservers.net"));
        }

        l->addWidget(mOpenPGPKeyserverEdit);

        glay->addLayout(l, row, 0, 1, 3);
        connect(mOpenPGPKeyserverEdit, &QLineEdit::textEdited, this, [this]() { Q_EMIT changed(true); });
    }

    // X.509 servers
    ++row;
168
169
170
171
172
173
174
175
176
177
178
179
    {
        auto groupBox = new QGroupBox{i18n("X.509 Directory Services"), this};
        auto groupBoxLayout = new QVBoxLayout{groupBox};

        mWidget = new Kleo::DirectoryServicesWidget(this);
        if (QLayout *l = mWidget->layout()) {
            l->setContentsMargins(0, 0, 0, 0);
        }
        groupBoxLayout->addWidget(mWidget);

        glay->addWidget(groupBox, row, 0, 1, 3);
        connect(mWidget, SIGNAL(changed()), this, SLOT(changed()));
Laurent Montel's avatar
Laurent Montel committed
180
181
182
183
    }

    // LDAP timeout
    ++row;
Laurent Montel's avatar
Laurent Montel committed
184
    auto label = new QLabel(i18n("LDAP &timeout (minutes:seconds):"), this);
Laurent Montel's avatar
Laurent Montel committed
185
    mTimeout = new QTimeEdit(this);
Laurent Montel's avatar
Laurent Montel committed
186
    mTimeout->setDisplayFormat(QStringLiteral("mm:ss"));
Laurent Montel's avatar
Laurent Montel committed
187
188
189
190
191
192
193
194
195
196
197
198
199
200
    connect(mTimeout, SIGNAL(timeChanged(QTime)), this, SLOT(changed()));
    label->setBuddy(mTimeout);
    glay->addWidget(label, row, 0);
    glay->addWidget(mTimeout, row, 1);

    // Max number of items returned by queries
    ++row;
    mMaxItemsLabel = new QLabel(i18n("&Maximum number of items returned by query:"), this);
    mMaxItems = new QSpinBox(this);
    mMaxItems->setMinimum(0);
    mMaxItemsLabel->setBuddy(mMaxItems);
    connect(mMaxItems, SIGNAL(valueChanged(int)), this, SLOT(changed()));
    glay->addWidget(mMaxItemsLabel, row, 0);
    glay->addWidget(mMaxItems, row, 1);
201
202

#ifdef NOT_USEFUL_CURRENTLY
Laurent Montel's avatar
Laurent Montel committed
203
204
205
206
    ++row
    mAddNewServersCB = new QCheckBox(i18n("Automatically add &new servers discovered in CRL distribution points"), this);
    connect(mAddNewServersCB, SIGNAL(clicked()), this, SLOT(changed()));
    glay->addWidget(mAddNewServersCB, row, 0, 1, 3);
207
208
#endif

Laurent Montel's avatar
Laurent Montel committed
209
210
    glay->setRowStretch(++row, 1);
    glay->setColumnStretch(2, 1);
211

Laurent Montel's avatar
Laurent Montel committed
212
    load();
213
214
215
216
}

void DirectoryServicesConfigurationPage::load()
{
Laurent Montel's avatar
Laurent Montel committed
217
218
    mWidget->clear();

219
    // gpgsm's keyserver option is not provided by very old gpgconf versions
220
221
222
223
224
225
226
    mX509ServicesEntry = configEntry(s_x509services_componentName, s_x509services_entryName,
                                     CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError);
    if (!mX509ServicesEntry) {
        mX509ServicesEntry = configEntry(s_x509services_legacy_componentName, s_x509services_legacy_entryName,
                                         CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoShowError);
    }
    if (mX509ServicesEntry) {
227
228
229
230
231
232
        std::vector<KeyserverConfig> servers;
        const auto urls = mX509ServicesEntry->urlValueList();
        servers.reserve(urls.size());
        std::transform(std::begin(urls), std::end(urls), std::back_inserter(servers), [](const auto &url) { return KeyserverConfig::fromUrl(url); });
        mWidget->setKeyservers(servers);
        mWidget->setReadOnly(mX509ServicesEntry->isReadOnly());
233
234
235
236
    } else {
        mWidget->setDisabled(true);
    }

237
    {
238
239
        // gpg prefers the deprecated keyserver option in gpg.conf over the keyserver option in dirmngr.conf;
        // therefore, we use the deprecated keyserver option if it is set or if the new option doesn't exist (gpg < 2.1.9)
Laurent Montel's avatar
Laurent Montel committed
240
        auto const newEntry = configEntry(s_pgpservice_componentName, s_pgpservice_entryName,
241
                                          CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError);
Laurent Montel's avatar
Laurent Montel committed
242
        auto const legacyEntry = configEntry(s_pgpservice_legacy_componentName, s_pgpservice_legacy_entryName,
243
244
245
246
247
248
249
250
251
252
253
                                             CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError);
        mOpenPGPServiceEntry = ((legacyEntry && legacyEntry->isSet()) || !newEntry) ? legacyEntry : newEntry;

        if (!mOpenPGPServiceEntry) {
            qCWarning(KLEOPATRA_LOG) << "Unknown or wrong typed config entries"
                << s_pgpservice_componentName << "/" << s_pgpservice_entryName
                << "and"
                << s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName;
        } else if (mOpenPGPServiceEntry == legacyEntry) {
            qCDebug(KLEOPATRA_LOG) << "Using config entry"
                << s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName;
254
        } else {
255
            qCDebug(KLEOPATRA_LOG) << "Using config entry"
256
                << s_pgpservice_componentName << "/" << s_pgpservice_entryName;
257
        }
258

259
260
        mOpenPGPKeyserverEdit->setText(mOpenPGPServiceEntry ? mOpenPGPServiceEntry->stringValue() : QString());
        mOpenPGPKeyserverEdit->setEnabled(mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly());
Laurent Montel's avatar
Laurent Montel committed
261
262
    }

263
264
    // read LDAP timeout
    // first try to read the config entry as int (GnuPG 2.3)
265
    mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError);
266
267
    if (!mTimeoutConfigEntry) {
        // if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2)
268
        mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError);
269
    }
Laurent Montel's avatar
Laurent Montel committed
270
    if (mTimeoutConfigEntry) {
271
272
273
274
        const int ldapTimeout = mTimeoutConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ?
                                mTimeoutConfigEntry->intValue() :
                                static_cast<int>(mTimeoutConfigEntry->uintValue());
        const QTime time = QTime(0, 0, 0, 0).addSecs(ldapTimeout);
Laurent Montel's avatar
Laurent Montel committed
275
        //qCDebug(KLEOPATRA_LOG) <<"timeout:" << mTimeoutConfigEntry->uintValue() <<"  ->" << time;
Laurent Montel's avatar
Laurent Montel committed
276
277
278
        mTimeout->setTime(time);
    }

279
280
281
282
283
284
    // read max-replies config entry
    // first try to read the config entry as int (GnuPG 2.3)
    mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError);
    if (!mMaxItemsConfigEntry) {
        // if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2)
        mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError);
285
    }
Laurent Montel's avatar
Laurent Montel committed
286
    if (mMaxItemsConfigEntry) {
287
288
289
        const int value = mMaxItemsConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ?
                          mMaxItemsConfigEntry->intValue() :
                          static_cast<int>(mMaxItemsConfigEntry->uintValue());
Laurent Montel's avatar
Laurent Montel committed
290
        mMaxItems->blockSignals(true);   // KNumInput emits valueChanged from setValue!
291
        mMaxItems->setValue(value);
Laurent Montel's avatar
Laurent Montel committed
292
293
294
295
296
        mMaxItems->blockSignals(false);
    }
    const bool maxItemsEnabled = mMaxItemsConfigEntry && !mMaxItemsConfigEntry->isReadOnly();
    mMaxItems->setEnabled(maxItemsEnabled);
    mMaxItemsLabel->setEnabled(maxItemsEnabled);
297
298

#ifdef NOT_USEFUL_CURRENTLY
Ingo Klöcker's avatar
Ingo Klöcker committed
299
    mAddNewServersConfigEntry = configEntry(s_addnewservers_componentName, s_addnewservers_groupName, s_addnewservers_entryName, CryptoConfigEntry::ArgType_None, SingleValue, DoShowError);
Laurent Montel's avatar
Laurent Montel committed
300
301
302
    if (mAddNewServersConfigEntry) {
        mAddNewServersCB->setChecked(mAddNewServersConfigEntry->boolValue());
    }
303
304
305
#endif
}

306
307
308
309
310
311
312
313
314
315
316
namespace
{
void updateIntegerConfigEntry(QGpgME::CryptoConfigEntry *configEntry, int value) {
    if (!configEntry) {
        return;
    }
    if (configEntry->argType() == CryptoConfigEntry::ArgType_Int) {
        if (configEntry->intValue() != value) {
            configEntry->setIntValue(value);
        }
    } else {
Laurent Montel's avatar
Laurent Montel committed
317
        const auto newValue = static_cast<unsigned>(value);
318
319
320
321
322
323
324
        if (configEntry->uintValue() != newValue) {
            configEntry->setUIntValue(newValue);
        }
    }
}
}

325
326
void DirectoryServicesConfigurationPage::save()
{
Laurent Montel's avatar
Laurent Montel committed
327
    if (mX509ServicesEntry) {
328
329
330
331
332
        QList<QUrl> urls;
        const auto servers = mWidget->keyservers();
        urls.reserve(servers.size());
        std::transform(std::begin(servers), std::end(servers), std::back_inserter(urls), [](const auto &server) { return server.toUrl(); });
        mX509ServicesEntry->setURLValueList(urls);
Laurent Montel's avatar
Laurent Montel committed
333
334
335
    }

    if (mOpenPGPServiceEntry) {
336
337
338
        const auto keyserver = mOpenPGPKeyserverEdit->text();
        const auto keyserverUrl = keyserver.contains(QLatin1String{"://"}) ? keyserver : (QLatin1String{"hkps://"} + keyserver);
        mOpenPGPServiceEntry->setStringValue(keyserverUrl);
Laurent Montel's avatar
Laurent Montel committed
339
340
    }

341
342
343
344
345
    const QTime time{mTimeout->time()};
    updateIntegerConfigEntry(mTimeoutConfigEntry, time.minute() * 60 + time.second());

    updateIntegerConfigEntry(mMaxItemsConfigEntry, mMaxItems->value());

346
#ifdef NOT_USEFUL_CURRENTLY
Laurent Montel's avatar
Laurent Montel committed
347
348
349
    if (mAddNewServersConfigEntry && mAddNewServersConfigEntry->boolValue() != mAddNewServersCB->isChecked()) {
        mAddNewServersConfigEntry->setBoolValue(mAddNewServersCB->isChecked());
    }
350
351
#endif

Laurent Montel's avatar
Laurent Montel committed
352
    mConfig->sync(true);
353

Till Adam's avatar
Till Adam committed
354
#if 0
Laurent Montel's avatar
Laurent Montel committed
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
    // Also write the LDAP URLs to kabldaprc so that they are used by kaddressbook
    KABSynchronizer sync;
    const KUrl::List toAdd = mWidget->urlList();
    KUrl::List currentList = sync.readCurrentList();

    KUrl::List::const_iterator it = toAdd.begin();
    KUrl::List::const_iterator end = toAdd.end();
    for (; it != end; ++it) {
        // check if the URL is already in currentList
        if (currentList.find(*it) == currentList.end())
            // if not, add it
        {
            currentList.append(*it);
        }
    }
    sync.writeList(currentList);
Till Adam's avatar
Till Adam committed
371
#endif
372
373
374
375
}

void DirectoryServicesConfigurationPage::defaults()
{
Laurent Montel's avatar
Laurent Montel committed
376
377
    // these guys don't have a default, to clear them:
    if (mX509ServicesEntry) {
378
        mX509ServicesEntry->setURLValueList(QList<QUrl>());
Laurent Montel's avatar
Laurent Montel committed
379
380
381
382
383
384
385
386
387
388
389
    }
    if (mOpenPGPServiceEntry) {
        mOpenPGPServiceEntry->setStringValue(QString());
    }
    // these presumably have a default, use that one:
    if (mTimeoutConfigEntry) {
        mTimeoutConfigEntry->resetToDefault();
    }
    if (mMaxItemsConfigEntry) {
        mMaxItemsConfigEntry->resetToDefault();
    }
390
#ifdef NOT_USEFUL_CURRENTLY
Laurent Montel's avatar
Laurent Montel committed
391
392
393
    if (mAddNewServersConfigEntry) {
        mAddNewServersConfigEntry->resetToDefault();
    }
394
#endif
Laurent Montel's avatar
Laurent Montel committed
395
    load();
396
397
398
}

// Find config entry for ldap servers. Implements runtime checks on the configuration option.
Ingo Klöcker's avatar
Ingo Klöcker committed
399
CryptoConfigEntry *DirectoryServicesConfigurationPage::configEntry(const char *componentName,
Laurent Montel's avatar
Laurent Montel committed
400
        const char *entryName,
Ingo Klöcker's avatar
Ingo Klöcker committed
401
        CryptoConfigEntry::ArgType argType,
402
403
        EntryMultiplicity multiplicity,
        ShowError showError)
404
{
405
    CryptoConfigEntry *const entry = Kleo::getCryptoConfigEntry(mConfig, componentName, entryName);
Laurent Montel's avatar
Laurent Montel committed
406
    if (!entry) {
407
        if (showError == DoShowError) {
408
            KMessageBox::error(this, i18n("Backend error: gpgconf does not seem to know the entry for %1/%2", QLatin1String(componentName), QLatin1String(entryName)));
Laurent Montel's avatar
Laurent Montel committed
409
        }
Laurent Montel's avatar
Laurent Montel committed
410
        return nullptr;
411
    }
412
413
    if (entry->argType() != argType || entry->isList() != bool(multiplicity)) {
        if (showError == DoShowError) {
414
            KMessageBox::error(this, i18n("Backend error: gpgconf has wrong type for %1/%2: %3 %4", QLatin1String(componentName), QLatin1String(entryName), entry->argType(), entry->isList()));
Laurent Montel's avatar
Laurent Montel committed
415
        }
Laurent Montel's avatar
Laurent Montel committed
416
        return nullptr;
417
418
419
    }
    return entry;
}