blob: 660f57e7ce65da7d09874365d9a905923929126f [file] [log] [blame]
// Copyright 2014 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 <memory>
#include <set>
#include <string>
#include <sysexits.h>
#include <base/bind.h>
#include <base/command_line.h>
#include <base/json/json_reader.h>
#include <base/memory/weak_ptr.h>
#include <base/scoped_observer.h>
#include <base/strings/string_number_conversions.h>
#include <base/values.h>
#include <chromeos/daemons/dbus_daemon.h>
#include <chromeos/flag_helper.h>
#include <chromeos/http/http_request.h>
#include <chromeos/key_value_store.h>
#include <chromeos/mime_utils.h>
#include <chromeos/strings/string_utils.h>
#include <chromeos/syslog_logging.h>
#include <libwebserv/protocol_handler.h>
#include <libwebserv/request.h>
#include <libwebserv/response.h>
#include <libwebserv/server.h>
#include "privetd/ap_manager_client.h"
#include "privetd/cloud_delegate.h"
#include "privetd/constants.h"
#include "privetd/daemon_state.h"
#include "privetd/dbus_manager.h"
#include "privetd/device_delegate.h"
#include "privetd/peerd_client.h"
#include "privetd/privet_handler.h"
#include "privetd/privetd_conf_parser.h"
#include "privetd/security_manager.h"
#include "privetd/shill_client.h"
#include "privetd/wifi_bootstrap_manager.h"
namespace privetd {
namespace {
using chromeos::dbus_utils::AsyncEventSequencer;
using libwebserv::ProtocolHandler;
using libwebserv::Request;
using libwebserv::Response;
const char kDefaultConfigFilePath[] = "/etc/privetd/privetd.conf";
const char kDefaultStateFilePath[] = "/var/lib/privetd/privetd.state";
std::string GetFirstHeader(const Request& request, const std::string& name) {
std::vector<std::string> headers = request.GetHeader(name);
return headers.empty() ? std::string() : headers.front();
}
const char kServiceName[] = "org.chromium.privetd";
const char kRootPath[] = "/org/chromium/privetd";
class Daemon : public chromeos::DBusServiceDaemon,
public CloudDelegate::Observer {
public:
Daemon(bool disable_security,
bool enable_ping,
const std::set<std::string>& device_whitelist,
const base::FilePath& config_path,
const base::FilePath& state_path)
: DBusServiceDaemon(kServiceName, kRootPath),
disable_security_(disable_security),
enable_ping_(enable_ping),
device_whitelist_(device_whitelist),
config_path_(config_path),
state_store_(new DaemonState(state_path)) {}
void RegisterDBusObjectsAsync(AsyncEventSequencer* sequencer) override {
chromeos::KeyValueStore config_store;
if (!config_store.Load(config_path_)) {
LOG(ERROR) << "Failed to read privetd config file from "
<< config_path_.value();
} else {
CHECK(parser_.Parse(config_store))
<< "Failed to read configuration file.";
}
state_store_->Init();
// This state store key doesn't exist naturally, but developers
// sometime put it in their state store to cause the device to bring
// up WiFi bootstrapping while being connected to an ethernet interface.
std::string test_device_whitelist;
if (device_whitelist_.empty() &&
state_store_->GetString(kWiFiBootstrapInterfaces,
&test_device_whitelist)) {
auto interfaces =
chromeos::string_utils::Split(test_device_whitelist, ",", true, true);
device_whitelist_.insert(interfaces.begin(), interfaces.end());
}
device_ = DeviceDelegate::CreateDefault();
cloud_ = CloudDelegate::CreateDefault(
bus_, parser_.gcd_bootstrap_mode() != GcdBootstrapMode::kDisabled);
cloud_observer_.Add(cloud_.get());
security_.reset(new SecurityManager(parser_.pairing_modes(),
parser_.embedded_code_path(),
disable_security_));
shill_client_.reset(new ShillClient(
bus_, device_whitelist_.empty() ? parser_.automatic_wifi_interfaces()
: device_whitelist_));
shill_client_->RegisterConnectivityListener(
base::Bind(&Daemon::OnConnectivityChanged, base::Unretained(this)));
ap_manager_client_.reset(new ApManagerClient(bus_));
if (parser_.wifi_bootstrap_mode() != WiFiBootstrapMode::kDisabled) {
VLOG(1) << "Enabling WiFi bootstrapping.";
wifi_bootstrap_manager_.reset(new WifiBootstrapManager(
state_store_.get(), shill_client_.get(), ap_manager_client_.get(),
cloud_.get(), parser_.connect_timeout_seconds(),
parser_.bootstrap_timeout_seconds(),
parser_.monitor_timeout_seconds()));
wifi_bootstrap_manager_->Init();
}
peerd_client_.reset(new PeerdClient(bus_, device_.get(), cloud_.get(),
wifi_bootstrap_manager_.get()));
privet_handler_.reset(new PrivetHandler(cloud_.get(), device_.get(),
security_.get(),
wifi_bootstrap_manager_.get(),
peerd_client_.get()));
web_server_.OnProtocolHandlerConnected(
base::Bind(&Daemon::OnProtocolHandlerConnected,
weak_ptr_factory_.GetWeakPtr()));
web_server_.OnProtocolHandlerDisconnected(
base::Bind(&Daemon::OnProtocolHandlerDisconnected,
weak_ptr_factory_.GetWeakPtr()));
web_server_.Connect(
bus_,
kServiceName,
sequencer->GetHandler("Server::Connect failed.", true),
base::Bind(&base::DoNothing),
base::Bind(&base::DoNothing));
web_server_.GetDefaultHttpHandler()->AddHandlerCallback(
"/privet/", "",
base::Bind(&Daemon::PrivetRequestHandler, base::Unretained(this)));
web_server_.GetDefaultHttpsHandler()->AddHandlerCallback(
"/privet/", "",
base::Bind(&Daemon::PrivetRequestHandler, base::Unretained(this)));
if (enable_ping_) {
web_server_.GetDefaultHttpHandler()->AddHandlerCallback(
"/privet/ping", chromeos::http::request_type::kGet,
base::Bind(&Daemon::HelloWorldHandler, base::Unretained(this)));
web_server_.GetDefaultHttpsHandler()->AddHandlerCallback(
"/privet/ping", chromeos::http::request_type::kGet,
base::Bind(&Daemon::HelloWorldHandler, base::Unretained(this)));
}
dbus_manager_.reset(new DBusManager{object_manager_.get(),
wifi_bootstrap_manager_.get(),
cloud_.get(),
security_.get()});
dbus_manager_->RegisterAsync(
sequencer->GetHandler("DBusManager.RegisterAsync() failed.", true));
}
void OnShutdown(int* return_code) override {
web_server_.Disconnect();
DBusDaemon::OnShutdown(return_code);
}
void OnDeviceInfoChanged() override { OnChanged(); };
private:
void PrivetRequestHandler(std::unique_ptr<Request> request,
std::unique_ptr<Response> response) {
std::string auth_header = GetFirstHeader(
*request, chromeos::http::request_header::kAuthorization);
if (auth_header.empty() && disable_security_)
auth_header = "Privet anonymous";
std::string data(request->GetData().begin(), request->GetData().end());
VLOG(3) << "Input: " << data;
base::DictionaryValue empty;
std::unique_ptr<base::Value> value;
const base::DictionaryValue* dictionary = nullptr;
if (data.empty()) {
dictionary = &empty;
} else {
std::string content_type =
chromeos::mime::RemoveParameters(GetFirstHeader(
*request, chromeos::http::request_header::kContentType));
if (content_type == chromeos::mime::application::kJson) {
value.reset(base::JSONReader::Read(data));
if (value)
value->GetAsDictionary(&dictionary);
}
}
privet_handler_->HandleRequest(
request->GetPath(), auth_header, dictionary,
base::Bind(&Daemon::PrivetResponseHandler, base::Unretained(this),
base::Passed(&response)));
}
void PrivetResponseHandler(std::unique_ptr<Response> response,
int status,
const base::DictionaryValue& output) {
VLOG(3) << "status: " << status << ", Output: " << output;
response->ReplyWithJson(status, &output);
}
void HelloWorldHandler(std::unique_ptr<Request> request,
std::unique_ptr<Response> response) {
response->ReplyWithText(chromeos::http::status_code::Ok, "Hello, world!",
chromeos::mime::text::kPlain);
}
void OnChanged() {
if (peerd_client_)
peerd_client_->Update();
}
void OnConnectivityChanged(bool online) {
OnChanged();
}
void OnProtocolHandlerConnected(ProtocolHandler* protocol_handler) {
if (protocol_handler->GetName() == ProtocolHandler::kHttp) {
device_->SetHttpPort(*protocol_handler->GetPorts().begin());
if (peerd_client_)
peerd_client_->Update();
} else if (protocol_handler->GetName() == ProtocolHandler::kHttps) {
device_->SetHttpsPort(*protocol_handler->GetPorts().begin());
security_->SetCertificateFingerprint(
protocol_handler->GetCertificateFingerprint());
}
}
void OnProtocolHandlerDisconnected(ProtocolHandler* protocol_handler) {
if (protocol_handler->GetName() == ProtocolHandler::kHttp) {
device_->SetHttpPort(0);
if (peerd_client_)
peerd_client_->Update();
} else if (protocol_handler->GetName() == ProtocolHandler::kHttps) {
device_->SetHttpsPort(0);
security_->SetCertificateFingerprint({});
}
}
bool disable_security_;
bool enable_ping_;
PrivetdConfigParser parser_;
std::set<std::string> device_whitelist_;
base::FilePath config_path_;
std::unique_ptr<DaemonState> state_store_;
std::unique_ptr<CloudDelegate> cloud_;
std::unique_ptr<DeviceDelegate> device_;
std::unique_ptr<SecurityManager> security_;
std::unique_ptr<ShillClient> shill_client_;
std::unique_ptr<ApManagerClient> ap_manager_client_;
std::unique_ptr<WifiBootstrapManager> wifi_bootstrap_manager_;
std::unique_ptr<PeerdClient> peerd_client_;
std::unique_ptr<PrivetHandler> privet_handler_;
ScopedObserver<CloudDelegate, CloudDelegate::Observer> cloud_observer_{this};
std::unique_ptr<DBusManager> dbus_manager_;
libwebserv::Server web_server_;
base::WeakPtrFactory<Daemon> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(Daemon);
};
} // anonymous namespace
} // namespace privetd
int main(int argc, char* argv[]) {
DEFINE_bool(disable_security, false, "disable Privet security for tests");
DEFINE_bool(enable_ping, false, "enable test HTTP handler at /privet/ping");
DEFINE_bool(log_to_stderr, false, "log trace messages to stderr as well");
DEFINE_string(config_path, privetd::kDefaultConfigFilePath,
"Path to file containing config information.");
DEFINE_string(state_path, privetd::kDefaultStateFilePath,
"Path to file containing state information.");
DEFINE_string(device_whitelist, "",
"Comma separated list of network interfaces to monitor for "
"connectivity (an empty list enables all interfaces).");
chromeos::FlagHelper::Init(argc, argv, "Privet protocol handler daemon");
int flags = chromeos::kLogToSyslog;
if (FLAGS_log_to_stderr)
flags |= chromeos::kLogToStderr;
chromeos::InitLog(flags | chromeos::kLogHeader);
if (FLAGS_config_path.empty())
FLAGS_config_path = privetd::kDefaultConfigFilePath;
if (FLAGS_state_path.empty())
FLAGS_state_path = privetd::kDefaultStateFilePath;
auto device_whitelist =
chromeos::string_utils::Split(FLAGS_device_whitelist, ",", true, true);
privetd::Daemon daemon(
FLAGS_disable_security, FLAGS_enable_ping,
std::set<std::string>(device_whitelist.begin(), device_whitelist.end()),
base::FilePath(FLAGS_config_path), base::FilePath(FLAGS_state_path));
return daemon.Run();
}