2021-05-19 15:07:10 +02:00
|
|
|
#include "DBusConnection.h"
|
2024-02-19 12:52:17 +01:00
|
|
|
#include "IsolationManager.h"
|
2021-05-19 15:07:10 +02:00
|
|
|
|
|
|
|
|
#include <QDBusConnection>
|
2021-05-20 12:43:04 +02:00
|
|
|
#include <QDBusError>
|
2024-02-18 20:58:27 +01:00
|
|
|
#include <QSettings>
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <qdbusargument.h>
|
|
|
|
|
#include <QXmlStreamWriter>
|
|
|
|
|
#include <QBuffer>
|
2024-02-26 11:11:25 +01:00
|
|
|
#include <cstdlib>
|
2021-05-19 15:07:10 +02:00
|
|
|
|
2024-02-19 12:52:17 +01:00
|
|
|
constexpr const char *SERVICE_NAME = "org.tom.IsolationRunner";
|
2021-05-19 15:07:10 +02:00
|
|
|
|
2024-02-19 12:52:17 +01:00
|
|
|
DBusConnection::DBusConnection(IsolationManager *parent)
|
2021-05-19 15:07:10 +02:00
|
|
|
: QDBusAbstractAdaptor(parent),
|
2021-05-20 12:43:04 +02:00
|
|
|
m_parent(parent)
|
2021-05-19 15:07:10 +02:00
|
|
|
{
|
2024-02-18 20:58:27 +01:00
|
|
|
/*
|
|
|
|
|
* Registering ourself like this means that all public slots will become
|
|
|
|
|
* remote-callable.
|
|
|
|
|
*/
|
2024-02-19 09:23:40 +01:00
|
|
|
if (!QDBusConnection::sessionBus().isConnected())
|
2021-05-19 15:07:10 +02:00
|
|
|
throw std::runtime_error("Failed to find the DBUS session");
|
2024-02-19 09:23:40 +01:00
|
|
|
QDBusConnection::sessionBus().registerObject("/apps", parent);
|
|
|
|
|
if (!QDBusConnection::sessionBus().registerService(SERVICE_NAME))
|
|
|
|
|
throw std::runtime_error("Registration with DBus failed, am I already running?");
|
2021-05-19 15:07:10 +02:00
|
|
|
}
|
|
|
|
|
|
2024-02-19 10:47:36 +01:00
|
|
|
void DBusConnection::run(const QString &fullPath, const QStringList &arguments, const QDBusMessage &message)
|
2021-05-19 15:07:10 +02:00
|
|
|
{
|
2024-02-19 19:52:24 +01:00
|
|
|
run2(message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DBusConnection::run2(const QDBusMessage &message)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* The complex run option. With more features.
|
|
|
|
|
* 0: app-name
|
|
|
|
|
* 1: app-arguments
|
|
|
|
|
* 2: executable-path (if different from 0)
|
2024-02-25 23:29:33 +01:00
|
|
|
* 3: bool: lockdown (minimum rights)
|
|
|
|
|
* 4: bool: autodelete
|
2024-04-21 23:55:14 +02:00
|
|
|
* 5: string: jailId
|
2024-02-19 19:52:24 +01:00
|
|
|
*/
|
|
|
|
|
const auto args = message.arguments();
|
|
|
|
|
if (args.size() < 2) {
|
|
|
|
|
QDBusConnection::sessionBus().send(message.createErrorReply(QDBusError::InvalidArgs, "Need more args"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-03-06 11:48:16 +01:00
|
|
|
QString profileName = args.at(0).toString();
|
2024-02-25 19:48:41 +01:00
|
|
|
if (!args.at(1).canConvert(QMetaType::QStringList)) {
|
2024-02-19 19:52:24 +01:00
|
|
|
QDBusConnection::sessionBus().send(message.createErrorReply(QDBusError::InvalidArgs, "invalid args"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
QStringList arguments = args.at(1).toStringList();
|
|
|
|
|
|
2024-02-26 11:11:25 +01:00
|
|
|
bool lockdown = false;
|
|
|
|
|
bool autoDelete = false;
|
2024-04-21 23:55:14 +02:00
|
|
|
QString jailId; // to run a specific executable in an existing jailId, this is the 'id'
|
2026-04-11 14:54:32 +02:00
|
|
|
QString vpnConf, vpnAc;
|
2024-02-26 11:11:25 +01:00
|
|
|
if (args.size() >= 4)
|
|
|
|
|
lockdown = args.at(3).toBool();
|
|
|
|
|
if (args.size() >= 5)
|
|
|
|
|
autoDelete = args.at(4).toBool(); // auto-delete is not allowed for existing profiles
|
2024-04-21 23:55:14 +02:00
|
|
|
if (args.size() >= 6)
|
|
|
|
|
jailId = args.at(5).toString();
|
2026-04-11 14:54:32 +02:00
|
|
|
if (args.size() >= 8) {
|
|
|
|
|
vpnAc = args.at(6).toString();
|
|
|
|
|
vpnConf = args.at(7).toString();
|
|
|
|
|
}
|
2024-02-26 11:11:25 +01:00
|
|
|
|
2024-04-21 23:55:14 +02:00
|
|
|
// jailId means that the profile we run in is defined by the ID,
|
|
|
|
|
// instead of the executable.
|
|
|
|
|
if (!jailId.isEmpty()) {
|
|
|
|
|
if (autoDelete)
|
|
|
|
|
return fail(message, "not valid combination of options");
|
|
|
|
|
profileName = jailId;
|
|
|
|
|
}
|
|
|
|
|
else if (autoDelete) // avoid app namespace collisions.
|
2024-03-06 11:48:16 +01:00
|
|
|
profileName += QString("_tmp-%1").arg(std::rand());
|
2024-02-19 19:52:24 +01:00
|
|
|
|
2024-04-22 17:06:48 +02:00
|
|
|
auto existingAppData = m_parent->lookupApp(profileName, IsolationManager::OnlyExisting);
|
|
|
|
|
if (!jailId.isEmpty()) {
|
|
|
|
|
int jail = existingAppData.appId;
|
|
|
|
|
|
|
|
|
|
// is jailId an int based ID?
|
|
|
|
|
bool isNumberId = false;
|
|
|
|
|
if (existingAppData.appId == -1) {
|
|
|
|
|
jail = jailId.toInt(&isNumberId);
|
|
|
|
|
if (!isNumberId)
|
|
|
|
|
return fail(message, "jail not found");
|
|
|
|
|
}
|
|
|
|
|
for (const auto &profile : m_parent->listProfiles()) {
|
|
|
|
|
if (profile.jailId == jail) {
|
|
|
|
|
if (!profile.active)
|
|
|
|
|
return fail(message, "jail not running");
|
2024-04-22 17:47:13 +02:00
|
|
|
if (isNumberId)
|
|
|
|
|
existingAppData = m_parent->lookupApp(profile.name, IsolationManager::OnlyExisting);
|
2024-04-22 17:06:48 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(jail > 1);
|
|
|
|
|
assert(existingAppData.appId > 0);
|
|
|
|
|
}
|
2024-02-19 19:52:24 +01:00
|
|
|
if (existingAppData.appId == -1) {
|
2024-04-21 23:55:14 +02:00
|
|
|
if (!jailId.isEmpty())
|
|
|
|
|
return fail(message, "jail not found");
|
2024-02-26 11:11:25 +01:00
|
|
|
QString exe;
|
|
|
|
|
if (args.size() >= 3)
|
|
|
|
|
exe = args.at(2).toString();
|
|
|
|
|
if (exe.isEmpty())
|
|
|
|
|
exe = args.at(0).toString();
|
2024-02-25 23:29:33 +01:00
|
|
|
|
2026-04-11 14:44:27 +02:00
|
|
|
if (!exe.startsWith("/"))
|
|
|
|
|
return fail(message, "Invalid path, not absolute");
|
2024-02-25 19:48:41 +01:00
|
|
|
|
2024-02-19 19:52:24 +01:00
|
|
|
QFileInfo info(exe);
|
|
|
|
|
if (!info.exists())
|
|
|
|
|
return fail(message, "File not found");
|
|
|
|
|
else if (!info.isExecutable())
|
|
|
|
|
return fail(message, "Path doesn't point to an executable");
|
|
|
|
|
|
|
|
|
|
// create new entry
|
2024-03-06 11:48:16 +01:00
|
|
|
auto entry = m_parent->startEditApp(profileName, IsolationManager::MaybeCreate);
|
|
|
|
|
entry->setValue("path-to-exe", exe);
|
2024-02-25 23:29:33 +01:00
|
|
|
if (lockdown)
|
2024-03-06 11:48:16 +01:00
|
|
|
entry->setValue("denied", "homedir dbus git ssh media dbus-system audio");
|
2026-04-11 14:54:32 +02:00
|
|
|
if (!vpnAc.isEmpty())
|
|
|
|
|
entry->setValue("vpnAc", vpnAc);
|
|
|
|
|
if (!vpnConf.isEmpty())
|
|
|
|
|
entry->setValue("vpnConf", vpnConf);
|
|
|
|
|
}
|
|
|
|
|
else if (!jailId.isEmpty()) {
|
2024-04-21 23:55:14 +02:00
|
|
|
auto appData = existingAppData;
|
|
|
|
|
// no init script should be run
|
|
|
|
|
appData.initScript.clear();
|
|
|
|
|
if (args.size() >= 3)
|
|
|
|
|
appData.pathToExe = args.at(2).toString();
|
|
|
|
|
if (appData.pathToExe.isEmpty())
|
|
|
|
|
appData.pathToExe = args.at(0).toString();
|
2024-04-22 17:47:13 +02:00
|
|
|
if (!appData.pathToExe.startsWith('/'))
|
|
|
|
|
appData.pathToExe = "/usr/bin/" + appData.pathToExe;
|
|
|
|
|
|
2024-04-21 23:55:14 +02:00
|
|
|
QFileInfo exe(appData.pathToExe);
|
|
|
|
|
if (!exe.isExecutable())
|
|
|
|
|
return fail(message, "Path doesn't point to an executable");
|
|
|
|
|
|
|
|
|
|
auto answer = m_parent->startApplicationRequest(appData, arguments);
|
|
|
|
|
if (answer == "ok")
|
|
|
|
|
QDBusConnection::sessionBus().send(message.createReply(answer));
|
|
|
|
|
else
|
|
|
|
|
fail(message, answer);
|
|
|
|
|
return;
|
2024-02-26 11:11:25 +01:00
|
|
|
} else {
|
|
|
|
|
QString exe;
|
|
|
|
|
if (args.size() >= 3)
|
|
|
|
|
exe = args.at(2).toString();
|
|
|
|
|
if (!exe.isEmpty() && existingAppData.pathToExe != exe) // reject.
|
|
|
|
|
return fail(message, "Exe path not accepted (try without)");
|
2024-02-19 19:52:24 +01:00
|
|
|
}
|
|
|
|
|
|
2024-03-06 11:48:16 +01:00
|
|
|
auto appData = m_parent->lookupApp(profileName, IsolationManager::OnlyExisting);
|
2024-02-19 19:52:24 +01:00
|
|
|
assert(appData.appId != -1);
|
2024-02-25 23:29:33 +01:00
|
|
|
appData.autoDelete = autoDelete;
|
2024-02-19 19:52:24 +01:00
|
|
|
|
|
|
|
|
auto answer = m_parent->startApplicationRequest(appData, arguments);
|
2024-02-18 22:13:16 +01:00
|
|
|
if (answer == "ok")
|
2024-02-19 10:47:36 +01:00
|
|
|
QDBusConnection::sessionBus().send(message.createReply(answer));
|
2024-02-19 19:52:24 +01:00
|
|
|
else
|
|
|
|
|
fail(message, answer);
|
2021-05-19 15:07:10 +02:00
|
|
|
}
|
2024-02-18 20:58:27 +01:00
|
|
|
|
2024-02-25 19:48:41 +01:00
|
|
|
QDBusVariant DBusConnection::listProfiles(bool verbose) const
|
2024-02-18 20:58:27 +01:00
|
|
|
{
|
2024-02-25 16:21:41 +01:00
|
|
|
auto profileInfos = m_parent->listProfiles();
|
|
|
|
|
std::sort(profileInfos.begin(), profileInfos.end(),
|
|
|
|
|
[](const IsolationManager::ProfileInfo &a,
|
|
|
|
|
const IsolationManager::ProfileInfo &b) {
|
|
|
|
|
return a.lastRun > b.lastRun;
|
|
|
|
|
});
|
|
|
|
|
|
2024-03-06 11:48:16 +01:00
|
|
|
QStringList answer;
|
2024-02-25 16:21:41 +01:00
|
|
|
for (const auto &profileInfo : profileInfos) {
|
|
|
|
|
QString profile = profileInfo.active ? "*" : " ";
|
2024-02-25 19:48:41 +01:00
|
|
|
if (verbose)
|
|
|
|
|
profile = profile % " " % QString::number(profileInfo.jailId);
|
|
|
|
|
profile = profile % " " % profileInfo.name;
|
2024-03-06 11:48:16 +01:00
|
|
|
answer.append(profile);
|
2024-03-06 12:17:02 +01:00
|
|
|
if (verbose && !profileInfo.exe.startsWith("/usr/bin/")
|
|
|
|
|
&& !profileInfo.exe.startsWith("/bin/")) {
|
|
|
|
|
// if the exe isn't in /usr/bin or /bin, show it in verbose mode
|
|
|
|
|
answer << QString(" -> ") % profileInfo.exe;
|
|
|
|
|
}
|
2024-02-25 16:21:41 +01:00
|
|
|
}
|
2024-03-06 11:48:16 +01:00
|
|
|
return QDBusVariant(answer);
|
2024-02-18 20:58:27 +01:00
|
|
|
}
|
|
|
|
|
|
2024-02-20 20:27:07 +01:00
|
|
|
void DBusConnection::details(const QString &profile, const QDBusMessage &message) const
|
2024-02-18 20:58:27 +01:00
|
|
|
{
|
2024-02-20 20:27:07 +01:00
|
|
|
if (profile.isEmpty())
|
2024-02-18 20:58:27 +01:00
|
|
|
QDBusConnection::sessionBus().send(message.createErrorReply(QDBusError::InvalidArgs, "Missing arg"));
|
2024-02-20 20:27:07 +01:00
|
|
|
QString path(profile);
|
|
|
|
|
if (!profile.startsWith("/"))
|
|
|
|
|
path = "/bin/" + profile;
|
2024-02-18 20:58:27 +01:00
|
|
|
|
2024-02-19 12:52:17 +01:00
|
|
|
auto appData = m_parent->lookupApp(path, IsolationManager::OnlyExisting);
|
2024-02-25 19:48:41 +01:00
|
|
|
if (appData.appId == -1) {
|
|
|
|
|
bool isNum;
|
|
|
|
|
const int profileId = profile.toLongLong(&isNum);
|
|
|
|
|
if (isNum) {
|
|
|
|
|
// the user-entered number may be a appId
|
|
|
|
|
for (const auto &profile : m_parent->listProfiles()) {
|
|
|
|
|
if (profileId == profile.jailId) {
|
|
|
|
|
appData = m_parent->lookupApp(profile.name, IsolationManager::OnlyExisting);
|
|
|
|
|
assert(appData.appId == profile.jailId);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-18 20:58:27 +01:00
|
|
|
if (appData.appId == -1) {
|
2024-02-19 20:17:07 +01:00
|
|
|
QDBusConnection::sessionBus().send(message.createErrorReply(QDBusError::InvalidArgs, "unknown profile"));
|
2024-02-18 20:58:27 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QBuffer data;
|
|
|
|
|
data.open(QIODevice::WriteOnly);
|
|
|
|
|
QXmlStreamWriter writer(&data);
|
|
|
|
|
writer.setAutoFormatting(true);
|
|
|
|
|
writer.writeStartDocument();
|
|
|
|
|
writer.writeStartElement("app");
|
2024-03-06 11:48:16 +01:00
|
|
|
writer.writeAttribute("name", appData.profileName);
|
2024-02-18 20:58:27 +01:00
|
|
|
writer.writeAttribute("id", QString::number(appData.appId));
|
2024-02-19 20:17:07 +01:00
|
|
|
writer.writeAttribute("exe", appData.pathToExe);
|
2026-04-11 14:44:27 +02:00
|
|
|
for (const auto &denied : std::as_const(appData.denied)) {
|
2024-02-18 20:58:27 +01:00
|
|
|
writer.writeStartElement("denied");
|
|
|
|
|
writer.writeCharacters(denied);
|
|
|
|
|
writer.writeEndElement();
|
|
|
|
|
}
|
2026-04-11 14:44:27 +02:00
|
|
|
for (const auto &allowed : std::as_const(appData.allowed)) {
|
2024-02-18 20:58:27 +01:00
|
|
|
writer.writeStartElement("allowed");
|
|
|
|
|
writer.writeCharacters(allowed);
|
|
|
|
|
writer.writeEndElement();
|
|
|
|
|
}
|
2024-02-25 19:48:41 +01:00
|
|
|
|
2024-02-25 23:29:33 +01:00
|
|
|
QString appStateFile = m_parent->stateFile(appData.appId);
|
2024-02-25 19:48:41 +01:00
|
|
|
if (QFile::exists(appStateFile)) {
|
|
|
|
|
QSettings appState(appStateFile, QSettings::IniFormat);
|
|
|
|
|
writer.writeStartElement("lastRun");
|
|
|
|
|
writer.writeAttribute("start", appState.value("start").toDateTime().toString(Qt::ISODate));
|
|
|
|
|
int pid = appState.value("pid").toInt();
|
|
|
|
|
if (pid && QFile::exists(QString("/proc/%1").arg(pid) )) {
|
|
|
|
|
writer.writeAttribute("pid", QString::number(pid));
|
|
|
|
|
}
|
|
|
|
|
writer.writeEndElement();
|
|
|
|
|
}
|
2024-02-18 20:58:27 +01:00
|
|
|
writer.writeEndElement();
|
|
|
|
|
QDBusConnection::sessionBus().send(message.createReply(QString::fromLatin1(data.buffer())));
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-20 20:27:07 +01:00
|
|
|
void DBusConnection::setRights(const QString &profile, const QStringList &denied, const QStringList &allowed)
|
2024-02-18 20:58:27 +01:00
|
|
|
{
|
|
|
|
|
// this method is allowed to create the app if it does not exist.
|
2024-02-20 20:27:07 +01:00
|
|
|
QString path(profile);
|
|
|
|
|
if (!profile.startsWith("/"))
|
|
|
|
|
path = "/bin/" + profile;
|
2024-02-19 12:52:17 +01:00
|
|
|
auto appData = m_parent->lookupApp(path, IsolationManager::MaybeCreate);
|
2024-02-18 20:58:27 +01:00
|
|
|
appData.setAllowed(allowed);
|
|
|
|
|
appData.setDenied(denied);
|
|
|
|
|
|
2024-03-06 11:48:16 +01:00
|
|
|
QSettings entry(m_parent->dbDir().absoluteFilePath(appData.profileName + ".info"), QSettings::IniFormat);
|
2024-02-18 20:58:27 +01:00
|
|
|
entry.setValue("denied", appData.denied.join(' '));
|
|
|
|
|
entry.setValue("allowed", appData.allowed.join(' '));
|
|
|
|
|
}
|
2024-02-19 19:52:24 +01:00
|
|
|
|
|
|
|
|
void DBusConnection::fail(const QDBusMessage &message, const QString &errorMessage) const
|
|
|
|
|
{
|
|
|
|
|
QDBusConnection::sessionBus().send(message.createErrorReply(QDBusError::InvalidArgs, errorMessage));
|
|
|
|
|
}
|