/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "dbusservice.h" #include "dbus_adaptor.h" #include "leakdetector.h" #include "logger.h" #include "loghandler.h" #include "polkithelper.h" #include #include #include namespace { Logger logger(LOG_LINUX, "DBusService"); } constexpr const char* APP_STATE_ACTIVE = "active"; constexpr const char* APP_STATE_EXCLUDED = "excluded"; constexpr const char* APP_STATE_BLOCKED = "blocked"; DBusService::DBusService(QObject* parent) : Daemon(parent) { MVPN_COUNT_CTOR(DBusService); m_wgutils = new WireguardUtilsLinux(this); m_apptracker = new AppTracker(this); m_pidtracker = new PidTracker(this); connect(m_apptracker, SIGNAL(appLaunched(const QString&, int)), this, SLOT(appLaunched(const QString&, int))); connect(m_pidtracker, SIGNAL(terminated(const QString&, int)), this, SLOT(appTerminated(const QString&, int))); if (!removeInterfaceIfExists()) { qFatal("Interface `%s` exists and cannot be removed. Cannot proceed!", WG_INTERFACE); } } DBusService::~DBusService() { MVPN_COUNT_DTOR(DBusService); } IPUtils* DBusService::iputils() { if (!m_iputils) { m_iputils = new IPUtilsLinux(this); } return m_iputils; } DnsUtils* DBusService::dnsutils() { if (!m_dnsutils) { m_dnsutils = new DnsUtilsLinux(this); } return m_dnsutils; } void DBusService::setAdaptor(DbusAdaptor* adaptor) { Q_ASSERT(!m_adaptor); m_adaptor = adaptor; } bool DBusService::removeInterfaceIfExists() { if (m_wgutils->interfaceExists()) { logger.warning() << "Device already exists. Let's remove it."; if (!m_wgutils->deleteInterface()) { logger.error() << "Failed to remove the device."; return false; } } return true; } QString DBusService::version() { logger.debug() << "Version request"; return PROTOCOL_VERSION; } bool DBusService::activate(const QString& jsonConfig) { logger.debug() << "Activate"; if (!PolkitHelper::instance()->checkAuthorization( "org.mozilla.vpn.activate")) { logger.error() << "Polkit rejected"; return false; } QJsonDocument json = QJsonDocument::fromJson(jsonConfig.toLocal8Bit()); if (!json.isObject()) { logger.error() << "Invalid input"; return false; } QJsonObject obj = json.object(); InterfaceConfig config; if (!parseConfig(obj, config)) { logger.error() << "Invalid configuration"; return false; } if (obj.contains("vpnDisabledApps")) { QJsonArray disabledApps = obj["vpnDisabledApps"].toArray(); for (const QJsonValue& app : disabledApps) { firewallApp(app.toString(), APP_STATE_EXCLUDED); } } return Daemon::activate(config); } bool DBusService::deactivate(bool emitSignals) { logger.debug() << "Deactivate"; firewallClear(); return Daemon::deactivate(emitSignals); } QString DBusService::status() { return QString(getStatus()); } QByteArray DBusService::getStatus() { logger.debug() << "Status request"; QJsonObject json; if (!m_connections.contains(0)) { json.insert("status", QJsonValue(false)); return QJsonDocument(json).toJson(QJsonDocument::Compact); } const InterfaceConfig& config = m_connections.value(0).m_config; if (!m_wgutils->interfaceExists()) { logger.error() << "Unable to get device"; json.insert("status", QJsonValue(false)); return QJsonDocument(json).toJson(QJsonDocument::Compact); } json.insert("status", QJsonValue(true)); json.insert("serverIpv4Gateway", QJsonValue(config.m_serverIpv4Gateway)); json.insert("deviceIpv4Address", QJsonValue(config.m_deviceIpv4Address)); WireguardUtilsLinux::peerStatus status = m_wgutils->getPeerStatus(config.m_serverPublicKey); json.insert("txBytes", QJsonValue(status.txBytes)); json.insert("rxBytes", QJsonValue(status.rxBytes)); return QJsonDocument(json).toJson(QJsonDocument::Compact); } QString DBusService::getLogs() { logger.debug() << "Log request"; return Daemon::logs(); } void DBusService::appLaunched(const QString& name, int rootpid) { logger.debug() << "tracking:" << name << "PID:" << rootpid; ProcessGroup* group = m_pidtracker->track(name, rootpid); if (m_firewallApps.contains(name)) { group->state = m_firewallApps[name]; group->moveToCgroup(getAppStateCgroup(group->state)); } } void DBusService::appTerminated(const QString& name, int rootpid) { logger.debug() << "terminate:" << name << "PID:" << rootpid; } /* Get the list of running applications that the firewall knows about. */ QString DBusService::runningApps() { QJsonArray result; for (auto i = m_pidtracker->begin(); i != m_pidtracker->end(); i++) { const ProcessGroup* group = *i; QJsonObject appObject; QJsonArray pidList; appObject.insert("name", QJsonValue(group->name)); appObject.insert("rootpid", QJsonValue(group->rootpid)); appObject.insert("state", QJsonValue(group->state)); for (auto pid : group->kthreads.keys()) { pidList.append(QJsonValue(pid)); } appObject.insert("pids", pidList); result.append(appObject); } return QJsonDocument(result).toJson(QJsonDocument::Compact); } /* Update the firewall for running applications matching the application ID. */ bool DBusService::firewallApp(const QString& appName, const QString& state) { logger.debug() << "Setting" << appName << "to firewall state" << state; m_firewallApps[appName] = state; QString cgroup = getAppStateCgroup(state); /* Change matching applications' state to excluded */ for (auto i = m_pidtracker->begin(); i != m_pidtracker->end(); i++) { ProcessGroup* group = *i; if (group->name != appName) { continue; } group->state = state; group->moveToCgroup(cgroup); } return true; } /* Update the firewall for the application matching the desired PID. */ bool DBusService::firewallPid(int rootpid, const QString& state) { ProcessGroup* group = m_pidtracker->group(rootpid); if (!group) { return false; } group->state = state; group->moveToCgroup(getAppStateCgroup(group->state)); logger.debug() << "Setting" << group->name << "PID:" << rootpid << "to firewall state" << state; return true; } /* Clear the firewall and return all applications to the active state */ bool DBusService::firewallClear() { const QString cgroup = getAppStateCgroup(APP_STATE_ACTIVE); m_firewallApps.clear(); for (auto i = m_pidtracker->begin(); i != m_pidtracker->end(); i++) { ProcessGroup* group = *i; if (group->state == APP_STATE_ACTIVE) { continue; } group->state = APP_STATE_ACTIVE; group->moveToCgroup(cgroup); logger.debug() << "Setting" << group->name << "PID:" << group->rootpid << "to firewall state" << group->state; } return true; } QString DBusService::getAppStateCgroup(const QString& state) { if (state == APP_STATE_EXCLUDED) { return m_wgutils->getExcludeCgroup(); } if (state == APP_STATE_BLOCKED) { return m_wgutils->getBlockCgroup(); } return m_wgutils->getDefaultCgroup(); }