#include <iostream>
#include <string>
#include "build/build_config.h"
#include "base/at_exit.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/environment.h"
#include "base/file_path.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "base/threading/thread.h"
#include "crypto/nss_util.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/constants.h"
#include "remoting/host/audio_capturer.h"
#include "remoting/host/chromoting_host_context.h"
#include "remoting/host/chromoting_host.h"
#include "remoting/host/constants.h"
#include "remoting/host/desktop_environment.h"
#include "remoting/host/dns_blackhole_checker.h"
#include "remoting/host/event_executor.h"
#include "remoting/host/desktop_environment.h"
#include "remoting/host/desktop_environment_factory.h"
#include "remoting/host/heartbeat_sender.h"
#include "remoting/host/host_key_pair.h"
#include "remoting/host/host_secret.h"
#include "remoting/host/it2me_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/register_support_host_request.h"
#include "remoting/host/session_manager_factory.h"
#include "remoting/host/signaling_connector.h"
#include "remoting/host/video_frame_capturer_fake.h"
#include "remoting/jingle_glue/xmpp_signal_strategy.h"
#include "remoting/proto/video.pb.h"
#include "remoting/protocol/it2me_host_authenticator_factory.h"
#include "remoting/protocol/me2me_host_authenticator_factory.h"
#if defined(TOOLKIT_GTK)
#include "ui/gfx/gtk_util.h"
#elif defined(OS_MACOSX)
#include "base/mac/scoped_nsautorelease_pool.h"
#elif defined(OS_WIN)
HMODULE g_hModule = NULL;
#endif
using remoting::protocol::CandidateSessionConfig;
using remoting::protocol::ChannelConfig;
namespace {
const FilePath::CharType kDefaultConfigPath[] =
FILE_PATH_LITERAL(".ChromotingConfig.json");
const char kHomeDrive[] = "HOMEDRIVE";
const char kHomePath[] = "HOMEPATH";
const char kFakeSwitchName[] = "fake";
const char kIT2MeSwitchName[] = "it2me";
const char kConfigSwitchName[] = "config";
const char kVideoSwitchName[] = "video";
const char kDisableNatTraversalSwitchName[] = "disable-nat-traversal";
const char kMinPortSwitchName[] = "min-port";
const char kMaxPortSwitchName[] = "max-port";
const char kVideoSwitchValueVerbatim[] = "verbatim";
const char kVideoSwitchValueZip[] = "zip";
const char kVideoSwitchValueVp8[] = "vp8";
}
namespace remoting {
class FakeDesktopEnvironmentFactory : public DesktopEnvironmentFactory {
public:
FakeDesktopEnvironmentFactory();
virtual ~FakeDesktopEnvironmentFactory();
virtual scoped_ptr<DesktopEnvironment> Create(
ChromotingHostContext* context) OVERRIDE;
DISALLOW_COPY_AND_ASSIGN(FakeDesktopEnvironmentFactory);
};
FakeDesktopEnvironmentFactory::FakeDesktopEnvironmentFactory() {
}
FakeDesktopEnvironmentFactory::~FakeDesktopEnvironmentFactory() {
}
scoped_ptr<DesktopEnvironment> FakeDesktopEnvironmentFactory::Create(
ChromotingHostContext* context) {
scoped_ptr<VideoFrameCapturer> capturer(new VideoFrameCapturerFake());
scoped_ptr<EventExecutor> event_executor = EventExecutor::Create(
context->desktop_task_runner(),
context->ui_task_runner());
scoped_ptr<AudioCapturer> audio_capturer(NULL);
return scoped_ptr<DesktopEnvironment>(new DesktopEnvironment(
audio_capturer.Pass(),
event_executor.Pass(),
capturer.Pass()));
}
class SimpleHost : public HeartbeatSender::Listener {
public:
SimpleHost()
: message_loop_(MessageLoop::TYPE_UI),
context_(new AutoThreadTaskRunner(message_loop_.message_loop_proxy())),
fake_(false),
is_it2me_(false),
shutting_down_(false),
exit_code_(kSuccessExitCode) {
context_.Start();
network_change_notifier_.reset(net::NetworkChangeNotifier::Create());
}
virtual void OnUnknownHostIdError() OVERRIDE {
LOG(ERROR) << "Host ID not found.";
Shutdown(kInvalidHostIdExitCode);
}
int Run() {
FilePath config_path = GetConfigPath();
JsonHostConfig config(config_path);
if (!config.Read()) {
LOG(ERROR) << "Failed to read configuration file "
<< config_path.value();
return 1;
}
if (!config.GetString(kHostIdConfigPath, &host_id_)) {
LOG(ERROR) << "host_id is not defined in the config.";
return 1;
}
if (!key_pair_.Load(config)) {
return 1;
}
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_)) {
LOG(ERROR) << "XMPP credentials are not defined in the config.";
return 1;
}
if (!config.GetString(kXmppAuthServiceConfigPath, &xmpp_auth_service_)) {
xmpp_auth_service_ = kChromotingTokenDefaultServiceName;
}
context_.network_task_runner()->PostTask(FROM_HERE, base::Bind(
&SimpleHost::StartHost, base::Unretained(this)));
message_loop_.MessageLoop::Run();
return exit_code_;
}
void set_config_path(const FilePath& config_path) {
config_path_ = config_path;
}
void set_fake(bool fake) { fake_ = fake; }
void set_is_it2me(bool is_it2me) { is_it2me_ = is_it2me; }
void set_protocol_config(CandidateSessionConfig* protocol_config) {
protocol_config_.reset(protocol_config);
}
NetworkSettings* network_settings() { return &network_settings_; }
private:
static void SetIT2MeAccessCode(scoped_refptr<ChromotingHost> host,
HostKeyPair* key_pair,
bool successful,
const std::string& support_id,
const base::TimeDelta& lifetime) {
if (successful) {
std::string host_secret = GenerateSupportHostSecret();
std::string access_code = support_id + host_secret;
std::cout << "Support id: " << access_code << std::endl;
scoped_ptr<protocol::AuthenticatorFactory> factory(
new protocol::It2MeHostAuthenticatorFactory(
key_pair->GenerateCertificate(), *key_pair->private_key(),
access_code));
host->SetAuthenticatorFactory(factory.Pass());
} else {
LOG(ERROR) << "If you haven't done so recently, try running"
<< " remoting/tools/register_host.py.";
}
}
FilePath GetConfigPath() {
if (!config_path_.empty())
return config_path_;
scoped_ptr<base::Environment> env(base::Environment::Create());
#if defined(OS_WIN)
std::string home_drive;
env->GetVar(kHomeDrive, &home_drive);
std::string home_path;
env->GetVar(kHomePath, &home_path);
return FilePath(UTF8ToWide(home_drive))
.Append(UTF8ToWide(home_path))
.Append(kDefaultConfigPath);
#else
std::string home_path;
env->GetVar(base::env_vars::kHome, &home_path);
return FilePath(home_path).Append(kDefaultConfigPath);
#endif
}
void StartHost() {
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_, kDefaultHostTalkGadgetPrefix));
signaling_connector_.reset(new SignalingConnector(
signal_strategy_.get(), &context_, dns_blackhole_checker.Pass(),
base::Bind(&SimpleHost::OnAuthFailed, base::Unretained(this))));
if (fake_) {
desktop_environment_factory_.reset(new FakeDesktopEnvironmentFactory());
} else {
desktop_environment_factory_.reset(new DesktopEnvironmentFactory());
}
host_ = new ChromotingHost(
&context_, signal_strategy_.get(), desktop_environment_factory_.get(),
CreateHostSessionManager(network_settings_,
context_.url_request_context_getter()));
ServerLogEntry::Mode mode =
is_it2me_ ? ServerLogEntry::IT2ME : ServerLogEntry::ME2ME;
log_to_server_.reset(new LogToServer(host_, mode, signal_strategy_.get()));
if (is_it2me_) {
it2me_host_user_interface_.reset(new It2MeHostUserInterface(&context_));
it2me_host_user_interface_->Start(
host_,
base::Bind(&ChromotingHost::Shutdown, host_, base::Closure()));
}
if (protocol_config_.get()) {
host_->set_protocol_config(protocol_config_.Pass());
}
if (is_it2me_) {
register_request_.reset(new RegisterSupportHostRequest(
signal_strategy_.get(), &key_pair_,
base::Bind(&SimpleHost::SetIT2MeAccessCode, host_, &key_pair_)));
} else {
heartbeat_sender_.reset(new HeartbeatSender(
this, host_id_, signal_strategy_.get(), &key_pair_));
}
host_->Start(xmpp_login_);
if (!is_it2me_) {
scoped_ptr<protocol::AuthenticatorFactory> factory(
new protocol::Me2MeHostAuthenticatorFactory(
key_pair_.GenerateCertificate(), *key_pair_.private_key(),
host_secret_hash_));
host_->SetAuthenticatorFactory(factory.Pass());
}
}
void OnAuthFailed() {
Shutdown(kInvalidOauthCredentialsExitCode);
}
void Shutdown(int exit_code) {
DCHECK(context_.network_task_runner()->BelongsToCurrentThread());
if (shutting_down_)
return;
shutting_down_ = true;
exit_code_ = exit_code;
host_->Shutdown(base::Bind(
&SimpleHost::OnShutdownFinished, base::Unretained(this)));
}
void OnShutdownFinished() {
DCHECK(context_.network_task_runner()->BelongsToCurrentThread());
host_ = NULL;
log_to_server_.reset();
heartbeat_sender_.reset();
signaling_connector_.reset();
dns_blackhole_checker_.reset();
signal_strategy_.reset();
message_loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure());
}
MessageLoop message_loop_;
ChromotingHostContext context_;
scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_;
FilePath config_path_;
bool fake_;
bool is_it2me_;
NetworkSettings network_settings_;
scoped_ptr<CandidateSessionConfig> protocol_config_;
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_;
scoped_ptr<DesktopEnvironmentFactory> desktop_environment_factory_;
scoped_ptr<XmppSignalStrategy> signal_strategy_;
scoped_ptr<DnsBlackholeChecker> dns_blackhole_checker_;
scoped_ptr<SignalingConnector> signaling_connector_;
scoped_ptr<LogToServer> log_to_server_;
scoped_ptr<It2MeHostUserInterface> it2me_host_user_interface_;
scoped_ptr<RegisterSupportHostRequest> register_request_;
scoped_ptr<HeartbeatSender> heartbeat_sender_;
scoped_refptr<ChromotingHost> host_;
bool shutting_down_;
int exit_code_;
};
}
int main(int argc, char** argv) {
#if defined(OS_MACOSX)
base::mac::ScopedNSAutoreleasePool pool;
#endif
CommandLine::Init(argc, argv);
const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
base::AtExitManager exit_manager;
FilePath debug_log(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)
gfx::GtkInitFromCommandLine(*cmd_line);
#endif
net::EnableSSLServerSockets();
remoting::SimpleHost simple_host;
if (cmd_line->HasSwitch(kConfigSwitchName)) {
simple_host.set_config_path(
cmd_line->GetSwitchValuePath(kConfigSwitchName));
}
simple_host.set_fake(cmd_line->HasSwitch(kFakeSwitchName));
simple_host.set_is_it2me(cmd_line->HasSwitch(kIT2MeSwitchName));
if (cmd_line->HasSwitch(kVideoSwitchName)) {
std::string video_codec = cmd_line->GetSwitchValueASCII(kVideoSwitchName);
scoped_ptr<CandidateSessionConfig> config(
CandidateSessionConfig::CreateDefault());
config->mutable_video_configs()->clear();
ChannelConfig::Codec codec;
if (video_codec == kVideoSwitchValueVerbatim) {
codec = ChannelConfig::CODEC_VERBATIM;
} else if (video_codec == kVideoSwitchValueZip) {
codec = ChannelConfig::CODEC_ZIP;
} else if (video_codec == kVideoSwitchValueVp8) {
codec = ChannelConfig::CODEC_VP8;
} else {
LOG(ERROR) << "Unknown video codec: " << video_codec;
return 1;
}
config->mutable_video_configs()->push_back(ChannelConfig(
ChannelConfig::TRANSPORT_STREAM,
remoting::protocol::kDefaultStreamVersion, codec));
simple_host.set_protocol_config(config.release());
}
simple_host.network_settings()->nat_traversal_mode =
cmd_line->HasSwitch(kDisableNatTraversalSwitchName) ?
remoting::NetworkSettings::NAT_TRAVERSAL_DISABLED :
remoting::NetworkSettings::NAT_TRAVERSAL_ENABLED;
if (cmd_line->HasSwitch(kMinPortSwitchName)) {
std::string min_port_str =
cmd_line->GetSwitchValueASCII(kMinPortSwitchName);
int min_port = 0;
if (!base::StringToInt(min_port_str, &min_port) ||
min_port < 0 || min_port > 65535) {
LOG(ERROR) << "Invalid min-port value: " << min_port
<< ". Expected integer in range [0, 65535].";
return 1;
}
simple_host.network_settings()->min_port = min_port;
}
if (cmd_line->HasSwitch(kMaxPortSwitchName)) {
std::string max_port_str =
cmd_line->GetSwitchValueASCII(kMaxPortSwitchName);
int max_port = 0;
if (!base::StringToInt(max_port_str, &max_port) ||
max_port < 0 || max_port > 65535) {
LOG(ERROR) << "Invalid max-port value: " << max_port
<< ". Expected integer in range [0, 65535].";
return 1;
}
simple_host.network_settings()->max_port = max_port;
}
return simple_host.Run();
}