File indexing completed on 2024-04-28 16:52:17

0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 // SPDX-FileCopyrightText: 2020 Tomaz Canabrava <tcanabrava@kde.org>
0003 
0004 #include <QCoreApplication>
0005 #include <QDebug>
0006 
0007 #include "firewallclient.h"
0008 #include "rule.h"
0009 #include "rulelistmodel.h"
0010 
0011 #include <KJob>
0012 
0013 // Start
0014 void testDisableClient(FirewallClient *client);
0015 void testDisableClientResult(FirewallClient *client);
0016 
0017 // Second Method.
0018 void testEnableClient(FirewallClient *client);
0019 void testEnableClientResult(FirewallClient *client);
0020 
0021 // Third Method
0022 void testCurrentRulesEmpty(FirewallClient *client);
0023 void testCurrentRulesEmptyResult(FirewallClient *client);
0024 
0025 // Fourth Method
0026 void testAddRules(FirewallClient *client);
0027 void testAddRulesResult(FirewallClient *client);
0028 
0029 // Fifth Method
0030 void testRemoveRules(FirewallClient *client);
0031 void testRemoveRulesResult(FirewallClient *client);
0032 
0033 void printHelp()
0034 {
0035     qDebug() << "Usage Information:";
0036     qDebug() << "$"
0037              << "firewall_test backend_name";
0038     qDebug() << "Where backend name is ether ufw or firewalld.";
0039     qDebug() << "Make sure you have no firewall running";
0040     qDebug() << "And make sure you have no profile configured on either firewall.";
0041 }
0042 
0043 int main(int argc, char *argv[])
0044 {
0045     QCoreApplication app(argc, argv);
0046 
0047     qDebug() << "argc" << argc;
0048     if (argc != 2) {
0049         qDebug() << "Invalid number of arguments, please use:";
0050         printHelp();
0051         exit(1);
0052     }
0053 
0054     QString firewallBackendName = argv[1];
0055     if (firewallBackendName != "ufw" && firewallBackendName != "firewalld") {
0056         printHelp();
0057         exit(1);
0058     }
0059     qDebug() << firewallBackendName << "test starting";
0060 
0061     auto *client = new FirewallClient();
0062     client->setBackend({firewallBackendName});
0063     Q_ASSERT(client->backend() == firewallBackendName);
0064 
0065     // Start trying to disable, the order of calls is the same as the definition order.
0066     // Please don't change as this is really annoying to test.
0067     testDisableClient(client);
0068 
0069     return app.exec();
0070 }
0071 
0072 void testDisableClient(FirewallClient *client)
0073 {
0074     qDebug() << "Test Disable the Firewall";
0075     KJob *enableJob = client->setEnabled(false);
0076 
0077     // Ignore, we are already disabled.
0078     if (enableJob == nullptr) {
0079         testEnableClient(client);
0080         return;
0081     }
0082 
0083     // Nice hack: One time connections.
0084     QMetaObject::Connection *const connection = new QMetaObject::Connection;
0085     *connection = QObject::connect(client, &FirewallClient::enabledChanged, [client, connection] {
0086         QObject::disconnect(*connection);
0087         delete connection;
0088         testDisableClientResult(client);
0089     });
0090 
0091     enableJob->start();
0092 }
0093 
0094 void testDisableClientResult(FirewallClient *client)
0095 {
0096     Q_ASSERT(client->enabled() == false);
0097     testEnableClient(client);
0098 }
0099 
0100 void testEnableClient(FirewallClient *client)
0101 {
0102     qDebug() << "Test Enable the Firewall";
0103 
0104     // From here on, We will jump thru the usage via connects.
0105     KJob *enableJob = client->setEnabled(true);
0106 
0107     // Ignore, we are already enabled.
0108     if (enableJob == nullptr) {
0109         testCurrentRulesEmpty(client);
0110         return;
0111     }
0112 
0113     // Nice hack: One time connections.
0114     QMetaObject::Connection *const connection = new QMetaObject::Connection;
0115     *connection = QObject::connect(client, &FirewallClient::enabledChanged, [client, connection] {
0116         qDebug() << "Enabled Changed";
0117         QObject::disconnect(*connection);
0118         delete connection;
0119         testEnableClientResult(client);
0120     });
0121 
0122     enableJob->start();
0123 }
0124 
0125 void testEnableClientResult(FirewallClient *client)
0126 {
0127     qDebug() << "Client Enabled" << client->enabled();
0128     testCurrentRulesEmpty(client);
0129 }
0130 
0131 void testCurrentRulesEmpty(FirewallClient *client)
0132 {
0133     if (client->rulesModel()->rowCount() != 0) {
0134         qDebug() << "We need a clean firewall rules to do the testing, please backup your rules and try again.";
0135         exit(1);
0136     }
0137 
0138     Q_ASSERT(client->rulesModel()->rowCount() == 0);
0139     testCurrentRulesEmptyResult(client);
0140 }
0141 
0142 void testCurrentRulesEmptyResult(FirewallClient *client)
0143 {
0144     testAddRules(client);
0145 }
0146 
0147 // Fourth Method
0148 void testAddRules(FirewallClient *client)
0149 {
0150     QString interface = client->knownInterfaces()[0];
0151 
0152     // Expected output for firewalld
0153     // firewalld.client: ("-j", "DROP", "-p", "tcp", "-d", "127.0.0.1", "--dport=21", "-s", "127.0.0.1", "--sport=12")
0154     // firewalld.job: firewalld  "addRule" (
0155     //     QVariant(QString, "ipv4"),
0156     //     QVariant(QString, "filter"),
0157     //     QVariant(QString, "INPUT"),
0158     //     QVariant(int, 0),
0159     //     QVariant(QStringList, ("-j", "DROP", "-p", "tcp", "-d", "127.0.0.1", "--dport=21", "-s", "127.0.0.1", "--sport=12"))
0160     //  )
0161 
0162     // Current Output for firewalld:
0163     // firewalld.client: ("-j", "DROP", "-p", "tcp", "-d", "127.0.0.1", "--dport=21", "-s", "127.0.0.1", "--sport=12")
0164     //
0165     // firewalld.job: firewalld  "addRule" (
0166     //     QVariant(QString, "ipv4"),
0167     //     QVariant(QString, "filter"),
0168     //     QVariant(QString, "INPUT"),
0169     //     QVariant(int, 0),
0170     //     QVariant(QStringList, ("-j", "DROP", "-p", "tcp", "-d", "127.0.0.1", "--dport=21", "-s", "127.0.0.1", "--sport=12")))
0171 
0172     // Expected output for ufw
0173     // Debug message from helper: Cmd args passed to ufw: (
0174     // "--add=<rule action=\"deny\"
0175     //         direction=\"in\"
0176     //         dport=\"21\"
0177     //         sport=\"12\"
0178     //         protocol=\"TCP\"
0179     //         dst=\"127.0.0.1\"
0180     //         src=\"127.0.0.1\"
0181     //         logtype=\"\"
0182     //         v6=\"False\"/>")
0183 
0184     // Current Output for ufw
0185     // Debug message from helper: Cmd args passed to ufw: (
0186     // "--add=<rule action=\"deny\"
0187     //    direction=\"in\"
0188     //    dport=\"21\"
0189     //    sport=\"12\"
0190     //    dst=\"127.0.0.1\"
0191     //    src=\"127.0.0.1\"
0192     //    logtype=\"\"
0193     //    v6=\"False\"/>")
0194 
0195     auto *rule = new Rule(Types::Policy::POLICY_DENY, // Policy
0196                           true, // Incomming?
0197                           Types::Logging::LOGGING_OFF, // Logging
0198                           0, // Protocol Id on knownProtocols
0199                           "127.0.0.1", // Source Host
0200                           "12", // Source Port
0201                           "127.0.0.1", // Destination Port
0202                           "21", // Destination Port
0203                           QString(), // Interface In
0204                           QString(), // Interface Out
0205                           QString(), // Source App // Only used in UFW - Remove?
0206                           QString(), // Destination App // Only used in UFW - Remove?
0207                           0, // Index (TODO: Remove This)
0208                           false); // IPV6?
0209 
0210     // This call should perhaps have an client::addRuleFinished(), but currently we need
0211     // to rely on the model refresh
0212     client->addRule(rule);
0213 
0214     // Nice hack: One time connections.
0215     QMetaObject::Connection *const connection = new QMetaObject::Connection;
0216     *connection = QObject::connect(client->rulesModel(), &RuleListModel::modelReset, [client, connection] {
0217         qDebug() << "Add rule finished.";
0218         QObject::disconnect(*connection);
0219         delete connection;
0220         testAddRulesResult(client);
0221     });
0222 
0223     // TODO:
0224     // This job is started inside of the addRule, currently we have an inconsistency on what's
0225     // started by default and what's started by Qml. We need to fix this.
0226     // job->start();
0227 }
0228 
0229 void testAddRulesResult(FirewallClient *client)
0230 {
0231     Q_ASSERT(client->rulesModel()->rowCount() == 1);
0232     client->removeRule(0);
0233 
0234     // Nice hack: One time connections.
0235     QMetaObject::Connection *const connection = new QMetaObject::Connection;
0236     *connection = QObject::connect(client->rulesModel(), &RuleListModel::modelReset, [client, connection] {
0237         qDebug() << "Remove Rule Finished.";
0238         QObject::disconnect(*connection);
0239         delete connection;
0240         testRemoveRulesResult(client);
0241     });
0242 }
0243 
0244 void testRemoveRulesResult(FirewallClient *client)
0245 {
0246     Q_ASSERT(client->rulesModel()->rowCount() == 0);
0247     qDebug() << "Test Finished";
0248 }