X-Git-Url: https://git.quassel-irc.org/?a=blobdiff_plain;f=src%2Fqtui%2Fsettingspages%2Fnetworkssettingspage.cpp;h=d4fdca1f412778faf60b8e917eee67f282952c18;hb=HEAD;hp=468f2b57ec751d786b8060790e0698c23fd50252;hpb=e567422cdd0e3c805d4bb37b81d532cb762b983e;p=quassel.git diff --git a/src/qtui/settingspages/networkssettingspage.cpp b/src/qtui/settingspages/networkssettingspage.cpp index 468f2b57..dfd1f62f 100644 --- a/src/qtui/settingspages/networkssettingspage.cpp +++ b/src/qtui/settingspages/networkssettingspage.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2020 by the Quassel Project * + * Copyright (C) 2005-2022 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -67,6 +67,7 @@ NetworksSettingsPage::NetworksSettingsPage(QWidget* parent) disconnectedIcon = icon::get("network-disconnect"); // Status icons + infoIcon = icon::get({"emblem-information", "dialog-information"}); successIcon = icon::get({"emblem-success", "dialog-information"}); unavailableIcon = icon::get({"emblem-unavailable", "dialog-warning"}); questionIcon = icon::get({"emblem-question", "dialog-question", "dialog-information"}); @@ -90,7 +91,8 @@ NetworksSettingsPage::NetworksSettingsPage(QWidget* parent) ui.sendEncoding, ui.recvEncoding, ui.serverEncoding, ui.autoReconnect, ui.reconnectInterval, ui.reconnectRetries, ui.unlimitedRetries, ui.rejoinOnReconnect, ui.useCustomMessageRate, - ui.messageRateBurstSize, ui.messageRateDelay, ui.unlimitedMessageRate}, + ui.messageRateBurstSize, ui.messageRateDelay, ui.unlimitedMessageRate, + ui.enableCapServerTime}, this, &NetworksSettingsPage::widgetHasChanged); @@ -178,6 +180,20 @@ void NetworksSettingsPage::load() "modify message rate limits."))); } + if (!Client::isConnected() || Client::isCoreFeatureEnabled(Quassel::Feature::SkipIrcCaps)) { + // Either disconnected or IRCv3 capability skippping supported, enable configuration and + // hide warning. Don't show the warning needlessly when disconnected. + ui.enableCapsConfigWidget->setEnabled(true); + ui.enableCapsStatusLabel->setText(tr("These features require support from the network")); + ui.enableCapsStatusIcon->setPixmap(infoIcon.pixmap(16)); + } + else { + // Core does not IRCv3 capability skipping, show warning and disable configuration + ui.enableCapsConfigWidget->setEnabled(false); + ui.enableCapsStatusLabel->setText(tr("Your Quassel core is too old to configure IRCv3 features")); + ui.enableCapsStatusIcon->setPixmap(unavailableIcon.pixmap(16)); + } + // Hide the SASL EXTERNAL notice until a network's shown. Stops it from showing while loading // backlog from the core. sslUpdated(); @@ -342,7 +358,7 @@ void NetworksSettingsPage::setItemState(NetworkId id, QListWidgetItem* item) void NetworksSettingsPage::resetNetworkCapStates() { - // Set the status to a blank (invalid) network ID, reseting all UI + // Set the status to a blank (invalid) network ID, resetting all UI setNetworkCapStates(NetworkId()); } @@ -356,11 +372,15 @@ void NetworksSettingsPage::setNetworkCapStates(NetworkId id) if (net->connectionState() != Network::Disconnected) { // Network exists and is connected, check available capabilities... // [SASL] - if (net->saslMaybeSupports(IrcCap::SaslMech::PLAIN)) { - setSASLStatus(CapSupportStatus::MaybeSupported); + // Quassel switches between SASL PLAIN and SASL EXTERNAL based on the existence of an + // SSL certificate on the identity - check EXTERNAL if CertID exists, PLAIN if not + bool usingSASLExternal = displayedNetworkHasCertId(); + if ((usingSASLExternal && net->saslMaybeSupports(IrcCap::SaslMech::EXTERNAL)) + || (!usingSASLExternal && net->saslMaybeSupports(IrcCap::SaslMech::PLAIN))) { + setCapSASLStatus(CapSupportStatus::MaybeSupported, usingSASLExternal); } else { - setSASLStatus(CapSupportStatus::MaybeUnsupported); + setCapSASLStatus(CapSupportStatus::MaybeUnsupported, usingSASLExternal); } // Add additional capability-dependent interface updates here @@ -368,7 +388,7 @@ void NetworksSettingsPage::setNetworkCapStates(NetworkId id) else { // Network is disconnected // [SASL] - setSASLStatus(CapSupportStatus::Disconnected); + setCapSASLStatus(CapSupportStatus::Disconnected); // Add additional capability-dependent interface updates here } @@ -377,7 +397,7 @@ void NetworksSettingsPage::setNetworkCapStates(NetworkId id) // Capability negotiation is not supported and/or network doesn't exist. // Don't assume anything and reset all capability-dependent interface elements to neutral. // [SASL] - setSASLStatus(CapSupportStatus::Unknown); + setCapSASLStatus(CapSupportStatus::Unknown); // Add additional capability-dependent interface updates here } @@ -574,12 +594,21 @@ void NetworksSettingsPage::displayNetwork(NetworkId id) // this is only needed when the core supports SASL EXTERNAL if (Client::isCoreFeatureEnabled(Quassel::Feature::SaslExternal)) { if (_cid) { + // Clean up existing CertIdentity disconnect(_cid, &CertIdentity::sslSettingsUpdated, this, &NetworksSettingsPage::sslUpdated); delete _cid; + _cid = nullptr; + } + auto *identity = Client::identity(info.identity); + if (identity) { + // Connect new CertIdentity + _cid = new CertIdentity(*identity, this); + _cid->enableEditSsl(true); + connect(_cid, &CertIdentity::sslSettingsUpdated, this, &NetworksSettingsPage::sslUpdated); + } + else { + qWarning() << "NetworksSettingsPage::displayNetwork can't find Identity for IdentityId:" << info.identity; } - _cid = new CertIdentity(*Client::identity(info.identity), this); - _cid->enableEditSsl(true); - connect(_cid, &CertIdentity::sslSettingsUpdated, this, &NetworksSettingsPage::sslUpdated); } ui.identityList->setCurrentIndex(ui.identityList->findData(info.identity.toInt())); @@ -626,6 +655,8 @@ void NetworksSettingsPage::displayNetwork(NetworkId id) ui.messageRateBurstSize->setValue(info.messageRateBurstSize); // Convert milliseconds (integer) into seconds (double) ui.messageRateDelay->setValue(info.messageRateDelay / 1000.0f); + // Skipped IRCv3 capabilities + ui.enableCapServerTime->setChecked(!info.skipCaps.contains(IrcCap::SERVER_TIME)); } else { // just clear widgets @@ -678,6 +709,14 @@ void NetworksSettingsPage::saveToNetworkInfo(NetworkInfo& info) // Convert seconds (double) into milliseconds (integer) info.messageRateDelay = static_cast((ui.messageRateDelay->value() * 1000)); info.unlimitedMessageRate = ui.unlimitedMessageRate->isChecked(); + // Skipped IRCv3 capabilities + if (ui.enableCapServerTime->isChecked()) { + // Capability enabled, remove it from the skip list + info.skipCaps.removeAll(IrcCap::SERVER_TIME); + } else if (!info.skipCaps.contains(IrcCap::SERVER_TIME)) { + // Capability disabled and not in the skip list, add it + info.skipCaps.append(IrcCap::SERVER_TIME); + } } void NetworksSettingsPage::clientNetworkCapsUpdated() @@ -695,11 +734,12 @@ void NetworksSettingsPage::clientNetworkCapsUpdated() } } -void NetworksSettingsPage::setSASLStatus(const CapSupportStatus saslStatus) +void NetworksSettingsPage::setCapSASLStatus(const CapSupportStatus saslStatus, bool usingSASLExternal) { - if (_saslStatusSelected != saslStatus) { + if (_capSaslStatusSelected != saslStatus || _capSaslStatusUsingExternal != usingSASLExternal) { // Update the cached copy of SASL status used with the Details dialog - _saslStatusSelected = saslStatus; + _capSaslStatusSelected = saslStatus; + _capSaslStatusUsingExternal = usingSASLExternal; // Update the user interface switch (saslStatus) { @@ -715,14 +755,23 @@ void NetworksSettingsPage::setSASLStatus(const CapSupportStatus saslStatus) ui.saslStatusIcon->setPixmap(questionIcon.pixmap(16)); break; case CapSupportStatus::MaybeUnsupported: - // The network doesn't advertise support for SASL PLAIN. Here be dragons. + // The network doesn't advertise support for SASL PLAIN/EXTERNAL. Here be dragons. ui.saslStatusLabel->setText(QString("%1").arg(tr("Not currently supported by network"))); ui.saslStatusIcon->setPixmap(unavailableIcon.pixmap(16)); break; case CapSupportStatus::MaybeSupported: - // The network advertises support for SASL PLAIN. Encourage using it! + // The network advertises support for SASL PLAIN/EXTERNAL. Encourage using it! // Unfortunately we don't know for sure if it's desired or functional. - ui.saslStatusLabel->setText(QString("%1").arg(tr("Supported by network"))); + if (usingSASLExternal) { + // SASL EXTERNAL is used + // With SASL v3.1, it's not possible to reliably tell if SASL EXTERNAL is supported, + // or just SASL PLAIN. Use less assertive phrasing. + ui.saslStatusLabel->setText(QString("%1").arg(tr("May be supported by network"))); + } + else { + // SASL PLAIN is used + ui.saslStatusLabel->setText(QString("%1").arg(tr("Supported by network"))); + } ui.saslStatusIcon->setPixmap(successIcon.pixmap(16)); break; } @@ -731,25 +780,29 @@ void NetworksSettingsPage::setSASLStatus(const CapSupportStatus saslStatus) void NetworksSettingsPage::sslUpdated() { - if (_cid && !_cid->sslKey().isNull()) { - ui.saslContents->setDisabled(true); + if (displayedNetworkHasCertId()) { + ui.saslPlainContents->setDisabled(true); ui.saslExtInfo->setHidden(false); } else { - ui.saslContents->setDisabled(false); + ui.saslPlainContents->setDisabled(false); // Directly re-enabling causes the widgets to ignore the parent "Use SASL Authentication" // state to indicate whether or not it's disabled. To workaround this, keep track of // whether or not "Use SASL Authentication" is enabled, then quickly uncheck/recheck the // group box. if (!ui.sasl->isChecked()) { - // SASL is not enabled, uncheck/recheck the group box to re-disable saslContents. - // Leaving saslContents disabled doesn't work as that prevents it from re-enabling if + // SASL is not enabled, uncheck/recheck the group box to re-disable saslPlainContents. + // Leaving saslPlainContents disabled doesn't work as that prevents it from re-enabling if // sasl is later checked. ui.sasl->setChecked(true); ui.sasl->setChecked(false); } ui.saslExtInfo->setHidden(true); } + // Update whether SASL PLAIN or SASL EXTERNAL is used to detect SASL status + if (currentId != 0) { + setNetworkCapStates(currentId); + } } /*** Network list ***/ @@ -933,7 +986,7 @@ void NetworksSettingsPage::on_saslStatusDetails_clicked() bool useWarningIcon = false; // Determine which explanation to show - switch (_saslStatusSelected) { + switch (_capSaslStatusSelected) { case CapSupportStatus::Unknown: saslStatusHeader = tr("Could not check if SASL supported by network"); saslStatusExplanation = tr("Quassel could not check if \"%1\" supports SASL. This may " @@ -949,17 +1002,41 @@ void NetworksSettingsPage::on_saslStatusDetails_clicked() .arg(netName); break; case CapSupportStatus::MaybeUnsupported: - saslStatusHeader = tr("SASL not currently supported by network"); - saslStatusExplanation = tr("The network \"%1\" does not currently support SASL. " - "However, support might be added later on.") - .arg(netName); + if (displayedNetworkHasCertId()) { + // SASL EXTERNAL is used + saslStatusHeader = tr("SASL EXTERNAL not currently supported by network"); + saslStatusExplanation = tr("The network \"%1\" does not currently support SASL " + "EXTERNAL for SSL certificate authentication. However, " + "support might be added later on.") + .arg(netName); + } + else { + // SASL PLAIN is used + saslStatusHeader = tr("SASL not currently supported by network"); + saslStatusExplanation = tr("The network \"%1\" does not currently support SASL. " + "However, support might be added later on.") + .arg(netName); + } useWarningIcon = true; break; case CapSupportStatus::MaybeSupported: - saslStatusHeader = tr("SASL supported by network"); - saslStatusExplanation = tr("The network \"%1\" supports SASL. In most cases, you " - "should use SASL instead of NickServ identification.") - .arg(netName); + if (displayedNetworkHasCertId()) { + // SASL EXTERNAL is used + // With SASL v3.1, it's not possible to reliably tell if SASL EXTERNAL is supported, + // or just SASL PLAIN. Caution about this in the details dialog. + saslStatusHeader = tr("SASL EXTERNAL may be supported by network"); + saslStatusExplanation = tr("The network \"%1\" may support SASL EXTERNAL for SSL " + "certificate authentication. In most cases, you should " + "use SASL instead of NickServ identification.") + .arg(netName); + } + else { + // SASL PLAIN is used + saslStatusHeader = tr("SASL supported by network"); + saslStatusExplanation = tr("The network \"%1\" supports SASL. In most cases, you " + "should use SASL instead of NickServ identification.") + .arg(netName); + } break; } @@ -982,6 +1059,73 @@ void NetworksSettingsPage::on_saslStatusDetails_clicked() } } +void NetworksSettingsPage::on_enableCapsStatusDetails_clicked() +{ + if (!Client::isConnected() || Client::isCoreFeatureEnabled(Quassel::Feature::SkipIrcCaps)) { + // Either disconnected or IRCv3 capability skippping supported + + // Try to get a list of currently enabled features + QStringList sortedCapsEnabled; + // Check if a network is selected + if (ui.networkList->selectedItems().count()) { + // Get the underlying Network from the selected network + NetworkId netid = ui.networkList->selectedItems()[0]->data(Qt::UserRole).value(); + const Network* net = Client::network(netid); + if (net && Client::isCoreFeatureEnabled(Quassel::Feature::CapNegotiation)) { + // Capability negotiation is supported, network exists. + // If the network is disconnected, the list of enabled capabilities will be empty, + // no need to check for that specifically. + // Sorting isn't required, but it looks nicer. + sortedCapsEnabled = net->capsEnabled(); + sortedCapsEnabled.sort(); + } + } + + // Try to explain IRCv3 network features in a friendly way, including showing the currently + // enabled features if available + auto messageText = QString("

%1


%2

") + .arg(tr("Quassel makes use of newer IRC features when supported by the IRC network." + " If desired, you can disable unwanted or problematic features here."), + tr("The IRCv3 website provides more " + "technical details on the IRCv3 capabilities powering these features.")); + + if (!sortedCapsEnabled.isEmpty()) { + // Format the capabilities within blocks + auto formattedCaps = QString("%1") + .arg(sortedCapsEnabled.join(", ")); + + // Add the currently enabled capabilities to the list + // This creates a new QString, but this code is not performance-critical. + messageText = messageText.append(QString("

%1

").arg( + tr("Currently enabled IRCv3 capabilities for this " + "network: %1").arg(formattedCaps))); + } + + QMessageBox::information(this, tr("Configuring network features"), messageText); + } + else { + // Core does not IRCv3 capability skipping, show warning + QMessageBox::warning(this, tr("Configuring network features unsupported"), + QString("

%1


%2

") + .arg(tr("Your Quassel core is too old to configure IRCv3 network features"), + tr("You need a Quassel core v0.14.0 or newer to control what network " + "features Quassel will use."))); + } +} + +void NetworksSettingsPage::on_enableCapsAdvanced_clicked() +{ + if (currentId == 0) + return; + + CapsEditDlg dlg(networkInfos[currentId].skipCapsToString(), this); + if (dlg.exec() == QDialog::Accepted) { + networkInfos[currentId].skipCapsFromString(dlg.skipCapsString()); + displayNetwork(currentId); + widgetHasChanged(); + } +} + IdentityId NetworksSettingsPage::defaultIdentity() const { IdentityId defaultId = 0; @@ -993,6 +1137,12 @@ IdentityId NetworksSettingsPage::defaultIdentity() const return defaultId; } +bool NetworksSettingsPage::displayedNetworkHasCertId() const +{ + // Check if the CertIdentity exists and that it has a non-null SSL key set + return (_cid && !_cid->sslKey().isNull()); +} + /************************************************************************** * NetworkAddDlg *************************************************************************/ @@ -1006,7 +1156,7 @@ NetworkAddDlg::NetworkAddDlg(QStringList exist, QWidget* parent) // Whenever useSSL is toggled, update the port number if not changed from the default connect(ui.useSSL, &QAbstractButton::toggled, this, &NetworkAddDlg::updateSslPort); - // Do NOT call updateSslPort when loading settings, otherwise port settings may be overriden. + // Do NOT call updateSslPort when loading settings, otherwise port settings may be overridden. // If useSSL is later changed to be checked by default, change port's default value, too. if (Client::isCoreFeatureEnabled(Quassel::Feature::VerifyServerSSL)) { @@ -1156,7 +1306,7 @@ ServerEditDlg::ServerEditDlg(const Network::Server& server, QWidget* parent) // Whenever useSSL is toggled, update the port number if not changed from the default connect(ui.useSSL, &QAbstractButton::toggled, this, &ServerEditDlg::updateSslPort); - // Do NOT call updateSslPort when loading settings, otherwise port settings may be overriden. + // Do NOT call updateSslPort when loading settings, otherwise port settings may be overridden. // If useSSL is later changed to be checked by default, change port's default value, too. if (Client::isCoreFeatureEnabled(Quassel::Feature::VerifyServerSSL)) { @@ -1213,6 +1363,47 @@ void ServerEditDlg::updateSslPort(bool isChecked) } } +/************************************************************************** + * CapsEditDlg + *************************************************************************/ + +CapsEditDlg::CapsEditDlg(const QString& oldSkipCapsString, QWidget* parent) + : QDialog(parent) + , oldSkipCapsString(oldSkipCapsString) +{ + ui.setupUi(this); + + // Connect to the reset button to reset the text + // This provides an explicit way to "get back to defaults" in case someone changes settings to + // experiment + QPushButton* defaultsButton = ui.buttonBox->button(QDialogButtonBox::RestoreDefaults); + connect(defaultsButton, &QPushButton::clicked, this, &CapsEditDlg::defaultSkipCaps); + + if (oldSkipCapsString.isEmpty()) { + // Disable Reset button + on_skipCapsEdit_textChanged(""); + } + else { + ui.skipCapsEdit->setText(oldSkipCapsString); + } +} + + +QString CapsEditDlg::skipCapsString() const +{ + return ui.skipCapsEdit->text(); +} + +void CapsEditDlg::defaultSkipCaps() +{ + ui.skipCapsEdit->setText(""); +} + +void CapsEditDlg::on_skipCapsEdit_textChanged(const QString& text) +{ + ui.buttonBox->button(QDialogButtonBox::RestoreDefaults)->setDisabled(text.isEmpty()); +} + /************************************************************************** * SaveNetworksDlg *************************************************************************/