You've already forked isolationRunner
c0f579ff6d
This allows a jail to have a VPN config associated and as a result we start a new net namespace, completely isolating the jails networking. We then start an openVPN client to route between the main network and the jails' network. The main limitation here is that we don't setup DNS, which basically means that the VPN will route DNS calls to the other side, but since we don't remount resolv.conf this depends on the vpn provider actually mapping the nameserver we use. For people that use a nameserver like 192.168.100.1, this most of the time works just fine. Improvement is possible.
283 lines
10 KiB
C++
283 lines
10 KiB
C++
#include "DBusConnection.h"
|
|
#include "IsolationManager.h"
|
|
|
|
#include <QDBusConnection>
|
|
#include <QDBusError>
|
|
#include <QSettings>
|
|
#include <QDebug>
|
|
#include <qdbusargument.h>
|
|
#include <QXmlStreamWriter>
|
|
#include <QBuffer>
|
|
#include <cstdlib>
|
|
|
|
constexpr const char *SERVICE_NAME = "org.tom.IsolationRunner";
|
|
|
|
DBusConnection::DBusConnection(IsolationManager *parent)
|
|
: QDBusAbstractAdaptor(parent),
|
|
m_parent(parent)
|
|
{
|
|
/*
|
|
* 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?");
|
|
}
|
|
|
|
void DBusConnection::run(const QString &fullPath, const QStringList &arguments, const QDBusMessage &message)
|
|
{
|
|
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)
|
|
* 3: bool: lockdown (minimum rights)
|
|
* 4: bool: autodelete
|
|
* 5: string: jailId
|
|
*/
|
|
const auto args = message.arguments();
|
|
if (args.size() < 2) {
|
|
QDBusConnection::sessionBus().send(message.createErrorReply(QDBusError::InvalidArgs, "Need more args"));
|
|
return;
|
|
}
|
|
QString profileName = args.at(0).toString();
|
|
if (!args.at(1).canConvert(QMetaType::QStringList)) {
|
|
QDBusConnection::sessionBus().send(message.createErrorReply(QDBusError::InvalidArgs, "invalid args"));
|
|
return;
|
|
}
|
|
QStringList arguments = args.at(1).toStringList();
|
|
|
|
bool lockdown = false;
|
|
bool autoDelete = false;
|
|
QString jailId; // to run a specific executable in an existing jailId, this is the 'id'
|
|
QString vpnConf, vpnAc;
|
|
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
|
|
if (args.size() >= 6)
|
|
jailId = args.at(5).toString();
|
|
if (args.size() >= 8) {
|
|
vpnAc = args.at(6).toString();
|
|
vpnConf = args.at(7).toString();
|
|
}
|
|
|
|
// 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.
|
|
profileName += QString("_tmp-%1").arg(std::rand());
|
|
|
|
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");
|
|
if (isNumberId)
|
|
existingAppData = m_parent->lookupApp(profile.name, IsolationManager::OnlyExisting);
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(jail > 1);
|
|
assert(existingAppData.appId > 0);
|
|
}
|
|
if (existingAppData.appId == -1) {
|
|
if (!jailId.isEmpty())
|
|
return fail(message, "jail not found");
|
|
QString exe;
|
|
if (args.size() >= 3)
|
|
exe = args.at(2).toString();
|
|
if (exe.isEmpty())
|
|
exe = args.at(0).toString();
|
|
|
|
if (!exe.startsWith("/"))
|
|
return fail(message, "Invalid path, not absolute");
|
|
|
|
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
|
|
auto entry = m_parent->startEditApp(profileName, IsolationManager::MaybeCreate);
|
|
entry->setValue("path-to-exe", exe);
|
|
if (lockdown)
|
|
entry->setValue("denied", "homedir dbus git ssh media dbus-system audio");
|
|
if (!vpnAc.isEmpty())
|
|
entry->setValue("vpnAc", vpnAc);
|
|
if (!vpnConf.isEmpty())
|
|
entry->setValue("vpnConf", vpnConf);
|
|
}
|
|
else if (!jailId.isEmpty()) {
|
|
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();
|
|
if (!appData.pathToExe.startsWith('/'))
|
|
appData.pathToExe = "/usr/bin/" + appData.pathToExe;
|
|
|
|
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;
|
|
} 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)");
|
|
}
|
|
|
|
auto appData = m_parent->lookupApp(profileName, IsolationManager::OnlyExisting);
|
|
assert(appData.appId != -1);
|
|
appData.autoDelete = autoDelete;
|
|
|
|
auto answer = m_parent->startApplicationRequest(appData, arguments);
|
|
if (answer == "ok")
|
|
QDBusConnection::sessionBus().send(message.createReply(answer));
|
|
else
|
|
fail(message, answer);
|
|
}
|
|
|
|
QDBusVariant DBusConnection::listProfiles(bool verbose) const
|
|
{
|
|
auto profileInfos = m_parent->listProfiles();
|
|
std::sort(profileInfos.begin(), profileInfos.end(),
|
|
[](const IsolationManager::ProfileInfo &a,
|
|
const IsolationManager::ProfileInfo &b) {
|
|
return a.lastRun > b.lastRun;
|
|
});
|
|
|
|
QStringList answer;
|
|
for (const auto &profileInfo : profileInfos) {
|
|
QString profile = profileInfo.active ? "*" : " ";
|
|
if (verbose)
|
|
profile = profile % " " % QString::number(profileInfo.jailId);
|
|
profile = profile % " " % profileInfo.name;
|
|
answer.append(profile);
|
|
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;
|
|
}
|
|
}
|
|
return QDBusVariant(answer);
|
|
}
|
|
|
|
void DBusConnection::details(const QString &profile, const QDBusMessage &message) const
|
|
{
|
|
if (profile.isEmpty())
|
|
QDBusConnection::sessionBus().send(message.createErrorReply(QDBusError::InvalidArgs, "Missing arg"));
|
|
QString path(profile);
|
|
if (!profile.startsWith("/"))
|
|
path = "/bin/" + profile;
|
|
|
|
auto appData = m_parent->lookupApp(path, IsolationManager::OnlyExisting);
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (appData.appId == -1) {
|
|
QDBusConnection::sessionBus().send(message.createErrorReply(QDBusError::InvalidArgs, "unknown profile"));
|
|
return;
|
|
}
|
|
|
|
QBuffer data;
|
|
data.open(QIODevice::WriteOnly);
|
|
QXmlStreamWriter writer(&data);
|
|
writer.setAutoFormatting(true);
|
|
writer.writeStartDocument();
|
|
writer.writeStartElement("app");
|
|
writer.writeAttribute("name", appData.profileName);
|
|
writer.writeAttribute("id", QString::number(appData.appId));
|
|
writer.writeAttribute("exe", appData.pathToExe);
|
|
for (const auto &denied : std::as_const(appData.denied)) {
|
|
writer.writeStartElement("denied");
|
|
writer.writeCharacters(denied);
|
|
writer.writeEndElement();
|
|
}
|
|
for (const auto &allowed : std::as_const(appData.allowed)) {
|
|
writer.writeStartElement("allowed");
|
|
writer.writeCharacters(allowed);
|
|
writer.writeEndElement();
|
|
}
|
|
|
|
QString appStateFile = m_parent->stateFile(appData.appId);
|
|
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();
|
|
}
|
|
writer.writeEndElement();
|
|
QDBusConnection::sessionBus().send(message.createReply(QString::fromLatin1(data.buffer())));
|
|
}
|
|
|
|
void DBusConnection::setRights(const QString &profile, const QStringList &denied, const QStringList &allowed)
|
|
{
|
|
// this method is allowed to create the app if it does not exist.
|
|
QString path(profile);
|
|
if (!profile.startsWith("/"))
|
|
path = "/bin/" + profile;
|
|
auto appData = m_parent->lookupApp(path, IsolationManager::MaybeCreate);
|
|
appData.setAllowed(allowed);
|
|
appData.setDenied(denied);
|
|
|
|
QSettings entry(m_parent->dbDir().absoluteFilePath(appData.profileName + ".info"), QSettings::IniFormat);
|
|
entry.setValue("denied", appData.denied.join(' '));
|
|
entry.setValue("allowed", appData.allowed.join(' '));
|
|
}
|
|
|
|
void DBusConnection::fail(const QDBusMessage &message, const QString &errorMessage) const
|
|
{
|
|
QDBusConnection::sessionBus().send(message.createErrorReply(QDBusError::InvalidArgs, errorMessage));
|
|
}
|