| // Copyright 2015 The Chromium OS Authors. All rights reserved. |
| // 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 <algorithm> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <brillo/errors/error.h> |
| |
| using std::string; |
| |
| namespace lorgnette { |
| |
| namespace { |
| |
| const uint16_t kCanonBjnpPort = 8612; |
| |
| } // 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(const scoped_refptr<dbus::Bus>& bus) { |
| CHECK(!permission_broker_proxy_) << "Already started"; |
| |
| if (!SetupLifelinePipe()) { |
| return; |
| } |
| |
| if (!bus) { |
| LOG(ERROR) << "Bus is null; assuming we are in tests."; |
| return; |
| } |
| |
| permission_broker_proxy_.reset(new org::chromium::PermissionBrokerProxy(bus)); |
| |
| // 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::Bind(&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::Bind(&FirewallManager::OnServiceNameChanged, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| PortToken FirewallManager::RequestPixmaPortAccess() { |
| // Request access for the well-known port used by the Pixma backend. |
| return RequestUdpPortAccess(kCanonBjnpPort); |
| } |
| |
| 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::OnServiceAvailabe " << 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::set<uint16_t> attempted_ports; |
| attempted_ports.swap(requested_ports_); |
| for (const auto& port : attempted_ports) { |
| SendPortAccessRequest(port); |
| } |
| } |
| |
| void FirewallManager::SendPortAccessRequest(uint16_t port) { |
| if (!permission_broker_proxy_) { |
| LOG(INFO) << "Permission broker does not exist (yet); adding request for " |
| << "port " << port << " to queue."; |
| requested_ports_.insert(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_, 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; |
| } |
| LOG(INFO) << "Access granted for UDP port " << port << " on interface " |
| << interface_; |
| requested_ports_.insert(port); |
| } |
| |
| 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; |
| } |
| if (!permission_broker_proxy_) { |
| requested_ports_.erase(port); |
| 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_; |
| requested_ports_.erase(port); |
| } |
| |
| } // namespace lorgnette |