File indexing completed on 2024-12-01 08:10:02

0001 /*
0002     SPDX-FileCopyrightText: 2013 Lukas Tinkl <ltinkl@redhat.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "ipv6widget.h"
0008 #include "intdelegate.h"
0009 #include "ipv6delegate.h"
0010 #include "ui_ipv6.h"
0011 
0012 #include <NetworkManagerQt/Manager>
0013 
0014 #include <QDialog>
0015 #include <QDialogButtonBox>
0016 #include <QItemSelection>
0017 #include <QNetworkAddressEntry>
0018 #include <QStandardItemModel>
0019 
0020 #include <KEditListWidget>
0021 #include <KLocalizedString>
0022 
0023 quint32 suggestNetmask(Q_IPV6ADDR ip)
0024 {
0025     Q_UNUSED(ip);
0026 
0027     /*
0028     TODO: find out common IPv6-netmasks and make a complete function
0029 
0030     */
0031     quint32 netmask = 64;
0032 
0033     return netmask;
0034 }
0035 
0036 class IPv6Widget::Private
0037 {
0038 public:
0039     Private()
0040         : model(0, 3)
0041     {
0042         auto headerItem = new QStandardItem(i18nc("Header text for IPv6 address", "Address"));
0043         model.setHorizontalHeaderItem(0, headerItem);
0044         headerItem = new QStandardItem(i18nc("Header text for IPv6 prefix", "Prefix"));
0045         model.setHorizontalHeaderItem(1, headerItem);
0046         headerItem = new QStandardItem(i18nc("Header text for IPv6 gateway", "Gateway"));
0047         model.setHorizontalHeaderItem(2, headerItem);
0048     }
0049     QStandardItemModel model;
0050 };
0051 
0052 IPv6Widget::IPv6Widget(const NetworkManager::Setting::Ptr &setting, QWidget *parent, Qt::WindowFlags f)
0053     : SettingWidget(setting, parent, f)
0054     , m_ui(new Ui::IPv6Widget)
0055     , d(new IPv6Widget::Private())
0056 {
0057     m_ui->setupUi(this);
0058 
0059     m_ui->tableViewAddresses->setModel(&d->model);
0060     m_ui->tableViewAddresses->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
0061     m_ui->tableViewAddresses->horizontalHeader()->setStretchLastSection(true);
0062 
0063     auto ipDelegate = new IpV6Delegate(this);
0064     auto prefixDelegate = new IntDelegate(0, 128, this);
0065     m_ui->tableViewAddresses->setItemDelegateForColumn(0, ipDelegate);
0066     m_ui->tableViewAddresses->setItemDelegateForColumn(1, prefixDelegate);
0067     m_ui->tableViewAddresses->setItemDelegateForColumn(2, ipDelegate);
0068 
0069     connect(m_ui->btnAdd, &QPushButton::clicked, this, &IPv6Widget::slotAddIPAddress);
0070     connect(m_ui->btnRemove, &QPushButton::clicked, this, &IPv6Widget::slotRemoveIPAddress);
0071 
0072     connect(m_ui->dnsMorePushButton, &QPushButton::clicked, this, &IPv6Widget::slotDnsServers);
0073     connect(m_ui->dnsSearchMorePushButton, &QPushButton::clicked, this, &IPv6Widget::slotDnsDomains);
0074 
0075     connect(m_ui->tableViewAddresses->selectionModel(), &QItemSelectionModel::selectionChanged, this, &IPv6Widget::selectionChanged);
0076 
0077     connect(&d->model, &QStandardItemModel::itemChanged, this, &IPv6Widget::tableViewItemChanged);
0078 
0079     // IPv6Method::Disabled is available in NM 1.20+
0080     if (!NetworkManager::checkVersion(1, 20, 0)) {
0081         m_ui->method->removeItem(7);
0082     }
0083 
0084     if (setting) {
0085         loadConfig(setting);
0086     }
0087 
0088     connect(m_ui->method, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &IPv6Widget::slotModeComboChanged);
0089     slotModeComboChanged(m_ui->method->currentIndex());
0090 
0091     connect(m_ui->btnRoutes, &QPushButton::clicked, this, &IPv6Widget::slotRoutesDialog);
0092 
0093     // Connect for setting check
0094     watchChangedSetting();
0095 
0096     // Connect for validity check
0097     connect(m_ui->dns, &KLineEdit::textChanged, this, &IPv6Widget::slotWidgetChanged);
0098     connect(m_ui->method, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &IPv6Widget::slotWidgetChanged);
0099     connect(&d->model, &QStandardItemModel::dataChanged, this, &IPv6Widget::slotWidgetChanged);
0100     connect(&d->model, &QStandardItemModel::rowsRemoved, this, &IPv6Widget::slotWidgetChanged);
0101 
0102     KAcceleratorManager::manage(this);
0103 }
0104 
0105 IPv6Widget::~IPv6Widget()
0106 {
0107     delete d;
0108     delete m_ui;
0109 }
0110 
0111 void IPv6Widget::loadConfig(const NetworkManager::Setting::Ptr &setting)
0112 {
0113     NetworkManager::Ipv6Setting::Ptr ipv6Setting = setting.staticCast<NetworkManager::Ipv6Setting>();
0114 
0115     // BUG:406118
0116     // We don't have route-metric in the UI, maybe even won't have for now, but that doesn't mean we
0117     // want to loose it when it's configured manually in a config file
0118     m_tmpIpv6Setting.setRouteMetric(ipv6Setting->routeMetric());
0119 
0120     m_tmpIpv6Setting.setRoutes(ipv6Setting->routes());
0121     m_tmpIpv6Setting.setNeverDefault(ipv6Setting->neverDefault());
0122     m_tmpIpv6Setting.setIgnoreAutoRoutes(ipv6Setting->ignoreAutoRoutes());
0123 
0124     // method
0125     switch (ipv6Setting->method()) {
0126     case NetworkManager::Ipv6Setting::Automatic:
0127         if (ipv6Setting->ignoreAutoDns()) {
0128             m_ui->method->setCurrentIndex(AutomaticOnlyIP);
0129         } else {
0130             m_ui->method->setCurrentIndex(Automatic);
0131         }
0132         break;
0133     case NetworkManager::Ipv6Setting::Dhcp:
0134         m_ui->method->setCurrentIndex(AutomaticOnlyDHCP);
0135         break;
0136     case NetworkManager::Ipv6Setting::Manual:
0137         m_ui->method->setCurrentIndex(Manual);
0138         break;
0139     case NetworkManager::Ipv6Setting::LinkLocal:
0140         m_ui->method->setCurrentIndex(LinkLocal);
0141         break;
0142     case NetworkManager::Ipv6Setting::Ignored:
0143         m_ui->method->setCurrentIndex(Ignored);
0144         break;
0145     case NetworkManager::Ipv6Setting::ConfigDisabled:
0146         m_ui->method->setCurrentIndex(Disabled);
0147         break;
0148     }
0149 
0150     // dns
0151     QStringList tmp;
0152     for (const QHostAddress &addr : ipv6Setting->dns()) {
0153         tmp.append(addr.toString());
0154     }
0155     m_ui->dns->setText(tmp.join(QStringLiteral(",")));
0156     m_ui->dnsSearch->setText(ipv6Setting->dnsSearch().join(QStringLiteral(",")));
0157 
0158     // addresses
0159     for (const NetworkManager::IpAddress &address : ipv6Setting->addresses()) {
0160         QList<QStandardItem *> item{
0161             new QStandardItem(address.ip().toString()),
0162             new QStandardItem(QString::number(address.prefixLength(), 10)),
0163             new QStandardItem(address.gateway().toString()),
0164         };
0165 
0166         d->model.appendRow(item);
0167     }
0168 
0169     // may-fail
0170     m_ui->ipv6RequiredCB->setChecked(!ipv6Setting->mayFail());
0171 
0172     // privacy
0173     if (ipv6Setting->privacy() != NetworkManager::Ipv6Setting::Unknown) {
0174         m_ui->privacyCombo->setCurrentIndex(static_cast<int>(ipv6Setting->privacy()) + 1);
0175     }
0176 }
0177 
0178 QVariantMap IPv6Widget::setting() const
0179 {
0180     NetworkManager::Ipv6Setting ipv6Setting;
0181 
0182     // BUG:406118
0183     // We don't have route-metric in the UI, maybe even won't have for now, but that doesn't mean we
0184     // want to loose it when it's configured manually in a config file
0185     ipv6Setting.setRouteMetric(m_tmpIpv6Setting.routeMetric());
0186 
0187     ipv6Setting.setRoutes(m_tmpIpv6Setting.routes());
0188     ipv6Setting.setNeverDefault(m_tmpIpv6Setting.neverDefault());
0189     ipv6Setting.setIgnoreAutoRoutes(m_tmpIpv6Setting.ignoreAutoRoutes());
0190 
0191     // method
0192     switch ((MethodIndex)m_ui->method->currentIndex()) {
0193     case Automatic:
0194         ipv6Setting.setMethod(NetworkManager::Ipv6Setting::Automatic);
0195         break;
0196     case AutomaticOnlyIP:
0197         ipv6Setting.setMethod(NetworkManager::Ipv6Setting::Automatic);
0198         ipv6Setting.setIgnoreAutoDns(true);
0199         break;
0200     case IPv6Widget::AutomaticOnlyDHCP:
0201         ipv6Setting.setMethod(NetworkManager::Ipv6Setting::Dhcp);
0202         break;
0203     case Manual:
0204         ipv6Setting.setMethod(NetworkManager::Ipv6Setting::Manual);
0205         break;
0206     case LinkLocal:
0207         ipv6Setting.setMethod(NetworkManager::Ipv6Setting::LinkLocal);
0208         break;
0209     case Ignored:
0210         ipv6Setting.setMethod(NetworkManager::Ipv6Setting::Ignored);
0211         break;
0212     case Disabled:
0213         ipv6Setting.setMethod(NetworkManager::Ipv6Setting::ConfigDisabled);
0214         break;
0215     }
0216 
0217     // dns
0218     if (m_ui->dns->isEnabled() && !m_ui->dns->text().isEmpty()) {
0219         QStringList tmp = m_ui->dns->text().split(QLatin1Char(','));
0220         QList<QHostAddress> tmpAddrList;
0221         for (const QString &str : tmp) {
0222             QHostAddress addr(str);
0223             if (!addr.isNull())
0224                 tmpAddrList.append(addr);
0225         }
0226         ipv6Setting.setDns(tmpAddrList);
0227     }
0228     if (m_ui->dnsSearch->isEnabled() && !m_ui->dnsSearch->text().isEmpty()) {
0229         ipv6Setting.setDnsSearch(m_ui->dnsSearch->text().split(QLatin1Char(',')));
0230     }
0231 
0232     // addresses
0233     if (m_ui->tableViewAddresses->isEnabled()) {
0234         QList<NetworkManager::IpAddress> list;
0235         for (int i = 0, rowCount = d->model.rowCount(); i < rowCount; i++) {
0236             NetworkManager::IpAddress address;
0237             address.setIp(QHostAddress(d->model.item(i, 0)->text()));
0238             address.setPrefixLength(d->model.item(i, 1)->text().toInt());
0239             address.setGateway(QHostAddress(d->model.item(i, 2)->text()));
0240 
0241             list << address;
0242         }
0243         ipv6Setting.setAddresses(list);
0244     }
0245 
0246     // may-fail
0247     if (m_ui->ipv6RequiredCB->isEnabled()) {
0248         ipv6Setting.setMayFail(!m_ui->ipv6RequiredCB->isChecked());
0249     }
0250 
0251     // privacy
0252     if (m_ui->privacyCombo->isEnabled() && m_ui->privacyCombo->currentIndex()) {
0253         ipv6Setting.setPrivacy(static_cast<NetworkManager::Ipv6Setting::IPv6Privacy>(m_ui->privacyCombo->currentIndex() - 1));
0254     }
0255 
0256     return ipv6Setting.toMap();
0257 }
0258 
0259 void IPv6Widget::slotModeComboChanged(int index)
0260 {
0261     if (index == Automatic) { // Automatic
0262         m_ui->dnsLabel->setText(i18n("Other DNS Servers:"));
0263         m_ui->dns->setEnabled(true);
0264         m_ui->dnsMorePushButton->setEnabled(true);
0265         m_ui->dnsSearch->setEnabled(true);
0266         m_ui->dnsSearchMorePushButton->setEnabled(true);
0267         m_ui->ipv6RequiredCB->setEnabled(true);
0268         m_ui->privacyCombo->setEnabled(true);
0269         m_ui->btnRoutes->setEnabled(true);
0270         m_ui->tableViewAddresses->setEnabled(false);
0271         m_ui->btnAdd->setEnabled(false);
0272         m_ui->btnRemove->setEnabled(false);
0273     } else if (index == AutomaticOnlyIP) {
0274         m_ui->dnsLabel->setText(i18n("DNS Servers:"));
0275         m_ui->dns->setEnabled(true);
0276         m_ui->dnsMorePushButton->setEnabled(true);
0277         m_ui->dnsSearch->setEnabled(true);
0278         m_ui->dnsSearchMorePushButton->setEnabled(true);
0279         m_ui->ipv6RequiredCB->setEnabled(true);
0280         m_ui->privacyCombo->setEnabled(true);
0281         m_ui->btnRoutes->setEnabled(true);
0282         m_ui->tableViewAddresses->setEnabled(false);
0283         m_ui->btnAdd->setEnabled(false);
0284         m_ui->btnRemove->setEnabled(false);
0285     } else if (index == Manual) { // Manual
0286         m_ui->dnsLabel->setText(i18n("DNS Servers:"));
0287         m_ui->dns->setEnabled(true);
0288         m_ui->dnsMorePushButton->setEnabled(true);
0289         m_ui->dnsSearch->setEnabled(true);
0290         m_ui->dnsSearchMorePushButton->setEnabled(true);
0291         m_ui->ipv6RequiredCB->setEnabled(true);
0292         m_ui->privacyCombo->setEnabled(true);
0293         m_ui->btnRoutes->setEnabled(true);
0294         m_ui->tableViewAddresses->setEnabled(true);
0295         m_ui->btnAdd->setEnabled(true);
0296         m_ui->btnRemove->setEnabled(true);
0297     } else if (index == AutomaticOnlyDHCP || index == LinkLocal) { // Link-local or DHCP
0298         m_ui->dnsLabel->setText(i18n("DNS Servers:"));
0299         m_ui->dns->setEnabled(false);
0300         m_ui->dnsMorePushButton->setEnabled(false);
0301         m_ui->dnsSearch->setEnabled(false);
0302         m_ui->dnsSearchMorePushButton->setEnabled(false);
0303         m_ui->ipv6RequiredCB->setEnabled(true);
0304         m_ui->privacyCombo->setEnabled(true);
0305         m_ui->btnRoutes->setEnabled(false);
0306         m_ui->tableViewAddresses->setEnabled(false);
0307         m_ui->btnAdd->setEnabled(false);
0308         m_ui->btnRemove->setEnabled(false);
0309     } else if (index == Ignored || index == Disabled) { // Ignored and Disabled
0310         m_ui->dnsLabel->setText(i18n("DNS Servers:"));
0311         m_ui->dns->setEnabled(false);
0312         m_ui->dnsMorePushButton->setEnabled(false);
0313         m_ui->dnsSearch->setEnabled(false);
0314         m_ui->dnsSearchMorePushButton->setEnabled(false);
0315         m_ui->ipv6RequiredCB->setEnabled(false);
0316         m_ui->privacyCombo->setEnabled(false);
0317         m_ui->btnRoutes->setEnabled(false);
0318         m_ui->tableViewAddresses->setEnabled(false);
0319         m_ui->btnAdd->setEnabled(false);
0320         m_ui->btnRemove->setEnabled(false);
0321     }
0322 }
0323 
0324 void IPv6Widget::slotAddIPAddress()
0325 {
0326     QList<QStandardItem *> item{new QStandardItem, new QStandardItem, new QStandardItem};
0327     d->model.appendRow(item);
0328 
0329     const int rowCount = d->model.rowCount();
0330     if (rowCount > 0) {
0331         m_ui->tableViewAddresses->selectRow(rowCount - 1);
0332 
0333         QItemSelectionModel *selectionModel = m_ui->tableViewAddresses->selectionModel();
0334         QModelIndexList list = selectionModel->selectedIndexes();
0335         if (list.size()) {
0336             // QTableView is configured to select only rows.
0337             // So, list[0] - IP address.
0338             m_ui->tableViewAddresses->edit(list[0]);
0339         }
0340     }
0341 }
0342 
0343 void IPv6Widget::slotRemoveIPAddress()
0344 {
0345     QItemSelectionModel *selectionModel = m_ui->tableViewAddresses->selectionModel();
0346     if (selectionModel->hasSelection()) {
0347         QModelIndexList indexes = selectionModel->selectedIndexes();
0348         d->model.takeRow(indexes[0].row());
0349     }
0350     m_ui->btnRemove->setEnabled(m_ui->tableViewAddresses->selectionModel()->hasSelection());
0351 }
0352 
0353 void IPv6Widget::selectionChanged(const QItemSelection &selected)
0354 {
0355     m_ui->btnRemove->setEnabled(!selected.isEmpty());
0356 }
0357 
0358 void IPv6Widget::tableViewItemChanged(QStandardItem *item)
0359 {
0360     if (item->text().isEmpty()) {
0361         return;
0362     }
0363 
0364     const int column = item->column();
0365     if (column == 0) { // ip
0366         int row = item->row();
0367 
0368         QStandardItem *netmaskItem = d->model.item(row, column + 1); // netmask
0369         if (netmaskItem && netmaskItem->text().isEmpty()) {
0370             QHostAddress addr(item->text());
0371             const quint32 netmask = suggestNetmask(addr.toIPv6Address());
0372             if (netmask) {
0373                 netmaskItem->setText(QString::number(netmask, 10));
0374             }
0375         }
0376     }
0377 }
0378 
0379 void IPv6Widget::slotRoutesDialog()
0380 {
0381     QPointer<IpV6RoutesWidget> dlg = new IpV6RoutesWidget(this);
0382     dlg->setAttribute(Qt::WA_DeleteOnClose);
0383 
0384     dlg->setRoutes(m_tmpIpv6Setting.routes());
0385     dlg->setNeverDefault(m_tmpIpv6Setting.neverDefault());
0386     if (m_ui->method->currentIndex() == 3) { // manual
0387         dlg->setIgnoreAutoRoutesCheckboxEnabled(false);
0388     } else {
0389         dlg->setIgnoreAutoRoutes(m_tmpIpv6Setting.ignoreAutoRoutes());
0390     }
0391 
0392     connect(dlg.data(), &QDialog::accepted, [dlg, this]() {
0393         m_tmpIpv6Setting.setRoutes(dlg->routes());
0394         m_tmpIpv6Setting.setNeverDefault(dlg->neverDefault());
0395         m_tmpIpv6Setting.setIgnoreAutoRoutes(dlg->ignoreautoroutes());
0396     });
0397     dlg->setModal(true);
0398     dlg->show();
0399 }
0400 
0401 void IPv6Widget::slotDnsServers()
0402 {
0403     QPointer<QDialog> dialog = new QDialog(this);
0404     dialog->setAttribute(Qt::WA_DeleteOnClose);
0405     dialog->setWindowTitle(i18n("Edit DNS servers"));
0406     dialog->setLayout(new QVBoxLayout);
0407     auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog);
0408     connect(buttons, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept);
0409     connect(buttons, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject);
0410     auto listWidget = new KEditListWidget(dialog);
0411     listWidget->setItems(m_ui->dns->text().split(QLatin1Char(',')).replaceInStrings(QStringLiteral(" "), QLatin1String("")));
0412     listWidget->lineEdit()->setFocus(Qt::OtherFocusReason);
0413     dialog->layout()->addWidget(listWidget);
0414     dialog->layout()->addWidget(buttons);
0415     connect(dialog.data(), &QDialog::accepted, [listWidget, this]() {
0416         QString text = listWidget->items().join(QStringLiteral(","));
0417         if (text.endsWith(',')) {
0418             text.chop(1);
0419         }
0420         m_ui->dns->setText(text);
0421     });
0422     dialog->setModal(true);
0423     dialog->show();
0424 }
0425 
0426 void IPv6Widget::slotDnsDomains()
0427 {
0428     QPointer<QDialog> dialog = new QDialog(this);
0429     dialog->setAttribute(Qt::WA_DeleteOnClose);
0430     dialog->setWindowTitle(i18n("Edit DNS search domains"));
0431     dialog->setLayout(new QVBoxLayout);
0432     auto buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog);
0433     connect(buttons, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept);
0434     connect(buttons, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject);
0435     auto listWidget = new KEditListWidget(dialog);
0436     listWidget->setItems(m_ui->dnsSearch->text().split(QLatin1Char(',')).replaceInStrings(QStringLiteral(" "), QLatin1String("")));
0437     listWidget->lineEdit()->setFocus(Qt::OtherFocusReason);
0438     dialog->layout()->addWidget(listWidget);
0439     dialog->layout()->addWidget(buttons);
0440     connect(dialog.data(), &QDialog::accepted, [listWidget, this]() {
0441         QString text = listWidget->items().join(QStringLiteral(","));
0442         if (text.endsWith(',')) {
0443             text.chop(1);
0444         }
0445         m_ui->dnsSearch->setText(text);
0446     });
0447     dialog->setModal(true);
0448     dialog->show();
0449 }
0450 
0451 bool IPv6Widget::isValid() const
0452 {
0453     if (m_ui->method->currentIndex() == Manual) {
0454         if (!d->model.rowCount()) {
0455             return false;
0456         }
0457 
0458         for (int i = 0, rowCount = d->model.rowCount(); i < rowCount; i++) {
0459             QHostAddress ip = QHostAddress(d->model.item(i, 0)->text());
0460             const int prefix = d->model.item(i, 1)->text().toInt();
0461             QHostAddress gateway = QHostAddress(d->model.item(i, 2)->text());
0462 
0463             if (ip.isNull() || !(prefix >= 1 && prefix <= 128) || (gateway.isNull() && !d->model.item(i, 2)->text().isEmpty())) {
0464                 return false;
0465             }
0466         }
0467     }
0468 
0469     if (!m_ui->dns->text().isEmpty()
0470         && (m_ui->method->currentIndex() == Automatic || m_ui->method->currentIndex() == Manual || m_ui->method->currentIndex() == AutomaticOnlyIP)) {
0471         const QStringList tmp = m_ui->dns->text().split(QLatin1Char(','));
0472         for (const QString &str : tmp) {
0473             QHostAddress addr(str);
0474             if (addr.isNull()) {
0475                 return false;
0476             }
0477         }
0478     }
0479 
0480     return true;
0481 }
0482 
0483 #include "moc_ipv6widget.cpp"