File indexing completed on 2025-04-27 14:24:19
0001 /* 0002 SPDX-FileCopyrightText: 2011 Ilia Kats <ilia-kats@gmx.net> 0003 SPDX-FileCopyrightText: 2013 Lukáš 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 "openconnectauth.h" 0009 #include "openconnectauthworkerthread.h" 0010 #include "ui_openconnectauth.h" 0011 0012 #include "passwordfield.h" 0013 #include "plasma_nm_openconnect.h" 0014 0015 #include <QComboBox> 0016 #include <QCryptographicHash> 0017 #include <QDialog> 0018 #include <QDialogButtonBox> 0019 #include <QDomDocument> 0020 #include <QEventLoop> 0021 #include <QFile> 0022 #include <QFormLayout> 0023 #include <QIcon> 0024 #include <QLabel> 0025 #include <QMutex> 0026 #include <QPointer> 0027 #include <QPushButton> 0028 #include <QTimer> 0029 #include <QWaitCondition> 0030 0031 #include <KLocalizedString> 0032 0033 #include "nm-openconnect-service.h" 0034 0035 #include <cstdarg> 0036 0037 extern "C" { 0038 #include <cstring> 0039 #include <fcntl.h> 0040 #include <unistd.h> 0041 } 0042 0043 #if !OPENCONNECT_CHECK_VER(2, 1) 0044 #define __openconnect_set_token_mode(...) -EOPNOTSUPP 0045 #elif !OPENCONNECT_CHECK_VER(2, 2) 0046 #define __openconnect_set_token_mode(vpninfo, mode, secret) openconnect_set_stoken_mode(vpninfo, 1, secret) 0047 #else 0048 #define __openconnect_set_token_mode openconnect_set_token_mode 0049 #endif 0050 0051 #if OPENCONNECT_CHECK_VER(3, 4) 0052 static int updateToken(void *, const char *); 0053 #endif 0054 0055 // name/address: IP/domain name of the host (OpenConnect accepts both, so no difference here) 0056 // group: user group on the server 0057 using VPNHost = struct { 0058 QString name; 0059 QString group; 0060 QString address; 0061 }; 0062 0063 using Token = struct { 0064 oc_token_mode_t tokenMode; 0065 QByteArray tokenSecret; 0066 }; 0067 0068 class OpenconnectAuthWidgetPrivate 0069 { 0070 public: 0071 Ui_OpenconnectAuth ui; 0072 NetworkManager::VpnSetting::Ptr setting; 0073 struct openconnect_info *vpninfo; 0074 NMStringMap secrets; 0075 NMStringMap tmpSecrets; 0076 QMutex mutex; 0077 QWaitCondition workerWaiting; 0078 OpenconnectAuthWorkerThread *worker; 0079 QList<VPNHost> hosts; 0080 bool userQuit; 0081 bool formGroupChanged; 0082 int cancelPipes[2]; 0083 QList<QPair<QString, int>> serverLog; 0084 int passwordFormIndex; 0085 QByteArray tokenMode; 0086 Token token; 0087 0088 enum LogLevels { Error = 0, Info, Debug, Trace }; 0089 }; 0090 0091 OpenconnectAuthWidget::OpenconnectAuthWidget(const NetworkManager::VpnSetting::Ptr &setting, const QStringList &hints, QWidget *parent) 0092 : SettingWidget(setting, hints, parent) 0093 , d_ptr(new OpenconnectAuthWidgetPrivate) 0094 { 0095 Q_D(OpenconnectAuthWidget); 0096 d->setting = setting; 0097 d->ui.setupUi(this); 0098 d->userQuit = false; 0099 d->formGroupChanged = false; 0100 0101 if (pipe2(d->cancelPipes, O_NONBLOCK | O_CLOEXEC)) { 0102 // Should never happen. Just don't do real cancellation if it does 0103 d->cancelPipes[0] = -1; 0104 d->cancelPipes[1] = -1; 0105 } 0106 0107 connect(d->ui.cmbLogLevel, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &OpenconnectAuthWidget::logLevelChanged); 0108 connect(d->ui.viewServerLog, &QCheckBox::toggled, this, &OpenconnectAuthWidget::viewServerLogToggled); 0109 connect(d->ui.btnConnect, &QPushButton::clicked, this, &OpenconnectAuthWidget::connectHost); 0110 0111 d->ui.cmbLogLevel->setCurrentIndex(OpenconnectAuthWidgetPrivate::Debug); 0112 d->ui.btnConnect->setIcon(QIcon::fromTheme("network-connect")); 0113 d->ui.viewServerLog->setChecked(false); 0114 0115 d->worker = new OpenconnectAuthWorkerThread(&d->mutex, &d->workerWaiting, &d->userQuit, &d->formGroupChanged, d->cancelPipes[0]); 0116 0117 // gets the pointer to struct openconnect_info (defined in openconnect.h), which contains data that OpenConnect needs, 0118 // and which needs to be populated with settings we get from NM, like host, certificate or private key 0119 d->vpninfo = d->worker->getOpenconnectInfo(); 0120 0121 connect(d->worker, 0122 QOverload<const QString &, const QString &, const QString &, bool *>::of(&OpenconnectAuthWorkerThread::validatePeerCert), 0123 this, 0124 &OpenconnectAuthWidget::validatePeerCert); 0125 connect(d->worker, &OpenconnectAuthWorkerThread::processAuthForm, this, &OpenconnectAuthWidget::processAuthForm); 0126 connect(d->worker, &OpenconnectAuthWorkerThread::updateLog, this, &OpenconnectAuthWidget::updateLog); 0127 connect(d->worker, QOverload<const QString &>::of(&OpenconnectAuthWorkerThread::writeNewConfig), this, &OpenconnectAuthWidget::writeNewConfig); 0128 connect(d->worker, &OpenconnectAuthWorkerThread::cookieObtained, this, &OpenconnectAuthWidget::workerFinished); 0129 connect(d->worker, &OpenconnectAuthWorkerThread::initTokens, this, &OpenconnectAuthWidget::initTokens); 0130 0131 readConfig(); 0132 readSecrets(); 0133 0134 #if OPENCONNECT_CHECK_VER(3, 4) 0135 openconnect_set_token_callbacks(d->vpninfo, &d->secrets, NULL, &updateToken); 0136 #endif 0137 0138 // This might be set by readSecrets() so don't connect it until now 0139 connect(d->ui.cmbHosts, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &OpenconnectAuthWidget::connectHost); 0140 0141 KAcceleratorManager::manage(this); 0142 } 0143 0144 OpenconnectAuthWidget::~OpenconnectAuthWidget() 0145 { 0146 Q_D(OpenconnectAuthWidget); 0147 d->userQuit = true; 0148 if (write(d->cancelPipes[1], "x", 1)) { 0149 // not a lot we can do 0150 } 0151 d->workerWaiting.wakeAll(); 0152 d->worker->wait(); 0153 ::close(d->cancelPipes[0]); 0154 ::close(d->cancelPipes[1]); 0155 deleteAllFromLayout(d->ui.loginBoxLayout); 0156 delete d->worker; 0157 delete d; 0158 } 0159 0160 void OpenconnectAuthWidget::readConfig() 0161 { 0162 Q_D(OpenconnectAuthWidget); 0163 0164 const NMStringMap dataMap = d->setting->data(); 0165 0166 if (!dataMap[NM_OPENCONNECT_KEY_GATEWAY].isEmpty()) { 0167 const QString gw = dataMap[NM_OPENCONNECT_KEY_GATEWAY]; 0168 VPNHost host; 0169 const int index = gw.indexOf(QLatin1Char('/')); 0170 if (index > -1) { 0171 host.name = host.address = gw.left(index); 0172 host.group = gw.right(gw.length() - index - 1); 0173 } else { 0174 host.name = host.address = gw; 0175 } 0176 d->hosts.append(host); 0177 } 0178 if (!dataMap[NM_OPENCONNECT_KEY_CACERT].isEmpty()) { 0179 const QByteArray crt = QFile::encodeName(dataMap[NM_OPENCONNECT_KEY_CACERT]); 0180 openconnect_set_cafile(d->vpninfo, OC3DUP(crt.data())); 0181 } 0182 if (dataMap[NM_OPENCONNECT_KEY_CSD_ENABLE] == "yes") { 0183 char *wrapper; 0184 wrapper = nullptr; 0185 if (!dataMap[NM_OPENCONNECT_KEY_CSD_WRAPPER].isEmpty()) { 0186 const QByteArray wrapperScript = QFile::encodeName(dataMap[NM_OPENCONNECT_KEY_CSD_WRAPPER]); 0187 wrapper = strdup(wrapperScript.data()); 0188 } 0189 openconnect_setup_csd(d->vpninfo, getuid(), 1, wrapper); 0190 } 0191 if (!dataMap[NM_OPENCONNECT_KEY_PROXY].isEmpty()) { 0192 const QByteArray proxy = QFile::encodeName(dataMap[NM_OPENCONNECT_KEY_PROXY]); 0193 openconnect_set_http_proxy(d->vpninfo, OC3DUP(proxy.data())); 0194 } 0195 #if OPENCONNECT_CHECK_VER(5, 8) 0196 if (!dataMap[NM_OPENCONNECT_KEY_USERAGENT].isEmpty()) { 0197 const QByteArray useragent = QFile::encodeName(dataMap[NM_OPENCONNECT_KEY_USERAGENT]); 0198 openconnect_set_useragent(d->vpninfo, OC3DUP(useragent.data())); 0199 } 0200 #endif 0201 if (!dataMap[NM_OPENCONNECT_KEY_USERCERT].isEmpty()) { 0202 const QByteArray crt = QFile::encodeName(dataMap[NM_OPENCONNECT_KEY_USERCERT]); 0203 const QByteArray key = QFile::encodeName(dataMap[NM_OPENCONNECT_KEY_PRIVKEY]); 0204 openconnect_set_client_cert(d->vpninfo, OC3DUP(crt.data()), OC3DUP(key.isEmpty() ? nullptr : key.data())); 0205 0206 if (!crt.isEmpty() && dataMap[NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID] == "yes") { 0207 openconnect_passphrase_from_fsid(d->vpninfo); 0208 } 0209 } 0210 if (!dataMap[NM_OPENCONNECT_KEY_PROTOCOL].isEmpty()) { 0211 const QString protocol = dataMap[NM_OPENCONNECT_KEY_PROTOCOL]; 0212 openconnect_set_protocol(d->vpninfo, OC3DUP(protocol == "juniper" ? "nc" : protocol.toUtf8().data())); 0213 } 0214 if (!dataMap[NM_OPENCONNECT_KEY_REPORTED_OS].isEmpty()) { 0215 const QString reportedOs = dataMap[NM_OPENCONNECT_KEY_REPORTED_OS]; 0216 openconnect_set_reported_os(d->vpninfo, reportedOs.toUtf8().data()); 0217 } 0218 0219 d->tokenMode = dataMap[NM_OPENCONNECT_KEY_TOKEN_MODE].toUtf8(); 0220 } 0221 0222 void OpenconnectAuthWidget::readSecrets() 0223 { 0224 Q_D(OpenconnectAuthWidget); 0225 0226 d->secrets = d->setting->secrets(); 0227 0228 if (!d->secrets["xmlconfig"].isEmpty()) { 0229 const QByteArray config = QByteArray::fromBase64(d->secrets["xmlconfig"].toLatin1()); 0230 0231 QCryptographicHash hash(QCryptographicHash::Sha1); 0232 hash.addData(config.data(), config.size()); 0233 const char *sha1_text = hash.result().toHex(); 0234 openconnect_set_xmlsha1(d->vpninfo, (char *)sha1_text, strlen(sha1_text) + 1); 0235 0236 QDomDocument xmlconfig; 0237 xmlconfig.setContent(config); 0238 const QDomNode anyConnectProfile = xmlconfig.elementsByTagName(QLatin1String("AnyConnectProfile")).at(0); 0239 bool matchedGw = false; 0240 const QDomNode serverList = anyConnectProfile.firstChildElement(QLatin1String("ServerList")); 0241 for (QDomElement entry = serverList.firstChildElement(QLatin1String("HostEntry")); !entry.isNull(); 0242 entry = entry.nextSiblingElement(QLatin1String("HostEntry"))) { 0243 VPNHost host; 0244 host.name = entry.firstChildElement(QLatin1String("HostName")).text(); 0245 host.group = entry.firstChildElement(QLatin1String("UserGroup")).text(); 0246 host.address = entry.firstChildElement(QLatin1String("HostAddress")).text(); 0247 // We added the originally configured host in readConfig(). But if 0248 // it matches one of the ones in the XML config (as presumably it 0249 // should), remove the original and use the one with the pretty name. 0250 if (!matchedGw && host.address == d->hosts.at(0).address) { 0251 d->hosts.removeFirst(); 0252 matchedGw = true; 0253 } 0254 d->hosts.append(host); 0255 } 0256 } 0257 0258 for (int i = 0; i < d->hosts.size(); i++) { 0259 d->ui.cmbHosts->addItem(d->hosts.at(i).name, i); 0260 if (d->secrets["lasthost"] == d->hosts.at(i).name || d->secrets["lasthost"] == d->hosts.at(i).address) { 0261 d->ui.cmbHosts->setCurrentIndex(i); 0262 } 0263 } 0264 0265 if (d->secrets["autoconnect"] == "yes") { 0266 d->ui.chkAutoconnect->setChecked(true); 0267 QTimer::singleShot(0, this, &OpenconnectAuthWidget::connectHost); 0268 } 0269 0270 if (d->secrets["save_passwords"] == "yes") { 0271 d->ui.chkStorePasswords->setChecked(true); 0272 } 0273 0274 d->token.tokenMode = OC_TOKEN_MODE_NONE; 0275 d->token.tokenSecret = nullptr; 0276 0277 if (!d->tokenMode.isEmpty()) { 0278 int ret = 0; 0279 QByteArray tokenSecret = d->secrets[NM_OPENCONNECT_KEY_TOKEN_SECRET].toUtf8(); 0280 0281 if (d->tokenMode == QStringLiteral("manual") && !tokenSecret.isEmpty()) { 0282 ret = __openconnect_set_token_mode(d->vpninfo, OC_TOKEN_MODE_STOKEN, tokenSecret); 0283 } else if (d->tokenMode == QStringLiteral("stokenrc")) { 0284 ret = __openconnect_set_token_mode(d->vpninfo, OC_TOKEN_MODE_STOKEN, NULL); 0285 } else if (d->tokenMode == QStringLiteral("totp") && !tokenSecret.isEmpty()) { 0286 ret = __openconnect_set_token_mode(d->vpninfo, OC_TOKEN_MODE_TOTP, tokenSecret); 0287 } 0288 #if OPENCONNECT_CHECK_VER(3, 4) 0289 else if (d->tokenMode == QStringLiteral("hotp") && !tokenSecret.isEmpty()) { 0290 ret = __openconnect_set_token_mode(d->vpninfo, OC_TOKEN_MODE_HOTP, tokenSecret); 0291 } 0292 #endif 0293 #if OPENCONNECT_CHECK_VER(5, 0) 0294 else if (d->tokenMode == "yubioath") { 0295 /* This needs to be done from a thread because it can call back to 0296 ask for the PIN */ 0297 d->token.tokenMode = OC_TOKEN_MODE_YUBIOATH; 0298 if (!tokenSecret.isEmpty()) { 0299 d->token.tokenSecret = tokenSecret; 0300 } 0301 } 0302 #endif 0303 if (ret) { 0304 addFormInfo(QLatin1String("dialog-error"), i18n("Failed to initialize software token: %1", ret)); 0305 } 0306 } 0307 } 0308 0309 void OpenconnectAuthWidget::acceptDialog() 0310 { 0311 // Find top-level widget as this should be the QDialog itself 0312 QWidget *widget = parentWidget(); 0313 while (widget->parentWidget() != nullptr) { 0314 widget = widget->parentWidget(); 0315 } 0316 0317 auto dialog = qobject_cast<QDialog *>(widget); 0318 if (dialog) { 0319 dialog->accept(); 0320 } 0321 } 0322 0323 // This starts the worker thread, which connects to the selected AnyConnect host 0324 // and retrieves the login form 0325 void OpenconnectAuthWidget::connectHost() 0326 { 0327 Q_D(OpenconnectAuthWidget); 0328 0329 d->userQuit = true; 0330 if (write(d->cancelPipes[1], "x", 1)) { 0331 // not a lot we can do 0332 } 0333 d->workerWaiting.wakeAll(); 0334 d->worker->wait(); 0335 d->userQuit = false; 0336 0337 /* Suck out the cancel byte(s) */ 0338 char buf; 0339 while (read(d->cancelPipes[0], &buf, 1) == 1) { 0340 ; 0341 } 0342 deleteAllFromLayout(d->ui.loginBoxLayout); 0343 int i = d->ui.cmbHosts->currentIndex(); 0344 if (i == -1) { 0345 return; 0346 } 0347 i = d->ui.cmbHosts->itemData(i).toInt(); 0348 const VPNHost &host = d->hosts.at(i); 0349 if (openconnect_parse_url(d->vpninfo, host.address.toLatin1().data())) { 0350 qCWarning(PLASMA_NM_OPENCONNECT_LOG) << "Failed to parse server URL" << host.address; 0351 openconnect_set_hostname(d->vpninfo, OC3DUP(host.address.toLatin1().data())); 0352 } 0353 if (!openconnect_get_urlpath(d->vpninfo) && !host.group.isEmpty()) { 0354 openconnect_set_urlpath(d->vpninfo, OC3DUP(host.group.toLatin1().data())); 0355 } 0356 d->secrets["lasthost"] = host.name; 0357 addFormInfo(QLatin1String("dialog-information"), i18n("Contacting host, please wait…")); 0358 d->worker->start(); 0359 } 0360 0361 void OpenconnectAuthWidget::initTokens() 0362 { 0363 Q_D(OpenconnectAuthWidget); 0364 0365 if (d->token.tokenMode != OC_TOKEN_MODE_NONE) { 0366 __openconnect_set_token_mode(d->vpninfo, d->token.tokenMode, d->token.tokenSecret); 0367 } 0368 } 0369 0370 QVariantMap OpenconnectAuthWidget::setting() const 0371 { 0372 Q_D(const OpenconnectAuthWidget); 0373 0374 NMStringMap secrets; 0375 QVariantMap secretData; 0376 0377 secrets.insert(d->secrets); 0378 QString host(openconnect_get_hostname(d->vpninfo)); 0379 const QString port = QString::number(openconnect_get_port(d->vpninfo)); 0380 QString gateway = host + ':' + port; 0381 const char *urlpath = openconnect_get_urlpath(d->vpninfo); 0382 if (urlpath) { 0383 gateway += '/'; 0384 gateway += urlpath; 0385 } 0386 secrets.insert(QLatin1String(NM_OPENCONNECT_KEY_GATEWAY), gateway); 0387 0388 secrets.insert(QLatin1String(NM_OPENCONNECT_KEY_COOKIE), QLatin1String(openconnect_get_cookie(d->vpninfo))); 0389 openconnect_clear_cookie(d->vpninfo); 0390 0391 #if OPENCONNECT_CHECK_VER(5, 0) 0392 const char *fingerprint = openconnect_get_peer_cert_hash(d->vpninfo); 0393 #else 0394 OPENCONNECT_X509 *cert = openconnect_get_peer_cert(d->vpninfo); 0395 char fingerprint[41]; 0396 openconnect_get_cert_sha1(d->vpninfo, cert, fingerprint); 0397 #endif 0398 secrets.insert(QLatin1String(NM_OPENCONNECT_KEY_GWCERT), QLatin1String(fingerprint)); 0399 secrets.insert(QLatin1String("autoconnect"), d->ui.chkAutoconnect->isChecked() ? "yes" : "no"); 0400 secrets.insert(QLatin1String("save_passwords"), d->ui.chkStorePasswords->isChecked() ? "yes" : "no"); 0401 0402 NMStringMap::iterator i = secrets.begin(); 0403 while (i != secrets.end()) { 0404 if (i.value().isEmpty()) { 0405 i = secrets.erase(i); 0406 } else { 0407 ++i; 0408 } 0409 } 0410 0411 secretData.insert("secrets", QVariant::fromValue<NMStringMap>(secrets)); 0412 0413 // These secrets are not officially part of the secrets which would be returned back to NetworkManager. We just 0414 // need to somehow get them to our secret agent which will handle them separately and store them. 0415 if (!d->tmpSecrets.isEmpty()) { 0416 secretData.insert("tmp-secrets", QVariant::fromValue<NMStringMap>(d->tmpSecrets)); 0417 } 0418 return secretData; 0419 } 0420 0421 #if OPENCONNECT_CHECK_VER(3, 4) 0422 static int updateToken(void *cbdata, const char *tok) 0423 { 0424 auto secrets = static_cast<NMStringMap *>(cbdata); 0425 secrets->insert(QLatin1String(NM_OPENCONNECT_KEY_TOKEN_SECRET), QLatin1String(tok)); 0426 return 0; 0427 } 0428 #endif 0429 0430 void OpenconnectAuthWidget::writeNewConfig(const QString &buf) 0431 { 0432 Q_D(OpenconnectAuthWidget); 0433 d->secrets["xmlconfig"] = buf; 0434 } 0435 0436 void OpenconnectAuthWidget::updateLog(const QString &message, const int &level) 0437 { 0438 Q_D(OpenconnectAuthWidget); 0439 0440 QPair<QString, int> pair; 0441 pair.first = message; 0442 if (pair.first.endsWith(QLatin1String("\n"))) { 0443 pair.first.chop(1); 0444 } 0445 switch (level) { 0446 case PRG_ERR: 0447 pair.second = OpenconnectAuthWidgetPrivate::Error; 0448 break; 0449 case PRG_INFO: 0450 pair.second = OpenconnectAuthWidgetPrivate::Info; 0451 break; 0452 case PRG_DEBUG: 0453 pair.second = OpenconnectAuthWidgetPrivate::Debug; 0454 break; 0455 case PRG_TRACE: 0456 pair.second = OpenconnectAuthWidgetPrivate::Trace; 0457 break; 0458 } 0459 if (pair.second <= d->ui.cmbLogLevel->currentIndex()) { 0460 d->ui.serverLog->append(pair.first); 0461 } 0462 0463 d->serverLog.append(pair); 0464 if (d->serverLog.size() > 100) { 0465 d->serverLog.removeFirst(); 0466 } 0467 } 0468 0469 void OpenconnectAuthWidget::logLevelChanged(int newLevel) 0470 { 0471 Q_D(OpenconnectAuthWidget); 0472 d->ui.serverLog->clear(); 0473 QList<QPair<QString, int>>::const_iterator i; 0474 0475 for (i = d->serverLog.constBegin(); i != d->serverLog.constEnd(); ++i) { 0476 QPair<QString, int> pair = *i; 0477 if (pair.second <= newLevel) { 0478 d->ui.serverLog->append(pair.first); 0479 } 0480 } 0481 } 0482 0483 void OpenconnectAuthWidget::addFormInfo(const QString &iconName, const QString &message) 0484 { 0485 Q_D(OpenconnectAuthWidget); 0486 0487 auto layout = new QHBoxLayout(); 0488 auto icon = new QLabel(this); 0489 QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0490 sizePolicy.setHorizontalStretch(0); 0491 sizePolicy.setVerticalStretch(0); 0492 sizePolicy.setHeightForWidth(icon->sizePolicy().hasHeightForWidth()); 0493 icon->setSizePolicy(sizePolicy); 0494 icon->setMinimumSize(QSize(16, 16)); 0495 icon->setMaximumSize(QSize(16, 16)); 0496 layout->addWidget(icon); 0497 0498 auto text = new QLabel(this); 0499 text->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); 0500 text->setWordWrap(true); 0501 layout->addWidget(text); 0502 0503 const int iconSize = icon->style()->pixelMetric(QStyle::PixelMetric::PM_SmallIconSize); 0504 icon->setPixmap(QIcon::fromTheme(iconName).pixmap(iconSize)); 0505 text->setText(message); 0506 0507 d->ui.loginBoxLayout->addLayout(layout); 0508 } 0509 0510 void OpenconnectAuthWidget::processAuthForm(struct oc_auth_form *form) 0511 { 0512 Q_D(OpenconnectAuthWidget); 0513 0514 deleteAllFromLayout(d->ui.loginBoxLayout); 0515 0516 struct oc_form_opt *opt; 0517 auto layout = new QFormLayout(); 0518 QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Fixed); 0519 bool focusSet = false; 0520 for (opt = form->opts; opt; opt = opt->next) { 0521 if (opt->type == OC_FORM_OPT_HIDDEN || IGNORE_OPT(opt)) { 0522 continue; 0523 } 0524 auto text = new QLabel(this); 0525 text->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); 0526 text->setText(QString(opt->label)); 0527 QWidget *widget = nullptr; 0528 const QString key = QString("form:%1:%2").arg(QLatin1String(form->auth_id)).arg(QLatin1String(opt->name)); 0529 const QString value = d->secrets.value(key); 0530 if (opt->type == OC_FORM_OPT_PASSWORD || opt->type == OC_FORM_OPT_TEXT) { 0531 auto le = new PasswordField(this); 0532 le->setText(value); 0533 if (opt->type == OC_FORM_OPT_PASSWORD) { 0534 le->setPasswordModeEnabled(true); 0535 } 0536 if (!focusSet && le->text().isEmpty()) { 0537 le->setFocus(Qt::OtherFocusReason); 0538 focusSet = true; 0539 } 0540 widget = qobject_cast<QWidget *>(le); 0541 } else if (opt->type == OC_FORM_OPT_SELECT) { 0542 auto cmb = new QComboBox(this); 0543 auto sopt = reinterpret_cast<oc_form_opt_select *>(opt); 0544 #if !OPENCONNECT_CHECK_VER(8, 0) 0545 const QString protocol = d->setting->data()[NM_OPENCONNECT_KEY_PROTOCOL]; 0546 #endif 0547 for (int i = 0; i < sopt->nr_choices; i++) { 0548 cmb->addItem(QString::fromUtf8(FORMCHOICE(sopt, i)->label), QString::fromUtf8(FORMCHOICE(sopt, i)->name)); 0549 if (value == QString::fromUtf8(FORMCHOICE(sopt, i)->name)) { 0550 cmb->setCurrentIndex(i); 0551 #if !OPENCONNECT_CHECK_VER(8, 0) 0552 if (protocol != QLatin1String("nc") && sopt == AUTHGROUP_OPT(form) && i != AUTHGROUP_SELECTION(form)) { 0553 #else 0554 if (sopt == AUTHGROUP_OPT(form) && i != AUTHGROUP_SELECTION(form)) { 0555 #endif 0556 QTimer::singleShot(0, this, &OpenconnectAuthWidget::formGroupChanged); 0557 } 0558 } 0559 } 0560 #if !OPENCONNECT_CHECK_VER(8, 0) 0561 if (protocol != QLatin1String("nc") && sopt == AUTHGROUP_OPT(form)) { 0562 #else 0563 if (sopt == AUTHGROUP_OPT(form)) { 0564 #endif 0565 connect(cmb, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &OpenconnectAuthWidget::formGroupChanged); 0566 } 0567 widget = qobject_cast<QWidget *>(cmb); 0568 } 0569 if (widget) { 0570 widget->setProperty("openconnect_opt", (quintptr)opt); 0571 widget->setSizePolicy(policy); 0572 layout->addRow(text, widget); 0573 } 0574 } 0575 if (!layout->rowCount()) { 0576 delete layout; 0577 d->workerWaiting.wakeAll(); 0578 return; 0579 } 0580 0581 if (form->banner) { 0582 addFormInfo(QLatin1String("dialog-information"), form->banner); 0583 } 0584 if (form->message) { 0585 addFormInfo(QLatin1String("dialog-information"), form->message); 0586 } 0587 if (form->error) { 0588 addFormInfo(QLatin1String("dialog-error"), form->error); 0589 } 0590 0591 d->ui.loginBoxLayout->addLayout(layout); 0592 d->passwordFormIndex = d->ui.loginBoxLayout->count() - 1; 0593 0594 auto box = new QDialogButtonBox(this); 0595 QPushButton *btn = box->addButton(QDialogButtonBox::Ok); 0596 btn->setText(i18nc("Verb, to proceed with login", "Login")); 0597 btn->setDefault(true); 0598 d->ui.loginBoxLayout->addWidget(box); 0599 box->setProperty("openconnect_form", (quintptr)form); 0600 0601 connect(box, &QDialogButtonBox::accepted, this, &OpenconnectAuthWidget::formLoginClicked); 0602 } 0603 0604 void OpenconnectAuthWidget::validatePeerCert(const QString &fingerprint, const QString &peerCert, const QString &reason, bool *accepted) 0605 { 0606 Q_D(OpenconnectAuthWidget); 0607 0608 const QString host = QLatin1String(openconnect_get_hostname(d->vpninfo)); 0609 const QString port = QString::number(openconnect_get_port(d->vpninfo)); 0610 const QString key = QString("certificate:%1:%2").arg(host, port); 0611 const QString value = d->secrets.value(key); 0612 0613 #if !OPENCONNECT_CHECK_VER(5, 0) 0614 #define openconnect_check_peer_cert_hash(v, d) strcmp(d, fingerprint.toUtf8().data()) 0615 #endif 0616 0617 if (openconnect_check_peer_cert_hash(d->vpninfo, value.toUtf8().data())) { 0618 QPointer<QDialog> dialog = new QDialog(this); 0619 dialog->setAttribute(Qt::WA_DeleteOnClose); 0620 dialog.data()->setWindowModality(Qt::WindowModal); 0621 0622 auto widget = new QWidget(dialog.data()); 0623 QVBoxLayout *verticalLayout; 0624 QHBoxLayout *horizontalLayout; 0625 QLabel *icon; 0626 QLabel *infoText; 0627 QTextBrowser *certificate; 0628 0629 verticalLayout = new QVBoxLayout(widget); 0630 horizontalLayout = new QHBoxLayout(widget); 0631 icon = new QLabel(widget); 0632 QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0633 sizePolicy.setHorizontalStretch(0); 0634 sizePolicy.setVerticalStretch(0); 0635 sizePolicy.setHeightForWidth(icon->sizePolicy().hasHeightForWidth()); 0636 icon->setSizePolicy(sizePolicy); 0637 icon->setMinimumSize(QSize(48, 48)); 0638 icon->setMaximumSize(QSize(48, 48)); 0639 0640 horizontalLayout->addWidget(icon); 0641 0642 infoText = new QLabel(widget); 0643 infoText->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); 0644 0645 horizontalLayout->addWidget(infoText); 0646 0647 verticalLayout->addLayout(horizontalLayout); 0648 0649 certificate = new QTextBrowser(widget); 0650 certificate->setTextInteractionFlags(Qt::TextSelectableByMouse); 0651 certificate->setOpenLinks(false); 0652 0653 verticalLayout->addWidget(certificate); 0654 0655 const int iconSize = icon->style()->pixelMetric(QStyle::PixelMetric::PM_LargeIconSize); 0656 icon->setPixmap(QIcon::fromTheme("dialog-information").pixmap(iconSize)); 0657 infoText->setText( 0658 i18n("Check failed for certificate from VPN server \"%1\".\n" 0659 "Reason: %2\nAccept it anyway?", 0660 openconnect_get_hostname(d->vpninfo), 0661 reason)); 0662 infoText->setWordWrap(true); 0663 certificate->setText(peerCert); 0664 0665 dialog->setLayout(new QVBoxLayout); 0666 auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog); 0667 connect(buttons, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept); 0668 connect(buttons, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject); 0669 dialog->layout()->addWidget(widget); 0670 dialog->layout()->addWidget(buttons); 0671 0672 const NMStringMap dataMap = d->setting->data(); 0673 buttons->button(QDialogButtonBox::Ok)->setEnabled(dataMap[NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT] != "yes"); 0674 0675 if (dialog.data()->exec() == QDialog::Accepted) { 0676 *accepted = true; 0677 } else { 0678 *accepted = false; 0679 } 0680 } else { 0681 *accepted = true; 0682 } 0683 if (*accepted) { 0684 d->secrets.insert(key, QString(fingerprint)); 0685 } 0686 d->mutex.lock(); 0687 d->workerWaiting.wakeAll(); 0688 d->mutex.unlock(); 0689 } 0690 0691 void OpenconnectAuthWidget::formGroupChanged() 0692 { 0693 Q_D(OpenconnectAuthWidget); 0694 0695 d->formGroupChanged = true; 0696 formLoginClicked(); 0697 } 0698 0699 // Writes the user input from the form into the oc_auth_form structs we got from 0700 // libopenconnect, and wakes the worker thread up to try to log in and obtain a 0701 // cookie with this data 0702 void OpenconnectAuthWidget::formLoginClicked() 0703 { 0704 Q_D(OpenconnectAuthWidget); 0705 0706 const int lastIndex = d->ui.loginBoxLayout->count() - 1; 0707 QLayout *layout = d->ui.loginBoxLayout->itemAt(d->passwordFormIndex)->layout(); 0708 struct oc_auth_form *form = (struct oc_auth_form *)d->ui.loginBoxLayout->itemAt(lastIndex)->widget()->property("openconnect_form").value<quintptr>(); 0709 0710 for (int i = 0; i < layout->count(); i++) { 0711 QLayoutItem *item = layout->itemAt(i); 0712 QWidget *widget = item->widget(); 0713 if (widget && widget->property("openconnect_opt").isValid()) { 0714 struct oc_form_opt *opt = (struct oc_form_opt *)widget->property("openconnect_opt").value<quintptr>(); 0715 const QString key = QString("form:%1:%2").arg(QLatin1String(form->auth_id)).arg(QLatin1String(opt->name)); 0716 if (opt->type == OC_FORM_OPT_PASSWORD || opt->type == OC_FORM_OPT_TEXT) { 0717 auto le = qobject_cast<PasswordField *>(widget); 0718 QByteArray text = le->text().toUtf8(); 0719 openconnect_set_option_value(opt, text.data()); 0720 if (opt->type == OC_FORM_OPT_TEXT) { 0721 d->secrets.insert(key, le->text()); 0722 } else { 0723 d->tmpSecrets.insert(key, le->text()); 0724 } 0725 } else if (opt->type == OC_FORM_OPT_SELECT) { 0726 auto cbo = qobject_cast<QComboBox *>(widget); 0727 QByteArray text = cbo->itemData(cbo->currentIndex()).toString().toLatin1(); 0728 openconnect_set_option_value(opt, text.data()); 0729 d->secrets.insert(key, cbo->itemData(cbo->currentIndex()).toString()); 0730 } 0731 } 0732 } 0733 0734 deleteAllFromLayout(d->ui.loginBoxLayout); 0735 d->workerWaiting.wakeAll(); 0736 } 0737 0738 void OpenconnectAuthWidget::workerFinished(const int &ret) 0739 { 0740 Q_D(OpenconnectAuthWidget); 0741 0742 if (ret < 0) { 0743 QString message; 0744 QList<QPair<QString, int>>::const_iterator i; 0745 for (i = d->serverLog.constEnd() - 1; i >= d->serverLog.constBegin(); --i) { 0746 QPair<QString, int> pair = *i; 0747 if (pair.second <= OpenconnectAuthWidgetPrivate::Error) { 0748 message = pair.first; 0749 break; 0750 } 0751 } 0752 if (message.isEmpty()) { 0753 message = i18n("Connection attempt was unsuccessful."); 0754 } 0755 deleteAllFromLayout(d->ui.loginBoxLayout); 0756 addFormInfo(QLatin1String("dialog-error"), message); 0757 } else { 0758 deleteAllFromLayout(d->ui.loginBoxLayout); 0759 acceptDialog(); 0760 } 0761 } 0762 0763 void OpenconnectAuthWidget::deleteAllFromLayout(QLayout *layout) 0764 { 0765 while (QLayoutItem *item = layout->takeAt(0)) { 0766 if (QLayout *itemLayout = item->layout()) { 0767 deleteAllFromLayout(itemLayout); 0768 itemLayout->deleteLater(); 0769 } else { 0770 item->widget()->deleteLater(); 0771 } 0772 delete item; 0773 } 0774 layout->invalidate(); 0775 } 0776 0777 void OpenconnectAuthWidget::viewServerLogToggled(bool toggled) 0778 { 0779 Q_D(OpenconnectAuthWidget); 0780 d->ui.lblLogLevel->setVisible(toggled); 0781 d->ui.cmbLogLevel->setVisible(toggled); 0782 if (toggled) { 0783 delete d->ui.verticalLayout->takeAt(5); 0784 QSizePolicy policy = d->ui.serverLogBox->sizePolicy(); 0785 policy.setVerticalPolicy(QSizePolicy::Expanding); 0786 d->ui.serverLogBox->setSizePolicy(policy); 0787 d->ui.serverLog->setVisible(true); 0788 } else { 0789 auto verticalSpacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding); 0790 d->ui.verticalLayout->addItem(verticalSpacer); 0791 d->ui.serverLog->setVisible(false); 0792 QSizePolicy policy = d->ui.serverLogBox->sizePolicy(); 0793 policy.setVerticalPolicy(QSizePolicy::Fixed); 0794 d->ui.serverLogBox->setSizePolicy(policy); 0795 } 0796 }