blob: 73f30cea7bf61e43b62d3bd759b85fd8c0a0262a [file] [log] [blame]
// Copyright 2015 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "lorgnette/firewall_manager.h"
#include <unistd.h>
#include <algorithm>
#include <utility>
#include <vector>
#include <base/check.h>
#include <base/files/scoped_file.h>
#include <base/functional/bind.h>
#include <brillo/errors/error.h>
#include "lorgnette/enums.h"
#include "lorgnette/scanner_match.h"
using std::string;
namespace lorgnette {
namespace {
const uint16_t kCanonBjnpPort = 8612;
const uint16_t kEpson2Port = 1865;
} // namespace
PortToken::PortToken(base::WeakPtr<FirewallManager> firewall_manager,
uint16_t port)
: firewall_manager_(firewall_manager), port_(port) {}
PortToken::PortToken(PortToken&& token) : firewall_manager_(nullptr), port_(0) {
firewall_manager_ = token.firewall_manager_;
port_ = token.port_;
token.firewall_manager_ = nullptr;
token.port_ = 0;
}
PortToken::~PortToken() {
if (firewall_manager_) {
firewall_manager_->ReleaseUdpPortAccess(port_);
}
}
FirewallManager::FirewallManager(const std::string& interface)
: interface_(interface) {}
void FirewallManager::Init(
std::unique_ptr<org::chromium::PermissionBrokerProxyInterface>
permission_broker_proxy) {
CHECK(!permission_broker_proxy_) << "Already started";
if (!SetupLifelinePipe()) {
return;
}
permission_broker_proxy_ = std::move(permission_broker_proxy);
// This will connect the name owner changed signal in DBus object proxy,
// The callback will be invoked as soon as service is avalilable and will
// be cleared after it is invoked. So this will be an one time callback.
permission_broker_proxy_->GetObjectProxy()->WaitForServiceToBeAvailable(
base::BindOnce(&FirewallManager::OnServiceAvailable,
weak_factory_.GetWeakPtr()));
// This will continuously monitor the name owner of the service. However,
// it does not connect the name owner changed signal in DBus object proxy
// for some reason. In order to connect the name owner changed signal,
// either WaitForServiceToBeAvaiable or ConnectToSignal need to be invoked.
// Since we're not interested in any signals from the proxy,
// WaitForServiceToBeAvailable is used.
permission_broker_proxy_->GetObjectProxy()->SetNameOwnerChangedCallback(
base::BindRepeating(&FirewallManager::OnServiceNameChanged,
weak_factory_.GetWeakPtr()));
}
std::vector<PortToken> FirewallManager::RequestPortsForDiscovery() {
std::vector<PortToken> ports;
ports.emplace_back(PortToken(RequestPixmaPortAccess()));
ports.emplace_back(PortToken(RequestEpsonPortAccess()));
return ports;
}
PortToken FirewallManager::RequestPixmaPortAccess() {
// Request access for the well-known port used by the Pixma backend.
return RequestUdpPortAccess(kCanonBjnpPort);
}
PortToken FirewallManager::RequestEpsonPortAccess() {
// Request access for the port used by the epson2 backend.
return RequestUdpPortAccess(kEpson2Port);
}
bool FirewallManager::SetupLifelinePipe() {
if (lifeline_read_.is_valid()) {
LOG(ERROR) << "Lifeline pipe already created";
return false;
}
// Setup lifeline pipe.
int fds[2];
if (pipe(fds) != 0) {
PLOG(ERROR) << "Failed to create lifeline pipe";
return false;
}
lifeline_read_ = base::ScopedFD(fds[0]);
lifeline_write_ = base::ScopedFD(fds[1]);
return true;
}
void FirewallManager::OnServiceAvailable(bool service_available) {
LOG(INFO) << "FirewallManager::OnServiceAvailable " << service_available;
// Nothing to be done if proxy service is not available.
if (!service_available) {
return;
}
RequestAllPortsAccess();
}
void FirewallManager::OnServiceNameChanged(const string& old_owner,
const string& new_owner) {
LOG(INFO) << "FirewallManager::OnServiceNameChanged old " << old_owner
<< " new " << new_owner;
// Nothing to be done if no owner is attached to the proxy service.
if (new_owner.empty()) {
return;
}
RequestAllPortsAccess();
}
void FirewallManager::RequestAllPortsAccess() {
std::map<uint16_t, size_t> attempted_ports;
attempted_ports.swap(requested_ports_);
for (const auto& [port, count] : attempted_ports) {
// Just perform the actual request once and then manually set the count to
// what it should be.
SendPortAccessRequest(port);
requested_ports_[port] = count;
}
}
void FirewallManager::SendPortAccessRequest(uint16_t port) {
LOG(INFO) << "Received port access request for UDP port " << port;
// If this port is already open just increment the count.
auto it = requested_ports_.find(port);
if (it != requested_ports_.end()) {
it->second++;
LOG(INFO) << "Port " << port
<< " already requested. Incrementing count to " << it->second;
return;
}
if (!permission_broker_proxy_) {
requested_ports_[port]++;
LOG(INFO) << "Permission broker does not exist (yet); adding request for "
<< "port " << port << " to queue. Count is "
<< requested_ports_[port] << ".";
return;
}
bool allowed = false;
// Pass the read end of the pipe to permission_broker, for it to monitor this
// process.
brillo::ErrorPtr error;
if (!permission_broker_proxy_->RequestUdpPortAccess(
port, interface_, base::ScopedFD(dup(lifeline_read_.get())), &allowed,
&error)) {
LOG(ERROR) << "Failed to request UDP port access: " << error->GetCode()
<< " " << error->GetMessage();
return;
}
if (!allowed) {
LOG(ERROR) << "Access request for UDP port " << port << " on interface "
<< interface_ << " is denied";
return;
}
requested_ports_[port]++;
LOG(INFO) << "Access granted for UDP port " << port << " on interface "
<< interface_ << ". Count is " << requested_ports_[port];
}
std::unique_ptr<PortToken> FirewallManager::RequestPortAccessIfNeeded(
const std::string& device_name) {
if (BackendFromDeviceName(device_name) != kPixma) {
return std::unique_ptr<PortToken>();
}
if (ConnectionTypeForScanner(device_name) != lorgnette::CONNECTION_NETWORK) {
return std::unique_ptr<PortToken>();
}
return std::make_unique<PortToken>(RequestPixmaPortAccess());
}
base::WeakPtr<FirewallManager> FirewallManager::GetWeakPtrForTesting() {
return weak_factory_.GetWeakPtr();
}
PortToken FirewallManager::RequestUdpPortAccess(uint16_t port) {
SendPortAccessRequest(port);
return PortToken(weak_factory_.GetWeakPtr(), port);
}
void FirewallManager::ReleaseUdpPortAccess(uint16_t port) {
brillo::ErrorPtr error;
bool success;
if (requested_ports_.find(port) == requested_ports_.end()) {
LOG(ERROR) << "UDP access has not been requested for port: " << port;
return;
}
requested_ports_[port]--;
size_t count = requested_ports_[port];
if (count == 0) {
requested_ports_.erase(port);
}
// This port will not actually get released until all clients have requested
// it to be released.
if (count > 0) {
LOG(INFO) << "Requested to release port " << port << ". Count is " << count
<< ".";
return;
}
if (!permission_broker_proxy_) {
return;
}
if (!permission_broker_proxy_->ReleaseUdpPort(port, interface_, &success,
&error)) {
LOG(ERROR) << "Failed to release UDP port access: " << error->GetCode()
<< " " << error->GetMessage();
return;
}
if (!success) {
LOG(ERROR) << "Release request for UDP port " << port << " on interface "
<< interface_ << " is denied";
return;
}
LOG(INFO) << "Access released for UDP port " << port << " on interface "
<< interface_ << ". Count is 0.";
}
} // namespace lorgnette