File indexing completed on 2025-01-05 05:07:03
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 "helper.h" 0010 #include <QByteArray> 0011 #include <QDebug> 0012 #include <QDir> 0013 #include <QFile> 0014 #include <QProcess> 0015 #include <QProcessEnvironment> 0016 #include <QString> 0017 #include <QStringList> 0018 #include <QStandardPaths> 0019 #include <sys/stat.h> 0020 0021 #include <KAuth/HelperSupport> 0022 #include <KLocalizedString> 0023 0024 #include "ufw_helper_config.h" 0025 0026 namespace 0027 { 0028 constexpr int FILE_PERMS = 0644; 0029 constexpr int DIR_PERMS = 0755; 0030 const QString KCM_UFW_DIR = QStringLiteral("/etc/kcm_ufw"); 0031 const QString PROFILE_EXTENSION = QStringLiteral(".ufw"); 0032 0033 void setPermissions(const QString &f, int perms) 0034 { 0035 // Clear any umask before setting file perms 0036 mode_t oldMask(umask(0000)); 0037 ::chmod(QFile::encodeName(f).constData(), perms); 0038 // Reset umask 0039 ::umask(oldMask); 0040 } 0041 0042 void checkFolder() 0043 { 0044 QDir d(KCM_UFW_DIR); 0045 0046 if (!d.exists()) { 0047 d.mkpath(KCM_UFW_DIR); 0048 setPermissions(d.absolutePath(), DIR_PERMS); 0049 } 0050 } 0051 0052 } // namespace 0053 0054 namespace UFW 0055 { 0056 ActionReply Helper::query(const QVariantMap &args) 0057 { 0058 ActionReply reply = 0059 args[QStringLiteral("defaults")].toBool() ? run({"--status", "--defaults", "--list", "--modules"}, "query") : run({"--status", "--list"}, "query"); 0060 0061 if (args[QStringLiteral("profiles")].toBool()) { 0062 QDir dir(KCM_UFW_DIR); 0063 const QStringList profiles = dir.entryList({"*" + PROFILE_EXTENSION}); 0064 QMap<QString, QVariant> data; 0065 for (const QString &profile : profiles) { 0066 QFile f(dir.canonicalPath() + QChar('/') + profile); 0067 if (f.open(QIODevice::ReadOnly)) { 0068 data.insert(profile, f.readAll()); 0069 } 0070 } 0071 reply.addData(QStringLiteral("profiles"), data); 0072 } 0073 0074 return reply; 0075 } 0076 0077 QStringList getLogFromSystemd(const QString &lastLine) 0078 { 0079 QString program = QStringLiteral("journalctl"); 0080 QStringList arguments{"-xb", "-n", "100", "-g", "UFW"}; 0081 0082 QProcess myProcess; 0083 myProcess.start(program, arguments); 0084 myProcess.waitForFinished(); 0085 0086 auto resultString = QString(myProcess.readAllStandardOutput()); 0087 const auto resultList = resultString.split(QStringLiteral("\n")); 0088 0089 // Example Line from Systemd: 0090 // Dec 06 17:42:45 tomatoland kernel: [UFW BLOCK] IN=wlan0 OUT= MAC= SRC=192.168.50.181 DST=224.0.0.252 LEN=56 TOS=0x00 0091 // PREC=0x00 TTL=255 ID=52151 PROTO=UDP SPT=5355 DPT=5355 LEN=36 0092 // We need to remove everything up to the space after ']'. 0093 0094 QStringList result; 0095 for (const QString &line : resultList) { 0096 if (!lastLine.isEmpty() && line == lastLine) { 0097 result.clear(); 0098 continue; 0099 } 0100 result.append(line); 0101 } 0102 return result; 0103 } 0104 0105 ActionReply Helper::queryapps(const QVariantMap &args) 0106 { 0107 Q_UNUSED(args); 0108 QProcess ufw; 0109 ActionReply reply; 0110 0111 const QString ufwexe = QStandardPaths::findExecutable("ufw", {"/usr/sbin", "/usr/bin", "/sbin", "/bin"}); 0112 0113 if (ufwexe.isEmpty()) { 0114 qDebug() << "Executable not found: ufw"; 0115 return reply; 0116 } 0117 0118 ufw.start(ufwexe, {"app", "list"}, QIODevice::ReadOnly); 0119 if (ufw.waitForStarted()) { 0120 ufw.waitForFinished(); 0121 } 0122 0123 auto result = QString::fromLocal8Bit(ufw.readAllStandardOutput()).split(QLatin1Char('\n'), Qt::SkipEmptyParts); 0124 0125 // The first line of the array is "Available Applications:", remove that. 0126 if (result.count()) { 0127 result.removeAt(0); 0128 } 0129 0130 for (auto &value : result) { 0131 value = value.trimmed(); 0132 } 0133 0134 reply.setData({{"response", result}}); 0135 return reply; 0136 } 0137 0138 ActionReply Helper::viewlog(const QVariantMap &args) 0139 { 0140 ActionReply reply; 0141 QString lastLine = args["lastLine"].toString(); 0142 0143 QStringList result = getLogFromSystemd(lastLine); 0144 reply.addData(QStringLiteral("lines"), result); 0145 return reply; 0146 } 0147 0148 ActionReply Helper::modify(const QVariantMap &args) 0149 { 0150 QString cmd = args[QStringLiteral("cmd")].toString(); 0151 0152 return QStringLiteral("setStatus") == cmd ? setStatus(args, cmd) 0153 : QStringLiteral("addRules") == cmd ? addRules(args, cmd) 0154 : QStringLiteral("removeRule") == cmd ? removeRule(args, cmd) 0155 : QStringLiteral("moveRule") == cmd ? moveRule(args, cmd) 0156 : QStringLiteral("editRule") == cmd ? editRule(args, cmd) 0157 : QStringLiteral("reset") == cmd ? reset(cmd) 0158 : QStringLiteral("setDefaults") == cmd ? setDefaults(args, cmd) 0159 : QStringLiteral("setModules") == cmd ? setModules(args, cmd) 0160 : QStringLiteral("setProfile") == cmd ? setProfile(args, cmd) 0161 : QStringLiteral("saveProfile") == cmd ? saveProfile(args, cmd) 0162 : QStringLiteral("deleteProfile") == cmd ? deleteProfile(args, cmd) 0163 : ActionReply::HelperErrorReply(STATUS_INVALID_CMD); 0164 } 0165 0166 ActionReply Helper::setStatus(const QVariantMap &args, const QString &cmd) 0167 { 0168 const QString enabled = args[QStringLiteral("status")].toBool() ? "true" : "false"; 0169 0170 return run({"--setEnabled=" + enabled}, {"--status"}, cmd); 0171 } 0172 0173 ActionReply Helper::setDefaults(const QVariantMap &args, const QString &cmd) 0174 { 0175 QStringList pquery({"--defaults"}); 0176 if (args[QStringLiteral("ipv6")].toBool()) { 0177 pquery.append(QStringLiteral("--list")); 0178 } 0179 0180 const QString defaults = args[QStringLiteral("xml")].toString(); 0181 0182 return run({"--setDefaults=" + defaults}, pquery, cmd); 0183 } 0184 0185 ActionReply Helper::setModules(const QVariantMap &args, const QString &cmd) 0186 { 0187 return run({"--setModules=" + args["xml"].toString()}, {"--modules"}, cmd); 0188 } 0189 0190 ActionReply Helper::setProfile(const QVariantMap &args, const QString &cmd) 0191 { 0192 QStringList cmdArgs; 0193 0194 if (args.contains(QStringLiteral("ruleCount"))) { 0195 unsigned int count = args[QStringLiteral("ruleCount")].toUInt(); 0196 0197 cmdArgs.append(QStringLiteral("--clearRules")); 0198 for (unsigned int i = 0; i < count; ++i) { 0199 const QString argument = args["rule" + QString::number(i)].toString(); 0200 cmdArgs.append("--add=" + argument); 0201 } 0202 } 0203 0204 if (args.contains(QStringLiteral("defaults"))) { 0205 cmdArgs << "--setDefaults=" + args[QStringLiteral("defaults")].toString(); 0206 } 0207 if (args.contains(QStringLiteral("modules"))) { 0208 cmdArgs << "--setModules=" + args[QStringLiteral("modules")].toString(); 0209 } 0210 0211 if (cmdArgs.isEmpty()) { 0212 auto action = ActionReply::HelperErrorReply(STATUS_INVALID_ARGUMENTS); 0213 action.setErrorDescription(i18n("Invalid arguments passed to the profile")); 0214 return action; 0215 } 0216 0217 checkFolder(); 0218 return run(cmdArgs, {"--status", "--defaults", "--list", "--modules"}, cmd); 0219 } 0220 0221 ActionReply Helper::saveProfile(const QVariantMap &args, const QString &cmd) 0222 { 0223 QString name(args[QStringLiteral("name")].toString()), xml(args["xml"].toString()); 0224 ActionReply reply; 0225 auto prepareData = [&] { 0226 reply.addData(QStringLiteral("cmd"), cmd); 0227 reply.addData(QStringLiteral("name"), name); 0228 reply.addData("profiles", QDir(KCM_UFW_DIR).entryList({"*" + PROFILE_EXTENSION})); 0229 }; 0230 0231 if (name.isEmpty() || xml.isEmpty()) { 0232 reply = ActionReply::HelperErrorReply(STATUS_INVALID_ARGUMENTS); 0233 prepareData(); 0234 return reply; 0235 } 0236 0237 checkFolder(); 0238 0239 QFile f(QString(KCM_UFW_DIR) + "/" + name + PROFILE_EXTENSION); 0240 0241 if (!f.open(QIODevice::WriteOnly)) { 0242 reply = ActionReply::HelperErrorReply(STATUS_OPERATION_FAILED); 0243 reply.setErrorDescription(i18n("Error saving the profile.")); 0244 prepareData(); 0245 return reply; 0246 } 0247 0248 QTextStream(&f) << xml; 0249 f.close(); 0250 setPermissions(f.fileName(), FILE_PERMS); 0251 prepareData(); 0252 return reply; 0253 } 0254 0255 ActionReply Helper::deleteProfile(const QVariantMap &args, const QString &cmd) 0256 { 0257 QString name(args[QStringLiteral("name")].toString()); 0258 ActionReply reply; 0259 auto prepareData = [&] { 0260 reply.addData(QStringLiteral("cmd"), cmd); 0261 reply.addData(QStringLiteral("name"), name); 0262 reply.addData(QStringLiteral("profiles"), QDir(KCM_UFW_DIR).entryList({"*" + PROFILE_EXTENSION})); 0263 }; 0264 0265 if (name.isEmpty()) { 0266 reply = ActionReply::HelperErrorReply(STATUS_INVALID_ARGUMENTS); 0267 reply.setErrorDescription(i18n("Invalid arguments passed to delete profile")); 0268 prepareData(); 0269 return reply; 0270 } 0271 0272 if (!QFile::remove(QString(KCM_UFW_DIR) + "/" + name + PROFILE_EXTENSION)) { 0273 reply = ActionReply::HelperErrorReply(STATUS_OPERATION_FAILED); 0274 reply.setErrorDescription(i18n("Could not remove the profile from disk.")); 0275 prepareData(); 0276 return reply; 0277 } 0278 0279 prepareData(); 0280 return reply; 0281 } 0282 0283 ActionReply Helper::addRules(const QVariantMap &args, const QString &cmd) 0284 { 0285 int count = args[QStringLiteral("count")].toInt(); 0286 0287 if (count <= 0) { 0288 ActionReply reply = ActionReply::HelperErrorReply(STATUS_INVALID_ARGUMENTS); 0289 reply.setErrorDescription(i18n("Invalid argument passed to add Rules")); 0290 return reply; 0291 } 0292 QStringList cmdArgs; 0293 0294 for (int i = 0; i < count; ++i) { 0295 cmdArgs << "--add=" + args["xml" + QString::number(i)].toString(); 0296 } 0297 qDebug() << "Cmd args passed to ufw:" << cmdArgs; 0298 0299 checkFolder(); 0300 return run(cmdArgs, {"--list"}, cmd); 0301 } 0302 0303 ActionReply Helper::removeRule(const QVariantMap &args, const QString &cmd) 0304 { 0305 checkFolder(); 0306 return run({"--remove=" + args["index"].toString()}, {"--list"}, cmd); 0307 } 0308 0309 ActionReply Helper::moveRule(const QVariantMap &args, const QString &cmd) 0310 { 0311 checkFolder(); 0312 const QString from = QString::number(args[QStringLiteral("from")].toUInt()); 0313 const QString to = QString::number(args[QStringLiteral("to")].toUInt()); 0314 0315 return run({"--move=" + from + ':' + to}, {"--list"}, cmd); 0316 } 0317 0318 ActionReply Helper::editRule(const QVariantMap &args, const QString &cmd) 0319 { 0320 checkFolder(); 0321 0322 qDebug() << args[QStringLiteral("xml")].toString(); 0323 0324 return run({"--update=" + args["xml"].toString()}, {"--list"}, cmd); 0325 } 0326 0327 ActionReply Helper::reset(const QString &cmd) 0328 { 0329 return run({"--reset"}, {"--status", "--defaults", "--list", "--modules"}, cmd); 0330 } 0331 0332 ActionReply Helper::run(const QStringList &args, const QStringList &second, const QString &cmd) 0333 { 0334 ActionReply reply = run(args, cmd); 0335 if (reply.errorCode() == EXIT_SUCCESS) { 0336 reply = run(second, cmd); 0337 } 0338 return reply; 0339 } 0340 0341 ActionReply Helper::run(const QStringList &args, const QString &cmd) 0342 { 0343 QProcess ufw; 0344 ActionReply reply; 0345 ufw.start(UFW_PLUGIN_HELPER_PATH, args, QIODevice::ReadOnly); 0346 if (ufw.waitForStarted()) { 0347 ufw.waitForFinished(); 0348 } 0349 0350 int exitCode(ufw.exitCode()); 0351 0352 if (exitCode != EXIT_SUCCESS) { 0353 QString errorString = ufw.readAllStandardError().simplified(); 0354 0355 const QString errorPrefix = QStringLiteral("ERROR: "); 0356 if (errorString.startsWith(errorPrefix)) { 0357 errorString = errorString.mid(errorPrefix.length()); 0358 } 0359 0360 reply = ActionReply::HelperErrorReply(exitCode); 0361 reply.setErrorDescription(i18n("An error occurred in command '%1': %2", cmd, errorString)); 0362 reply.addData(QStringLiteral("cmd"), cmd); 0363 return reply; 0364 } 0365 0366 // reply.addData(QStringLiteral("response"), ufw.readAllStandardOutput()); 0367 QString output = ufw.readAllStandardOutput(); 0368 qDebug() << "Command" << UFW_PLUGIN_HELPER_PATH << args << output; 0369 0370 reply.addData("response", output); 0371 reply.addData(QStringLiteral("cmd"), cmd); 0372 return reply; 0373 } 0374 0375 } 0376 0377 KAUTH_HELPER_MAIN("org.kde.ufw", UFW::Helper)