openconnectauth.cpp 28.9 KB
Newer Older
Lukáš Tinkl's avatar
Lukáš Tinkl committed
1
/*
2
3
    SPDX-FileCopyrightText: 2011 Ilia Kats <ilia-kats@gmx.net>
    SPDX-FileCopyrightText: 2013 Lukáš Tinkl <ltinkl@redhat.com>
Jan Grulich's avatar
Jan Grulich committed
4

5
    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
Lukáš Tinkl's avatar
Lukáš Tinkl committed
6
7
8
9
10
11
*/

#include "openconnectauth.h"
#include "openconnectauthworkerthread.h"
#include "ui_openconnectauth.h"

12
#include "debug.h"
13
#include "passwordfield.h"
14

Alexander Lohnau's avatar
Alexander Lohnau committed
15
16
#include <QComboBox>
#include <QCryptographicHash>
17
#include <QDialog>
Alexander Lohnau's avatar
Alexander Lohnau committed
18
19
#include <QDialogButtonBox>
#include <QDomDocument>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
20
#include <QEventLoop>
Alexander Lohnau's avatar
Alexander Lohnau committed
21
#include <QFile>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
22
#include <QFormLayout>
23
#include <QIcon>
Alexander Lohnau's avatar
Alexander Lohnau committed
24
#include <QLabel>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
25
#include <QMutex>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
26
#include <QPointer>
Alexander Lohnau's avatar
Alexander Lohnau committed
27
28
29
#include <QPushButton>
#include <QTimer>
#include <QWaitCondition>
30
31

#include <KLocalizedString>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
32
33
34
35
36

#include "nm-openconnect-service.h"

#include <cstdarg>

Alexander Lohnau's avatar
Alexander Lohnau committed
37
38
extern "C" {
#include <fcntl.h>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
39
40
41
42
#include <string.h>
#include <unistd.h>
}

Alexander Lohnau's avatar
Alexander Lohnau committed
43
#if !OPENCONNECT_CHECK_VER(2, 1)
44
#define __openconnect_set_token_mode(...) -EOPNOTSUPP
Alexander Lohnau's avatar
Alexander Lohnau committed
45
#elif !OPENCONNECT_CHECK_VER(2, 2)
46
47
48
49
50
#define __openconnect_set_token_mode(vpninfo, mode, secret) openconnect_set_stoken_mode(vpninfo, 1, secret)
#else
#define __openconnect_set_token_mode openconnect_set_token_mode
#endif

Alexander Lohnau's avatar
Alexander Lohnau committed
51
52
#if OPENCONNECT_CHECK_VER(3, 4)
static int updateToken(void *, const char *);
53
54
#endif

Lukáš Tinkl's avatar
Lukáš Tinkl committed
55
56
57
58
59
60
61
62
// name/address: IP/domain name of the host (OpenConnect accepts both, so no difference here)
// group: user group on the server
typedef struct {
    QString name;
    QString group;
    QString address;
} VPNHost;

63
64
65
66
67
typedef struct {
    oc_token_mode_t tokenMode;
    QByteArray tokenSecret;
} Token;

Lukáš Tinkl's avatar
Lukáš Tinkl committed
68
69
70
71
72
73
74
class OpenconnectAuthWidgetPrivate
{
public:
    Ui_OpenconnectAuth ui;
    NetworkManager::VpnSetting::Ptr setting;
    struct openconnect_info *vpninfo;
    NMStringMap secrets;
75
    NMStringMap tmpSecrets;
Lukáš Tinkl's avatar
Lukáš Tinkl committed
76
77
78
79
80
    QMutex mutex;
    QWaitCondition workerWaiting;
    OpenconnectAuthWorkerThread *worker;
    QList<VPNHost> hosts;
    bool userQuit;
81
    bool formGroupChanged;
Lukáš Tinkl's avatar
Lukáš Tinkl committed
82
    int cancelPipes[2];
Alexander Lohnau's avatar
Alexander Lohnau committed
83
    QList<QPair<QString, int>> serverLog;
Jan Grulich's avatar
Jan Grulich committed
84
    int passwordFormIndex;
85
86
    QByteArray tokenMode;
    Token token;
Lukáš Tinkl's avatar
Lukáš Tinkl committed
87

Alexander Lohnau's avatar
Alexander Lohnau committed
88
    enum LogLevels { Error = 0, Info, Debug, Trace };
Lukáš Tinkl's avatar
Lukáš Tinkl committed
89
90
};

Alexander Lohnau's avatar
Alexander Lohnau committed
91
OpenconnectAuthWidget::OpenconnectAuthWidget(const NetworkManager::VpnSetting::Ptr &setting, const QStringList &hints, QWidget *parent)
Jan Grulich's avatar
Jan Grulich committed
92
    : SettingWidget(setting, hints, parent)
Jan Grulich's avatar
Jan Grulich committed
93
    , d_ptr(new OpenconnectAuthWidgetPrivate)
Lukáš Tinkl's avatar
Lukáš Tinkl committed
94
95
96
97
98
{
    Q_D(OpenconnectAuthWidget);
    d->setting = setting;
    d->ui.setupUi(this);
    d->userQuit = false;
99
100
    d->formGroupChanged = false;

Alexander Lohnau's avatar
Alexander Lohnau committed
101
    if (pipe2(d->cancelPipes, O_NONBLOCK | O_CLOEXEC)) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
102
103
104
105
106
        // Should never happen. Just don't do real cancellation if it does
        d->cancelPipes[0] = -1;
        d->cancelPipes[1] = -1;
    }

107
    connect(d->ui.cmbLogLevel, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &OpenconnectAuthWidget::logLevelChanged);
108
109
    connect(d->ui.viewServerLog, &QCheckBox::toggled, this, &OpenconnectAuthWidget::viewServerLogToggled);
    connect(d->ui.btnConnect, &QPushButton::clicked, this, &OpenconnectAuthWidget::connectHost);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
110
111

    d->ui.cmbLogLevel->setCurrentIndex(OpenconnectAuthWidgetPrivate::Debug);
112
    d->ui.btnConnect->setIcon(QIcon::fromTheme("network-connect"));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
113
114
    d->ui.viewServerLog->setChecked(false);

115
    d->worker = new OpenconnectAuthWorkerThread(&d->mutex, &d->workerWaiting, &d->userQuit, &d->formGroupChanged, d->cancelPipes[0]);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
116
117
118
119
120

    // gets the pointer to struct openconnect_info (defined in openconnect.h), which contains data that OpenConnect needs,
    // and which needs to be populated with settings we get from NM, like host, certificate or private key
    d->vpninfo = d->worker->getOpenconnectInfo();

Alexander Lohnau's avatar
Alexander Lohnau committed
121
122
123
124
    connect(d->worker,
            QOverload<const QString &, const QString &, const QString &, bool *>::of(&OpenconnectAuthWorkerThread::validatePeerCert),
            this,
            &OpenconnectAuthWidget::validatePeerCert);
125
126
    connect(d->worker, &OpenconnectAuthWorkerThread::processAuthForm, this, &OpenconnectAuthWidget::processAuthForm);
    connect(d->worker, &OpenconnectAuthWorkerThread::updateLog, this, &OpenconnectAuthWidget::updateLog);
Alexander Lohnau's avatar
Alexander Lohnau committed
127
    connect(d->worker, QOverload<const QString &>::of(&OpenconnectAuthWorkerThread::writeNewConfig), this, &OpenconnectAuthWidget::writeNewConfig);
128
    connect(d->worker, &OpenconnectAuthWorkerThread::cookieObtained, this, &OpenconnectAuthWidget::workerFinished);
129
    connect(d->worker, &OpenconnectAuthWorkerThread::initTokens, this, &OpenconnectAuthWidget::initTokens);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
130
131
132

    readConfig();
    readSecrets();
133

Alexander Lohnau's avatar
Alexander Lohnau committed
134
#if OPENCONNECT_CHECK_VER(3, 4)
135
136
137
    openconnect_set_token_callbacks(d->vpninfo, &d->secrets, NULL, &updateToken);
#endif

138
    // This might be set by readSecrets() so don't connect it until now
139
    connect(d->ui.cmbHosts, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &OpenconnectAuthWidget::connectHost);
140

141
    KAcceleratorManager::manage(this);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
}

OpenconnectAuthWidget::~OpenconnectAuthWidget()
{
    Q_D(OpenconnectAuthWidget);
    d->userQuit = true;
    if (write(d->cancelPipes[1], "x", 1)) {
        // not a lot we can do
    }
    d->workerWaiting.wakeAll();
    d->worker->wait();
    ::close(d->cancelPipes[0]);
    ::close(d->cancelPipes[1]);
    deleteAllFromLayout(d->ui.loginBoxLayout);
    delete d->worker;
    delete d;
}

void OpenconnectAuthWidget::readConfig()
{
    Q_D(OpenconnectAuthWidget);

    const NMStringMap dataMap = d->setting->data();

    if (!dataMap[NM_OPENCONNECT_KEY_GATEWAY].isEmpty()) {
        const QString gw = dataMap[NM_OPENCONNECT_KEY_GATEWAY];
        VPNHost host;
        const int index = gw.indexOf(QLatin1Char('/'));
        if (index > -1) {
            host.name = host.address = gw.left(index);
            host.group = gw.right(gw.length() - index - 1);
        } else {
            host.name = host.address = gw;
        }
        d->hosts.append(host);
    }
    if (!dataMap[NM_OPENCONNECT_KEY_CACERT].isEmpty()) {
        const QByteArray crt = QFile::encodeName(dataMap[NM_OPENCONNECT_KEY_CACERT]);
180
        openconnect_set_cafile(d->vpninfo, OC3DUP(crt.data()));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
181
182
183
    }
    if (dataMap[NM_OPENCONNECT_KEY_CSD_ENABLE] == "yes") {
        char *wrapper;
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
184
        wrapper = nullptr;
Lukáš Tinkl's avatar
Lukáš Tinkl committed
185
186
187
188
189
190
191
192
        if (!dataMap[NM_OPENCONNECT_KEY_CSD_WRAPPER].isEmpty()) {
            const QByteArray wrapperScript = QFile::encodeName(dataMap[NM_OPENCONNECT_KEY_CSD_WRAPPER]);
            wrapper = strdup(wrapperScript.data());
        }
        openconnect_setup_csd(d->vpninfo, getuid(), 1, wrapper);
    }
    if (!dataMap[NM_OPENCONNECT_KEY_PROXY].isEmpty()) {
        const QByteArray proxy = QFile::encodeName(dataMap[NM_OPENCONNECT_KEY_PROXY]);
193
        openconnect_set_http_proxy(d->vpninfo, OC3DUP(proxy.data()));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
194
195
196
197
    }
    if (!dataMap[NM_OPENCONNECT_KEY_USERCERT].isEmpty()) {
        const QByteArray crt = QFile::encodeName(dataMap[NM_OPENCONNECT_KEY_USERCERT]);
        const QByteArray key = QFile::encodeName(dataMap[NM_OPENCONNECT_KEY_PRIVKEY]);
198
        openconnect_set_client_cert(d->vpninfo, OC3DUP(crt.data()), OC3DUP(key.isEmpty() ? nullptr : key.data()));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
199
200
201
202
203

        if (!crt.isEmpty() && dataMap[NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID] == "yes") {
            openconnect_passphrase_from_fsid(d->vpninfo);
        }
    }
Ian Whyman's avatar
Ian Whyman committed
204
    if (!dataMap[NM_OPENCONNECT_KEY_PROTOCOL].isEmpty()) {
205
206
        const QString protocol = dataMap[NM_OPENCONNECT_KEY_PROTOCOL];
        openconnect_set_protocol(d->vpninfo, OC3DUP(protocol == "juniper" ? "nc" : protocol.toUtf8().data()));
Ian Whyman's avatar
Ian Whyman committed
207
    }
208
209
210
211
    if (!dataMap[NM_OPENCONNECT_KEY_REPORTED_OS].isEmpty()) {
        const QString reportedOs = dataMap[NM_OPENCONNECT_KEY_REPORTED_OS];
        openconnect_set_reported_os(d->vpninfo, reportedOs.toUtf8().data());
    }
212
213

    d->tokenMode = dataMap[NM_OPENCONNECT_KEY_TOKEN_MODE].toUtf8();
Lukáš Tinkl's avatar
Lukáš Tinkl committed
214
215
216
217
218
219
220
221
222
}

void OpenconnectAuthWidget::readSecrets()
{
    Q_D(OpenconnectAuthWidget);

    d->secrets = d->setting->secrets();

    if (!d->secrets["xmlconfig"].isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
223
        const QByteArray config = QByteArray::fromBase64(d->secrets["xmlconfig"].toLatin1());
Lukáš Tinkl's avatar
Lukáš Tinkl committed
224
225
226
227

        QCryptographicHash hash(QCryptographicHash::Sha1);
        hash.addData(config.data(), config.size());
        const char *sha1_text = hash.result().toHex();
Alexander Lohnau's avatar
Alexander Lohnau committed
228
        openconnect_set_xmlsha1(d->vpninfo, (char *)sha1_text, strlen(sha1_text) + 1);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
229
230
231
232
233
234

        QDomDocument xmlconfig;
        xmlconfig.setContent(config);
        const QDomNode anyConnectProfile = xmlconfig.elementsByTagName(QLatin1String("AnyConnectProfile")).at(0);
        bool matchedGw = false;
        const QDomNode serverList = anyConnectProfile.firstChildElement(QLatin1String("ServerList"));
Alexander Lohnau's avatar
Alexander Lohnau committed
235
236
        for (QDomElement entry = serverList.firstChildElement(QLatin1String("HostEntry")); !entry.isNull();
             entry = entry.nextSiblingElement(QLatin1String("HostEntry"))) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
            VPNHost host;
            host.name = entry.firstChildElement(QLatin1String("HostName")).text();
            host.group = entry.firstChildElement(QLatin1String("UserGroup")).text();
            host.address = entry.firstChildElement(QLatin1String("HostAddress")).text();
            // We added the originally configured host in readConfig(). But if
            // it matches one of the ones in the XML config (as presumably it
            // should), remove the original and use the one with the pretty name.
            if (!matchedGw && host.address == d->hosts.at(0).address) {
                d->hosts.removeFirst();
                matchedGw = true;
            }
            d->hosts.append(host);
        }
    }

    for (int i = 0; i < d->hosts.size(); i++) {
        d->ui.cmbHosts->addItem(d->hosts.at(i).name, i);
Jan Grulich's avatar
Jan Grulich committed
254
        if (d->secrets["lasthost"] == d->hosts.at(i).name || d->secrets["lasthost"] == d->hosts.at(i).address) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
255
            d->ui.cmbHosts->setCurrentIndex(i);
Jan Grulich's avatar
Jan Grulich committed
256
        }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
257
258
259
260
    }

    if (d->secrets["autoconnect"] == "yes") {
        d->ui.chkAutoconnect->setChecked(true);
261
        QTimer::singleShot(0, this, &OpenconnectAuthWidget::connectHost);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
262
    }
263
264
265
266

    if (d->secrets["save_passwords"] == "yes") {
        d->ui.chkStorePasswords->setChecked(true);
    }
267
268
269
270
271
272
273
274
275
276

    d->token.tokenMode = OC_TOKEN_MODE_NONE;
    d->token.tokenSecret = nullptr;

    if (!d->tokenMode.isEmpty()) {
        int ret = 0;
        QByteArray tokenSecret = d->secrets[NM_OPENCONNECT_KEY_TOKEN_SECRET].toUtf8();

        if (d->tokenMode == QStringLiteral("manual") && !tokenSecret.isEmpty()) {
            ret = __openconnect_set_token_mode(d->vpninfo, OC_TOKEN_MODE_STOKEN, tokenSecret);
Alexander Lohnau's avatar
Alexander Lohnau committed
277
        } else if (d->tokenMode == QStringLiteral("stokenrc")) {
278
279
280
281
            ret = __openconnect_set_token_mode(d->vpninfo, OC_TOKEN_MODE_STOKEN, NULL);
        } else if (d->tokenMode == QStringLiteral("totp") && !tokenSecret.isEmpty()) {
            ret = __openconnect_set_token_mode(d->vpninfo, OC_TOKEN_MODE_TOTP, tokenSecret);
        }
Alexander Lohnau's avatar
Alexander Lohnau committed
282
283
#if OPENCONNECT_CHECK_VER(3, 4)
        else if (d->tokenMode == QStringLiteral("hotp") && !tokenSecret.isEmpty()) {
284
285
286
            ret = __openconnect_set_token_mode(d->vpninfo, OC_TOKEN_MODE_HOTP, tokenSecret);
        }
#endif
Alexander Lohnau's avatar
Alexander Lohnau committed
287
#if OPENCONNECT_CHECK_VER(5, 0)
288
289
290
291
292
293
294
295
296
297
298
299
300
        else if (d->tokenMode == "yubioath") {
            /* This needs to be done from a thread because it can call back to
                ask for the PIN */
            d->token.tokenMode = OC_TOKEN_MODE_YUBIOATH;
            if (!tokenSecret.isEmpty()) {
                d->token.tokenSecret = tokenSecret;
            }
        }
#endif
        if (ret) {
            addFormInfo(QLatin1String("dialog-error"), i18n("Failed to initialize software token: %1", ret));
        }
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
301
302
303
304
}

void OpenconnectAuthWidget::acceptDialog()
{
305
306
307
308
309
310
    // Find top-level widget as this should be the QDialog itself
    QWidget *widget = parentWidget();
    while (widget->parentWidget() != nullptr) {
        widget = widget->parentWidget();
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
311
    QDialog *dialog = qobject_cast<QDialog *>(widget);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
312
313
314
315
316
317
318
319
320
321
    if (dialog) {
        dialog->accept();
    }
}

// This starts the worker thread, which connects to the selected AnyConnect host
// and retrieves the login form
void OpenconnectAuthWidget::connectHost()
{
    Q_D(OpenconnectAuthWidget);
322

Lukáš Tinkl's avatar
Lukáš Tinkl committed
323
324
325
326
327
328
329
330
331
332
    d->userQuit = true;
    if (write(d->cancelPipes[1], "x", 1)) {
        // not a lot we can do
    }
    d->workerWaiting.wakeAll();
    d->worker->wait();
    d->userQuit = false;

    /* Suck out the cancel byte(s) */
    char buf;
Jan Grulich's avatar
Jan Grulich committed
333
    while (read(d->cancelPipes[0], &buf, 1) == 1) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
334
        ;
Jan Grulich's avatar
Jan Grulich committed
335
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
336
337
    deleteAllFromLayout(d->ui.loginBoxLayout);
    int i = d->ui.cmbHosts->currentIndex();
Jan Grulich's avatar
Jan Grulich committed
338
    if (i == -1) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
339
        return;
Jan Grulich's avatar
Jan Grulich committed
340
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
341
342
    i = d->ui.cmbHosts->itemData(i).toInt();
    const VPNHost &host = d->hosts.at(i);
Laurent Montel's avatar
Laurent Montel committed
343
    if (openconnect_parse_url(d->vpninfo, host.address.toLatin1().data())) {
344
        qCWarning(PLASMA_NM) << "Failed to parse server URL" << host.address;
Laurent Montel's avatar
Laurent Montel committed
345
        openconnect_set_hostname(d->vpninfo, OC3DUP(host.address.toLatin1().data()));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
346
    }
Jan Grulich's avatar
Jan Grulich committed
347
    if (!openconnect_get_urlpath(d->vpninfo) && !host.group.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
348
        openconnect_set_urlpath(d->vpninfo, OC3DUP(host.group.toLatin1().data()));
Jan Grulich's avatar
Jan Grulich committed
349
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
350
    d->secrets["lasthost"] = host.name;
351
    addFormInfo(QLatin1String("dialog-information"), i18n("Contacting host, please wait…"));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
352
353
354
    d->worker->start();
}

355
356
357
358
359
void OpenconnectAuthWidget::initTokens()
{
    Q_D(OpenconnectAuthWidget);

    if (d->token.tokenMode != OC_TOKEN_MODE_NONE) {
Alexander Lohnau's avatar
Alexander Lohnau committed
360
        __openconnect_set_token_mode(d->vpninfo, d->token.tokenMode, d->token.tokenSecret);
361
362
363
    }
}

364
QVariantMap OpenconnectAuthWidget::setting() const
Lukáš Tinkl's avatar
Lukáš Tinkl committed
365
366
367
368
369
370
371
372
{
    Q_D(const OpenconnectAuthWidget);

    NMStringMap secrets;
    QVariantMap secretData;

    secrets.unite(d->secrets);
    QString host(openconnect_get_hostname(d->vpninfo));
Jan Grulich's avatar
Jan Grulich committed
373
    const QString port = QString::number(openconnect_get_port(d->vpninfo));
Aaron Barany's avatar
Aaron Barany committed
374
    QString gateway = host + ':' + port;
Alexander Lohnau's avatar
Alexander Lohnau committed
375
    const char *urlpath = openconnect_get_urlpath(d->vpninfo);
Aaron Barany's avatar
Aaron Barany committed
376
377
378
379
380
    if (urlpath) {
        gateway += '/';
        gateway += urlpath;
    }
    secrets.insert(QLatin1String(NM_OPENCONNECT_KEY_GATEWAY), gateway);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
381
382
383
384

    secrets.insert(QLatin1String(NM_OPENCONNECT_KEY_COOKIE), QLatin1String(openconnect_get_cookie(d->vpninfo)));
    openconnect_clear_cookie(d->vpninfo);

Alexander Lohnau's avatar
Alexander Lohnau committed
385
#if OPENCONNECT_CHECK_VER(5, 0)
386
387
    const char *fingerprint = openconnect_get_peer_cert_hash(d->vpninfo);
#else
Lukáš Tinkl's avatar
Lukáš Tinkl committed
388
389
390
    OPENCONNECT_X509 *cert = openconnect_get_peer_cert(d->vpninfo);
    char fingerprint[41];
    openconnect_get_cert_sha1(d->vpninfo, cert, fingerprint);
391
#endif
Lukáš Tinkl's avatar
Lukáš Tinkl committed
392
393
    secrets.insert(QLatin1String(NM_OPENCONNECT_KEY_GWCERT), QLatin1String(fingerprint));
    secrets.insert(QLatin1String("autoconnect"), d->ui.chkAutoconnect->isChecked() ? "yes" : "no");
394
    secrets.insert(QLatin1String("save_passwords"), d->ui.chkStorePasswords->isChecked() ? "yes" : "no");
Lukáš Tinkl's avatar
Lukáš Tinkl committed
395
396
397

    NMStringMap::iterator i = secrets.begin();
    while (i != secrets.end()) {
Jan Grulich's avatar
Jan Grulich committed
398
        if (i.value().isEmpty()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
399
            i = secrets.erase(i);
Jan Grulich's avatar
Jan Grulich committed
400
        } else {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
401
            i++;
Jan Grulich's avatar
Jan Grulich committed
402
        }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
403
404
405
    }

    secretData.insert("secrets", QVariant::fromValue<NMStringMap>(secrets));
406
407
408
409
410
411

    // These secrets are not officially part of the secrets which would be returned back to NetworkManager. We just
    // need to somehow get them to our secret agent which will handle them separately and store them.
    if (!d->tmpSecrets.isEmpty()) {
        secretData.insert("tmp-secrets", QVariant::fromValue<NMStringMap>(d->tmpSecrets));
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
412
413
414
    return secretData;
}

Alexander Lohnau's avatar
Alexander Lohnau committed
415
#if OPENCONNECT_CHECK_VER(3, 4)
416
417
418
419
420
421
422
423
static int updateToken(void *cbdata, const char *tok)
{
    NMStringMap *secrets = static_cast<NMStringMap *>(cbdata);
    secrets->insert(QLatin1String(NM_OPENCONNECT_KEY_TOKEN_SECRET), QLatin1String(tok));
    return 0;
}
#endif

Alexander Lohnau's avatar
Alexander Lohnau committed
424
void OpenconnectAuthWidget::writeNewConfig(const QString &buf)
Lukáš Tinkl's avatar
Lukáš Tinkl committed
425
426
427
428
429
430
431
432
{
    Q_D(OpenconnectAuthWidget);
    d->secrets["xmlconfig"] = buf;
}

void OpenconnectAuthWidget::updateLog(const QString &message, const int &level)
{
    Q_D(OpenconnectAuthWidget);
433

Lukáš Tinkl's avatar
Lukáš Tinkl committed
434
435
    QPair<QString, int> pair;
    pair.first = message;
Jan Grulich's avatar
Jan Grulich committed
436
    if (pair.first.endsWith(QLatin1String("\n"))) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
437
        pair.first.chop(1);
Jan Grulich's avatar
Jan Grulich committed
438
439
    }
    switch (level) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
    case PRG_ERR:
        pair.second = OpenconnectAuthWidgetPrivate::Error;
        break;
    case PRG_INFO:
        pair.second = OpenconnectAuthWidgetPrivate::Info;
        break;
    case PRG_DEBUG:
        pair.second = OpenconnectAuthWidgetPrivate::Debug;
        break;
    case PRG_TRACE:
        pair.second = OpenconnectAuthWidgetPrivate::Trace;
        break;
    }
    if (pair.second <= d->ui.cmbLogLevel->currentIndex()) {
        d->ui.serverLog->append(pair.first);
    }

    d->serverLog.append(pair);
    if (d->serverLog.size() > 100) {
        d->serverLog.removeFirst();
    }
}

void OpenconnectAuthWidget::logLevelChanged(int newLevel)
{
    Q_D(OpenconnectAuthWidget);
    d->ui.serverLog->clear();
Alexander Lohnau's avatar
Alexander Lohnau committed
467
    QList<QPair<QString, int>>::const_iterator i;
Lukáš Tinkl's avatar
Lukáš Tinkl committed
468
469
470

    for (i = d->serverLog.constBegin(); i != d->serverLog.constEnd(); ++i) {
        QPair<QString, int> pair = *i;
Alexander Lohnau's avatar
Alexander Lohnau committed
471
        if (pair.second <= newLevel) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
472
473
474
475
476
477
478
479
            d->ui.serverLog->append(pair.first);
        }
    }
}

void OpenconnectAuthWidget::addFormInfo(const QString &iconName, const QString &message)
{
    Q_D(OpenconnectAuthWidget);
480

Lukáš Tinkl's avatar
Lukáš Tinkl committed
481
482
483
484
485
486
487
488
489
490
491
492
    QHBoxLayout *layout = new QHBoxLayout();
    QLabel *icon = new QLabel(this);
    QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    sizePolicy.setHorizontalStretch(0);
    sizePolicy.setVerticalStretch(0);
    sizePolicy.setHeightForWidth(icon->sizePolicy().hasHeightForWidth());
    icon->setSizePolicy(sizePolicy);
    icon->setMinimumSize(QSize(16, 16));
    icon->setMaximumSize(QSize(16, 16));
    layout->addWidget(icon);

    QLabel *text = new QLabel(this);
Alexander Lohnau's avatar
Alexander Lohnau committed
493
    text->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
494
    text->setWordWrap(true);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
495
496
    layout->addWidget(text);

Nicolas Fella's avatar
Nicolas Fella committed
497
498
    const int iconSize = icon->style()->pixelMetric(QStyle::PixelMetric::PM_SmallIconSize);
    icon->setPixmap(QIcon::fromTheme(iconName).pixmap(iconSize));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
499
500
501
502
503
504
505
506
    text->setText(message);

    d->ui.loginBoxLayout->addLayout(layout);
}

void OpenconnectAuthWidget::processAuthForm(struct oc_auth_form *form)
{
    Q_D(OpenconnectAuthWidget);
507

Lukáš Tinkl's avatar
Lukáš Tinkl committed
508
509
510
511
512
513
514
    deleteAllFromLayout(d->ui.loginBoxLayout);

    struct oc_form_opt *opt;
    QFormLayout *layout = new QFormLayout();
    QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    bool focusSet = false;
    for (opt = form->opts; opt; opt = opt->next) {
Jan Grulich's avatar
Jan Grulich committed
515
        if (opt->type == OC_FORM_OPT_HIDDEN || IGNORE_OPT(opt)) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
516
            continue;
Jan Grulich's avatar
Jan Grulich committed
517
        }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
518
        QLabel *text = new QLabel(this);
Alexander Lohnau's avatar
Alexander Lohnau committed
519
        text->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
520
        text->setText(QString(opt->label));
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
521
        QWidget *widget = nullptr;
Lukáš Tinkl's avatar
Lukáš Tinkl committed
522
523
524
        const QString key = QString("form:%1:%2").arg(QLatin1String(form->auth_id)).arg(QLatin1String(opt->name));
        const QString value = d->secrets.value(key);
        if (opt->type == OC_FORM_OPT_PASSWORD || opt->type == OC_FORM_OPT_TEXT) {
525
            PasswordField *le = new PasswordField(this);
526
            le->setText(value);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
527
            if (opt->type == OC_FORM_OPT_PASSWORD) {
528
                le->setPasswordModeEnabled(true);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
529
530
531
532
533
            }
            if (!focusSet && le->text().isEmpty()) {
                le->setFocus(Qt::OtherFocusReason);
                focusSet = true;
            }
Alexander Lohnau's avatar
Alexander Lohnau committed
534
            widget = qobject_cast<QWidget *>(le);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
535
        } else if (opt->type == OC_FORM_OPT_SELECT) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
536
            QComboBox *cmb = new QComboBox(this);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
537
            struct oc_form_opt_select *sopt = reinterpret_cast<oc_form_opt_select *>(opt);
Alexander Lohnau's avatar
Alexander Lohnau committed
538
#if !OPENCONNECT_CHECK_VER(8, 0)
539
540
            const QString protocol = d->setting->data()[NM_OPENCONNECT_KEY_PROTOCOL];
#endif
Lukáš Tinkl's avatar
Lukáš Tinkl committed
541
            for (int i = 0; i < sopt->nr_choices; i++) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
542
                cmb->addItem(QString::fromUtf8(FORMCHOICE(sopt, i)->label), QString::fromUtf8(FORMCHOICE(sopt, i)->name));
Jan Grulich's avatar
Jan Grulich committed
543
                if (value == QString::fromUtf8(FORMCHOICE(sopt, i)->name)) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
544
                    cmb->setCurrentIndex(i);
Alexander Lohnau's avatar
Alexander Lohnau committed
545
#if !OPENCONNECT_CHECK_VER(8, 0)
546
                    if (protocol != QLatin1String("nc") && sopt == AUTHGROUP_OPT(form) && i != AUTHGROUP_SELECTION(form)) {
547
#else
548
                    if (sopt == AUTHGROUP_OPT(form) && i != AUTHGROUP_SELECTION(form)) {
549
#endif
550
551
                        QTimer::singleShot(0, this, &OpenconnectAuthWidget::formGroupChanged);
                    }
Jan Grulich's avatar
Jan Grulich committed
552
553
                }
            }
Alexander Lohnau's avatar
Alexander Lohnau committed
554
#if !OPENCONNECT_CHECK_VER(8, 0)
555
556
            if (protocol != QLatin1String("nc") && sopt == AUTHGROUP_OPT(form)) {
#else
Jan Grulich's avatar
Jan Grulich committed
557
            if (sopt == AUTHGROUP_OPT(form)) {
558
#endif
559
                connect(cmb, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &OpenconnectAuthWidget::formGroupChanged);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
560
            }
Alexander Lohnau's avatar
Alexander Lohnau committed
561
            widget = qobject_cast<QWidget *>(cmb);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
562
563
564
565
566
567
568
        }
        if (widget) {
            widget->setProperty("openconnect_opt", (quintptr)opt);
            widget->setSizePolicy(policy);
            layout->addRow(text, widget);
        }
    }
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
    if (!layout->rowCount()) {
        delete layout;
        d->workerWaiting.wakeAll();
        return;
    }

    if (form->banner) {
        addFormInfo(QLatin1String("dialog-information"), form->banner);
    }
    if (form->message) {
        addFormInfo(QLatin1String("dialog-information"), form->message);
    }
    if (form->error) {
        addFormInfo(QLatin1String("dialog-error"), form->error);
    }

Lukáš Tinkl's avatar
Lukáš Tinkl committed
585
    d->ui.loginBoxLayout->addLayout(layout);
Jan Grulich's avatar
Jan Grulich committed
586
    d->passwordFormIndex = d->ui.loginBoxLayout->count() - 1;
587

588
    QDialogButtonBox *box = new QDialogButtonBox(this);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
589
    QPushButton *btn = box->addButton(QDialogButtonBox::Ok);
590
    btn->setText(i18nc("Verb, to proceed with login", "Login"));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
591
592
593
594
    btn->setDefault(true);
    d->ui.loginBoxLayout->addWidget(box);
    box->setProperty("openconnect_form", (quintptr)form);

595
    connect(box, &QDialogButtonBox::accepted, this, &OpenconnectAuthWidget::formLoginClicked);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
596
597
}

Alexander Lohnau's avatar
Alexander Lohnau committed
598
void OpenconnectAuthWidget::validatePeerCert(const QString &fingerprint, const QString &peerCert, const QString &reason, bool *accepted)
Lukáš Tinkl's avatar
Lukáš Tinkl committed
599
600
601
{
    Q_D(OpenconnectAuthWidget);

602
603
    const QString host = QLatin1String(openconnect_get_hostname(d->vpninfo));
    const QString port = QString::number(openconnect_get_port(d->vpninfo));
Alexander Lohnau's avatar
Alexander Lohnau committed
604
    const QString key = QString("certificate:%1:%2").arg(host, port);
605
606
    const QString value = d->secrets.value(key);

Alexander Lohnau's avatar
Alexander Lohnau committed
607
608
#if !OPENCONNECT_CHECK_VER(5, 0)
#define openconnect_check_peer_cert_hash(v, d) strcmp(d, fingerprint.toUtf8().data())
609
610
611
#endif

    if (openconnect_check_peer_cert_hash(d->vpninfo, value.toUtf8().data())) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
612
613
614
615
616
        QWidget *widget = new QWidget();
        QVBoxLayout *verticalLayout;
        QHBoxLayout *horizontalLayout;
        QLabel *icon;
        QLabel *infoText;
Lukáš Tinkl's avatar
Lukáš Tinkl committed
617
        QTextBrowser *certificate;
Lukáš Tinkl's avatar
Lukáš Tinkl committed
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632

        verticalLayout = new QVBoxLayout(widget);
        horizontalLayout = new QHBoxLayout(widget);
        icon = new QLabel(widget);
        QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        sizePolicy.setHorizontalStretch(0);
        sizePolicy.setVerticalStretch(0);
        sizePolicy.setHeightForWidth(icon->sizePolicy().hasHeightForWidth());
        icon->setSizePolicy(sizePolicy);
        icon->setMinimumSize(QSize(48, 48));
        icon->setMaximumSize(QSize(48, 48));

        horizontalLayout->addWidget(icon);

        infoText = new QLabel(widget);
Alexander Lohnau's avatar
Alexander Lohnau committed
633
        infoText->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
634
635
636
637
638

        horizontalLayout->addWidget(infoText);

        verticalLayout->addLayout(horizontalLayout);

Lukáš Tinkl's avatar
Lukáš Tinkl committed
639
        certificate = new QTextBrowser(widget);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
640
641
642
643
644
        certificate->setTextInteractionFlags(Qt::TextSelectableByMouse);
        certificate->setOpenLinks(false);

        verticalLayout->addWidget(certificate);

Nicolas Fella's avatar
Nicolas Fella committed
645
646
        const int iconSize = icon->style()->pixelMetric(QStyle::PixelMetric::PM_LargeIconSize);
        icon->setPixmap(QIcon::fromTheme("dialog-information").pixmap(iconSize));
Alexander Lohnau's avatar
Alexander Lohnau committed
647
648
649
650
651
        infoText->setText(
            i18n("Check failed for certificate from VPN server \"%1\".\n"
                 "Reason: %2\nAccept it anyway?",
                 openconnect_get_hostname(d->vpninfo),
                 reason));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
652
653
654
        infoText->setWordWrap(true);
        certificate->setText(peerCert);

655
        QPointer<QDialog> dialog = new QDialog(this);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
656
        dialog.data()->setWindowModality(Qt::WindowModal);
Jan Grulich's avatar
Jan Grulich committed
657
        dialog->setLayout(new QVBoxLayout);
Alexander Lohnau's avatar
Alexander Lohnau committed
658
        QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog);
Lamarque Souza's avatar
Lamarque Souza committed
659
660
        connect(buttons, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept);
        connect(buttons, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject);
Jan Grulich's avatar
Jan Grulich committed
661
662
        dialog->layout()->addWidget(widget);
        dialog->layout()->addWidget(buttons);
663
664
665
666

        const NMStringMap dataMap = d->setting->data();
        buttons->button(QDialogButtonBox::Ok)->setEnabled(dataMap[NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT] != "yes");

Alexander Lohnau's avatar
Alexander Lohnau committed
667
        if (dialog.data()->exec() == QDialog::Accepted) {
668
669
670
671
672
673
674
675
            *accepted = true;
        } else {
            *accepted = false;
        }
        if (dialog) {
            dialog.data()->deleteLater();
        }
        widget->deleteLater();
Lukáš Tinkl's avatar
Lukáš Tinkl committed
676
677
678
    } else {
        *accepted = true;
    }
Jan Grulich's avatar
Jan Grulich committed
679
    if (*accepted) {
680
        d->secrets.insert(key, QString(fingerprint));
Jan Grulich's avatar
Jan Grulich committed
681
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
682
683
684
685
686
    d->mutex.lock();
    d->workerWaiting.wakeAll();
    d->mutex.unlock();
}

687
688
689
690
691
692
693
694
void OpenconnectAuthWidget::formGroupChanged()
{
    Q_D(OpenconnectAuthWidget);

    d->formGroupChanged = true;
    formLoginClicked();
}

Lukáš Tinkl's avatar
Lukáš Tinkl committed
695
696
697
698
699
700
// Writes the user input from the form into the oc_auth_form structs we got from
// libopenconnect, and wakes the worker thread up to try to log in and obtain a
// cookie with this data
void OpenconnectAuthWidget::formLoginClicked()
{
    Q_D(OpenconnectAuthWidget);
701

Jan Grulich's avatar
Jan Grulich committed
702
    const int lastIndex = d->ui.loginBoxLayout->count() - 1;
Jan Grulich's avatar
Jan Grulich committed
703
    QLayout *layout = d->ui.loginBoxLayout->itemAt(d->passwordFormIndex)->layout();
Alexander Lohnau's avatar
Alexander Lohnau committed
704
    struct oc_auth_form *form = (struct oc_auth_form *)d->ui.loginBoxLayout->itemAt(lastIndex)->widget()->property("openconnect_form").value<quintptr>();
Lukáš Tinkl's avatar
Lukáš Tinkl committed
705
706
707
708
709

    for (int i = 0; i < layout->count(); i++) {
        QLayoutItem *item = layout->itemAt(i);
        QWidget *widget = item->widget();
        if (widget && widget->property("openconnect_opt").isValid()) {
Alexander Lohnau's avatar
Alexander Lohnau committed
710
            struct oc_form_opt *opt = (struct oc_form_opt *)widget->property("openconnect_opt").value<quintptr>();
Jan Grulich's avatar
Jan Grulich committed
711
            const QString key = QString("form:%1:%2").arg(QLatin1String(form->auth_id)).arg(QLatin1String(opt->name));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
712
            if (opt->type == OC_FORM_OPT_PASSWORD || opt->type == OC_FORM_OPT_TEXT) {
Alexander Lohnau's avatar
Alexander Lohnau committed
713
                PasswordField *le = qobject_cast<PasswordField *>(widget);
714
715
716
                QByteArray text = le->text().toUtf8();
                openconnect_set_option_value(opt, text.data());
                if (opt->type == OC_FORM_OPT_TEXT) {
717
718
719
                    d->secrets.insert(key, le->text());
                } else {
                    d->tmpSecrets.insert(key, le->text());
Lukáš Tinkl's avatar
Lukáš Tinkl committed
720
721
                }
            } else if (opt->type == OC_FORM_OPT_SELECT) {
Alexander Lohnau's avatar
Alexander Lohnau committed
722
                QComboBox *cbo = qobject_cast<QComboBox *>(widget);
Laurent Montel's avatar
Laurent Montel committed
723
                QByteArray text = cbo->itemData(cbo->currentIndex()).toString().toLatin1();
724
                openconnect_set_option_value(opt, text.data());
Alexander Lohnau's avatar
Alexander Lohnau committed
725
                d->secrets.insert(key, cbo->itemData(cbo->currentIndex()).toString());
Lukáš Tinkl's avatar
Lukáš Tinkl committed
726
727
728
            }
        }
    }
729

Lukáš Tinkl's avatar
Lukáš Tinkl committed
730
731
732
733
734
735
736
    deleteAllFromLayout(d->ui.loginBoxLayout);
    d->workerWaiting.wakeAll();
}

void OpenconnectAuthWidget::workerFinished(const int &ret)
{
    Q_D(OpenconnectAuthWidget);
737

Lukáš Tinkl's avatar
Lukáš Tinkl committed
738
739
    if (ret < 0) {
        QString message;
Alexander Lohnau's avatar
Alexander Lohnau committed
740
741
        QList<QPair<QString, int>>::const_iterator i;
        for (i = d->serverLog.constEnd() - 1; i >= d->serverLog.constBegin(); --i) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
742
            QPair<QString, int> pair = *i;
Alexander Lohnau's avatar
Alexander Lohnau committed
743
            if (pair.second <= OpenconnectAuthWidgetPrivate::Error) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
744
745
746
747
                message = pair.first;
                break;
            }
        }
Jan Grulich's avatar
Jan Grulich committed
748
        if (message.isEmpty()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
749
            message = i18n("Connection attempt was unsuccessful.");
Jan Grulich's avatar
Jan Grulich committed
750
        }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
751
752
753
754
755
756
757
758
759
760
761
762
763
764
        deleteAllFromLayout(d->ui.loginBoxLayout);
        addFormInfo(QLatin1String("dialog-error"), message);
    } else {
        deleteAllFromLayout(d->ui.loginBoxLayout);
        acceptDialog();
    }
}

void OpenconnectAuthWidget::deleteAllFromLayout(QLayout *layout)
{
    while (QLayoutItem *item = layout->takeAt(0)) {
        if (QLayout *itemLayout = item->layout()) {
            deleteAllFromLayout(itemLayout);
            itemLayout->deleteLater();
Jan Grulich's avatar
Jan Grulich committed
765
        } else {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
766
            item->widget()->deleteLater();
Jan Grulich's avatar
Jan Grulich committed
767
        }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
768
769
770
771
772
773
774
775
776
777
778
        delete item;
    }
    layout->invalidate();
}

void OpenconnectAuthWidget::viewServerLogToggled(bool toggled)
{
    Q_D(OpenconnectAuthWidget);
    d->ui.lblLogLevel->setVisible(toggled);
    d->ui.cmbLogLevel->setVisible(toggled);
    if (toggled) {
Nick Shaforostoff's avatar
Nick Shaforostoff committed
779
        delete d->ui.verticalLayout->takeAt(5);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
780
781
782
783
784
785
786
787
788
789
790
791
792
        QSizePolicy policy = d->ui.serverLogBox->sizePolicy();
        policy.setVerticalPolicy(QSizePolicy::Expanding);
        d->ui.serverLogBox->setSizePolicy(policy);
        d->ui.serverLog->setVisible(true);
    } else {
        QSpacerItem *verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
        d->ui.verticalLayout->addItem(verticalSpacer);
        d->ui.serverLog->setVisible(false);
        QSizePolicy policy = d->ui.serverLogBox->sizePolicy();
        policy.setVerticalPolicy(QSizePolicy::Fixed);
        d->ui.serverLogBox->setSizePolicy(policy);
    }
}