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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

103 104
    KAcceleratorManager::manage(this);

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

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

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
void OpenconnectSettingWidget::handleTokenSecret(int index)
{
    Q_D(const OpenconnectSettingWidget);

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

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

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

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

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

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

186 187 188 189 190 191 192 193 194 195
    int cmbProtocolIndex;
    if (dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("anyconnect")) {
        cmbProtocolIndex = 0;
    } else if (dataMap[NM_OPENCONNECT_KEY_PROTOCOL] == QLatin1String("nc")) {
        cmbProtocolIndex = 1;
    } else {
        cmbProtocolIndex = 2; // paloAlto/GlobalProtect (gp)
    }

    d->ui.cmbProtocol->setCurrentIndex(cmbProtocolIndex);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
196
    d->ui.leGateway->setText(dataMap[NM_OPENCONNECT_KEY_GATEWAY]);
Jan Grulich's avatar
Jan Grulich committed
197
    d->ui.leCaCertificate->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_CACERT]));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
198 199
    d->ui.leProxy->setText(dataMap[NM_OPENCONNECT_KEY_PROXY]);
    d->ui.chkAllowTrojan->setChecked(dataMap[NM_OPENCONNECT_KEY_CSD_ENABLE] == "yes");
Jan Grulich's avatar
Jan Grulich committed
200 201 202
    d->ui.leCsdWrapperScript->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_CSD_WRAPPER]));
    d->ui.leUserCert->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_USERCERT]));
    d->ui.leUserPrivateKey->setUrl(QUrl::fromLocalFile(dataMap[NM_OPENCONNECT_KEY_PRIVKEY]));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
203
    d->ui.chkUseFsid->setChecked(dataMap[NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID] == "yes");
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
    d->ui.preventInvalidCert->setChecked(dataMap[NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT] == "yes");

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

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

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

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

240
QVariantMap OpenconnectSettingWidget::setting() const
Lukáš Tinkl's avatar
Lukáš Tinkl committed
241 242 243 244 245 246 247
{
    Q_D(const OpenconnectSettingWidget);

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

    NMStringMap data;
248
    NMStringMap secrets;
249 250 251 252 253 254 255 256 257 258 259
    QString protocol;
    switch (d->ui.cmbProtocol->currentIndex()) {
        case 0:
            protocol = QLatin1String("anyconnect");
            break;
        case 1:
            protocol = QLatin1String("nc");
            break;
        default:
            protocol = QLatin1String("gp");
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
260

261
    data.insert(NM_OPENCONNECT_KEY_PROTOCOL, protocol);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
262
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_GATEWAY), d->ui.leGateway->text());
Jan Grulich's avatar
Jan Grulich committed
263
    if (d->ui.leCaCertificate->url().isValid()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
264
        data.insert(QLatin1String(NM_OPENCONNECT_KEY_CACERT), d->ui.leCaCertificate->url().toLocalFile());
Jan Grulich's avatar
Jan Grulich committed
265 266
    }
    if (!d->ui.leProxy->text().isEmpty()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
267
        data.insert(QLatin1String(NM_OPENCONNECT_KEY_PROXY), d->ui.leProxy->text());
Jan Grulich's avatar
Jan Grulich committed
268
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
269
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_CSD_ENABLE), d->ui.chkAllowTrojan->isChecked() ? "yes" : "no");
Jan Grulich's avatar
Jan Grulich committed
270
    if (d->ui.leCsdWrapperScript->url().isValid()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
271
        data.insert(QLatin1String(NM_OPENCONNECT_KEY_CSD_WRAPPER), d->ui.leCsdWrapperScript->url().toLocalFile());
Jan Grulich's avatar
Jan Grulich committed
272 273
    }
    if (d->ui.leUserCert->url().isValid()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
274
        data.insert(QLatin1String(NM_OPENCONNECT_KEY_USERCERT), d->ui.leUserCert->url().toLocalFile());
Jan Grulich's avatar
Jan Grulich committed
275 276
    }
    if (d->ui.leUserPrivateKey->url().isValid()) {
Lukáš Tinkl's avatar
Lukáš Tinkl committed
277
        data.insert(QLatin1String(NM_OPENCONNECT_KEY_PRIVKEY), d->ui.leUserPrivateKey->url().toLocalFile());
Jan Grulich's avatar
Jan Grulich committed
278
    }
Lukáš Tinkl's avatar
Lukáš Tinkl committed
279
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID), d->ui.chkUseFsid->isChecked() ? "yes" : "no");
280 281 282 283 284
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT), d->ui.preventInvalidCert->isChecked() ? "yes" : "no");

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

286
    // Restore previous flags, this is necessary for keeping secrets stored in KWallet
Jan Grulich's avatar
Jan Grulich committed
287
    for (const QString &key : d->setting->data().keys()) {
288 289 290 291 292
        if (key.contains(QLatin1String("-flags"))) {
            data.insert(key, d->setting->data().value(key));
        }
    }

293 294 295 296 297 298 299 300
    if (d->tokenUi.leTokenSecret->passwordOption() == PasswordField::StoreForAllUsers) {
        data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET"-flags", QString::number(NetworkManager::Setting::None));
    } else if (d->tokenUi.leTokenSecret->passwordOption() == PasswordField::StoreForUser) {
        data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET"-flags", QString::number(NetworkManager::Setting::AgentOwned));
    } else {
        data.insert(NM_OPENCONNECT_KEY_TOKEN_SECRET"-flags", QString::number(NetworkManager::Setting::NotSaved));
    }

Lukáš Tinkl's avatar
Lukáš Tinkl committed
301 302 303 304 305 306
    /* These are different for every login session, and should not be stored */
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_COOKIE"-flags"), QString::number(NetworkManager::Setting::NotSaved));
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_GWCERT"-flags"), QString::number(NetworkManager::Setting::NotSaved));
    data.insert(QLatin1String(NM_OPENCONNECT_KEY_GATEWAY"-flags"), QString::number(NetworkManager::Setting::NotSaved));

    setting.setData(data);
307
    setting.setSecrets(secrets);
308

Lukáš Tinkl's avatar
Lukáš Tinkl committed
309 310 311
    return setting.toMap();
}

312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
void OpenconnectSettingWidget::restoreTokens()
{
    Q_D(const OpenconnectSettingWidget);

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

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

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

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

    d->tokenDlg->show();
}

Lukáš Tinkl's avatar
Lukáš Tinkl committed
335 336 337
bool OpenconnectSettingWidget::isValid() const
{
    Q_D(const OpenconnectSettingWidget);
338

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