File indexing completed on 2024-05-26 05:37:20

0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 // SPDX-FileCopyrightText: 2011 Craig Drummond <craig.p.drummond@gmail.com>
0003 // SPDX-FileCopyrightText: 2018 Alexis Lopes Zubeta <contact@azubieta.net>
0004 // SPDX-FileCopyrightText: 2020 Tomaz Canabrava <tcanabrava@kde.org>
0005 /*
0006  * UFW KControl Module
0007  */
0008 
0009 #include "ufwclient.h"
0010 #include "ufwlogmodel.h"
0011 
0012 #include <rule.h>
0013 #include <systemdjob.h>
0014 #include <types.h>
0015 
0016 #include <QDebug>
0017 #include <QDir>
0018 #include <QNetworkInterface>
0019 #include <QProcess>
0020 #include <QRegularExpression>
0021 #include <QStandardPaths>
0022 #include <QVariantMap>
0023 #include <QXmlStreamReader>
0024 
0025 #include <KAuth/ExecuteJob>
0026 
0027 #include <KConfig>
0028 #include <KConfigGroup>
0029 #include <KLocalizedString>
0030 #include <KPluginFactory>
0031 
0032 #include <loglistmodel.h>
0033 #include <rulelistmodel.h>
0034 
0035 K_PLUGIN_CLASS_WITH_JSON(UfwClient, "ufwbackend.json")
0036 Q_LOGGING_CATEGORY(UFWClientDebug, "ufw.client")
0037 
0038 namespace
0039 {
0040 void debugState(KAuth::Action::AuthStatus status)
0041 {
0042     using Status = KAuth::Action::AuthStatus;
0043     switch (status) {
0044     case Status::AuthorizedStatus:
0045         qCDebug(UFWClientDebug) << "Job Authorized";
0046         break;
0047     case Status::AuthRequiredStatus:
0048         qCDebug(UFWClientDebug) << "Job Requires authentication";
0049         break;
0050     case Status::UserCancelledStatus:
0051         qCDebug(UFWClientDebug) << "User cancelled!";
0052         break;
0053     case Status::DeniedStatus:
0054         qCDebug(UFWClientDebug) << "Password denied";
0055         break;
0056     case Status::InvalidStatus:
0057         qCDebug(UFWClientDebug) << "Invalid Status!";
0058         break;
0059     case Status::ErrorStatus:
0060         qCDebug(UFWClientDebug) << "Job is in an error state";
0061         break;
0062     }
0063 }
0064 }
0065 
0066 UfwClient::UfwClient(QObject *parent, const QVariantList &args)
0067     : IFirewallClientBackend(parent, args)
0068     , m_rulesModel(new RuleListModel(this))
0069     , m_logsAutoRefresh(true)
0070 {
0071     queryExecutable("ufw");
0072 }
0073 
0074 QString UfwClient::name() const
0075 {
0076     return QStringLiteral("ufw");
0077 }
0078 
0079 bool UfwClient::isTcpAndUdp(int protocolIdx)
0080 {
0081     return protocolIdx == 0;
0082 }
0083 
0084 void UfwClient::refresh()
0085 {
0086     queryStatus(FirewallClient::DefaultDataBehavior::ReadDefaults, FirewallClient::ProfilesBehavior::ListenProfiles);
0087 }
0088 void UfwClient::enableService(bool value)
0089 {
0090     SystemdJob *job = new SystemdJob(static_cast<SYSTEMD::actions>(value), QStringLiteral("ufw.service"), true);
0091 
0092     connect(job, &SystemdJob::result, this, [job] {
0093         if (job->error()) {
0094             qCDebug(UFWClientDebug) << "SystemdJob Error: " << job->error() << job->errorString();
0095             return;
0096         }
0097     });
0098     job->start();
0099 }
0100 bool UfwClient::enabled() const
0101 {
0102     return m_currentProfile.enabled();
0103 }
0104 
0105 KJob *UfwClient::setEnabled(bool value)
0106 {
0107     if (enabled() == value) {
0108         return nullptr;
0109     }
0110 
0111     QVariantMap args{
0112         {"cmd", "setStatus"},
0113         {"status", value},
0114     };
0115 
0116     KAuth::Action modifyAction = buildModifyAction(args);
0117     qCDebug(UFWClientDebug) << "Starting the set Enabled job";
0118     KAuth::ExecuteJob *job = modifyAction.execute();
0119 
0120     connect(job, &KAuth::ExecuteJob::result, this, [this, job, value] {
0121         qCDebug(UFWClientDebug) << "Set Enabled job finished, triggering a status query.";
0122         if (job->error() == KJob::NoError) {
0123             enableService(value);
0124             queryStatus(FirewallClient::DefaultDataBehavior::ReadDefaults, FirewallClient::ProfilesBehavior::DontListenProfiles);
0125         } else {
0126             qCDebug(UFWClientDebug) << "Job error: " << job->error();
0127         }
0128     });
0129 
0130     return job;
0131 }
0132 
0133 KJob *UfwClient::queryStatus(FirewallClient::DefaultDataBehavior defaultsBehavior, FirewallClient::ProfilesBehavior profilesBehavior)
0134 {
0135     qCDebug(UFWClientDebug) << "Status query starting";
0136     if (m_busy) {
0137         qWarning() << "Ufw client is busy";
0138         return nullptr;
0139     }
0140 
0141     m_busy = true;
0142 
0143     const bool readDefaults = defaultsBehavior == FirewallClient::DefaultDataBehavior::ReadDefaults;
0144     const bool listProfiles = profilesBehavior == FirewallClient::ProfilesBehavior::ListenProfiles;
0145 
0146     QVariantMap args{
0147         {"defaults", readDefaults},
0148         {"profiles", listProfiles},
0149     };
0150 
0151     if (m_queryAction.name().isEmpty()) {
0152         m_queryAction = buildQueryAction(args);
0153     }
0154 
0155     KAuth::ExecuteJob *job = m_queryAction.execute();
0156     connect(job, &KAuth::ExecuteJob::result, this, [this, job] {
0157         qCDebug(UFWClientDebug) << "Status Query finished, setting the profile";
0158         m_busy = false;
0159 
0160         if (job->error() != KJob::NoError) {
0161             Q_EMIT showErrorMessage(i18n("There was an error in the backend! Please report it.\n%1 %2", job->action().name(), job->errorString()));
0162             qWarning() << job->action().name() << job->errorString();
0163             return;
0164         }
0165         QByteArray response = job->data().value("response", "").toByteArray();
0166         setProfile(Profile(response));
0167     });
0168 
0169     qCDebug(UFWClientDebug) << "Starting the Status Query";
0170     job->start();
0171     return job;
0172 }
0173 
0174 KJob *UfwClient::setDefaultIncomingPolicy(QString policy)
0175 {
0176     if (policy == defaultIncomingPolicy()) {
0177         return nullptr;
0178     }
0179 
0180     const QString xmlArg = QStringLiteral("<defaults incoming=\"%1\"/>").arg(policy);
0181 
0182     QVariantMap args{
0183         {"cmd", "setDefaults"},
0184         {"xml", xmlArg},
0185     };
0186 
0187     KAuth::Action modifyAction = buildModifyAction(args);
0188 
0189     KAuth::ExecuteJob *job = modifyAction.execute();
0190     connect(job, &KAuth::ExecuteJob::result, this, [this, job] {
0191         if (!job->error()) {
0192             queryStatus(FirewallClient::DefaultDataBehavior::ReadDefaults, FirewallClient::ProfilesBehavior::DontListenProfiles);
0193         }
0194     });
0195 
0196     job->start();
0197     return job;
0198 }
0199 
0200 KJob *UfwClient::setDefaultOutgoingPolicy(QString policy)
0201 {
0202     if (policy == defaultOutgoingPolicy()) {
0203         return nullptr;
0204     }
0205 
0206     const QString xmlArg = QStringLiteral("<defaults outgoing=\"%1\"/>").arg(policy);
0207 
0208     QVariantMap args{
0209         {"cmd", "setDefaults"},
0210         {"xml", xmlArg},
0211     };
0212 
0213     KAuth::Action modifyAction = buildModifyAction(args);
0214 
0215     KAuth::ExecuteJob *job = modifyAction.execute();
0216     connect(job, &KAuth::ExecuteJob::result, this, [this, job] {
0217         if (!job->error()) {
0218             queryStatus(FirewallClient::DefaultDataBehavior::ReadDefaults, FirewallClient::ProfilesBehavior::DontListenProfiles);
0219         }
0220     });
0221 
0222     job->start();
0223     return job;
0224 }
0225 
0226 void UfwClient::setLogsAutoRefresh(bool logsAutoRefresh)
0227 {
0228     if (m_logsAutoRefresh == logsAutoRefresh)
0229         return;
0230 
0231     if (logsAutoRefresh) {
0232         connect(&m_logsRefreshTimer, &QTimer::timeout, this, &UfwClient::refreshLogs);
0233         m_logsRefreshTimer.setInterval(3000);
0234         m_logsRefreshTimer.start();
0235     } else {
0236         disconnect(&m_logsRefreshTimer, &QTimer::timeout, this, &UfwClient::refreshLogs);
0237         m_logsRefreshTimer.stop();
0238     }
0239 
0240     m_logsAutoRefresh = logsAutoRefresh;
0241     Q_EMIT logsAutoRefreshChanged(m_logsAutoRefresh);
0242 }
0243 
0244 void UfwClient::refreshLogs()
0245 {
0246     if (!m_logs) {
0247         logs();
0248         qWarning() << "Trying to refresh logs without logs model, creating the object.";
0249         return;
0250     }
0251 
0252     KAuth::Action action("org.kde.ufw.viewlog");
0253     action.setHelperId("org.kde.ufw");
0254 
0255     QVariantMap args;
0256     if (m_rawLogs.size() > 0)
0257         args["lastLine"] = m_rawLogs.last();
0258 
0259     action.setArguments(args);
0260 
0261     m_logs->setBusy(true);
0262 
0263     KAuth::ExecuteJob *job = action.execute();
0264     connect(job, &KAuth::ExecuteJob::finished, this, [this, job] {
0265         m_logs->setBusy(false);
0266 
0267         if (job->error()) {
0268             Q_EMIT m_logs->showErrorMessage(i18n("Error fetching firewall logs: %1", job->errorString()));
0269             return;
0270         }
0271 
0272         const QStringList newLogs = job->data().value("lines", "").toStringList();
0273         // FIXME do we really need to store this raw thing here and then processed in the model?
0274         m_rawLogs.append(newLogs);
0275         m_logs->addRawLogs(newLogs);
0276     });
0277 
0278     job->start();
0279 }
0280 
0281 void UfwClient::setProfile(Profile profile)
0282 {
0283     qCDebug(UFWClientDebug) << "Profile Received, Setting the profile on the model";
0284     auto oldProfile = m_currentProfile;
0285     m_currentProfile = profile;
0286     m_rulesModel->setProfile(m_currentProfile);
0287     if (m_currentProfile.enabled() != oldProfile.enabled())
0288         Q_EMIT enabledChanged(m_currentProfile.enabled());
0289 
0290     if (m_currentProfile.defaultIncomingPolicy() != oldProfile.defaultIncomingPolicy()) {
0291         const QString policy = Types::toString(m_currentProfile.defaultIncomingPolicy());
0292         Q_EMIT defaultIncomingPolicyChanged(policy);
0293     }
0294 
0295     if (m_currentProfile.defaultOutgoingPolicy() != oldProfile.defaultOutgoingPolicy()) {
0296         const QString policy = Types::toString(m_currentProfile.defaultOutgoingPolicy());
0297         Q_EMIT defaultOutgoingPolicyChanged(policy);
0298     }
0299 
0300     queryKnownApplications();
0301 }
0302 
0303 KAuth::Action UfwClient::buildQueryAction(const QVariantMap &arguments)
0304 {
0305     auto action = KAuth::Action("org.kde.ufw.query");
0306     action.setHelperId("org.kde.ufw");
0307     action.setArguments(arguments);
0308 
0309     return action;
0310 }
0311 
0312 KAuth::Action UfwClient::buildModifyAction(const QVariantMap &arguments)
0313 {
0314     auto action = KAuth::Action("org.kde.ufw.modify");
0315     action.setHelperId("org.kde.ufw");
0316     action.setArguments(arguments);
0317 
0318     return action;
0319 }
0320 
0321 RuleListModel *UfwClient::rules() const
0322 {
0323     return m_rulesModel;
0324 }
0325 
0326 Rule *UfwClient::ruleAt(int index)
0327 {
0328     auto cRules = m_currentProfile.rules();
0329 
0330     if (index < 0 || index >= cRules.count()) {
0331         return nullptr;
0332     }
0333 
0334     Rule *rule = cRules.at(index);
0335     return rule;
0336 }
0337 
0338 KJob *UfwClient::addRule(Rule *r)
0339 {
0340     if (!r) {
0341         qWarning() << "nullptr rule";
0342         return nullptr;
0343     }
0344 
0345     QVariantMap args{
0346         {"cmd", "addRules"},
0347         {"count", 1},
0348         {"xml0", toXml(r)},
0349     };
0350 
0351     KAuth::Action modifyAction = buildModifyAction(args);
0352 
0353     KAuth::ExecuteJob *job = modifyAction.execute();
0354     connect(job, &KAuth::ExecuteJob::result, this, [this, job] {
0355         if (job->error() == KJob::NoError) {
0356             queryStatus(FirewallClient::DefaultDataBehavior::ReadDefaults, FirewallClient::ProfilesBehavior::ListenProfiles);
0357         }
0358     });
0359 
0360     job->start();
0361     return job;
0362 }
0363 
0364 KJob *UfwClient::removeRule(int index)
0365 {
0366     if (index < 0 || index >= m_currentProfile.rules().count()) {
0367         qWarning() << __FUNCTION__ << "invalid rule index";
0368         return nullptr;
0369     }
0370 
0371     // Correct index
0372     index += 1;
0373 
0374     QVariantMap args{
0375         {"cmd", "removeRule"},
0376         {"index", QString::number(index)},
0377     };
0378 
0379     KAuth::Action modifyAction = buildModifyAction(args);
0380     KAuth::ExecuteJob *job = modifyAction.execute();
0381     connect(job, &KAuth::ExecuteJob::statusChanged, this, [](KAuth::Action::AuthStatus status) {
0382         debugState(status);
0383     });
0384 
0385     connect(job, &KAuth::ExecuteJob::result, this, [this, job] {
0386         if (!job->error()) {
0387             queryStatus(FirewallClient::DefaultDataBehavior::ReadDefaults, FirewallClient::ProfilesBehavior::ListenProfiles);
0388         }
0389     });
0390 
0391     job->start();
0392     return job;
0393 }
0394 
0395 KJob *UfwClient::updateRule(Rule *r)
0396 {
0397     /* UFW does not support update rules. the tests showed that remove / insert is flacky. */
0398     Q_UNUSED(r);
0399     return nullptr;
0400 }
0401 
0402 KJob *UfwClient::moveRule(int from, int to)
0403 {
0404     const QList<Rule *> cRules = m_currentProfile.rules();
0405     if (from < 0 || from >= cRules.count()) {
0406         qWarning() << "invalid from index";
0407         return nullptr;
0408     }
0409 
0410     // Correct indices
0411     from += 1;
0412     to += 1;
0413 
0414     QVariantMap args{
0415         {"cmd", "moveRule"},
0416         {"from", from},
0417         {"to", to},
0418     };
0419 
0420     KAuth::Action modifyAction = buildModifyAction(args);
0421     KAuth::ExecuteJob *job = modifyAction.execute();
0422     connect(job, &KAuth::ExecuteJob::finished, this, [this, job] {
0423         if (!job->error()) {
0424             queryStatus(FirewallClient::DefaultDataBehavior::ReadDefaults, FirewallClient::ProfilesBehavior::ListenProfiles);
0425         }
0426     });
0427 
0428     job->start();
0429     return job;
0430 }
0431 
0432 QString UfwClient::defaultIncomingPolicy() const
0433 {
0434     auto policy_t = m_currentProfile.defaultIncomingPolicy();
0435     return Types::toString(policy_t);
0436 }
0437 
0438 QString UfwClient::defaultOutgoingPolicy() const
0439 {
0440     auto policy_t = m_currentProfile.defaultOutgoingPolicy();
0441     return Types::toString(policy_t);
0442 }
0443 
0444 LogListModel *UfwClient::logs()
0445 {
0446     if (!m_logs) {
0447         m_logs = new UfwLogModel(this);
0448         refreshLogs();
0449     }
0450     return m_logs;
0451 }
0452 
0453 bool UfwClient::logsAutoRefresh() const
0454 {
0455     return m_logsAutoRefresh;
0456 }
0457 
0458 namespace
0459 {
0460 bool isNumber(const QString &s)
0461 {
0462     bool error = true;
0463     int dummy = s.toInt(&error);
0464     Q_UNUSED(dummy)
0465     return error;
0466 }
0467 
0468 QString portStrToInt(const QString &portStr)
0469 {
0470     QFile file("/etc/services");
0471     if (!file.open(QIODevice::ReadOnly)) {
0472         qCDebug(UFWClientDebug) << "Could not open file, returning";
0473         return portStr;
0474     }
0475     while (!file.atEnd()) {
0476         QString line = file.readLine();
0477         if (!line.startsWith(portStr.toLocal8Bit())) {
0478             continue;
0479         }
0480 
0481         // http      80/tcp
0482         auto list = line.split(QRegularExpression("\\s+"));
0483         if (list.size() > 1) {
0484             if (list[1].contains('/')) {
0485                 return list[1].split('/')[0];
0486             } else {
0487                 return list[1];
0488             }
0489         }
0490     }
0491     return "";
0492 }
0493 }
0494 
0495 Rule *UfwClient::createRuleFromConnection(const QString &protocol, const QString &localAddress, const QString &foreignAddres, const QString &status)
0496 {
0497     // FIXME use a regexp for that and support ipv6!
0498     auto _localAddress = localAddress;
0499     _localAddress.replace("*", "");
0500     _localAddress.replace("0.0.0.0", "");
0501 
0502     auto _foreignAddres = foreignAddres;
0503     _foreignAddres.replace("*", "");
0504     _foreignAddres.replace("0.0.0.0", "");
0505 
0506     auto localAddressData = _localAddress.split(":");
0507     auto foreignAddresData = _foreignAddres.split(":");
0508 
0509     if (!isNumber(localAddressData[1])) {
0510         localAddressData[1] = portStrToInt(localAddressData[1]);
0511     }
0512     if (!isNumber(foreignAddresData[1])) {
0513         foreignAddresData[1] = portStrToInt(foreignAddresData[1]);
0514     }
0515 
0516     auto rule = new Rule();
0517     rule->setIncoming(status == QStringLiteral("LISTEN"));
0518     rule->setPolicy("deny");
0519 
0520     qCDebug(UFWClientDebug) << "-----------------------";
0521     qCDebug(UFWClientDebug) << foreignAddresData << localAddressData;
0522     qCDebug(UFWClientDebug) << "------------------------";
0523 
0524     // Prepare rule draft
0525     if (status == QStringLiteral("LISTEN")) {
0526         rule->setSourceAddress(foreignAddresData[0]);
0527         rule->setSourcePort(foreignAddresData[1]);
0528         rule->setDestinationAddress(localAddressData[0]);
0529         rule->setDestinationPort(localAddressData[1]);
0530     } else {
0531         rule->setSourceAddress(localAddressData[0]);
0532         rule->setSourcePort(localAddressData[1]);
0533         rule->setDestinationAddress(foreignAddresData[0]);
0534         rule->setDestinationPort(foreignAddresData[1]);
0535     }
0536 
0537     rule->setProtocol(knownProtocols().indexOf(protocol.toUpper()));
0538     return rule;
0539 }
0540 
0541 Rule *UfwClient::createRuleFromLog(const QString &protocol,
0542                                    const QString &sourceAddress,
0543                                    const QString &sourcePort,
0544                                    const QString &destinationAddress,
0545                                    const QString &destinationPort,
0546                                    const QString &inn)
0547 {
0548     // Transform to the ufw notation
0549     auto rule = new Rule();
0550 
0551     auto _sourceAddress = sourceAddress;
0552     _sourceAddress.replace("*", "");
0553     _sourceAddress.replace("0.0.0.0", "");
0554 
0555     auto _destinationAddress = destinationAddress;
0556     _destinationAddress.replace("*", "");
0557     _destinationAddress.replace("0.0.0.0", "");
0558 
0559     // Heuristic to determine whether we should be ipv6
0560     // TODO error when one is ipv6 and the other isn't?
0561     if (sourceAddress.contains(QLatin1Char(':')) && destinationAddress.contains(QLatin1Char(':'))) {
0562         rule->setIpv6(true);
0563     }
0564 
0565     // Prepare rule draft
0566     rule->setIncoming(inn.size());
0567     rule->setPolicy("deny");
0568     rule->setSourceAddress(_sourceAddress);
0569     rule->setSourcePort(sourcePort);
0570 
0571     rule->setDestinationAddress(_destinationAddress);
0572     rule->setDestinationPort(destinationPort);
0573 
0574     rule->setProtocol(knownProtocols().indexOf(protocol.toUpper()));
0575     return rule;
0576 }
0577 
0578 IFirewallClientBackend *UfwClient::createMethod(FirewallClient *parent)
0579 {
0580     IFirewallClientBackend *instance = new UfwClient(parent, {} /*args*/);
0581     return instance;
0582 }
0583 
0584 void UfwClient::refreshProfiles()
0585 {
0586     static const char *constProfileDir = "/etc/ufw/applications.d/";
0587 
0588     const QStringList files(QDir(constProfileDir).entryList(QDir::NoDotAndDotDot));
0589 
0590     QList<Entry> profiles;
0591     for (const auto &file : files) {
0592         KConfig cfg(constProfileDir + file, KConfig::SimpleConfig);
0593 
0594         for (const auto &group : cfg.groupList()) {
0595             const QString ports(cfg.group(group).readEntry("ports", QString()));
0596 
0597             if (!ports.isEmpty() && !profiles.contains(group)) {
0598                 profiles.append(Entry(group, ports));
0599             }
0600         }
0601     }
0602 
0603     setProfiles(profiles);
0604 }
0605 
0606 QStringList UfwClient::knownProtocols()
0607 {
0608     return {i18n("Any"), "TCP", "UDP"};
0609 }
0610 
0611 QString UfwClient::toXml(Rule *r) const
0612 {
0613     QString xmlString;
0614 
0615     QXmlStreamWriter xml(&xmlString);
0616 
0617     xml.writeStartElement(QStringLiteral("rule"));
0618 
0619     if (r->position() != 0) {
0620         qCDebug(UFWClientDebug) << "Getting the position" << r->position();
0621         xml.writeAttribute(QStringLiteral("position"), QString::number(r->position()));
0622     }
0623 
0624     xml.writeAttribute(QStringLiteral("action"), Types::toString(r->action()));
0625     xml.writeAttribute(QStringLiteral("direction"), r->incoming() ? QStringLiteral("in") : QStringLiteral("out"));
0626     // ufw doesn't know when to treat a service (simplified rule) as in or out by itself, so we have to hint it the correct
0627     // direction.
0628     bool hintUFW = r->incoming() && r->simplified();
0629 
0630     if (!r->destinationApplication().isEmpty()) {
0631         xml.writeAttribute(QStringLiteral("dapp"), r->destinationApplication());
0632     } else if (!r->destinationPort().isEmpty()) {
0633         xml.writeAttribute(QStringLiteral("dport"), r->destinationPort());
0634     }
0635     if (!r->sourceApplication().isEmpty()) {
0636         hintUFW ? xml.writeAttribute(QStringLiteral("dapp"), r->sourceApplication()) : xml.writeAttribute(QStringLiteral("sapp"), r->sourceApplication());
0637     } else if (!r->sourcePort().isEmpty()) {
0638         hintUFW ? xml.writeAttribute(QStringLiteral("dport"), r->sourcePort()) : xml.writeAttribute(QStringLiteral("sport"), r->sourcePort());
0639     }
0640 
0641     if (!FirewallClient::isTcpAndUdp(r->protocol())) {
0642         xml.writeAttribute(QStringLiteral("protocol"), FirewallClient::knownProtocols().at(r->protocol()));
0643     }
0644 
0645     if (!r->destinationAddress().isEmpty()) {
0646         xml.writeAttribute(QStringLiteral("dst"), r->destinationAddress());
0647     }
0648     if (!r->sourceAddress().isEmpty()) {
0649         xml.writeAttribute(QStringLiteral("src"), r->sourceAddress());
0650     }
0651 
0652     if (!r->interfaceIn().isEmpty()) {
0653         xml.writeAttribute(QStringLiteral("interface_in"), r->interfaceIn());
0654     }
0655     if (!r->interfaceOut().isEmpty()) {
0656         xml.writeAttribute(QStringLiteral("interface_out"), r->interfaceOut());
0657     }
0658 
0659     xml.writeAttribute(QStringLiteral("logtype"), Types::toString(r->logging()));
0660 
0661     xml.writeAttribute(QStringLiteral("v6"), r->ipv6() ? QStringLiteral("True") : QStringLiteral("False"));
0662 
0663     xml.writeEndElement();
0664 
0665     return xmlString;
0666 }
0667 
0668 bool UfwClient::isCurrentlyLoaded() const
0669 {
0670     QProcess process;
0671     const QString pname = "systemctl";
0672     const QStringList args = {"status", "ufw"};
0673 
0674     process.start(pname, args);
0675     process.waitForFinished();
0676 
0677     // systemctl returns 0 for status if the app is loaded, and 3 otherwise.
0678     qCDebug(UFWClientDebug) << "Ufw is loaded?" << (process.exitCode() == EXIT_SUCCESS);
0679 
0680     return process.exitCode() == EXIT_SUCCESS;
0681 }
0682 
0683 bool UfwClient::supportsRuleUpdate() const
0684 {
0685     return false;
0686 }
0687 
0688 QString UfwClient::version() const
0689 {
0690     QProcess process;
0691     QStringList args = {"--version"};
0692 
0693     process.start(executablePath(), args);
0694     process.waitForFinished();
0695 
0696     if (process.exitCode() != EXIT_SUCCESS) {
0697         return i18n("Error fetching information from the firewall.");
0698     }
0699 
0700     return process.readAllStandardOutput();
0701 }
0702 
0703 QStringList UfwClient::knownApplications()
0704 {
0705     return m_knownApplications;
0706 }
0707 
0708 void UfwClient::queryKnownApplications()
0709 {
0710     auto action = KAuth::Action("org.kde.ufw.queryapps");
0711     action.setHelperId("org.kde.ufw");
0712 
0713     KAuth::ExecuteJob *job = action.execute();
0714 
0715     connect(job, &KAuth::ExecuteJob::result, this, [this, job] {
0716         if (job->error() == KJob::NoError) {
0717             m_knownApplications = job->data()["response"].toStringList();
0718             qCDebug(UFWClientDebug) << "Setting the known applications to" << m_knownApplications;
0719         } else {
0720             qCDebug(UFWClientDebug) << "Job error: " << job->error();
0721         }
0722     });
0723     job->start();
0724 }
0725 
0726 #include "ufwclient.moc"