// Copyright (c) 2011 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/host_script_object.h" #include "base/bind.h" #include "base/message_loop.h" #include "base/task.h" #include "base/threading/platform_thread.h" #include "remoting/base/auth_token_util.h" #include "remoting/host/chromoting_host.h" #include "remoting/host/chromoting_host_context.h" #include "remoting/host/host_config.h" #include "remoting/host/host_key_pair.h" #include "remoting/host/host_plugin_utils.h" #include "remoting/host/in_memory_host_config.h" #include "remoting/host/register_support_host_request.h" #include "remoting/host/support_access_verifier.h" namespace remoting { // Supported Javascript interface: // readonly attribute string accessCode; // readonly attribute int state; // // state: { // DISCONNECTED, // REQUESTED_ACCESS_CODE, // RECEIVED_ACCESS_CODE, // CONNECTED, // AFFIRMING_CONNECTION, // ERROR, // } // // attribute Function void logDebugInfo(string); // attribute Function void onStateChanged(); // // // The |auth_service_with_token| parameter should be in the format // // "auth_service:auth_token". An example would be "oauth2:1/2a3912vd". // void connect(string uid, string auth_service_with_token); // void disconnect(); namespace { const char* kAttrNameAccessCode = "accessCode"; const char* kAttrNameState = "state"; const char* kAttrNameLogDebugInfo = "logDebugInfo"; const char* kAttrNameOnStateChanged = "onStateChanged"; const char* kFuncNameConnect = "connect"; const char* kFuncNameDisconnect = "disconnect"; // States. const char* kAttrNameDisconnected = "DISCONNECTED"; const char* kAttrNameRequestedAccessCode = "REQUESTED_ACCESS_CODE"; const char* kAttrNameReceivedAccessCode = "RECEIVED_ACCESS_CODE"; const char* kAttrNameConnected = "CONNECTED"; const char* kAttrNameAffirmingConnection = "AFFIRMING_CONNECTION"; const char* kAttrNameError = "ERROR"; const int kMaxLoginAttempts = 5; } // namespace HostNPScriptObject::HostNPScriptObject(NPP plugin, NPObject* parent) : plugin_(plugin), parent_(parent), state_(kDisconnected), log_debug_info_func_(NULL), on_state_changed_func_(NULL), np_thread_id_(base::PlatformThread::CurrentId()), failed_login_attempts_(0), disconnected_event_(true, false) { VLOG(2) << "HostNPScriptObject"; host_context_.SetUITaskPostFunction(base::Bind( &HostNPScriptObject::PostTaskToNPThread, base::Unretained(this))); } HostNPScriptObject::~HostNPScriptObject() { CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); // Disconnect synchronously. We cannot disconnect asynchronously // here because |host_context_| needs to be stopped on the plugin // thread, but the plugin thread may not exist after the instance // is destroyed. destructing_.Set(); disconnected_event_.Reset(); DisconnectInternal(); disconnected_event_.Wait(); host_context_.Stop(); if (log_debug_info_func_) { g_npnetscape_funcs->releaseobject(log_debug_info_func_); } if (on_state_changed_func_) { g_npnetscape_funcs->releaseobject(on_state_changed_func_); } } bool HostNPScriptObject::Init() { VLOG(2) << "Init"; // TODO(wez): This starts a bunch of threads, which might fail. host_context_.Start(); return true; } bool HostNPScriptObject::HasMethod(const std::string& method_name) { VLOG(2) << "HasMethod " << method_name; CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); return (method_name == kFuncNameConnect || method_name == kFuncNameDisconnect); } bool HostNPScriptObject::InvokeDefault(const NPVariant* args, uint32_t argCount, NPVariant* result) { VLOG(2) << "InvokeDefault"; CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); SetException("exception during default invocation"); return false; } bool HostNPScriptObject::Invoke(const std::string& method_name, const NPVariant* args, uint32_t argCount, NPVariant* result) { VLOG(2) << "Invoke " << method_name; CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); if (method_name == kFuncNameConnect) { return Connect(args, argCount, result); } else if (method_name == kFuncNameDisconnect) { return Disconnect(args, argCount, result); } else { SetException("Invoke: unknown method " + method_name); return false; } } bool HostNPScriptObject::HasProperty(const std::string& property_name) { VLOG(2) << "HasProperty " << property_name; CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); return (property_name == kAttrNameAccessCode || property_name == kAttrNameState || property_name == kAttrNameLogDebugInfo || property_name == kAttrNameOnStateChanged || property_name == kAttrNameDisconnected || property_name == kAttrNameRequestedAccessCode || property_name == kAttrNameReceivedAccessCode || property_name == kAttrNameConnected || property_name == kAttrNameAffirmingConnection || property_name == kAttrNameError); } bool HostNPScriptObject::GetProperty(const std::string& property_name, NPVariant* result) { VLOG(2) << "GetProperty " << property_name; CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); if (!result) { SetException("GetProperty: NULL result"); return false; } if (property_name == kAttrNameOnStateChanged) { OBJECT_TO_NPVARIANT(on_state_changed_func_, *result); return true; } else if (property_name == kAttrNameLogDebugInfo) { OBJECT_TO_NPVARIANT(log_debug_info_func_, *result); return true; } else if (property_name == kAttrNameState) { INT32_TO_NPVARIANT(state_, *result); return true; } else if (property_name == kAttrNameAccessCode) { *result = NPVariantFromString(access_code_); return true; } else if (property_name == kAttrNameDisconnected) { INT32_TO_NPVARIANT(kDisconnected, *result); return true; } else if (property_name == kAttrNameRequestedAccessCode) { INT32_TO_NPVARIANT(kRequestedAccessCode, *result); return true; } else if (property_name == kAttrNameReceivedAccessCode) { INT32_TO_NPVARIANT(kReceivedAccessCode, *result); return true; } else if (property_name == kAttrNameConnected) { INT32_TO_NPVARIANT(kConnected, *result); return true; } else if (property_name == kAttrNameAffirmingConnection) { INT32_TO_NPVARIANT(kAffirmingConnection, *result); return true; } else if (property_name == kAttrNameError) { INT32_TO_NPVARIANT(kError, *result); return true; } else { SetException("GetProperty: unsupported property " + property_name); return false; } } bool HostNPScriptObject::SetProperty(const std::string& property_name, const NPVariant* value) { VLOG(2) << "SetProperty " << property_name; CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); if (property_name == kAttrNameOnStateChanged) { if (NPVARIANT_IS_OBJECT(*value)) { if (on_state_changed_func_) { g_npnetscape_funcs->releaseobject(on_state_changed_func_); } on_state_changed_func_ = NPVARIANT_TO_OBJECT(*value); if (on_state_changed_func_) { g_npnetscape_funcs->retainobject(on_state_changed_func_); } return true; } else { SetException("SetProperty: unexpected type for property " + property_name); } return false; } if (property_name == kAttrNameLogDebugInfo) { if (NPVARIANT_IS_OBJECT(*value)) { if (log_debug_info_func_) { g_npnetscape_funcs->releaseobject(log_debug_info_func_); } log_debug_info_func_ = NPVARIANT_TO_OBJECT(*value); if (log_debug_info_func_) { g_npnetscape_funcs->retainobject(log_debug_info_func_); } return true; } else { SetException("SetProperty: unexpected type for property " + property_name); } return false; } return false; } bool HostNPScriptObject::RemoveProperty(const std::string& property_name) { VLOG(2) << "RemoveProperty " << property_name; CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); return false; } bool HostNPScriptObject::Enumerate(std::vector* values) { VLOG(2) << "Enumerate"; CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); const char* entries[] = { kAttrNameAccessCode, kAttrNameState, kAttrNameLogDebugInfo, kAttrNameOnStateChanged, kFuncNameConnect, kFuncNameDisconnect, kAttrNameDisconnected, kAttrNameRequestedAccessCode, kAttrNameReceivedAccessCode, kAttrNameConnected, kAttrNameAffirmingConnection, kAttrNameError }; for (size_t i = 0; i < arraysize(entries); ++i) { values->push_back(entries[i]); } return true; } void HostNPScriptObject::OnSignallingConnected(SignalStrategy* signal_strategy, const std::string& full_jid) { } void HostNPScriptObject::OnSignallingDisconnected() { } void HostNPScriptObject::OnAccessDenied() { DCHECK_EQ(MessageLoop::current(), host_context_.network_message_loop()); ++failed_login_attempts_; if (failed_login_attempts_ == kMaxLoginAttempts) DisconnectInternal(); } void HostNPScriptObject::OnAuthenticatedClientsChanged(int clients_connected) { DCHECK_NE(base::PlatformThread::CurrentId(), np_thread_id_); OnStateChanged(clients_connected ? kConnected : kDisconnected); } void HostNPScriptObject::OnShutdown() { DCHECK_EQ(MessageLoop::current(), host_context_.main_message_loop()); OnStateChanged(kDisconnected); } // string uid, string auth_token bool HostNPScriptObject::Connect(const NPVariant* args, uint32_t arg_count, NPVariant* result) { CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); LogDebugInfo("Connecting..."); if (arg_count != 2) { SetException("connect: bad number of arguments"); return false; } std::string uid = StringFromNPVariant(args[0]); if (uid.empty()) { SetException("connect: bad uid argument"); return false; } std::string auth_service_with_token = StringFromNPVariant(args[1]); std::string auth_token; std::string auth_service; ParseAuthTokenWithService(auth_service_with_token, &auth_token, &auth_service); if (auth_token.empty()) { SetException("connect: auth_service_with_token argument has empty token"); return false; } ConnectInternal(uid, auth_token, auth_service); return true; } void HostNPScriptObject::ConnectInternal( const std::string& uid, const std::string& auth_token, const std::string& auth_service) { if (MessageLoop::current() != host_context_.main_message_loop()) { host_context_.main_message_loop()->PostTask( FROM_HERE, NewRunnableMethod(this, &HostNPScriptObject::ConnectInternal, uid, auth_token, auth_service)); return; } // Store the supplied user ID and token to the Host configuration. scoped_refptr host_config = new InMemoryHostConfig(); host_config->SetString(kXmppLoginConfigPath, uid); host_config->SetString(kXmppAuthTokenConfigPath, auth_token); host_config->SetString(kXmppAuthServiceConfigPath, auth_service); // Create an access verifier and fetch the host secret. scoped_ptr access_verifier; access_verifier.reset(new SupportAccessVerifier()); // Generate a key pair for the Host to use. // TODO(wez): Move this to the worker thread. HostKeyPair host_key_pair; host_key_pair.Generate(); host_key_pair.Save(host_config); // Request registration of the host for support. scoped_ptr register_request( new RegisterSupportHostRequest()); if (!register_request->Init( host_config.get(), base::Bind(&HostNPScriptObject::OnReceivedSupportID, base::Unretained(this), access_verifier.get()))) { OnStateChanged(kDisconnected); return; } // Create the Host. scoped_refptr host = ChromotingHost::Create(&host_context_, host_config, access_verifier.release()); host->AddStatusObserver(this); host->AddStatusObserver(register_request.get()); host->set_it2me(true); // Nothing went wrong, so lets save the host, config and request. host_ = host; host_config_ = host_config; register_request_.reset(register_request.release()); // Start the Host. host_->Start(); OnStateChanged(kRequestedAccessCode); return; } bool HostNPScriptObject::Disconnect(const NPVariant* args, uint32_t arg_count, NPVariant* result) { CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); if (arg_count != 0) { SetException("disconnect: bad number of arguments"); return false; } DisconnectInternal(); return true; } void HostNPScriptObject::DisconnectInternal() { if (MessageLoop::current() != host_context_.main_message_loop()) { host_context_.main_message_loop()->PostTask( FROM_HERE, NewRunnableMethod(this, &HostNPScriptObject::DisconnectInternal)); return; } if (!host_) { disconnected_event_.Signal(); return; } host_->Shutdown( NewRunnableMethod(this, &HostNPScriptObject::OnShutdownFinished)); } void HostNPScriptObject::OnShutdownFinished() { DCHECK_EQ(MessageLoop::current(), host_context_.main_message_loop()); host_ = NULL; register_request_.reset(); host_config_ = NULL; disconnected_event_.Signal(); } void HostNPScriptObject::OnReceivedSupportID( SupportAccessVerifier* access_verifier, bool success, const std::string& support_id) { CHECK_NE(base::PlatformThread::CurrentId(), np_thread_id_); if (!success) { // TODO(wez): Replace the success/fail flag with full error reporting. DisconnectInternal(); return; } // Inform the AccessVerifier of our Support-Id, for authentication. access_verifier->OnIT2MeHostRegistered(success, support_id); // Combine the Support Id with the Host Id to make the Access Code. // TODO(wez): Locking, anyone? access_code_ = support_id + "-" + access_verifier->host_secret(); // Let the caller know that life is good. OnStateChanged(kReceivedAccessCode); } void HostNPScriptObject::OnStateChanged(State state) { if (destructing_.IsSet()) { return; } if (!host_context_.IsUIThread()) { host_context_.PostToUIThread( FROM_HERE, NewRunnableMethod(this, &HostNPScriptObject::OnStateChanged, state)); return; } state_ = state; if (on_state_changed_func_) { VLOG(2) << "Calling state changed " << state; bool is_good = CallJSFunction(on_state_changed_func_, NULL, 0, NULL); LOG_IF(ERROR, !is_good) << "OnStateChanged failed"; } } void HostNPScriptObject::LogDebugInfo(const std::string& message) { if (!host_context_.IsUIThread()) { host_context_.PostToUIThread( FROM_HERE, NewRunnableMethod(this, &HostNPScriptObject::LogDebugInfo, message)); return; } if (log_debug_info_func_) { NPVariant* arg = new NPVariant(); LOG(INFO) << "Logging: " << message; STRINGZ_TO_NPVARIANT(message.c_str(), *arg); bool is_good = CallJSFunction(log_debug_info_func_, arg, 1, NULL); LOG_IF(ERROR, !is_good) << "LogDebugInfo failed"; } } void HostNPScriptObject::SetException(const std::string& exception_string) { CHECK_EQ(base::PlatformThread::CurrentId(), np_thread_id_); g_npnetscape_funcs->setexception(parent_, exception_string.c_str()); LogDebugInfo(exception_string); } bool HostNPScriptObject::CallJSFunction(NPObject* func, const NPVariant* args, uint32_t argCount, NPVariant* result) { NPVariant np_result; bool is_good = func->_class->invokeDefault(func, args, argCount, &np_result); if (is_good) { if (result) { *result = np_result; } else { g_npnetscape_funcs->releasevariantvalue(&np_result); } } return is_good; } void HostNPScriptObject::PostTaskToNPThread( const tracked_objects::Location& from_here, Task* task) { // The NPAPI functions cannot make use of |from_here|, but this method is // passed as a callback to ChromotingHostContext, so it needs to have the // appropriate signature. // Can be called from any thread. g_npnetscape_funcs->pluginthreadasynccall(plugin_, &NPTaskSpringboard, task); } void HostNPScriptObject::NPTaskSpringboard(void* task) { Task* real_task = reinterpret_cast(task); real_task->Run(); delete real_task; } } // namespace remoting