#include #include #include #include constexpr const char *SERVICE_NAME = "org.tom.IsolationRunner"; class Client: public QObject { Q_OBJECT public: void setVerbose(bool on) { m_verbose = on; } void setApp(const QString &appName, const QString &appPath) { m_appName = appName; m_appPath = appPath; } void setListDetails(bool getDetails) { m_listDetails = getDetails; } void setAppArguments(const QList &arguments) { m_arguments = arguments; } bool autodelete() const { return m_autodelete; } void setAutodelete(bool ad) { m_autodelete = ad; } bool lockedDown() const { return m_lockedDown; } void setLockedDown(bool newLockedDown) { m_lockedDown = newLockedDown; } void setEnclosingJailId(const QString &jailId) { m_enclosingJailId = jailId; } void setVpnOVPNPath(const QString &newVpnOVPNPath) { m_vpnOVPNPath = newVpnOVPNPath; } void setVpnAcPath(const QString &newVpnAcPath) { m_vpnAcPath = newVpnAcPath; } public slots: void start() { struct ShutDown { ~ShutDown() { QCoreApplication::exit(rc); } int rc = 0; }; ShutDown shutDownHandler; // find our remote auto iface = new QDBusInterface(SERVICE_NAME, "/apps", SERVICE_NAME, QDBusConnection::sessionBus(), this); if (!iface->isValid()) { shutDownHandler.rc = 1; std::cerr << "Could not connect" << std::endl; if (m_verbose) std::cerr << "[" << qPrintable(QDBusConnection::sessionBus().lastError().message()) << "]" << std::endl; return; } QTextStream out(stdout); QDBusMessage rc; if (m_listDetails && m_appName.isEmpty() && m_appPath.isEmpty()) { rc = iface->callWithArgumentList(QDBus::Block, "listProfiles", QVariantList() << QVariant::fromValue(m_verbose)); out << "Available profiles:" << Qt::endl << Qt::endl; } else { QVariantList list; list << m_appName; if (!m_listDetails) { // then its a call to 'run2'. list.append(m_arguments); list << m_appPath; list << QVariant::fromValue(m_lockedDown); list << QVariant::fromValue(m_autodelete); list << QVariant::fromValue(m_enclosingJailId); list << QVariant::fromValue(m_vpnAcPath); list << QVariant::fromValue(m_vpnOVPNPath); } rc = iface->callWithArgumentList(QDBus::Block, m_listDetails ? "details" : "run2", list); } if (rc.type() == QDBusMessage::ErrorMessage) { QTextStream err(stderr); err << "Failed: " << rc.errorMessage() << Qt::endl; shutDownHandler.rc = 1; return; } if (rc.type() != QDBusMessage::ReplyMessage) { qWarning() << "huh? Not a reply received"; return; } for (const auto &line : rc.arguments()) { if (line.canConvert(QMetaType::QString)) { QString content = line.toString(); if (m_listDetails) { printDetailsXml(out, content); return; } out << line.toString() << Qt::endl; continue; } // probably a wrapped one then. auto embedded = line.value().variant(); if (embedded.isValid()) { if (embedded.canConvert(QMetaType::QStringList)) { for (const auto &s : embedded.toStringList()) { out << s << Qt::endl; } } else if (embedded.canConvert(QMetaType::QString)) { out << embedded.toString() << Qt::endl; } } } } private: void printDetailsXml(QTextStream &out, const QString &xml) { QXmlStreamReader parser(xml); bool firstDeny = true; bool firstAllow = true; QStringView cur; while (parser.readNextStartElement()) { if (!cur.isEmpty() && cur != parser.name()) out << Qt::endl; cur = QStringView(); if (parser.name() == QLatin1String("app")) { auto att = parser.attributes(); out << "Profile: " << att.value("name") << Qt::endl; out << " Path: " << att.value("exe") << Qt::endl; if (m_verbose) out << "Storage: $HOME/.local/jails/" << att.value("id") << "/" << Qt::endl; } else if (parser.name() == QLatin1String("denied")) { if (firstDeny) out << "Denied :"; firstDeny = false; out << " "; out << parser.readElementText(); cur = parser.name(); } else if (parser.name() == QLatin1String("allowed")) { if (firstAllow) out << "Allowed:"; firstAllow = false; out << " "; out << parser.readElementText(); cur = parser.name(); } else if (parser.name() == QLatin1String("lastRun")) { auto att = parser.attributes(); auto start = QDateTime::fromString(att.value("start").toString(), Qt::ISODate); auto convertedStart = start.toLocalTime(); out << "Started: " << convertedStart.toString() << Qt::endl; if (!att.value("pid").isNull()) out << " [Currently Running] PID: " << att.value("pid").toString() << Qt::endl; } } } bool m_verbose = false; bool m_listDetails = false; bool m_lockedDown = false; bool m_autodelete = false; QString m_appName; QString m_appPath; QString m_enclosingJailId; QStringList m_arguments; // vpn QString m_vpnOVPNPath; QString m_vpnAcPath; }; int main(int x, char **y) { QCoreApplication app(x, y); QCommandLineParser parser; parser.setApplicationDescription("Isolation Application runner"); parser.addPositionalArgument("application", "The app you wish to run or list"); parser.addHelpOption(); // allows users to get an overview of options QCommandLineOption details(QStringList() << "details" << "l", "list details instead"); parser.addOption(details); QCommandLineOption verbose(QStringList() << "verbose" << "v", "More verbose output"); parser.addOption(verbose); QCommandLineOption exe(QStringList() << "exe" << "e", "Full path of exe to run", "PATH"); parser.addOption(exe); QCommandLineOption lockdown(QStringList() << "secure" << "s", "Run app with minimal permissions"); parser.addOption(lockdown); QCommandLineOption autodelete(QStringList() << "rm", "Auto delete upon completion"); parser.addOption(autodelete); QCommandLineOption inJail(QStringList() << "in", "Run exe IN argument jail", "JAIL"); parser.addOption(inJail); QCommandLineOption vpnConf(QStringList() << "vpn", "A OVPN open vpn config file", "PATH"); parser.addOption(vpnConf); QCommandLineOption vpnPwdFile(QStringList() << "vpnac", "VPN AccessCredentials file path", "PATH"); parser.addOption(vpnPwdFile); parser.process(app); /* * Users may simply want to start an executable and they pass in that exe * name as 'appName'. This should obviously be possible. * * What should also nicely work, it should be possible to start "socials" as * an appName and on first run we can pass in the appPath (/bin/firefox) and * any arguments. */ QString appName; QString appPath; if (parser.isSet(exe)) appPath = parser.value(exe); const auto args = parser.positionalArguments(); if (!args.isEmpty()) appName = args.first(); if (appName.isEmpty()) appName = appPath; if (appName.isEmpty() && !parser.isSet(details)) parser.showHelp(0); if (!QDBusConnection::sessionBus().isConnected()) { fprintf(stderr, "Cannot connect to the D-Bus session bus.\n"); return 1; } Client client; client.setVerbose(parser.isSet(verbose)); client.setListDetails(parser.isSet(details)); client.setAutodelete(parser.isSet(autodelete)); client.setLockedDown(parser.isSet(lockdown)); client.setVpnAcPath(parser.value(vpnPwdFile)); client.setVpnOVPNPath(parser.value(vpnConf)); if (parser.isSet(inJail)) client.setEnclosingJailId(parser.value(inJail)); client.setApp(appName, appPath); client.setAppArguments(args.mid(1)); QTimer::singleShot(0, &client, &Client::start); return app.exec(); } #include "client.moc"