Warning, file /plasma/plasma-nm/vpn/openconnect/openconnectwidget.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2011 Ilia Kats <ilia-kats@gmx.de>
0003     SPDX-FileCopyrightText: 2013 Lukas Tinkl <ltinkl@redhat.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "openconnectwidget.h"
0009 #include <QDialog>
0010 #include <QStringList>
0011 #include <QUrl>
0012 
0013 #include "ui_openconnectprop.h"
0014 #include "ui_openconnecttoken.h"
0015 
0016 #include "nm-openconnect-service.h"
0017 #include <QDialogButtonBox>
0018 #include <QString>
0019 
0020 #include <openconnect.h>
0021 
0022 #if !OPENCONNECT_CHECK_VER(2, 1)
0023 #define openconnect_has_stoken_support() 0
0024 #endif
0025 #if !OPENCONNECT_CHECK_VER(2, 2)
0026 #define openconnect_has_oath_support() 0
0027 #endif
0028 #if !OPENCONNECT_CHECK_VER(5, 0)
0029 #define openconnect_has_yubioath_support() 0
0030 #endif
0031 
0032 using Token = struct {
0033     int tokenIndex;
0034     QString tokenSecret;
0035 };
0036 
0037 class OpenconnectSettingWidgetPrivate
0038 {
0039 public:
0040     Ui_OpenconnectProp ui;
0041     Ui::OpenConnectToken tokenUi;
0042     NetworkManager::VpnSetting::Ptr setting;
0043     QDialog *tokenDlg;
0044     Token token;
0045 };
0046 
0047 OpenconnectSettingWidget::OpenconnectSettingWidget(const NetworkManager::VpnSetting::Ptr &setting, QWidget *parent)
0048     : SettingWidget(setting, parent)
0049     , d_ptr(new OpenconnectSettingWidgetPrivate)
0050 {
0051     Q_D(OpenconnectSettingWidget);
0052 
0053     d->ui.setupUi(this);
0054     d->setting = setting;
0055 
0056     // Connect for validity check
0057     connect(d->ui.leGateway, &QLineEdit::textChanged, this, &OpenconnectSettingWidget::slotWidgetChanged);
0058 
0059     connect(d->ui.buTokens, &QPushButton::clicked, this, &OpenconnectSettingWidget::showTokens);
0060 
0061     d->tokenDlg = new QDialog(this);
0062     d->tokenUi.setupUi(d->tokenDlg);
0063     d->tokenUi.leTokenSecret->setPasswordModeEnabled(true);
0064     d->tokenUi.leTokenSecret->setPasswordOptionsEnabled(true);
0065     auto layout = new QVBoxLayout(d->tokenDlg);
0066     layout->addWidget(d->tokenDlg);
0067     d->tokenDlg->setLayout(layout);
0068     connect(d->tokenUi.buttonBox, &QDialogButtonBox::accepted, d->tokenDlg, &QDialog::accept);
0069     connect(d->tokenUi.buttonBox, &QDialogButtonBox::rejected, d->tokenDlg, &QDialog::reject);
0070     connect(d->tokenDlg, &QDialog::rejected, this, &OpenconnectSettingWidget::restoreTokens);
0071     connect(d->tokenDlg, &QDialog::accepted, this, &OpenconnectSettingWidget::saveTokens);
0072 
0073     connect(d->tokenUi.cmbTokenMode,
0074             QOverload<int>::of(&QComboBox::currentIndexChanged),
0075             this,
0076             QOverload<int>::of((&OpenconnectSettingWidget::handleTokenSecret)));
0077 
0078     // Connect for setting check
0079     watchChangedSetting();
0080 
0081     // Remove these from setting check:
0082     // Just popping up the tokenDlg changes nothing
0083     disconnect(d->ui.buTokens, &QPushButton::clicked, this, &SettingWidget::settingChanged);
0084     // User cancels means nothing should change here
0085     disconnect(d->tokenUi.buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &SettingWidget::settingChanged);
0086 
0087     d->tokenUi.gbToken->setVisible(initTokenGroup());
0088 
0089     KAcceleratorManager::manage(this);
0090 
0091     if (d->setting) {
0092         loadConfig(d->setting);
0093     }
0094 }
0095 
0096 OpenconnectSettingWidget::~OpenconnectSettingWidget()
0097 {
0098     delete d_ptr;
0099 }
0100 
0101 void OpenconnectSettingWidget::handleTokenSecret(int index)
0102 {
0103     Q_D(const OpenconnectSettingWidget);
0104 
0105     QVariant mode = d->tokenUi.cmbTokenMode->itemData(index);
0106     if (mode == QStringLiteral("disabled")) {
0107         d->tokenUi.leTokenSecret->setEnabled(false);
0108         d->tokenUi.leTokenSecret->setToolTip("No secrets needed.");
0109     } else if (mode == QStringLiteral("stokenrc")) {
0110         d->tokenUi.leTokenSecret->setEnabled(false);
0111         d->tokenUi.leTokenSecret->setToolTip("No secrets needed; will read them from ~/.stokenrc.");
0112     } else if (mode == QStringLiteral("manual")) {
0113         d->tokenUi.leTokenSecret->setToolTip("Insert the secret here. See the openconnect documentation for syntax.");
0114         d->tokenUi.leTokenSecret->setEnabled(true);
0115     } else if (mode == QStringLiteral("totp")) {
0116         d->tokenUi.leTokenSecret->setEnabled(true);
0117         d->tokenUi.leTokenSecret->setToolTip(
0118             "Insert the secret here, with a sha specification and a leading '0x' or 'base32:'. See the openconnect documentation for syntax.");
0119     } else if (mode == QStringLiteral("hotp")) {
0120         d->tokenUi.leTokenSecret->setEnabled(true);
0121         d->tokenUi.leTokenSecret->setToolTip(
0122             "Insert the secret here, with a leading '0x' or 'base32:' and a trailing counter after a comma (','), See the openconnect documentation for "
0123             "syntax.");
0124     } else if (mode == QStringLiteral("yubioath")) {
0125         d->tokenUi.leTokenSecret->setEnabled(true);
0126         d->tokenUi.leTokenSecret->setToolTip("Insert the token Id here, in the form company:username. Make sure to set your Yubikey in CCID mode");
0127     } else { // Not really needed now, but who knows?
0128         d->tokenUi.leTokenSecret->setEnabled(false);
0129         d->tokenUi.leTokenSecret->setToolTip("");
0130     }
0131 }
0132 
0133 bool OpenconnectSettingWidget::initTokenGroup()
0134 {
0135     Q_D(const OpenconnectSettingWidget);
0136 
0137     int validRows = 0;
0138     QStringList tokenLabelList{
0139         "Disabled",
0140         "RSA SecurID — read from ~/.stokenrc",
0141         "RSA SecurID — manually entered",
0142         "TOTP — manually entered",
0143         "HOTP — manually entered",
0144         "Yubikey",
0145     };
0146     QStringList tokenModeList{
0147         "disabled",
0148         "stokenrc",
0149         "manual",
0150         "totp",
0151         "hotp",
0152         "yubioath",
0153     };
0154     QComboBox *combo = d->tokenUi.cmbTokenMode;
0155 
0156     combo->addItem(tokenLabelList[validRows]);
0157     combo->setItemData(validRows, tokenModeList[validRows], Qt::UserRole);
0158     validRows++;
0159     if (openconnect_has_stoken_support()) {
0160         for (; validRows < 3; validRows++) {
0161             combo->addItem(tokenLabelList[validRows]);
0162             combo->setItemData(validRows, tokenModeList[validRows], Qt::UserRole);
0163         }
0164     }
0165     if (openconnect_has_oath_support()) {
0166         combo->addItem(tokenLabelList[validRows]);
0167         combo->setItemData(validRows, tokenModeList[validRows], Qt::UserRole);
0168         validRows++;
0169         if (OPENCONNECT_CHECK_VER(3, 4)) {
0170             combo->addItem(tokenLabelList[validRows]);
0171             combo->setItemData(validRows, tokenModeList[validRows], Qt::UserRole);
0172             validRows++;
0173         }
0174     }
0175     if (openconnect_has_yubioath_support()) {
0176         combo->addItem(tokenLabelList[validRows]);
0177         combo->setItemData(validRows, tokenModeList[validRows], Qt::UserRole);
0178     }
0179     return validRows > 0;
0180 }
0181 
0182 void OpenconnectSettingWidget::loadConfig(const NetworkManager::Setting::Ptr &setting)
0183 {
0184     Q_D(OpenconnectSettingWidget);
0185 
0186     // General settings
0187     const NMStringMap dataMap = setting.staticCast<NetworkManager::VpnSetting>()->data();
0188 
0189     int cmbProtocolIndex;
0190     // No value corresponds to "anyconnect", matching GNOME and the openconnect binary itself.
0191     if (!dataMap.contains(NM_OPENCONNECT_KEY_PROTOCOL) || dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("anyconnect")) {
0192         cmbProtocolIndex = 0;
0193     } else if (dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("nc")) {
0194         cmbProtocolIndex = 1;
0195     } else if (dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("gp")) {
0196         cmbProtocolIndex = 2;
0197     } else if (dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("pulse")) {
0198         cmbProtocolIndex = 3; // pulse, Pulse Connect Secure
0199     } else if (dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("f5")) {
0200         cmbProtocolIndex = 4; // F5 BIG-IP SSL VPN
0201     } else if (dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("fortinet")) {
0202         cmbProtocolIndex = 5; // Fortinet SSL VPN
0203     } else if (dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("array")) {
0204         cmbProtocolIndex = 6; // Array SSL VPN
0205     } else {
0206         cmbProtocolIndex = 3; // pulse, Pulse Connect Secure is the default
0207     }
0208 
0209     int cmbReportedOsIndex;
0210     if (!dataMap.contains(NM_OPENCONNECT_KEY_REPORTED_OS)) {
0211         cmbReportedOsIndex = 0;
0212     } else if (dataMap[NM_OPENCONNECT_KEY_REPORTED_OS] == QLatin1String("linux")) {
0213         cmbReportedOsIndex = 1;
0214     } else if (dataMap[NM_OPENCONNECT_KEY_REPORTED_OS] == QLatin1String("linux-64")) {
0215         cmbReportedOsIndex = 2;
0216     } else if (dataMap[NM_OPENCONNECT_KEY_REPORTED_OS] == QLatin1String("win")) {
0217         cmbReportedOsIndex = 3;
0218     } else if (dataMap[NM_OPENCONNECT_KEY_REPORTED_OS] == QLatin1String("mac-intel")) {
0219         cmbReportedOsIndex = 4;
0220     } else if (dataMap[NM_OPENCONNECT_KEY_REPORTED_OS] == QLatin1String("android")) {
0221         cmbReportedOsIndex = 5;
0222     } else {
0223         cmbReportedOsIndex = 6; // apple-ios
0224     }
0225 
0226     d->ui.cmbProtocol->setCurrentIndex(cmbProtocolIndex);
0227     d->ui.leGateway->setText(dataMap[NM_OPENCONNECT_KEY_GATEWAY]);
0228     d->ui.leCaCertificate->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_CACERT]));
0229     d->ui.leProxy->setText(dataMap[NM_OPENCONNECT_KEY_PROXY]);
0230     d->ui.leUserAgent->setText(dataMap[NM_OPENCONNECT_KEY_USERAGENT]);
0231     d->ui.cmbReportedOs->setCurrentIndex(cmbReportedOsIndex);
0232     d->ui.chkAllowTrojan->setChecked(dataMap[NM_OPENCONNECT_KEY_CSD_ENABLE] == "yes");
0233     d->ui.leCsdWrapperScript->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_CSD_WRAPPER]));
0234     d->ui.leUserCert->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_USERCERT]));
0235     d->ui.leUserPrivateKey->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_PRIVKEY]));
0236     d->ui.chkUseFsid->setChecked(dataMap[NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID] == "yes");
0237     d->ui.preventInvalidCert->setChecked(dataMap[NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT] == "yes");
0238 
0239     // Token settings
0240     const NetworkManager::Setting::SecretFlags tokenSecretFlag =
0241         static_cast<NetworkManager::Setting::SecretFlags>(dataMap.value(NM_OPENCONNECT_KEY_TOKEN_SECRET "-flags").toInt());
0242     if (tokenSecretFlag == NetworkManager::Setting::None) {
0243         d->tokenUi.leTokenSecret->setPasswordOption(PasswordField::StoreForAllUsers);
0244     } else if (tokenSecretFlag == NetworkManager::Setting::AgentOwned) {
0245         d->tokenUi.leTokenSecret->setPasswordOption(PasswordField::StoreForUser);
0246     } else {
0247         d->tokenUi.leTokenSecret->setPasswordOption(PasswordField::AlwaysAsk);
0248     }
0249     for (int index = 0; index < d->tokenUi.cmbTokenMode->count(); index++) {
0250         if (d->tokenUi.cmbTokenMode->itemData(index, Qt::UserRole) == dataMap[NM_OPENCONNECT_KEY_TOKEN_MODE]) {
0251             d->tokenUi.cmbTokenMode->setCurrentIndex(index);
0252             d->token.tokenIndex = index;
0253             if (index > 1) {
0254                 loadSecrets(d->setting);
0255             }
0256             break;
0257         }
0258     }
0259 }
0260 
0261 void OpenconnectSettingWidget::loadSecrets(const NetworkManager::Setting::Ptr &setting)
0262 {
0263     Q_D(OpenconnectSettingWidget);
0264 
0265     NetworkManager::VpnSetting::Ptr vpnSetting = setting.staticCast<NetworkManager::VpnSetting>();
0266 
0267     if (vpnSetting) {
0268         const NMStringMap secrets = vpnSetting->secrets();
0269         d->tokenUi.leTokenSecret->setText(secrets.value(NM_OPENCONNECT_KEY_TOKEN_SECRET));
0270         d->token.tokenSecret = secrets.value(NM_OPENCONNECT_KEY_TOKEN_SECRET);
0271     }
0272 }
0273 
0274 QVariantMap OpenconnectSettingWidget::setting() const
0275 {
0276     Q_D(const OpenconnectSettingWidget);
0277 
0278     NetworkManager::VpnSetting setting;
0279     setting.setServiceType(QLatin1String(NM_DBUS_SERVICE_OPENCONNECT));
0280 
0281     NMStringMap data;
0282     NMStringMap secrets;
0283     QString protocol;
0284     switch (d->ui.cmbProtocol->currentIndex()) {
0285     case 0:
0286         protocol = QLatin1String("anyconnect");
0287         break;
0288     case 1:
0289         protocol = QLatin1String("nc");
0290         break;
0291     case 2:
0292         protocol = QLatin1String("gp");
0293         break;
0294     case 3:
0295         protocol = QLatin1String("pulse");
0296         break;
0297     case 4:
0298         protocol = QLatin1String("f5");
0299         break;
0300     case 5:
0301         protocol = QLatin1String("fortinet");
0302         break;
0303     case 6:
0304         protocol = QLatin1String("array");
0305         break;
0306     default:
0307         protocol = QLatin1String("pulse");
0308     }
0309 
0310     QString reportedOs;
0311     switch (d->ui.cmbReportedOs->currentIndex()) {
0312     case 0:
0313         reportedOs = QString();
0314         break;
0315     case 1:
0316         reportedOs = QLatin1String("linux");
0317         break;
0318     case 2:
0319         reportedOs = QLatin1String("linux-64");
0320         break;
0321     case 3:
0322         reportedOs = QLatin1String("win");
0323         break;
0324     case 4:
0325         reportedOs = QLatin1String("mac-intel");
0326         break;
0327     case 5:
0328         reportedOs = QLatin1String("android");
0329         break;
0330     default:
0331         reportedOs = QLatin1String("apple-ios");
0332     }
0333 
0334     data.insert(NM_OPENCONNECT_KEY_PROTOCOL, protocol);
0335     data.insert(QLatin1String(NM_OPENCONNECT_KEY_GATEWAY), d->ui.leGateway->text());
0336     if (d->ui.leCaCertificate->url().isValid()) {
0337         data.insert(QLatin1String(NM_OPENCONNECT_KEY_CACERT), d->ui.leCaCertificate->url().toLocalFile());
0338     }
0339     if (!d->ui.leProxy->text().isEmpty()) {
0340         data.insert(QLatin1String(NM_OPENCONNECT_KEY_PROXY), d->ui.leProxy->text());
0341     }
0342     if (!d->ui.leUserAgent->text().isEmpty()) {
0343         data.insert(QLatin1String(NM_OPENCONNECT_KEY_USERAGENT), d->ui.leUserAgent->text());
0344     }
0345     data.insert(NM_OPENCONNECT_KEY_REPORTED_OS, reportedOs);
0346     data.insert(QLatin1String(NM_OPENCONNECT_KEY_CSD_ENABLE), d->ui.chkAllowTrojan->isChecked() ? "yes" : "no");
0347     if (d->ui.leCsdWrapperScript->url().isValid()) {
0348         data.insert(QLatin1String(NM_OPENCONNECT_KEY_CSD_WRAPPER), d->ui.leCsdWrapperScript->url().toLocalFile());
0349     }
0350     if (d->ui.leUserCert->url().isValid()) {
0351         data.insert(QLatin1String(NM_OPENCONNECT_KEY_USERCERT), d->ui.leUserCert->url().toLocalFile());
0352     }
0353     if (d->ui.leUserPrivateKey->url().isValid()) {
0354         data.insert(QLatin1String(NM_OPENCONNECT_KEY_PRIVKEY), d->ui.leUserPrivateKey->url().toLocalFile());
0355     }
0356     data.insert(QLatin1String(NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID), d->ui.chkUseFsid->isChecked() ? "yes" : "no");
0357     data.insert(QLatin1String(NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT), d->ui.preventInvalidCert->isChecked() ? "yes" : "no");
0358 
0359     int index = d->tokenUi.cmbTokenMode->currentIndex();
0360     data.insert(QLatin1String(NM_OPENCONNECT_KEY_TOKEN_MODE), d->tokenUi.cmbTokenMode->itemData(index, Qt::UserRole).toString());
0361     secrets.insert(QLatin1String(NM_OPENCONNECT_KEY_TOKEN_SECRET), d->tokenUi.leTokenSecret->text());
0362 
0363     // Restore previous flags, this is necessary for keeping secrets stored in KWallet
0364     for (const QString &key : d->setting->data().keys()) {
0365         if (key.contains(QLatin1String("-flags"))) {
0366             data.insert(key, d->setting->data().value(key));
0367         }
0368     }
0369 
0370     if (d->tokenUi.leTokenSecret->passwordOption() == PasswordField::StoreForAllUsers) {
0371         data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET "-flags", QString::number(NetworkManager::Setting::None));
0372     } else if (d->tokenUi.leTokenSecret->passwordOption() == PasswordField::StoreForUser) {
0373         data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET "-flags", QString::number(NetworkManager::Setting::AgentOwned));
0374     } else {
0375         data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET "-flags", QString::number(NetworkManager::Setting::NotSaved));
0376     }
0377 
0378     /* These are different for every login session, and should not be stored */
0379     data.insert(QLatin1String(NM_OPENCONNECT_KEY_COOKIE "-flags"), QString::number(NetworkManager::Setting::NotSaved));
0380     data.insert(QLatin1String(NM_OPENCONNECT_KEY_GWCERT "-flags"), QString::number(NetworkManager::Setting::NotSaved));
0381     data.insert(QLatin1String(NM_OPENCONNECT_KEY_GATEWAY "-flags"), QString::number(NetworkManager::Setting::NotSaved));
0382 
0383     setting.setData(data);
0384     setting.setSecrets(secrets);
0385 
0386     return setting.toMap();
0387 }
0388 
0389 void OpenconnectSettingWidget::restoreTokens()
0390 {
0391     Q_D(const OpenconnectSettingWidget);
0392 
0393     d->tokenUi.cmbTokenMode->setCurrentIndex(d->token.tokenIndex);
0394     d->tokenUi.leTokenSecret->setText(d->token.tokenSecret);
0395 }
0396 
0397 void OpenconnectSettingWidget::saveTokens()
0398 {
0399     Q_D(OpenconnectSettingWidget);
0400 
0401     d->token.tokenIndex = d->tokenUi.cmbTokenMode->currentIndex();
0402     d->token.tokenSecret = d->tokenUi.leTokenSecret->text();
0403 }
0404 
0405 void OpenconnectSettingWidget::showTokens()
0406 {
0407     Q_D(OpenconnectSettingWidget);
0408 
0409     d->tokenDlg->show();
0410 }
0411 
0412 bool OpenconnectSettingWidget::isValid() const
0413 {
0414     Q_D(const OpenconnectSettingWidget);
0415 
0416     return !d->ui.leGateway->text().isEmpty();
0417 }