#include "DBusConnection.h" #include "IsolationManager.h" #include #include #include #include #include #include #include #include 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)); }