Files

283 lines
10 KiB
C++
Raw Permalink Normal View History

#include "DBusConnection.h"
#include "IsolationManager.h"
#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>
constexpr const char *SERVICE_NAME = "org.tom.IsolationRunner";
DBusConnection::DBusConnection(IsolationManager *parent)
: QDBusAbstractAdaptor(parent),
2021-05-20 12:43:04 +02:00
m_parent(parent)
{
2024-02-18 20:58:27 +01:00
/*
* Registering ourself like this means that all public slots will become
* remote-callable.
*/
if (!QDBusConnection::sessionBus().isConnected())
throw std::runtime_error("Failed to find the DBUS session");
QDBusConnection::sessionBus().registerObject("/apps", parent);
if (!QDBusConnection::sessionBus().registerService(SERVICE_NAME))
throw std::runtime_error("Registration with DBus failed, am I already running?");
}
2024-02-19 10:47:36 +01:00
void DBusConnection::run(const QString &fullPath, const QStringList &arguments, const QDBusMessage &message)
{
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);
}
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
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;
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));
}