// -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ /* globals DebuggerServer */ "use strict"; XPCOMUtils.defineLazyGetter(this, "DebuggerServer", () => { let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); let { DebuggerServer } = require("devtools/server/main"); return DebuggerServer; }); var RemoteDebugger = { init() { USBRemoteDebugger.init(); WiFiRemoteDebugger.init(); }, get isAnyEnabled() { return USBRemoteDebugger.isEnabled || WiFiRemoteDebugger.isEnabled; }, /** * Prompt the user to accept or decline the incoming connection. * * @param session object * The session object will contain at least the following fields: * { * authentication, * client: { * host, * port * }, * server: { * host, * port * } * } * Specific authentication modes may include additional fields. Check * the different |allowConnection| methods in * devtools/shared/security/auth.js. * @return An AuthenticationResult value. * A promise that will be resolved to the above is also allowed. */ allowConnection(session) { if (this._promptingForAllow) { // Don't stack connection prompts if one is already open return DebuggerServer.AuthenticationResult.DENY; } if (!session.server.port) { this._promptingForAllow = this._promptForUSB(session); } else { this._promptingForAllow = this._promptForTCP(session); } this._promptingForAllow.then(() => this._promptingForAllow = null); return this._promptingForAllow; }, _promptForUSB(session) { if (session.authentication !== 'PROMPT') { // This dialog is not prepared for any other authentication method at // this time. return DebuggerServer.AuthenticationResult.DENY; } return new Promise(resolve => { let title = Strings.browser.GetStringFromName("remoteIncomingPromptTitle"); let msg = Strings.browser.GetStringFromName("remoteIncomingPromptUSB"); let allow = Strings.browser.GetStringFromName("remoteIncomingPromptAllow"); let deny = Strings.browser.GetStringFromName("remoteIncomingPromptDeny"); // Make prompt. Note: button order is in reverse. let prompt = new Prompt({ window: null, hint: "remotedebug", title: title, message: msg, buttons: [ allow, deny ], priority: 1 }); prompt.show(data => { let result = data.button; if (result === 0) { resolve(DebuggerServer.AuthenticationResult.ALLOW); } else { resolve(DebuggerServer.AuthenticationResult.DENY); } }); }); }, _promptForTCP(session) { if (session.authentication !== 'OOB_CERT' || !session.client.cert) { // This dialog is not prepared for any other authentication method at // this time. return DebuggerServer.AuthenticationResult.DENY; } return new Promise(resolve => { let title = Strings.browser.GetStringFromName("remoteIncomingPromptTitle"); let msg = Strings.browser.formatStringFromName("remoteIncomingPromptTCP", [ session.client.host, session.client.port ], 2); let scan = Strings.browser.GetStringFromName("remoteIncomingPromptScan"); let scanAndRemember = Strings.browser.GetStringFromName("remoteIncomingPromptScanAndRemember"); let deny = Strings.browser.GetStringFromName("remoteIncomingPromptDeny"); // Make prompt. Note: button order is in reverse. let prompt = new Prompt({ window: null, hint: "remotedebug", title: title, message: msg, buttons: [ scan, scanAndRemember, deny ], priority: 1 }); prompt.show(data => { let result = data.button; if (result === 0) { resolve(DebuggerServer.AuthenticationResult.ALLOW); } else if (result === 1) { resolve(DebuggerServer.AuthenticationResult.ALLOW_PERSIST); } else { resolve(DebuggerServer.AuthenticationResult.DENY); } }); }); }, /** * During OOB_CERT authentication, the user must transfer some data through * some out of band mechanism from the client to the server to authenticate * the devices. * * This implementation instructs Fennec to invoke a QR decoder and return the * the data it contains back here. * * @return An object containing: * * sha256: hash(ClientCert) * * k : K(random 128-bit number) * A promise that will be resolved to the above is also allowed. */ receiveOOB() { if (this._receivingOOB) { return this._receivingOOB; } this._receivingOOB = Messaging.sendRequestForResult({ type: "DevToolsAuth:Scan" }).then(data => { return JSON.parse(data); }); this._receivingOOB.then(() => this._receivingOOB = null); return this._receivingOOB; }, initServer: function() { if (DebuggerServer.initialized) { return; } DebuggerServer.init(); // Add browser and Fennec specific actors DebuggerServer.addBrowserActors(); DebuggerServer.registerModule("resource://gre/modules/dbg-browser-actors.js"); // Allow debugging of chrome for any process DebuggerServer.allowChromeProcess = true; } }; RemoteDebugger.allowConnection = RemoteDebugger.allowConnection.bind(RemoteDebugger); RemoteDebugger.receiveOOB = RemoteDebugger.receiveOOB.bind(RemoteDebugger); var USBRemoteDebugger = { init() { Services.prefs.addObserver("devtools.", this, false); if (this.isEnabled) { this.start(); } }, observe(subject, topic, data) { if (topic != "nsPref:changed") { return; } switch (data) { case "devtools.remote.usb.enabled": Services.prefs.setBoolPref("devtools.debugger.remote-enabled", RemoteDebugger.isAnyEnabled); if (this.isEnabled) { this.start(); } else { this.stop(); } break; case "devtools.debugger.remote-port": case "devtools.debugger.unix-domain-socket": if (this.isEnabled) { this.stop(); this.start(); } break; } }, get isEnabled() { return Services.prefs.getBoolPref("devtools.remote.usb.enabled"); }, start: function() { if (this._listener) { return; } RemoteDebugger.initServer(); let portOrPath = Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") || Services.prefs.getIntPref("devtools.debugger.remote-port"); try { dump("Starting USB debugger on " + portOrPath); let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT"); let authenticator = new AuthenticatorType.Server(); authenticator.allowConnection = RemoteDebugger.allowConnection; this._listener = DebuggerServer.createListener(); this._listener.portOrPath = portOrPath; this._listener.authenticator = authenticator; this._listener.open(); } catch (e) { dump("Unable to start USB debugger server: " + e); } }, stop: function() { if (!this._listener) { return; } try { this._listener.close(); this._listener = null; } catch (e) { dump("Unable to stop USB debugger server: " + e); } } }; var WiFiRemoteDebugger = { init() { Services.prefs.addObserver("devtools.", this, false); if (this.isEnabled) { this.start(); } }, observe(subject, topic, data) { if (topic != "nsPref:changed") { return; } switch (data) { case "devtools.remote.wifi.enabled": Services.prefs.setBoolPref("devtools.debugger.remote-enabled", RemoteDebugger.isAnyEnabled); // Allow remote debugging on non-local interfaces when WiFi debug is // enabled // TODO: Bug 1034411: Lock down to WiFi interface only Services.prefs.setBoolPref("devtools.debugger.force-local", !this.isEnabled); if (this.isEnabled) { this.start(); } else { this.stop(); } break; } }, get isEnabled() { return Services.prefs.getBoolPref("devtools.remote.wifi.enabled"); }, start: function() { if (this._listener) { return; } RemoteDebugger.initServer(); try { dump("Starting WiFi debugger"); let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT"); let authenticator = new AuthenticatorType.Server(); authenticator.allowConnection = RemoteDebugger.allowConnection; authenticator.receiveOOB = RemoteDebugger.receiveOOB; this._listener = DebuggerServer.createListener(); this._listener.portOrPath = -1 /* any available port */; this._listener.authenticator = authenticator; this._listener.discoverable = true; this._listener.encryption = true; this._listener.open(); let port = this._listener.port; dump("Started WiFi debugger on " + port); } catch (e) { dump("Unable to start WiFi debugger server: " + e); } }, stop: function() { if (!this._listener) { return; } try { this._listener.close(); this._listener = null; } catch (e) { dump("Unable to stop WiFi debugger server: " + e); } } };