Warning, file /plasma/plasma-nm/vpn/openconnect/openconnectauth.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.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 }