/***************************************************************************
- * 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 *
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"});
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);
"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();
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());
}
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
else {
// Network is disconnected
// [SASL]
- setSASLStatus(CapSupportStatus::Disconnected);
+ setCapSASLStatus(CapSupportStatus::Disconnected);
// Add additional capability-dependent interface updates here
}
// 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
}
// 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()));
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
// Convert seconds (double) into milliseconds (integer)
info.messageRateDelay = static_cast<quint32>((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()
}
}
-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) {
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("<i>%1</i>").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("<i>%1</i>").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("<i>%1</i>").arg(tr("May be supported by network")));
+ }
+ else {
+ // SASL PLAIN is used
+ ui.saslStatusLabel->setText(QString("<i>%1</i>").arg(tr("Supported by network")));
+ }
ui.saslStatusIcon->setPixmap(successIcon.pixmap(16));
break;
}
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 ***/
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 "
.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;
}
}
}
+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<NetworkId>();
+ 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("<p>%1</p></br><p>%2</p>")
+ .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 <a href=\"https://ircv3.net/irc/\">IRCv3 website</a> provides more "
+ "technical details on the IRCv3 capabilities powering these features."));
+
+ if (!sortedCapsEnabled.isEmpty()) {
+ // Format the capabilities within <code></code> blocks
+ auto formattedCaps = QString("<code>%1</code>")
+ .arg(sortedCapsEnabled.join("</code>, <code>"));
+
+ // Add the currently enabled capabilities to the list
+ // This creates a new QString, but this code is not performance-critical.
+ messageText = messageText.append(QString("<p><i>%1</i></p>").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("<p><b>%1</b></p></br><p>%2</p>")
+ .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;
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
*************************************************************************/
// 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)) {
// 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)) {
}
}
+/**************************************************************************
+ * 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
*************************************************************************/