blob: bee792e0b34c3d493870a90687d64aa80a4d41b3 [file] [log] [blame]
// Copyright 2013 The Chromium 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 "remoting/host/it2me/it2me_native_messaging_host.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringize_macros.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/policy/policy_constants.h"
#include "net/base/url_util.h"
#include "net/socket/client_socket_factory.h"
#include "net/url_request/url_request_context_getter.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/name_value_map.h"
#include "remoting/base/service_urls.h"
#include "remoting/host/chromoting_host_context.h"
#include "remoting/host/host_exit_codes.h"
#include "remoting/host/it2me/it2me_confirmation_dialog.h"
#include "remoting/host/policy_watcher.h"
#include "remoting/signaling/delegating_signal_strategy.h"
#if defined(OS_WIN)
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "remoting/host/win/elevated_native_messaging_host.h"
#endif // defined(OS_WIN)
namespace remoting {
namespace {
const NameMapElement<It2MeHostState> kIt2MeHostStates[] = {
{kDisconnected, "DISCONNECTED"},
{kStarting, "STARTING"},
{kRequestedAccessCode, "REQUESTED_ACCESS_CODE"},
{kReceivedAccessCode, "RECEIVED_ACCESS_CODE"},
{kConnecting, "CONNECTING"},
{kConnected, "CONNECTED"},
{kError, "ERROR"},
{kInvalidDomainError, "INVALID_DOMAIN_ERROR"},
};
#if defined(OS_WIN)
const base::FilePath::CharType kBaseHostBinaryName[] =
FILE_PATH_LITERAL("remote_assistance_host.exe");
const base::FilePath::CharType kElevatedHostBinaryName[] =
FILE_PATH_LITERAL("remote_assistance_host_uiaccess.exe");
#endif // defined(OS_WIN)
// Helper functions to run |callback| asynchronously on the correct thread
// using |task_runner|.
void PolicyUpdateCallback(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
remoting::PolicyWatcher::PolicyUpdatedCallback callback,
std::unique_ptr<base::DictionaryValue> policies) {
DCHECK(callback);
task_runner->PostTask(FROM_HERE,
base::Bind(callback, base::Passed(&policies)));
}
void PolicyErrorCallback(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
remoting::PolicyWatcher::PolicyErrorCallback callback) {
DCHECK(callback);
task_runner->PostTask(FROM_HERE, callback);
}
} // namespace
It2MeNativeMessagingHost::It2MeNativeMessagingHost(
bool needs_elevation,
std::unique_ptr<PolicyWatcher> policy_watcher,
std::unique_ptr<ChromotingHostContext> context,
std::unique_ptr<It2MeHostFactory> factory)
: needs_elevation_(needs_elevation),
host_context_(std::move(context)),
factory_(std::move(factory)),
policy_watcher_(std::move(policy_watcher)),
weak_factory_(this) {
weak_ptr_ = weak_factory_.GetWeakPtr();
// The policy watcher runs on the |file_task_runner| but we want to run the
// callbacks on |task_runner| so we use a shim to post them to it.
PolicyWatcher::PolicyUpdatedCallback update_callback =
base::Bind(&It2MeNativeMessagingHost::OnPolicyUpdate, weak_ptr_);
PolicyWatcher::PolicyErrorCallback error_callback =
base::Bind(&It2MeNativeMessagingHost::OnPolicyError, weak_ptr_);
policy_watcher_->StartWatching(
base::Bind(&PolicyUpdateCallback, task_runner(), update_callback),
base::Bind(&PolicyErrorCallback, task_runner(), error_callback));
}
It2MeNativeMessagingHost::~It2MeNativeMessagingHost() {
DCHECK(task_runner()->BelongsToCurrentThread());
if (it2me_host_.get()) {
it2me_host_->Disconnect();
it2me_host_ = nullptr;
}
}
void It2MeNativeMessagingHost::OnMessage(const std::string& message) {
DCHECK(task_runner()->BelongsToCurrentThread());
std::unique_ptr<base::DictionaryValue> response(new base::DictionaryValue());
std::unique_ptr<base::Value> message_value = base::JSONReader::Read(message);
if (!message_value->IsType(base::Value::Type::DICTIONARY)) {
LOG(ERROR) << "Received a message that's not a dictionary.";
client_->CloseChannel(std::string());
return;
}
std::unique_ptr<base::DictionaryValue> message_dict(
static_cast<base::DictionaryValue*>(message_value.release()));
// If the client supplies an ID, it will expect it in the response. This
// might be a string or a number, so cope with both.
const base::Value* id;
if (message_dict->Get("id", &id))
response->Set("id", base::MakeUnique<base::Value>(*id));
std::string type;
if (!message_dict->GetString("type", &type)) {
SendErrorAndExit(std::move(response), "'type' not found in request.");
return;
}
response->SetString("type", type + "Response");
if (type == "hello") {
ProcessHello(std::move(message_dict), std::move(response));
} else if (type == "connect") {
ProcessConnect(std::move(message_dict), std::move(response));
} else if (type == "disconnect") {
ProcessDisconnect(std::move(message_dict), std::move(response));
} else if (type == "incomingIq") {
ProcessIncomingIq(std::move(message_dict), std::move(response));
} else {
SendErrorAndExit(std::move(response), "Unsupported request type: " + type);
}
}
void It2MeNativeMessagingHost::Start(Client* client) {
DCHECK(task_runner()->BelongsToCurrentThread());
client_ = client;
#if !defined(OS_CHROMEOS)
log_message_handler_.reset(
new LogMessageHandler(
base::Bind(&It2MeNativeMessagingHost::SendMessageToClient,
base::Unretained(this))));
#endif // !defined(OS_CHROMEOS)
}
void It2MeNativeMessagingHost::SendMessageToClient(
std::unique_ptr<base::Value> message) const {
DCHECK(task_runner()->BelongsToCurrentThread());
std::string message_json;
base::JSONWriter::Write(*message, &message_json);
client_->PostMessageFromNativeHost(message_json);
}
void It2MeNativeMessagingHost::ProcessHello(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) const {
DCHECK(task_runner()->BelongsToCurrentThread());
response->SetString("version", STRINGIZE(VERSION));
// This list will be populated when new features are added.
response->Set("supportedFeatures", base::MakeUnique<base::ListValue>());
SendMessageToClient(std::move(response));
}
void It2MeNativeMessagingHost::ProcessConnect(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
if (!policy_received_) {
DCHECK(!pending_connect_);
pending_connect_ =
base::Bind(&It2MeNativeMessagingHost::ProcessConnect, weak_ptr_,
base::Passed(&message), base::Passed(&response));
return;
}
if (needs_elevation_) {
// Attempt to pass the current message to the elevated process. This method
// will spin up the elevated process if it is not already running. On
// success, the elevated process will process the message and respond.
// If the process cannot be started or message passing fails, then return an
// error to the message sender.
if (!DelegateToElevatedHost(std::move(message))) {
SendErrorAndExit(std::move(response),
"Failed to send message to elevated host.");
}
return;
}
if (it2me_host_.get()) {
SendErrorAndExit(std::move(response),
"Connect can be called only when disconnected.");
return;
}
std::string username;
if (!message->GetString("userName", &username)) {
SendErrorAndExit(std::move(response), "'userName' not found in request.");
return;
}
bool use_signaling_proxy = false;
message->GetBoolean("useSignalingProxy", &use_signaling_proxy);
const ServiceUrls* service_urls = ServiceUrls::GetInstance();
std::unique_ptr<SignalStrategy> signal_strategy;
if (!use_signaling_proxy) {
XmppSignalStrategy::XmppServerConfig xmpp_config;
xmpp_config.username = username;
const bool xmpp_server_valid =
net::ParseHostAndPort(service_urls->xmpp_server_address(),
&xmpp_config.host, &xmpp_config.port);
DCHECK(xmpp_server_valid);
xmpp_config.use_tls = service_urls->xmpp_server_use_tls();
std::string auth_service_with_token;
if (!message->GetString("authServiceWithToken", &auth_service_with_token)) {
SendErrorAndExit(std::move(response),
"'authServiceWithToken' not found in request.");
return;
}
// For backward compatibility the webapp still passes OAuth service as part
// of the authServiceWithToken field. But auth service part is always
// expected to be set to oauth2.
const char kOAuth2ServicePrefix[] = "oauth2:";
if (!base::StartsWith(auth_service_with_token, kOAuth2ServicePrefix,
base::CompareCase::SENSITIVE)) {
SendErrorAndExit(std::move(response), "Invalid 'authServiceWithToken': " +
auth_service_with_token);
return;
}
xmpp_config.auth_token =
auth_service_with_token.substr(strlen(kOAuth2ServicePrefix));
#if !defined(NDEBUG)
std::string address;
if (!message->GetString("xmppServerAddress", &address)) {
SendErrorAndExit(std::move(response),
"'xmppServerAddress' not found in request.");
return;
}
if (!net::ParseHostAndPort(address, &xmpp_config.host, &xmpp_config.port)) {
SendErrorAndExit(std::move(response),
"Invalid 'xmppServerAddress': " + address);
return;
}
if (!message->GetBoolean("xmppServerUseTls", &xmpp_config.use_tls)) {
SendErrorAndExit(std::move(response),
"'xmppServerUseTls' not found in request.");
return;
}
#endif // !defined(NDEBUG)
signal_strategy.reset(new XmppSignalStrategy(
net::ClientSocketFactory::GetDefaultFactory(),
host_context_->url_request_context_getter(), xmpp_config));
} else {
std::string local_jid;
if (!message->GetString("localJid", &local_jid)) {
SendErrorAndExit(std::move(response), "'localJid' not found in request.");
return;
}
auto delegating_signal_strategy =
base::MakeUnique<DelegatingSignalStrategy>(
SignalingAddress(local_jid), host_context_->network_task_runner(),
base::Bind(&It2MeNativeMessagingHost::SendOutgoingIq,
weak_factory_.GetWeakPtr()));
incoming_message_callback_ =
delegating_signal_strategy->GetIncomingMessageCallback();
signal_strategy = std::move(delegating_signal_strategy);
}
std::string directory_bot_jid = service_urls->directory_bot_jid();
#if !defined(NDEBUG)
if (!message->GetString("directoryBotJid", &directory_bot_jid)) {
SendErrorAndExit(std::move(response),
"'directoryBotJid' not found in request.");
return;
}
#endif // !defined(NDEBUG)
std::unique_ptr<base::DictionaryValue> policies =
policy_watcher_->GetCurrentPolicies();
if (policies->size() == 0) {
// At this point policies have been read, so if there are none set then
// it indicates an error. Since this can be fixed by end users it has a
// dedicated message type rather than the generic "error" so that the
// right error message can be displayed.
SendPolicyErrorAndExit();
return;
}
// Create the It2Me host and start connecting.
it2me_host_ = factory_->CreateIt2MeHost();
it2me_host_->Connect(host_context_->Copy(), std::move(policies),
base::MakeUnique<It2MeConfirmationDialogFactory>(),
weak_ptr_, std::move(signal_strategy), username,
directory_bot_jid);
SendMessageToClient(std::move(response));
}
void It2MeNativeMessagingHost::ProcessDisconnect(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
DCHECK(policy_received_);
if (needs_elevation_) {
// Attempt to pass the current message to the elevated process. This method
// will spin up the elevated process if it is not already running. On
// success, the elevated process will process the message and respond.
// If the process cannot be started or message passing fails, then return an
// error to the message sender.
if (!DelegateToElevatedHost(std::move(message))) {
SendErrorAndExit(std::move(response),
"Failed to send message to elevated host.");
}
return;
}
if (it2me_host_.get()) {
it2me_host_->Disconnect();
it2me_host_ = nullptr;
}
SendMessageToClient(std::move(response));
}
void It2MeNativeMessagingHost::ProcessIncomingIq(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
std::string iq;
if (!message->GetString("iq", &iq)) {
LOG(ERROR) << "Invalid incomingIq() data.";
return;
}
incoming_message_callback_.Run(iq);
SendMessageToClient(std::move(response));
};
void It2MeNativeMessagingHost::SendOutgoingIq(const std::string& iq) {
std::unique_ptr<base::DictionaryValue> message(new base::DictionaryValue());
message->SetString("iq", iq);
message->SetString("type", "sendOutgoingIq");
SendMessageToClient(std::move(message));
}
void It2MeNativeMessagingHost::SendErrorAndExit(
std::unique_ptr<base::DictionaryValue> response,
const std::string& description) const {
DCHECK(task_runner()->BelongsToCurrentThread());
LOG(ERROR) << description;
response->SetString("type", "error");
response->SetString("description", description);
SendMessageToClient(std::move(response));
// Trigger a host shutdown by sending an empty message.
client_->CloseChannel(std::string());
}
void It2MeNativeMessagingHost::SendPolicyErrorAndExit() const {
DCHECK(task_runner()->BelongsToCurrentThread());
auto message = base::MakeUnique<base::DictionaryValue>();
message->SetString("type", "policyError");
SendMessageToClient(std::move(message));
client_->CloseChannel(std::string());
}
void It2MeNativeMessagingHost::OnStateChanged(
It2MeHostState state,
const std::string& error_message) {
DCHECK(task_runner()->BelongsToCurrentThread());
state_ = state;
std::unique_ptr<base::DictionaryValue> message(new base::DictionaryValue());
message->SetString("type", "hostStateChanged");
message->SetString("state", HostStateToString(state));
switch (state_) {
case kReceivedAccessCode:
message->SetString("accessCode", access_code_);
message->SetInteger("accessCodeLifetime",
access_code_lifetime_.InSeconds());
break;
case kConnected:
message->SetString("client", client_username_);
break;
case kDisconnected:
client_username_.clear();
break;
case kError:
// kError is an internal-only state, sent to the web-app by a separate
// "error" message so that errors that occur before the "connect" message
// is sent can be communicated.
message->SetString("type", "error");
message->SetString("description", error_message);
break;
default:
break;
}
SendMessageToClient(std::move(message));
}
void It2MeNativeMessagingHost::SetPolicyErrorClosureForTesting(
const base::Closure& closure) {
policy_error_closure_for_testing_ = closure;
}
void It2MeNativeMessagingHost::OnNatPolicyChanged(bool nat_traversal_enabled) {
DCHECK(task_runner()->BelongsToCurrentThread());
std::unique_ptr<base::DictionaryValue> message(new base::DictionaryValue());
message->SetString("type", "natPolicyChanged");
message->SetBoolean("natTraversalEnabled", nat_traversal_enabled);
SendMessageToClient(std::move(message));
}
// Stores the Access Code for the web-app to query.
void It2MeNativeMessagingHost::OnStoreAccessCode(
const std::string& access_code,
base::TimeDelta access_code_lifetime) {
DCHECK(task_runner()->BelongsToCurrentThread());
access_code_ = access_code;
access_code_lifetime_ = access_code_lifetime;
}
// Stores the client user's name for the web-app to query.
void It2MeNativeMessagingHost::OnClientAuthenticated(
const std::string& client_username) {
DCHECK(task_runner()->BelongsToCurrentThread());
client_username_ = client_username;
}
scoped_refptr<base::SingleThreadTaskRunner>
It2MeNativeMessagingHost::task_runner() const {
return host_context_->ui_task_runner();
}
/* static */
std::string It2MeNativeMessagingHost::HostStateToString(
It2MeHostState host_state) {
return ValueToName(kIt2MeHostStates, host_state);
}
void It2MeNativeMessagingHost::OnPolicyUpdate(
std::unique_ptr<base::DictionaryValue> policies) {
// Don't dynamically change the elevation status since we don't have a good
// way to communicate changes to the user.
if (!policy_received_) {
bool allow_elevated_host = false;
if (!policies->GetBoolean(
policy::key::kRemoteAccessHostAllowUiAccessForRemoteAssistance,
&allow_elevated_host)) {
LOG(WARNING) << "Failed to retrieve elevated host policy value.";
}
#if defined(OS_WIN)
LOG(INFO) << "Allow UiAccess for Remote Assistance: "
<< allow_elevated_host;
#endif // defined(OS_WIN)
policy_received_ = true;
// If |allow_elevated_host| is false, then we will fall back to using a host
// running in the current context regardless of the elevation request. This
// may not be ideal, but is still functional.
needs_elevation_ = needs_elevation_ && allow_elevated_host;
if (pending_connect_) {
base::ResetAndReturn(&pending_connect_).Run();
}
}
if (it2me_host_) {
it2me_host_->OnPolicyUpdate(std::move(policies));
}
}
void It2MeNativeMessagingHost::OnPolicyError() {
LOG(ERROR) << "Malformed policies detected.";
policy_received_ = true;
if (policy_error_closure_for_testing_) {
policy_error_closure_for_testing_.Run();
}
if (it2me_host_) {
// If there is already a connection, close it and notify the webapp.
it2me_host_->Disconnect();
it2me_host_ = nullptr;
SendPolicyErrorAndExit();
} else if (pending_connect_) {
// If there is no connection, run the pending connection callback if there
// is one, but otherwise do nothing. The policy error will be sent when a
// connection is made; doing so beforehand would break assumptions made by
// the Chrome app.
base::ResetAndReturn(&pending_connect_).Run();
}
}
#if defined(OS_WIN)
bool It2MeNativeMessagingHost::DelegateToElevatedHost(
std::unique_ptr<base::DictionaryValue> message) {
DCHECK(task_runner()->BelongsToCurrentThread());
DCHECK(needs_elevation_);
if (!elevated_host_) {
base::FilePath binary_path =
base::CommandLine::ForCurrentProcess()->GetProgram();
CHECK(binary_path.BaseName() == base::FilePath(kBaseHostBinaryName));
// The new process runs at an elevated level due to being granted uiAccess.
// |parent_window_handle| can be used to position dialog windows but is not
// currently used.
elevated_host_.reset(new ElevatedNativeMessagingHost(
binary_path.DirName().Append(kElevatedHostBinaryName),
/*parent_window_handle=*/0,
/*elevate_process=*/false,
/*host_timeout=*/base::TimeDelta(), client_));
}
if (elevated_host_->EnsureElevatedHostCreated()) {
elevated_host_->SendMessage(std::move(message));
return true;
}
return false;
}
#else // !defined(OS_WIN)
bool It2MeNativeMessagingHost::DelegateToElevatedHost(
std::unique_ptr<base::DictionaryValue> message) {
NOTREACHED();
return false;
}
#endif // !defined(OS_WIN)
} // namespace remoting