1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/host/setup/me2me_native_messaging_host.h"
9 #include "base/basictypes.h"
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/command_line.h"
13 #include "base/logging.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/run_loop.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/stringize_macros.h"
18 #include "base/threading/thread.h"
19 #include "base/values.h"
20 #include "google_apis/gaia/gaia_oauth_client.h"
21 #include "google_apis/google_api_keys.h"
22 #include "net/base/net_util.h"
23 #include "net/url_request/url_fetcher.h"
24 #include "remoting/base/rsa_key_pair.h"
25 #include "remoting/base/url_request_context.h"
26 #include "remoting/host/host_exit_codes.h"
27 #include "remoting/host/pairing_registry_delegate.h"
28 #include "remoting/host/pin_hash.h"
29 #include "remoting/host/setup/oauth_client.h"
30 #include "remoting/protocol/pairing_registry.h"
34 const char kParentWindowSwitchName[] = "parent-window";
36 // redirect_uri to use when authenticating service accounts (service account
37 // codes are obtained "out-of-band", i.e., not through an OAuth redirect).
38 const char* kServiceAccountRedirectUri = "oob";
40 // Features supported in addition to the base protocol.
41 const char* kSupportedFeatures[] = {
46 // Helper to extract the "config" part of a message as a DictionaryValue.
47 // Returns NULL on failure, and logs an error message.
48 scoped_ptr<base::DictionaryValue> ConfigDictionaryFromMessage(
49 const base::DictionaryValue& message) {
50 scoped_ptr<base::DictionaryValue> result;
51 const base::DictionaryValue* config_dict;
52 if (message.GetDictionary("config", &config_dict)) {
53 result.reset(config_dict->DeepCopy());
55 LOG(ERROR) << "'config' dictionary not found";
64 NativeMessagingHost::NativeMessagingHost(
65 scoped_refptr<DaemonController> daemon_controller,
66 scoped_refptr<protocol::PairingRegistry> pairing_registry,
67 scoped_ptr<OAuthClient> oauth_client)
68 : daemon_controller_(daemon_controller),
69 pairing_registry_(pairing_registry),
70 oauth_client_(oauth_client.Pass()),
72 weak_ptr_ = weak_factory_.GetWeakPtr();
75 NativeMessagingHost::~NativeMessagingHost() {
78 void NativeMessagingHost::ProcessMessage(
79 scoped_ptr<base::DictionaryValue> message,
80 const SendResponseCallback& done) {
81 scoped_ptr<base::DictionaryValue> response(new base::DictionaryValue());
83 // If the client supplies an ID, it will expect it in the response. This
84 // might be a string or a number, so cope with both.
85 const base::Value* id;
86 if (message->Get("id", &id))
87 response->Set("id", id->DeepCopy());
90 if (!message->GetString("type", &type)) {
91 LOG(ERROR) << "'type' not found";
92 done.Run(scoped_ptr<base::DictionaryValue>());
96 response->SetString("type", type + "Response");
99 if (type == "hello") {
100 success = ProcessHello(*message, response.Pass(), done);
101 } else if (type == "clearPairedClients") {
102 success = ProcessClearPairedClients(*message, response.Pass(), done);
103 } else if (type == "deletePairedClient") {
104 success = ProcessDeletePairedClient(*message, response.Pass(), done);
105 } else if (type == "getHostName") {
106 success = ProcessGetHostName(*message, response.Pass(), done);
107 } else if (type == "getPinHash") {
108 success = ProcessGetPinHash(*message, response.Pass(), done);
109 } else if (type == "generateKeyPair") {
110 success = ProcessGenerateKeyPair(*message, response.Pass(), done);
111 } else if (type == "updateDaemonConfig") {
112 success = ProcessUpdateDaemonConfig(*message, response.Pass(), done);
113 } else if (type == "getDaemonConfig") {
114 success = ProcessGetDaemonConfig(*message, response.Pass(), done);
115 } else if (type == "getPairedClients") {
116 success = ProcessGetPairedClients(*message, response.Pass(), done);
117 } else if (type == "getUsageStatsConsent") {
118 success = ProcessGetUsageStatsConsent(*message, response.Pass(), done);
119 } else if (type == "startDaemon") {
120 success = ProcessStartDaemon(*message, response.Pass(), done);
121 } else if (type == "stopDaemon") {
122 success = ProcessStopDaemon(*message, response.Pass(), done);
123 } else if (type == "getDaemonState") {
124 success = ProcessGetDaemonState(*message, response.Pass(), done);
125 } else if (type == "getHostClientId") {
126 success = ProcessGetHostClientId(*message, response.Pass(), done);
127 } else if (type == "getCredentialsFromAuthCode") {
128 success = ProcessGetCredentialsFromAuthCode(*message, response.Pass(),
131 LOG(ERROR) << "Unsupported request type: " << type;
135 done.Run(scoped_ptr<base::DictionaryValue>());
138 bool NativeMessagingHost::ProcessHello(
139 const base::DictionaryValue& message,
140 scoped_ptr<base::DictionaryValue> response,
141 const SendResponseCallback& done) {
142 response->SetString("version", STRINGIZE(VERSION));
143 scoped_ptr<base::ListValue> supported_features_list(new base::ListValue());
144 supported_features_list->AppendStrings(std::vector<std::string>(
145 kSupportedFeatures, kSupportedFeatures + arraysize(kSupportedFeatures)));
146 response->Set("supportedFeatures", supported_features_list.release());
147 done.Run(response.Pass());
151 bool NativeMessagingHost::ProcessClearPairedClients(
152 const base::DictionaryValue& message,
153 scoped_ptr<base::DictionaryValue> response,
154 const SendResponseCallback& done) {
155 if (pairing_registry_) {
156 pairing_registry_->ClearAllPairings(
157 base::Bind(&NativeMessagingHost::SendBooleanResult, weak_ptr_,
158 done, base::Passed(&response)));
160 SendBooleanResult(done, response.Pass(), false);
165 bool NativeMessagingHost::ProcessDeletePairedClient(
166 const base::DictionaryValue& message,
167 scoped_ptr<base::DictionaryValue> response,
168 const SendResponseCallback& done) {
169 std::string client_id;
170 if (!message.GetString(protocol::PairingRegistry::kClientIdKey, &client_id)) {
171 LOG(ERROR) << "'" << protocol::PairingRegistry::kClientIdKey
172 << "' string not found.";
176 if (pairing_registry_) {
177 pairing_registry_->DeletePairing(
178 client_id, base::Bind(&NativeMessagingHost::SendBooleanResult,
179 weak_ptr_, done, base::Passed(&response)));
181 SendBooleanResult(done, response.Pass(), false);
186 bool NativeMessagingHost::ProcessGetHostName(
187 const base::DictionaryValue& message,
188 scoped_ptr<base::DictionaryValue> response,
189 const SendResponseCallback& done) {
190 response->SetString("hostname", net::GetHostName());
191 done.Run(response.Pass());
195 bool NativeMessagingHost::ProcessGetPinHash(
196 const base::DictionaryValue& message,
197 scoped_ptr<base::DictionaryValue> response,
198 const SendResponseCallback& done) {
200 if (!message.GetString("hostId", &host_id)) {
201 LOG(ERROR) << "'hostId' not found: " << message;
205 if (!message.GetString("pin", &pin)) {
206 LOG(ERROR) << "'pin' not found: " << message;
209 response->SetString("hash", MakeHostPinHash(host_id, pin));
210 done.Run(response.Pass());
214 bool NativeMessagingHost::ProcessGenerateKeyPair(
215 const base::DictionaryValue& message,
216 scoped_ptr<base::DictionaryValue> response,
217 const SendResponseCallback& done) {
218 scoped_refptr<RsaKeyPair> key_pair = RsaKeyPair::Generate();
219 response->SetString("privateKey", key_pair->ToString());
220 response->SetString("publicKey", key_pair->GetPublicKey());
221 done.Run(response.Pass());
225 bool NativeMessagingHost::ProcessUpdateDaemonConfig(
226 const base::DictionaryValue& message,
227 scoped_ptr<base::DictionaryValue> response,
228 const SendResponseCallback& done) {
229 scoped_ptr<base::DictionaryValue> config_dict =
230 ConfigDictionaryFromMessage(message);
234 daemon_controller_->UpdateConfig(
236 base::Bind(&NativeMessagingHost::SendAsyncResult, weak_ptr_,
237 done, base::Passed(&response)));
241 bool NativeMessagingHost::ProcessGetDaemonConfig(
242 const base::DictionaryValue& message,
243 scoped_ptr<base::DictionaryValue> response,
244 const SendResponseCallback& done) {
245 daemon_controller_->GetConfig(
246 base::Bind(&NativeMessagingHost::SendConfigResponse, weak_ptr_,
247 done, base::Passed(&response)));
251 bool NativeMessagingHost::ProcessGetPairedClients(
252 const base::DictionaryValue& message,
253 scoped_ptr<base::DictionaryValue> response,
254 const SendResponseCallback& done) {
255 if (pairing_registry_) {
256 pairing_registry_->GetAllPairings(
257 base::Bind(&NativeMessagingHost::SendPairedClientsResponse, weak_ptr_,
258 done, base::Passed(&response)));
260 scoped_ptr<base::ListValue> no_paired_clients(new base::ListValue);
261 SendPairedClientsResponse(done, response.Pass(), no_paired_clients.Pass());
266 bool NativeMessagingHost::ProcessGetUsageStatsConsent(
267 const base::DictionaryValue& message,
268 scoped_ptr<base::DictionaryValue> response,
269 const SendResponseCallback& done) {
270 daemon_controller_->GetUsageStatsConsent(
271 base::Bind(&NativeMessagingHost::SendUsageStatsConsentResponse,
272 weak_ptr_, done, base::Passed(&response)));
276 bool NativeMessagingHost::ProcessStartDaemon(
277 const base::DictionaryValue& message,
278 scoped_ptr<base::DictionaryValue> response,
279 const SendResponseCallback& done) {
281 if (!message.GetBoolean("consent", &consent)) {
282 LOG(ERROR) << "'consent' not found.";
286 scoped_ptr<base::DictionaryValue> config_dict =
287 ConfigDictionaryFromMessage(message);
291 daemon_controller_->SetConfigAndStart(
292 config_dict.Pass(), consent,
293 base::Bind(&NativeMessagingHost::SendAsyncResult, weak_ptr_,
294 done, base::Passed(&response)));
298 bool NativeMessagingHost::ProcessStopDaemon(
299 const base::DictionaryValue& message,
300 scoped_ptr<base::DictionaryValue> response,
301 const SendResponseCallback& done) {
302 daemon_controller_->Stop(
303 base::Bind(&NativeMessagingHost::SendAsyncResult, weak_ptr_,
304 done, base::Passed(&response)));
308 bool NativeMessagingHost::ProcessGetDaemonState(
309 const base::DictionaryValue& message,
310 scoped_ptr<base::DictionaryValue> response,
311 const SendResponseCallback& done) {
312 DaemonController::State state = daemon_controller_->GetState();
314 case DaemonController::STATE_NOT_IMPLEMENTED:
315 response->SetString("state", "NOT_IMPLEMENTED");
317 case DaemonController::STATE_NOT_INSTALLED:
318 response->SetString("state", "NOT_INSTALLED");
320 case DaemonController::STATE_INSTALLING:
321 response->SetString("state", "INSTALLING");
323 case DaemonController::STATE_STOPPED:
324 response->SetString("state", "STOPPED");
326 case DaemonController::STATE_STARTING:
327 response->SetString("state", "STARTING");
329 case DaemonController::STATE_STARTED:
330 response->SetString("state", "STARTED");
332 case DaemonController::STATE_STOPPING:
333 response->SetString("state", "STOPPING");
335 case DaemonController::STATE_UNKNOWN:
336 response->SetString("state", "UNKNOWN");
339 done.Run(response.Pass());
343 bool NativeMessagingHost::ProcessGetHostClientId(
344 const base::DictionaryValue& message,
345 scoped_ptr<base::DictionaryValue> response,
346 const SendResponseCallback& done) {
347 response->SetString("clientId", google_apis::GetOAuth2ClientID(
348 google_apis::CLIENT_REMOTING_HOST));
349 done.Run(response.Pass());
353 bool NativeMessagingHost::ProcessGetCredentialsFromAuthCode(
354 const base::DictionaryValue& message,
355 scoped_ptr<base::DictionaryValue> response,
356 const SendResponseCallback& done) {
357 std::string auth_code;
358 if (!message.GetString("authorizationCode", &auth_code)) {
359 LOG(ERROR) << "'authorizationCode' string not found.";
363 gaia::OAuthClientInfo oauth_client_info = {
364 google_apis::GetOAuth2ClientID(google_apis::CLIENT_REMOTING_HOST),
365 google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_REMOTING_HOST),
366 kServiceAccountRedirectUri
369 oauth_client_->GetCredentialsFromAuthCode(
370 oauth_client_info, auth_code, base::Bind(
371 &NativeMessagingHost::SendCredentialsResponse, weak_ptr_,
372 done, base::Passed(&response)));
377 void NativeMessagingHost::SendConfigResponse(
378 const SendResponseCallback& done,
379 scoped_ptr<base::DictionaryValue> response,
380 scoped_ptr<base::DictionaryValue> config) {
382 response->Set("config", config.release());
384 response->Set("config", Value::CreateNullValue());
386 done.Run(response.Pass());
389 void NativeMessagingHost::SendPairedClientsResponse(
390 const SendResponseCallback& done,
391 scoped_ptr<base::DictionaryValue> response,
392 scoped_ptr<base::ListValue> pairings) {
393 response->Set("pairedClients", pairings.release());
394 done.Run(response.Pass());
397 void NativeMessagingHost::SendUsageStatsConsentResponse(
398 const SendResponseCallback& done,
399 scoped_ptr<base::DictionaryValue> response,
400 const DaemonController::UsageStatsConsent& consent) {
401 response->SetBoolean("supported", consent.supported);
402 response->SetBoolean("allowed", consent.allowed);
403 response->SetBoolean("setByPolicy", consent.set_by_policy);
404 done.Run(response.Pass());
407 void NativeMessagingHost::SendAsyncResult(
408 const SendResponseCallback& done,
409 scoped_ptr<base::DictionaryValue> response,
410 DaemonController::AsyncResult result) {
412 case DaemonController::RESULT_OK:
413 response->SetString("result", "OK");
415 case DaemonController::RESULT_FAILED:
416 response->SetString("result", "FAILED");
418 case DaemonController::RESULT_CANCELLED:
419 response->SetString("result", "CANCELLED");
421 case DaemonController::RESULT_FAILED_DIRECTORY:
422 response->SetString("result", "FAILED_DIRECTORY");
425 done.Run(response.Pass());
428 void NativeMessagingHost::SendBooleanResult(
429 const SendResponseCallback& done,
430 scoped_ptr<base::DictionaryValue> response, bool result) {
431 response->SetBoolean("result", result);
432 done.Run(response.Pass());
435 void NativeMessagingHost::SendCredentialsResponse(
436 const SendResponseCallback& done,
437 scoped_ptr<base::DictionaryValue> response,
438 const std::string& user_email,
439 const std::string& refresh_token) {
440 response->SetString("userEmail", user_email);
441 response->SetString("refreshToken", refresh_token);
442 done.Run(response.Pass());
445 int NativeMessagingHostMain() {
447 // GetStdHandle() returns pseudo-handles for stdin and stdout even if
448 // the hosting executable specifies "Windows" subsystem. However the returned
449 // handles are invalid in that case unless standard input and output are
450 // redirected to a pipe or file.
451 base::PlatformFile read_file = GetStdHandle(STD_INPUT_HANDLE);
452 base::PlatformFile write_file = GetStdHandle(STD_OUTPUT_HANDLE);
453 #elif defined(OS_POSIX)
454 base::PlatformFile read_file = STDIN_FILENO;
455 base::PlatformFile write_file = STDOUT_FILENO;
457 #error Not implemented.
460 // Mac OS X requires that the main thread be a UI message loop in order to
461 // receive distributed notifications from the System Preferences pane. An
462 // IO thread is needed for the pairing registry and URL context getter.
463 base::Thread io_thread("io_thread");
464 io_thread.StartWithOptions(
465 base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
467 base::MessageLoopForUI message_loop;
468 base::RunLoop run_loop;
470 scoped_refptr<DaemonController> daemon_controller =
471 DaemonController::Create();
473 // Pass handle of the native view to the controller so that the UAC prompts
474 // are focused properly.
475 const CommandLine* command_line = CommandLine::ForCurrentProcess();
476 if (command_line->HasSwitch(kParentWindowSwitchName)) {
477 std::string native_view =
478 command_line->GetSwitchValueASCII(kParentWindowSwitchName);
479 int64 native_view_handle = 0;
480 if (base::StringToInt64(native_view, &native_view_handle)) {
481 daemon_controller->SetWindow(reinterpret_cast<void*>(native_view_handle));
483 LOG(WARNING) << "Invalid parameter value --" << kParentWindowSwitchName
484 << "=" << native_view;
488 // OAuth client (for credential requests).
489 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter(
490 new URLRequestContextGetter(io_thread.message_loop_proxy()));
491 scoped_ptr<OAuthClient> oauth_client(
492 new OAuthClient(url_request_context_getter));
494 net::URLFetcher::SetIgnoreCertificateRequests(true);
496 // Create the pairing registry and native messaging host.
497 scoped_refptr<protocol::PairingRegistry> pairing_registry =
498 CreatePairingRegistry(io_thread.message_loop_proxy());
499 scoped_ptr<NativeMessagingChannel::Delegate> host(
500 new NativeMessagingHost(daemon_controller,
502 oauth_client.Pass()));
504 // Set up the native messaging channel.
505 scoped_ptr<NativeMessagingChannel> channel(
506 new NativeMessagingChannel(host.Pass(), read_file, write_file));
507 channel->Start(run_loop.QuitClosure());
509 // Run the loop until channel is alive.
511 return kSuccessExitCode;
514 } // namespace remoting