amnezia-client/client/platforms/linux/daemon/dbusservice.cpp
2022-03-22 03:40:47 -07:00

249 lines
7.1 KiB
C++

/* 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 <QCoreApplication>
#include <QJsonDocument>
#include <QJsonObject>
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();
}