#include <string>
#include "base/at_exit.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/scoped_native_library.h"
#include "base/string_util.h"
#include "base/stringize_macros.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "base/utf_string_conversions.h"
#include "base/win/windows_version.h"
#include "build/build_config.h"
#include "crypto/nss_util.h"
#include "google_apis/google_api_keys.h"
#include "ipc/ipc_channel.h"
#include "ipc/ipc_channel_proxy.h"
#include "net/base/network_change_notifier.h"
#include "net/socket/ssl_server_socket.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/breakpad.h"
#include "remoting/base/constants.h"
#include "remoting/host/branding.h"
#include "remoting/host/chromoting_host.h"
#include "remoting/host/chromoting_host_context.h"
#include "remoting/host/chromoting_messages.h"
#include "remoting/host/config_file_watcher.h"
#include "remoting/host/constants.h"
#include "remoting/host/config_file_watcher.h"
#include "remoting/host/desktop_environment_factory.h"
#include "remoting/host/desktop_resizer.h"
#include "remoting/host/dns_blackhole_checker.h"
#include "remoting/host/event_executor.h"
#include "remoting/host/heartbeat_sender.h"
#include "remoting/host/host_config.h"
#include "remoting/host/host_event_logger.h"
#include "remoting/host/host_user_interface.h"
#include "remoting/host/json_host_config.h"
#include "remoting/host/log_to_server.h"
#include "remoting/host/network_settings.h"
#include "remoting/host/policy_hack/policy_watcher.h"
#include "remoting/host/resizing_host_observer.h"
#include "remoting/host/session_manager_factory.h"
#include "remoting/host/signaling_connector.h"
#include "remoting/host/usage_stats_consent.h"
#include "remoting/host/video_frame_capturer.h"
#include "remoting/jingle_glue/xmpp_signal_strategy.h"
#include "remoting/protocol/me2me_host_authenticator_factory.h"
#if defined(OS_POSIX)
#include <signal.h>
#include "remoting/host/posix/signal_handler.h"
#endif
#if defined(OS_MACOSX)
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsautorelease_pool.h"
#include "remoting/host/curtain_mode_mac.h"
#endif
#if defined(OS_LINUX)
#include "remoting/host/audio_capturer_linux.h"
#endif
#if defined(OS_WIN)
#include <commctrl.h>
#include "remoting/host/win/session_desktop_environment_factory.h"
#endif
#if defined(TOOLKIT_GTK)
#include "ui/gfx/gtk_util.h"
#endif
namespace {
const char kApplicationName[] = "chromoting";
const char kDaemonIpcSwitchName[] = "daemon-pipe";
const char kVersionSwitchName[] = "version";
const char kAudioPipeSwitchName[] = "audio-pipe-name";
const char kUnofficialOAuth2ClientId[] =
"440925447803-2pi3v45bff6tp1rde2f7q6lgbor3o5uj.apps.googleusercontent.com";
const char kUnofficialOAuth2ClientSecret[] = "W2ieEsG-R1gIA4MMurGrgMc_";
void QuitMessageLoop(MessageLoop* message_loop) {
message_loop->PostTask(FROM_HERE, MessageLoop::QuitClosure());
}
}
namespace remoting {
class HostProcess
: public HeartbeatSender::Listener,
public IPC::Listener,
public ConfigFileWatcher::Delegate {
public:
HostProcess(scoped_ptr<ChromotingHostContext> context)
: context_(context.Pass()),
config_(FilePath()),
#ifdef OFFICIAL_BUILD
oauth_use_official_client_id_(true),
#else
oauth_use_official_client_id_(false),
#endif
allow_nat_traversal_(true),
restarting_(false),
shutting_down_(false),
#if defined(OS_WIN)
desktop_environment_factory_(new SessionDesktopEnvironmentFactory()),
#else
desktop_environment_factory_(new DesktopEnvironmentFactory()),
#endif
desktop_resizer_(DesktopResizer::Create()),
exit_code_(kSuccessExitCode)
#if defined(OS_MACOSX)
, curtain_(base::Bind(&HostProcess::OnDisconnectRequested,
base::Unretained(this)),
base::Bind(&HostProcess::OnDisconnectRequested,
base::Unretained(this)))
#endif
{
network_change_notifier_.reset(net::NetworkChangeNotifier::Create());
}
bool InitWithCommandLine(const CommandLine* cmd_line) {
std::string channel_name =
cmd_line->GetSwitchValueASCII(kDaemonIpcSwitchName);
#if defined(REMOTING_MULTI_PROCESS)
if (channel_name.empty())
return false;
#endif
if (!channel_name.empty()) {
daemon_channel_.reset(new IPC::ChannelProxy(
channel_name, IPC::Channel::MODE_CLIENT, this,
context_->network_task_runner()));
}
#if !defined(REMOTING_MULTI_PROCESS)
FilePath default_config_dir = remoting::GetConfigDir();
host_config_path_ = default_config_dir.Append(kDefaultHostConfigFile);
if (cmd_line->HasSwitch(kHostConfigSwitchName)) {
host_config_path_ = cmd_line->GetSwitchValuePath(kHostConfigSwitchName);
}
#endif
return true;
}
#if defined(OS_POSIX)
void SigTermHandler(int signal_number) {
DCHECK(signal_number == SIGTERM);
DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
LOG(INFO) << "Caught SIGTERM: Shutting down...";
Shutdown(kSuccessExitCode);
}
#endif
virtual void OnConfigUpdated(const std::string& serialized_config) OVERRIDE {
DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
LOG(INFO) << "Processing new host configuration.";
if (!config_.SetSerializedData(serialized_config)) {
LOG(ERROR) << "Invalid configuration.";
OnConfigWatcherError();
return;
}
if (!ApplyConfig()) {
LOG(ERROR) << "Failed to apply the configuration.";
OnConfigWatcherError();
return;
}
if (policy_watcher_.get() == NULL) {
#if defined(OS_MACOSX) || defined(OS_WIN)
bool want_user_interface = true;
#if defined(OS_MACOSX)
if (getuid() == 0) {
want_user_interface = false;
}
#endif
if (want_user_interface) {
host_user_interface_.reset(new HostUserInterface(context_.get()));
}
#endif
StartWatchingPolicy();
} else {
context_->network_task_runner()->PostTask(
FROM_HERE,
base::Bind(&HostProcess::CreateAuthenticatorFactory,
base::Unretained(this)));
}
}
virtual void OnConfigWatcherError() OVERRIDE {
DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
context_->network_task_runner()->PostTask(
FROM_HERE,
base::Bind(&HostProcess::Shutdown, base::Unretained(this),
kInvalidHostConfigurationExitCode));
}
void StartWatchingConfigChanges() {
#if !defined(REMOTING_MULTI_PROCESS)
config_watcher_.reset(new ConfigFileWatcher(context_->ui_task_runner(),
context_->file_task_runner(),
this));
config_watcher_->Watch(host_config_path_);
#endif
}
#if defined(OS_POSIX)
void ListenForShutdownSignal() {
remoting::RegisterSignalHandler(
SIGTERM,
base::Bind(&HostProcess::SigTermHandler, base::Unretained(this)));
}
#endif
void CreateAuthenticatorFactory() {
DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
std::string local_certificate = key_pair_.GenerateCertificate();
if (local_certificate.empty()) {
LOG(ERROR) << "Failed to generate host certificate.";
Shutdown(kHostInitializationFailed);
return;
}
scoped_ptr<protocol::AuthenticatorFactory> factory(
new protocol::Me2MeHostAuthenticatorFactory(
local_certificate, *key_pair_.private_key(), host_secret_hash_));
host_->SetAuthenticatorFactory(factory.Pass());
}
virtual bool OnMessageReceived(const IPC::Message& message) {
DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
#if defined(REMOTING_MULTI_PROCESS)
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(HostProcess, message)
IPC_MESSAGE_HANDLER(ChromotingDaemonNetworkMsg_Configuration,
OnConfigUpdated)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
#else
return false;
#endif
}
void StartHostProcess() {
DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
if (!InitWithCommandLine(CommandLine::ForCurrentProcess())) {
OnConfigWatcherError();
return;
}
#if defined(OS_POSIX)
context_->network_task_runner()->PostTask(
FROM_HERE,
base::Bind(&HostProcess::ListenForShutdownSignal,
base::Unretained(this)));
#endif
StartWatchingConfigChanges();
}
int get_exit_code() const { return exit_code_; }
private:
void ShutdownHostProcess() {
DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
#if !defined(REMOTING_MULTI_PROCESS)
config_watcher_.reset();
#endif
daemon_channel_.reset();
#if defined(OS_MACOSX) || defined(OS_WIN)
host_user_interface_.reset();
#endif
if (policy_watcher_.get()) {
base::WaitableEvent done_event(true, false);
policy_watcher_->StopWatching(&done_event);
done_event.Wait();
policy_watcher_.reset();
}
context_.reset();
}
virtual void OnUnknownHostIdError() OVERRIDE {
LOG(ERROR) << "Host ID not found.";
Shutdown(kInvalidHostIdExitCode);
}
void StartWatchingPolicy() {
policy_watcher_.reset(
policy_hack::PolicyWatcher::Create(context_->file_task_runner()));
policy_watcher_->StartWatching(
base::Bind(&HostProcess::OnPolicyUpdate, base::Unretained(this)));
}
bool ApplyConfig() {
DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
if (!config_.GetString(kHostIdConfigPath, &host_id_)) {
LOG(ERROR) << "host_id is not defined in the config.";
return false;
}
if (!key_pair_.Load(config_)) {
return false;
}
std::string host_secret_hash_string;
if (!config_.GetString(kHostSecretHashConfigPath,
&host_secret_hash_string)) {
host_secret_hash_string = "plain:";
}
if (!host_secret_hash_.Parse(host_secret_hash_string)) {
LOG(ERROR) << "Invalid host_secret_hash.";
return false;
}
if (!config_.GetString(kXmppLoginConfigPath, &xmpp_login_) ||
!(config_.GetString(kXmppAuthTokenConfigPath, &xmpp_auth_token_) ||
config_.GetString(kOAuthRefreshTokenConfigPath,
&oauth_refresh_token_))) {
LOG(ERROR) << "XMPP credentials are not defined in the config.";
return false;
}
config_.GetBoolean(kOAuthUseOfficialClientIdConfigPath,
&oauth_use_official_client_id_);
if (!oauth_refresh_token_.empty()) {
xmpp_auth_token_ = "";
xmpp_auth_service_ = "oauth2";
} else if (!config_.GetString(kXmppAuthServiceConfigPath,
&xmpp_auth_service_)) {
xmpp_auth_service_ = kChromotingTokenDefaultServiceName;
}
return true;
}
void OnPolicyUpdate(scoped_ptr<base::DictionaryValue> policies) {
if (!context_->network_task_runner()->BelongsToCurrentThread()) {
context_->network_task_runner()->PostTask(FROM_HERE, base::Bind(
&HostProcess::OnPolicyUpdate, base::Unretained(this),
base::Passed(&policies)));
return;
}
if (!host_) {
StartHost();
}
bool bool_value;
std::string string_value;
if (policies->GetString(policy_hack::PolicyWatcher::kHostDomainPolicyName,
&string_value)) {
OnHostDomainPolicyUpdate(string_value);
}
if (policies->GetBoolean(policy_hack::PolicyWatcher::kNatPolicyName,
&bool_value)) {
OnNatPolicyUpdate(bool_value);
}
if (policies->GetString(
policy_hack::PolicyWatcher::kHostTalkGadgetPrefixPolicyName,
&string_value)) {
OnHostTalkGadgetPrefixPolicyUpdate(string_value);
}
if (policies->GetBoolean(
policy_hack::PolicyWatcher::kHostRequireCurtainPolicyName,
&bool_value)) {
OnCurtainPolicyUpdate(bool_value);
}
}
void OnHostDomainPolicyUpdate(const std::string& host_domain) {
if (!context_->network_task_runner()->BelongsToCurrentThread()) {
context_->network_task_runner()->PostTask(FROM_HERE, base::Bind(
&HostProcess::OnHostDomainPolicyUpdate, base::Unretained(this),
host_domain));
return;
}
if (!host_domain.empty() &&
!EndsWith(xmpp_login_, std::string("@") + host_domain, false)) {
Shutdown(kInvalidHostDomainExitCode);
}
}
void OnNatPolicyUpdate(bool nat_traversal_enabled) {
if (!context_->network_task_runner()->BelongsToCurrentThread()) {
context_->network_task_runner()->PostTask(FROM_HERE, base::Bind(
&HostProcess::OnNatPolicyUpdate, base::Unretained(this),
nat_traversal_enabled));
return;
}
bool policy_changed = allow_nat_traversal_ != nat_traversal_enabled;
allow_nat_traversal_ = nat_traversal_enabled;
if (policy_changed) {
RestartHost();
}
}
void OnCurtainPolicyUpdate(bool curtain_required) {
#if defined(OS_MACOSX)
if (!context_->network_task_runner()->BelongsToCurrentThread()) {
context_->network_task_runner()->PostTask(FROM_HERE, base::Bind(
&HostProcess::OnCurtainPolicyUpdate, base::Unretained(this),
curtain_required));
return;
}
if (curtain_required) {
if (getuid() == 0) {
Shutdown(kLoginScreenNotSupportedExitCode);
return;
}
host_->AddStatusObserver(&curtain_);
curtain_.SetEnabled(true);
} else {
curtain_.SetEnabled(false);
host_->RemoveStatusObserver(&curtain_);
}
#endif
}
void OnHostTalkGadgetPrefixPolicyUpdate(
const std::string& talkgadget_prefix) {
if (!context_->network_task_runner()->BelongsToCurrentThread()) {
context_->network_task_runner()->PostTask(FROM_HERE, base::Bind(
&HostProcess::OnHostTalkGadgetPrefixPolicyUpdate,
base::Unretained(this), talkgadget_prefix));
return;
}
if (talkgadget_prefix != talkgadget_prefix_) {
LOG(INFO) << "Restarting host due to updated talkgadget policy:";
talkgadget_prefix_ = talkgadget_prefix;
RestartHost();
}
}
void StartHost() {
DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
DCHECK(!host_);
DCHECK(!signal_strategy_.get());
if (shutting_down_)
return;
signal_strategy_.reset(
new XmppSignalStrategy(context_->url_request_context_getter(),
xmpp_login_, xmpp_auth_token_,
xmpp_auth_service_));
scoped_ptr<DnsBlackholeChecker> dns_blackhole_checker(
new DnsBlackholeChecker(context_.get(), talkgadget_prefix_));
signaling_connector_.reset(new SignalingConnector(
signal_strategy_.get(), context_.get(), dns_blackhole_checker.Pass(),
base::Bind(&HostProcess::OnAuthFailed, base::Unretained(this))));
if (!oauth_refresh_token_.empty()) {
OAuthClientInfo client_info = {
kUnofficialOAuth2ClientId,
kUnofficialOAuth2ClientSecret
};
#ifdef OFFICIAL_BUILD
if (oauth_use_official_client_id_) {
OAuthClientInfo official_client_info = {
google_apis::GetOAuth2ClientID(google_apis::CLIENT_REMOTING),
google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_REMOTING)
};
client_info = official_client_info;
}
#endif
scoped_ptr<SignalingConnector::OAuthCredentials> oauth_credentials(
new SignalingConnector::OAuthCredentials(
xmpp_login_, oauth_refresh_token_, client_info));
signaling_connector_->EnableOAuth(oauth_credentials.Pass());
}
NetworkSettings network_settings(
allow_nat_traversal_ ?
NetworkSettings::NAT_TRAVERSAL_ENABLED :
NetworkSettings::NAT_TRAVERSAL_DISABLED);
if (!allow_nat_traversal_) {
network_settings.min_port = NetworkSettings::kDefaultMinPort;
network_settings.max_port = NetworkSettings::kDefaultMaxPort;
}
host_ = new ChromotingHost(
context_.get(), signal_strategy_.get(),
desktop_environment_factory_.get(),
CreateHostSessionManager(network_settings,
context_->url_request_context_getter()));
#if defined(OS_LINUX)
host_->SetMaximumSessionDuration(base::TimeDelta::FromHours(20));
#endif
heartbeat_sender_.reset(new HeartbeatSender(
this, host_id_, signal_strategy_.get(), &key_pair_));
log_to_server_.reset(
new LogToServer(host_, ServerLogEntry::ME2ME, signal_strategy_.get()));
host_event_logger_ = HostEventLogger::Create(host_, kApplicationName);
resizing_host_observer_.reset(
new ResizingHostObserver(desktop_resizer_.get(), host_));
#if defined(OS_MACOSX) || defined(OS_WIN)
if (host_user_interface_.get()) {
host_user_interface_->Start(
host_, base::Bind(&HostProcess::OnDisconnectRequested,
base::Unretained(this)));
}
#endif
host_->Start(xmpp_login_);
CreateAuthenticatorFactory();
}
void OnAuthFailed() {
Shutdown(kInvalidOauthCredentialsExitCode);
}
void OnDisconnectRequested() {
if (!context_->network_task_runner()->BelongsToCurrentThread()) {
context_->network_task_runner()->PostTask(FROM_HERE, base::Bind(
&HostProcess::OnDisconnectRequested, base::Unretained(this)));
return;
}
if (host_) {
host_->DisconnectAllClients();
}
}
void RestartHost() {
DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
if (restarting_ || shutting_down_)
return;
restarting_ = true;
host_->Shutdown(base::Bind(
&HostProcess::RestartOnHostShutdown, base::Unretained(this)));
}
void RestartOnHostShutdown() {
DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
if (shutting_down_)
return;
restarting_ = false;
host_ = NULL;
ResetHost();
StartHost();
}
void Shutdown(int exit_code) {
DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
if (shutting_down_)
return;
shutting_down_ = true;
exit_code_ = exit_code;
if (host_) {
host_->Shutdown(base::Bind(
&HostProcess::OnShutdownFinished, base::Unretained(this)));
} else {
OnShutdownFinished();
}
}
void OnShutdownFinished() {
DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
host_ = NULL;
ResetHost();
context_->ui_task_runner()->PostTask(
FROM_HERE,
base::Bind(&HostProcess::ShutdownHostProcess,
base::Unretained(this)));
}
void ResetHost() {
DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
host_event_logger_.reset();
log_to_server_.reset();
heartbeat_sender_.reset();
signaling_connector_.reset();
signal_strategy_.reset();
resizing_host_observer_.reset();
}
scoped_ptr<ChromotingHostContext> context_;
scoped_ptr<IPC::ChannelProxy> daemon_channel_;
scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_;
JsonHostConfig config_;
#if !defined(REMOTING_MULTI_PROCESS)
FilePath host_config_path_;
scoped_ptr<ConfigFileWatcher> config_watcher_;
#endif
std::string host_id_;
HostKeyPair key_pair_;
protocol::SharedSecretHash host_secret_hash_;
std::string xmpp_login_;
std::string xmpp_auth_token_;
std::string xmpp_auth_service_;
std::string oauth_refresh_token_;
bool oauth_use_official_client_id_;
scoped_ptr<policy_hack::PolicyWatcher> policy_watcher_;
bool allow_nat_traversal_;
std::string talkgadget_prefix_;
bool restarting_;
bool shutting_down_;
scoped_ptr<DesktopEnvironmentFactory> desktop_environment_factory_;
scoped_ptr<DesktopResizer> desktop_resizer_;
scoped_ptr<ResizingHostObserver> resizing_host_observer_;
scoped_ptr<XmppSignalStrategy> signal_strategy_;
scoped_ptr<SignalingConnector> signaling_connector_;
scoped_ptr<HeartbeatSender> heartbeat_sender_;
scoped_ptr<LogToServer> log_to_server_;
scoped_ptr<HostEventLogger> host_event_logger_;
#if defined(OS_MACOSX) || defined(OS_WIN)
scoped_ptr<HostUserInterface> host_user_interface_;
#endif
scoped_refptr<ChromotingHost> host_;
int exit_code_;
#if defined(OS_MACOSX)
remoting::CurtainMode curtain_;
#endif
};
}
int main(int argc, char** argv) {
#if defined(OS_MACOSX)
base::mac::ScopedNSAutoreleasePool pool;
#endif
CommandLine::Init(argc, argv);
base::AtExitManager exit_manager;
if (CommandLine::ForCurrentProcess()->HasSwitch(kVersionSwitchName)) {
printf("%s\n", STRINGIZE(VERSION));
return 0;
}
FilePath debug_log = remoting::GetConfigDir().
Append(FILE_PATH_LITERAL("debug.log"));
InitLogging(debug_log.value().c_str(),
#if defined(OS_WIN)
logging::LOG_ONLY_TO_FILE,
#else
logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG,
#endif
logging::DONT_LOCK_LOG_FILE,
logging::APPEND_TO_OLD_LOG_FILE,
logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS);
#if defined(TOOLKIT_GTK)
const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
gfx::GtkInitFromCommandLine(*cmd_line);
#endif
net::EnableSSLServerSockets();
MessageLoop message_loop(MessageLoop::TYPE_UI);
base::Closure quit_message_loop = base::Bind(&QuitMessageLoop, &message_loop);
scoped_ptr<remoting::ChromotingHostContext> context(
new remoting::ChromotingHostContext(
new remoting::AutoThreadTaskRunner(message_loop.message_loop_proxy(),
quit_message_loop)));
#if defined(OS_LINUX)
remoting::VideoFrameCapturer::EnableXDamage(true);
remoting::AudioCapturerLinux::SetPipeName(CommandLine::ForCurrentProcess()->
GetSwitchValuePath(kAudioPipeSwitchName));
#endif
if (!context->Start())
return remoting::kHostInitializationFailed;
remoting::HostProcess me2me_host(context.Pass());
me2me_host.StartHostProcess();
message_loop.Run();
return me2me_host.get_exit_code();
}
#if defined(OS_WIN)
HMODULE g_hModule = NULL;
int CALLBACK WinMain(HINSTANCE instance,
HINSTANCE previous_instance,
LPSTR command_line,
int show_command) {
#ifdef OFFICIAL_BUILD
if (remoting::IsUsageStatsAllowed()) {
remoting::InitializeCrashReporting();
}
#endif
g_hModule = instance;
INITCOMMONCONTROLSEX info;
info.dwSize = sizeof(info);
info.dwICC = ICC_STANDARD_CLASSES;
InitCommonControlsEx(&info);
if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
FilePath path(base::GetNativeLibraryName(UTF8ToUTF16("user32")));
base::ScopedNativeLibrary user32(path);
CHECK(user32.is_valid());
typedef BOOL (WINAPI * SetProcessDPIAwareFn)();
SetProcessDPIAwareFn set_process_dpi_aware =
static_cast<SetProcessDPIAwareFn>(
user32.GetFunctionPointer("SetProcessDPIAware"));
set_process_dpi_aware();
}
return main(0, NULL);
}
#endif