openconnectwidget.cpp 15.6 KB
Newer Older
Lukáš Tinkl's avatar
Lukáš Tinkl committed
1
/*
Jan Grulich's avatar
Jan Grulich committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    Copyright 2011 Ilia Kats <ilia-kats@gmx.de>
    Copyright 2013 Lukas Tinkl <ltinkl@redhat.com>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) version 3, or any
    later version accepted by the membership of KDE e.V. (or its
    successor approved by the membership of KDE e.V.), which shall
    act as a proxy defined in Section 6 of version 3 of the license.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library.  If not, see <http://www.gnu.org/licenses/>.
Lukáš Tinkl's avatar
Lukáš Tinkl committed
20
21
22
*/

#include "openconnectwidget.h"
23
#include <QDialog>
Jan Grulich's avatar
Jan Grulich committed
24
#include <QUrl>
25
#include <QStringList>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
26
27

#include "ui_openconnectprop.h"
28
#include "ui_openconnecttoken.h"
Lukáš Tinkl's avatar
Lukáš Tinkl committed
29
30

#include <QString>
31
#include <QDialogButtonBox>
Lukáš Tinkl's avatar
Lukáš Tinkl committed
32
33
#include "nm-openconnect-service.h"

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <openconnect.h>
#ifndef OPENCONNECT_CHECK_VER
#define OPENCONNECT_CHECK_VER(x,y) 0
#endif

#if !OPENCONNECT_CHECK_VER(2,1)
#define openconnect_has_stoken_support() 0
#endif
#if !OPENCONNECT_CHECK_VER(2,2)
#define openconnect_has_oath_support() 0
#endif
#if !OPENCONNECT_CHECK_VER(5,0)
#define openconnect_has_yubioath_support() 0
#endif

typedef struct {
    int tokenIndex;
    QString tokenSecret;
} Token;

Lukáš Tinkl's avatar
Lukáš Tinkl committed
54
55
56
57
class OpenconnectSettingWidgetPrivate
{
public:
    Ui_OpenconnectProp ui;
58
    Ui::OpenConnectToken tokenUi;
Lukáš Tinkl's avatar
Lukáš Tinkl committed
59
    NetworkManager::VpnSetting::Ptr setting;
60
61
    QDialog *tokenDlg;
    Token token;
Lukáš Tinkl's avatar
Lukáš Tinkl committed
62
63
64
};

OpenconnectSettingWidget::OpenconnectSettingWidget(const NetworkManager::VpnSetting::Ptr &setting, QWidget * parent)
Jan Grulich's avatar
Jan Grulich committed
65
66
    : SettingWidget(setting, parent)
    , d_ptr(new OpenconnectSettingWidgetPrivate)
Lukáš Tinkl's avatar
Lukáš Tinkl committed
67
68
{
    Q_D(OpenconnectSettingWidget);
69

Lukáš Tinkl's avatar
Lukáš Tinkl committed
70
71
72
    d->ui.setupUi(this);
    d->setting = setting;

73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
    // Connect for validity check
    connect(d->ui.leGateway, &QLineEdit::textChanged, this, &OpenconnectSettingWidget::slotWidgetChanged);

    connect(d->ui.buTokens, &QPushButton::clicked, this, &OpenconnectSettingWidget::showTokens);

    d->tokenDlg = new QDialog(this);
    d->tokenUi.setupUi(d->tokenDlg);
    d->tokenUi.leTokenSecret->setPasswordModeEnabled(true);
    d->tokenUi.leTokenSecret->setPasswordOptionsEnabled(true);
    QVBoxLayout * layout = new QVBoxLayout(d->tokenDlg);
    layout->addWidget(d->tokenDlg);
    d->tokenDlg->setLayout(layout);
    connect(d->tokenUi.buttonBox, &QDialogButtonBox::accepted, d->tokenDlg, &QDialog::accept);
    connect(d->tokenUi.buttonBox, &QDialogButtonBox::rejected, d->tokenDlg, &QDialog::reject);
    connect(d->tokenDlg, &QDialog::rejected, this, &OpenconnectSettingWidget::restoreTokens);
    connect(d->tokenDlg, &QDialog::accepted, this, &OpenconnectSettingWidget::saveTokens);

    connect(d->tokenUi.cmbTokenMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, QOverload<int>::of((&OpenconnectSettingWidget::handleTokenSecret)));

92
93
94
    // Connect for setting check
    watchChangedSetting();

95
96
97
98
99
100
101
    // Remove these from setting check:
    // Just popping up the tokenDlg changes nothing
    disconnect(d->ui.buTokens, &QPushButton::clicked, this, &SettingWidget::settingChanged);
    // User cancels means nothing should change here
    disconnect(d->tokenUi.buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &SettingWidget::settingChanged);

    d->tokenUi.gbToken->setVisible(initTokenGroup());
Lukáš Tinkl's avatar
Lukáš Tinkl committed
102

103
104
    KAcceleratorManager::manage(this);

Jan Grulich's avatar
Jan Grulich committed
105
    if (d->setting) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
106
        loadConfig(d->setting);
Jan Grulich's avatar
Jan Grulich committed
107
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
108
109
110
111
112
113
114
}

OpenconnectSettingWidget::~OpenconnectSettingWidget()
{
    delete d_ptr;
}

115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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
void OpenconnectSettingWidget::handleTokenSecret(int index)
{
    Q_D(const OpenconnectSettingWidget);

    QVariant mode = d->tokenUi.cmbTokenMode->itemData(index);
    if (mode == QStringLiteral("disabled")) {
        d->tokenUi.leTokenSecret->setEnabled(false);
        d->tokenUi.leTokenSecret->setToolTip("No secrets needed.");
    } else if (mode == QStringLiteral("stokenrc")) {
        d->tokenUi.leTokenSecret->setEnabled(false);
        d->tokenUi.leTokenSecret->setToolTip("No secrets needed; will read them from ~/.stokenrc.");
    } else if (mode == QStringLiteral("manual")) {
        d->tokenUi.leTokenSecret->setToolTip("Insert the secret here. See the openconnect documentation for syntax.");
        d->tokenUi.leTokenSecret->setEnabled(true);
    } else if (mode == QStringLiteral("totp")) {
        d->tokenUi.leTokenSecret->setEnabled(true);
        d->tokenUi.leTokenSecret->setToolTip("Insert the secret here, with a sha specification and a leading '0x' or 'base32:'. See the openconnect documentation for syntax.");
    } else if (mode ==QStringLiteral("hotp")) {
        d->tokenUi.leTokenSecret->setEnabled(true);
        d->tokenUi.leTokenSecret->setToolTip("Insert the secret here, with a leading '0x' or 'base32:' and a trailing counter after a comma (','), See the openconnect documentation for syntax.");
    } else if (mode == QStringLiteral("yubioath")) {
        d->tokenUi.leTokenSecret->setEnabled(true);
        d->tokenUi.leTokenSecret->setToolTip("Insert the token Id here, in the form company:username. Make sure to set your Yubikey in CCID mode");
    } else { // Not really needed now, but who knows?
        d->tokenUi.leTokenSecret->setEnabled(false);
        d->tokenUi.leTokenSecret->setToolTip("");
    }
}

bool OpenconnectSettingWidget::initTokenGroup()
{
    Q_D(const OpenconnectSettingWidget);

    int validRows = 0;
    QStringList tokenLabelList = QStringList() << "Disabled" << "RSA SecurID — read from ~/.stokenrc" << "RSA SecurID — manually entered" << "TOTP — manually entered" << "HOTP — manually entered" << "Yubikey";
    QStringList tokenModeList = QStringList() << "disabled" << "stokenrc" << "manual" << "totp" << "hotp" << "yubioath";
    QComboBox *combo = d->tokenUi.cmbTokenMode;

    combo->addItem(tokenLabelList[validRows]);
    combo->setItemData(validRows, tokenModeList[validRows], Qt::UserRole);
    validRows++;
    if (openconnect_has_stoken_support ()) {
        for ( ; validRows < 3; validRows++) {
            combo->addItem(tokenLabelList[validRows]);
            combo->setItemData(validRows, tokenModeList[validRows], Qt::UserRole);
        }
    }
    if (openconnect_has_oath_support ()) {
        combo->addItem(tokenLabelList[validRows]);
        combo->setItemData(validRows, tokenModeList[validRows], Qt::UserRole);
        validRows++;
        if (OPENCONNECT_CHECK_VER(3,4)) {
            combo->addItem(tokenLabelList[validRows]);
            combo->setItemData(validRows, tokenModeList[validRows], Qt::UserRole);
            validRows++;
        }
    }
    if (openconnect_has_yubioath_support ()) {
        combo->addItem(tokenLabelList[validRows]);
        combo->setItemData(validRows, tokenModeList[validRows], Qt::UserRole);
    }
    return validRows > 0;
}

Lukáš Tinkl's avatar
Lukáš Tinkl committed
179
180
181
182
183
void OpenconnectSettingWidget::loadConfig(const NetworkManager::Setting::Ptr &setting)
{
    Q_D(OpenconnectSettingWidget);

    // General settings
184
    const NMStringMap dataMap = setting.staticCast<NetworkManager::VpnSetting>()->data();
Lukáš Tinkl's avatar
Lukáš Tinkl committed
185

186
187
188
189
190
    int cmbProtocolIndex;
    if (dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("anyconnect")) {
        cmbProtocolIndex = 0;
    } else if (dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("nc")) {
        cmbProtocolIndex = 1;
191
192
    } else if(dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("gp")) {
        cmbProtocolIndex = 2;
193
    } else {
David Redondo's avatar
David Redondo committed
194
        cmbProtocolIndex = 3; // pulse, Pulse Connect Secure
195
196
    }

197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
    int cmbReportedOsIndex;
    if (dataMap[NM_OPENCONNECT_KEY_REPORTED_OS] == QString()) {
        cmbReportedOsIndex = 0;
    } else if (dataMap[NM_OPENCONNECT_KEY_REPORTED_OS] == QLatin1String("linux")) {
        cmbReportedOsIndex = 1;
    } else if (dataMap[NM_OPENCONNECT_KEY_REPORTED_OS] == QLatin1String("linux-64")) {
        cmbReportedOsIndex = 2;
    } else if (dataMap[NM_OPENCONNECT_KEY_REPORTED_OS] == QLatin1String("win")) {
        cmbReportedOsIndex = 3;
    } else if (dataMap[NM_OPENCONNECT_KEY_REPORTED_OS] == QLatin1String("mac-intel")) {
        cmbReportedOsIndex = 4;
    } else if (dataMap[NM_OPENCONNECT_KEY_REPORTED_OS] == QLatin1String("android")) {
        cmbReportedOsIndex = 5;
    } else {
        cmbReportedOsIndex = 6; // apple-ios
    }

214
    d->ui.cmbProtocol->setCurrentIndex(cmbProtocolIndex);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
215
    d->ui.leGateway->setText(dataMap[NM_OPENCONNECT_KEY_GATEWAY]);
Jan Grulich's avatar
Jan Grulich committed
216
    d->ui.leCaCertificate->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_CACERT]));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
217
    d->ui.leProxy->setText(dataMap[NM_OPENCONNECT_KEY_PROXY]);
218
    d->ui.cmbReportedOs->setCurrentIndex(cmbReportedOsIndex);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
219
    d->ui.chkAllowTrojan->setChecked(dataMap[NM_OPENCONNECT_KEY_CSD_ENABLE] == "yes");
Jan Grulich's avatar
Jan Grulich committed
220
221
222
    d->ui.leCsdWrapperScript->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_CSD_WRAPPER]));
    d->ui.leUserCert->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_USERCERT]));
    d->ui.leUserPrivateKey->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_PRIVKEY]));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
223
    d->ui.chkUseFsid->setChecked(dataMap[NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID] == "yes");
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
    d->ui.preventInvalidCert->setChecked(dataMap[NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT] == "yes");

    // Token settings
    const NetworkManager::Setting::SecretFlags tokenSecretFlag = static_cast<NetworkManager::Setting::SecretFlags>(dataMap.value(NM_OPENCONNECT_KEY_TOKEN_SECRET"-flags").toInt());
    if (tokenSecretFlag == NetworkManager::Setting::None) {
        d->tokenUi.leTokenSecret->setPasswordOption(PasswordField::StoreForAllUsers);
    } else if (tokenSecretFlag == NetworkManager::Setting::AgentOwned) {
        d->tokenUi.leTokenSecret->setPasswordOption(PasswordField::StoreForUser);
    } else {
        d->tokenUi.leTokenSecret->setPasswordOption(PasswordField::AlwaysAsk);
    }
    for (int index = 0; index < d->tokenUi.cmbTokenMode->count(); index++) {
        if (d->tokenUi.cmbTokenMode->itemData(index, Qt::UserRole) == dataMap[NM_OPENCONNECT_KEY_TOKEN_MODE]) {
            d->tokenUi.cmbTokenMode->setCurrentIndex(index);
            d->token.tokenIndex = index;
            if (index > 1) {
                loadSecrets(d->setting);
            }
            break;
        }
    }
}

void OpenconnectSettingWidget::loadSecrets(const NetworkManager::Setting::Ptr &setting)
{
    Q_D(OpenconnectSettingWidget);

    NetworkManager::VpnSetting::Ptr vpnSetting = setting.staticCast<NetworkManager::VpnSetting>();

    if (vpnSetting) {
        const NMStringMap secrets = vpnSetting->secrets();
        d->tokenUi.leTokenSecret->setText(secrets.value(NM_OPENCONNECT_KEY_TOKEN_SECRET));
        d->token.tokenSecret = secrets.value(NM_OPENCONNECT_KEY_TOKEN_SECRET);
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
258
259
}

260
QVariantMap OpenconnectSettingWidget::setting() const
Lukáš Tinkl's avatar
Lukáš Tinkl committed
261
262
263
264
265
266
267
{
    Q_D(const OpenconnectSettingWidget);

    NetworkManager::VpnSetting setting;
    setting.setServiceType(QLatin1String(NM_DBUS_SERVICE_OPENCONNECT));

    NMStringMap data;
268
    NMStringMap secrets;
269
270
271
272
273
274
275
276
    QString protocol;
    switch (d->ui.cmbProtocol->currentIndex()) {
        case 0:
            protocol = QLatin1String("anyconnect");
            break;
        case 1:
            protocol = QLatin1String("nc");
            break;
277
        case 2:
278
            protocol = QLatin1String("gp");
279
            break;
280
281
        default:
            protocol = QLatin1String("pulse");
282
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
283

284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
    QString reportedOs;
    switch (d->ui.cmbReportedOs->currentIndex()) {
        case 0:
            reportedOs = QString();
            break;
        case 1:
            reportedOs = QLatin1String("linux");
            break;
        case 2:
            reportedOs = QLatin1String("linux-64");
            break;
        case 3:
            reportedOs = QLatin1String("win");
            break;
        case 4:
            reportedOs = QLatin1String("mac-intel");
            break;
        case 5:
            reportedOs = QLatin1String("android");
            break;
        default:
            reportedOs = QLatin1String("apple-ios");
    }

308
    data.insert(NM_OPENCONNECT_KEY_PROTOCOL, protocol);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
309
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_GATEWAY), d->ui.leGateway->text());
Jan Grulich's avatar
Jan Grulich committed
310
    if (d->ui.leCaCertificate->url().isValid()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
311
        data.insert(QLatin1String(NM_OPENCONNECT_KEY_CACERT), d->ui.leCaCertificate->url().toLocalFile());
Jan Grulich's avatar
Jan Grulich committed
312
313
    }
    if (!d->ui.leProxy->text().isEmpty()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
314
        data.insert(QLatin1String(NM_OPENCONNECT_KEY_PROXY), d->ui.leProxy->text());
Jan Grulich's avatar
Jan Grulich committed
315
    }
316
    data.insert(NM_OPENCONNECT_KEY_REPORTED_OS, reportedOs);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
317
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_CSD_ENABLE), d->ui.chkAllowTrojan->isChecked() ? "yes" : "no");
Jan Grulich's avatar
Jan Grulich committed
318
    if (d->ui.leCsdWrapperScript->url().isValid()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
319
        data.insert(QLatin1String(NM_OPENCONNECT_KEY_CSD_WRAPPER), d->ui.leCsdWrapperScript->url().toLocalFile());
Jan Grulich's avatar
Jan Grulich committed
320
321
    }
    if (d->ui.leUserCert->url().isValid()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
322
        data.insert(QLatin1String(NM_OPENCONNECT_KEY_USERCERT), d->ui.leUserCert->url().toLocalFile());
Jan Grulich's avatar
Jan Grulich committed
323
324
    }
    if (d->ui.leUserPrivateKey->url().isValid()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
325
        data.insert(QLatin1String(NM_OPENCONNECT_KEY_PRIVKEY), d->ui.leUserPrivateKey->url().toLocalFile());
Jan Grulich's avatar
Jan Grulich committed
326
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
327
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID), d->ui.chkUseFsid->isChecked() ? "yes" : "no");
328
329
330
331
332
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT), d->ui.preventInvalidCert->isChecked() ? "yes" : "no");

    int index = d->tokenUi.cmbTokenMode->currentIndex();
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_TOKEN_MODE), d->tokenUi.cmbTokenMode->itemData(index, Qt::UserRole).toString());
    secrets.insert(QLatin1String(NM_OPENCONNECT_KEY_TOKEN_SECRET), d->tokenUi.leTokenSecret->text());
Lukáš Tinkl's avatar
Lukáš Tinkl committed
333

334
    // Restore previous flags, this is necessary for keeping secrets stored in KWallet
Jan Grulich's avatar
Jan Grulich committed
335
    for (const QString &key : d->setting->data().keys()) {
336
337
338
339
340
        if (key.contains(QLatin1String("-flags"))) {
            data.insert(key, d->setting->data().value(key));
        }
    }

341
342
343
344
345
346
347
348
    if (d->tokenUi.leTokenSecret->passwordOption() == PasswordField::StoreForAllUsers) {
        data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET"-flags", QString::number(NetworkManager::Setting::None));
    } else if (d->tokenUi.leTokenSecret->passwordOption() == PasswordField::StoreForUser) {
        data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET"-flags", QString::number(NetworkManager::Setting::AgentOwned));
    } else {
        data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET"-flags", QString::number(NetworkManager::Setting::NotSaved));
    }

Lukáš Tinkl's avatar
Lukáš Tinkl committed
349
350
351
352
353
354
    /* These are different for every login session, and should not be stored */
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_COOKIE"-flags"), QString::number(NetworkManager::Setting::NotSaved));
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_GWCERT"-flags"), QString::number(NetworkManager::Setting::NotSaved));
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_GATEWAY"-flags"), QString::number(NetworkManager::Setting::NotSaved));

    setting.setData(data);
355
    setting.setSecrets(secrets);
356

Lukáš Tinkl's avatar
Lukáš Tinkl committed
357
358
359
    return setting.toMap();
}

360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
void OpenconnectSettingWidget::restoreTokens()
{
    Q_D(const OpenconnectSettingWidget);

    d->tokenUi.cmbTokenMode->setCurrentIndex(d->token.tokenIndex);
    d->tokenUi.leTokenSecret->setText(d->token.tokenSecret);
}

void OpenconnectSettingWidget::saveTokens()
{
    Q_D(OpenconnectSettingWidget);

    d->token.tokenIndex = d->tokenUi.cmbTokenMode->currentIndex();
    d->token.tokenSecret = d->tokenUi.leTokenSecret->text();
}

void OpenconnectSettingWidget::showTokens()
{
    Q_D(OpenconnectSettingWidget);

    d->tokenDlg->show();
}

Lukáš Tinkl's avatar
Lukáš Tinkl committed
383
384
385
bool OpenconnectSettingWidget::isValid() const
{
    Q_D(const OpenconnectSettingWidget);
386

Lukáš Tinkl's avatar
Lukáš Tinkl committed
387
388
    return !d->ui.leGateway->text().isEmpty();
}