File indexing completed on 2024-05-12 05:36:38
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.leVersionString->setText(dataMap[NM_OPENCONNECT_KEY_VERSION_STRING]); 0232 d->ui.cmbReportedOs->setCurrentIndex(cmbReportedOsIndex); 0233 d->ui.chkAllowTrojan->setChecked(dataMap[NM_OPENCONNECT_KEY_CSD_ENABLE] == "yes"); 0234 d->ui.leCsdWrapperScript->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_CSD_WRAPPER])); 0235 d->ui.leUserCert->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_USERCERT])); 0236 d->ui.leUserPrivateKey->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_PRIVKEY])); 0237 d->ui.chkUseFsid->setChecked(dataMap[NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID] == "yes"); 0238 d->ui.preventInvalidCert->setChecked(dataMap[NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT] == "yes"); 0239 0240 // Token settings 0241 const NetworkManager::Setting::SecretFlags tokenSecretFlag = 0242 static_cast<NetworkManager::Setting::SecretFlags>(dataMap.value(NM_OPENCONNECT_KEY_TOKEN_SECRET "-flags").toInt()); 0243 if (tokenSecretFlag == NetworkManager::Setting::None) { 0244 d->tokenUi.leTokenSecret->setPasswordOption(PasswordField::StoreForAllUsers); 0245 } else if (tokenSecretFlag == NetworkManager::Setting::AgentOwned) { 0246 d->tokenUi.leTokenSecret->setPasswordOption(PasswordField::StoreForUser); 0247 } else { 0248 d->tokenUi.leTokenSecret->setPasswordOption(PasswordField::AlwaysAsk); 0249 } 0250 for (int index = 0; index < d->tokenUi.cmbTokenMode->count(); index++) { 0251 if (d->tokenUi.cmbTokenMode->itemData(index, Qt::UserRole) == dataMap[NM_OPENCONNECT_KEY_TOKEN_MODE]) { 0252 d->tokenUi.cmbTokenMode->setCurrentIndex(index); 0253 d->token.tokenIndex = index; 0254 if (index > 1) { 0255 loadSecrets(d->setting); 0256 } 0257 break; 0258 } 0259 } 0260 } 0261 0262 void OpenconnectSettingWidget::loadSecrets(const NetworkManager::Setting::Ptr &setting) 0263 { 0264 Q_D(OpenconnectSettingWidget); 0265 0266 NetworkManager::VpnSetting::Ptr vpnSetting = setting.staticCast<NetworkManager::VpnSetting>(); 0267 0268 if (vpnSetting) { 0269 const NMStringMap secrets = vpnSetting->secrets(); 0270 d->tokenUi.leTokenSecret->setText(secrets.value(NM_OPENCONNECT_KEY_TOKEN_SECRET)); 0271 d->token.tokenSecret = secrets.value(NM_OPENCONNECT_KEY_TOKEN_SECRET); 0272 } 0273 } 0274 0275 QVariantMap OpenconnectSettingWidget::setting() const 0276 { 0277 Q_D(const OpenconnectSettingWidget); 0278 0279 NetworkManager::VpnSetting setting; 0280 setting.setServiceType(QLatin1String(NM_DBUS_SERVICE_OPENCONNECT)); 0281 0282 NMStringMap data; 0283 NMStringMap secrets; 0284 QString protocol; 0285 switch (d->ui.cmbProtocol->currentIndex()) { 0286 case 0: 0287 protocol = QLatin1String("anyconnect"); 0288 break; 0289 case 1: 0290 protocol = QLatin1String("nc"); 0291 break; 0292 case 2: 0293 protocol = QLatin1String("gp"); 0294 break; 0295 case 3: 0296 protocol = QLatin1String("pulse"); 0297 break; 0298 case 4: 0299 protocol = QLatin1String("f5"); 0300 break; 0301 case 5: 0302 protocol = QLatin1String("fortinet"); 0303 break; 0304 case 6: 0305 protocol = QLatin1String("array"); 0306 break; 0307 default: 0308 protocol = QLatin1String("pulse"); 0309 } 0310 0311 QString reportedOs; 0312 switch (d->ui.cmbReportedOs->currentIndex()) { 0313 case 0: 0314 reportedOs = QString(); 0315 break; 0316 case 1: 0317 reportedOs = QLatin1String("linux"); 0318 break; 0319 case 2: 0320 reportedOs = QLatin1String("linux-64"); 0321 break; 0322 case 3: 0323 reportedOs = QLatin1String("win"); 0324 break; 0325 case 4: 0326 reportedOs = QLatin1String("mac-intel"); 0327 break; 0328 case 5: 0329 reportedOs = QLatin1String("android"); 0330 break; 0331 default: 0332 reportedOs = QLatin1String("apple-ios"); 0333 } 0334 0335 data.insert(NM_OPENCONNECT_KEY_PROTOCOL, protocol); 0336 data.insert(QLatin1String(NM_OPENCONNECT_KEY_GATEWAY), d->ui.leGateway->text()); 0337 if (d->ui.leCaCertificate->url().isValid()) { 0338 data.insert(QLatin1String(NM_OPENCONNECT_KEY_CACERT), d->ui.leCaCertificate->url().toLocalFile()); 0339 } 0340 if (!d->ui.leProxy->text().isEmpty()) { 0341 data.insert(QLatin1String(NM_OPENCONNECT_KEY_PROXY), d->ui.leProxy->text()); 0342 } 0343 if (!d->ui.leUserAgent->text().isEmpty()) { 0344 data.insert(QLatin1String(NM_OPENCONNECT_KEY_USERAGENT), d->ui.leUserAgent->text()); 0345 } 0346 if (!d->ui.leVersionString->text().isEmpty()) { 0347 data.insert(QLatin1String(NM_OPENCONNECT_KEY_VERSION_STRING), d->ui.leVersionString->text()); 0348 } 0349 data.insert(NM_OPENCONNECT_KEY_REPORTED_OS, reportedOs); 0350 data.insert(QLatin1String(NM_OPENCONNECT_KEY_CSD_ENABLE), d->ui.chkAllowTrojan->isChecked() ? "yes" : "no"); 0351 if (d->ui.leCsdWrapperScript->url().isValid()) { 0352 data.insert(QLatin1String(NM_OPENCONNECT_KEY_CSD_WRAPPER), d->ui.leCsdWrapperScript->url().toLocalFile()); 0353 } 0354 if (d->ui.leUserCert->url().isValid()) { 0355 data.insert(QLatin1String(NM_OPENCONNECT_KEY_USERCERT), d->ui.leUserCert->url().toLocalFile()); 0356 } 0357 if (d->ui.leUserPrivateKey->url().isValid()) { 0358 data.insert(QLatin1String(NM_OPENCONNECT_KEY_PRIVKEY), d->ui.leUserPrivateKey->url().toLocalFile()); 0359 } 0360 data.insert(QLatin1String(NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID), d->ui.chkUseFsid->isChecked() ? "yes" : "no"); 0361 data.insert(QLatin1String(NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT), d->ui.preventInvalidCert->isChecked() ? "yes" : "no"); 0362 0363 int index = d->tokenUi.cmbTokenMode->currentIndex(); 0364 data.insert(QLatin1String(NM_OPENCONNECT_KEY_TOKEN_MODE), d->tokenUi.cmbTokenMode->itemData(index, Qt::UserRole).toString()); 0365 secrets.insert(QLatin1String(NM_OPENCONNECT_KEY_TOKEN_SECRET), d->tokenUi.leTokenSecret->text()); 0366 0367 // Restore previous flags, this is necessary for keeping secrets stored in KWallet 0368 for (const QString &key : d->setting->data().keys()) { 0369 if (key.contains(QLatin1String("-flags"))) { 0370 data.insert(key, d->setting->data().value(key)); 0371 } 0372 } 0373 0374 if (d->tokenUi.leTokenSecret->passwordOption() == PasswordField::StoreForAllUsers) { 0375 data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET "-flags", QString::number(NetworkManager::Setting::None)); 0376 } else if (d->tokenUi.leTokenSecret->passwordOption() == PasswordField::StoreForUser) { 0377 data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET "-flags", QString::number(NetworkManager::Setting::AgentOwned)); 0378 } else { 0379 data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET "-flags", QString::number(NetworkManager::Setting::NotSaved)); 0380 } 0381 0382 /* These are different for every login session, and should not be stored */ 0383 data.insert(QLatin1String(NM_OPENCONNECT_KEY_COOKIE "-flags"), QString::number(NetworkManager::Setting::NotSaved)); 0384 data.insert(QLatin1String(NM_OPENCONNECT_KEY_GWCERT "-flags"), QString::number(NetworkManager::Setting::NotSaved)); 0385 data.insert(QLatin1String(NM_OPENCONNECT_KEY_GATEWAY "-flags"), QString::number(NetworkManager::Setting::NotSaved)); 0386 0387 setting.setData(data); 0388 setting.setSecrets(secrets); 0389 0390 return setting.toMap(); 0391 } 0392 0393 void OpenconnectSettingWidget::restoreTokens() 0394 { 0395 Q_D(const OpenconnectSettingWidget); 0396 0397 d->tokenUi.cmbTokenMode->setCurrentIndex(d->token.tokenIndex); 0398 d->tokenUi.leTokenSecret->setText(d->token.tokenSecret); 0399 } 0400 0401 void OpenconnectSettingWidget::saveTokens() 0402 { 0403 Q_D(OpenconnectSettingWidget); 0404 0405 d->token.tokenIndex = d->tokenUi.cmbTokenMode->currentIndex(); 0406 d->token.tokenSecret = d->tokenUi.leTokenSecret->text(); 0407 } 0408 0409 void OpenconnectSettingWidget::showTokens() 0410 { 0411 Q_D(OpenconnectSettingWidget); 0412 0413 d->tokenDlg->show(); 0414 } 0415 0416 bool OpenconnectSettingWidget::isValid() const 0417 { 0418 Q_D(const OpenconnectSettingWidget); 0419 0420 return !d->ui.leGateway->text().isEmpty(); 0421 } 0422 0423 #include "moc_openconnectwidget.cpp"