mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-02 15:15:23 +00:00
507 lines
14 KiB
JavaScript
507 lines
14 KiB
JavaScript
/* 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/. */
|
|
|
|
"use strict";
|
|
|
|
this.EXPORTED_SYMBOLS = [ "KillSwitchMain" ];
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/AppConstants.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "settings",
|
|
"@mozilla.org/settingsService;1",
|
|
"nsISettingsService");
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
|
|
"@mozilla.org/parentprocessmessagemanager;1",
|
|
"nsIMessageBroadcaster");
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "permMgr", function() {
|
|
return Cc["@mozilla.org/permissionmanager;1"]
|
|
.getService(Ci.nsIPermissionManager);
|
|
});
|
|
|
|
if (AppConstants.platform === "gonk") {
|
|
XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
|
|
Cu.import("resource://gre/modules/systemlibs.js");
|
|
return libcutils;
|
|
});
|
|
} else {
|
|
this.libcutils = null;
|
|
}
|
|
|
|
const DEBUG = false;
|
|
|
|
const kEnableKillSwitch = "KillSwitch:Enable";
|
|
const kEnableKillSwitchOK = "KillSwitch:Enable:OK";
|
|
const kEnableKillSwitchKO = "KillSwitch:Enable:KO";
|
|
|
|
const kDisableKillSwitch = "KillSwitch:Disable";
|
|
const kDisableKillSwitchOK = "KillSwitch:Disable:OK";
|
|
const kDisableKillSwitchKO = "KillSwitch:Disable:KO";
|
|
|
|
const kMessages = [kEnableKillSwitch, kDisableKillSwitch];
|
|
|
|
const kXpcomShutdownObserverTopic = "xpcom-shutdown";
|
|
|
|
const kProperty = "persist.moz.killswitch";
|
|
|
|
const kUserValues =
|
|
OS.Path.join(OS.Constants.Path.profileDir, "killswitch.json");
|
|
|
|
var inParent = Cc["@mozilla.org/xre/app-info;1"]
|
|
.getService(Ci.nsIXULRuntime)
|
|
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
|
|
|
function debug(aStr) {
|
|
dump("--*-- KillSwitchMain: " + aStr + "\n");
|
|
}
|
|
|
|
this.KillSwitchMain = {
|
|
_ksState: null,
|
|
_libcutils: null,
|
|
|
|
_enabledValues: {
|
|
// List of settings to set to a specific value
|
|
settings: {
|
|
"debugger.remote-mode": "disabled",
|
|
"developer.menu.enabled": false,
|
|
"devtools.unrestricted": false,
|
|
"lockscreen.enabled": true,
|
|
"lockscreen.locked": true,
|
|
"lockscreen.lock-immediately": true,
|
|
"tethering.usb.enabled": false,
|
|
"tethering.wifi.enabled": false,
|
|
"ums.enabled": false
|
|
},
|
|
|
|
// List of preferences to set to a specific value
|
|
prefs: {
|
|
"b2g.killswitch.test": true
|
|
},
|
|
|
|
// List of Android properties to set to a specific value
|
|
properties: {
|
|
"persist.sys.usb.config": "none" // will change sys.usb.config and sys.usb.state
|
|
},
|
|
|
|
// List of Android services to control
|
|
services: {
|
|
"adbd": "stop"
|
|
}
|
|
},
|
|
|
|
init: function() {
|
|
DEBUG && debug("init");
|
|
if (libcutils) {
|
|
this._libcutils = libcutils;
|
|
}
|
|
|
|
kMessages.forEach(m => {
|
|
ppmm.addMessageListener(m, this);
|
|
});
|
|
|
|
Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
|
|
|
|
this.readStateProperty();
|
|
},
|
|
|
|
uninit: function() {
|
|
kMessages.forEach(m => {
|
|
ppmm.removeMessageListener(m, this);
|
|
});
|
|
|
|
Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
|
|
},
|
|
|
|
checkLibcUtils: function() {
|
|
DEBUG && debug("checkLibcUtils");
|
|
if (!this._libcutils) {
|
|
debug("No proper libcutils binding, aborting.");
|
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
readStateProperty: function() {
|
|
DEBUG && debug("readStateProperty");
|
|
try {
|
|
this.checkLibcUtils();
|
|
} catch (ex) {
|
|
return;
|
|
}
|
|
|
|
this._ksState =
|
|
this._libcutils.property_get(kProperty, "false") === "true";
|
|
},
|
|
|
|
writeStateProperty: function() {
|
|
DEBUG && debug("writeStateProperty");
|
|
try {
|
|
this.checkLibcUtils();
|
|
} catch (ex) {
|
|
return;
|
|
}
|
|
|
|
this._libcutils.property_set(kProperty, this._ksState.toString());
|
|
},
|
|
|
|
getPref(name, value) {
|
|
let rv = undefined;
|
|
|
|
try {
|
|
switch (typeof value) {
|
|
case "boolean":
|
|
rv = Services.prefs.getBoolPref(name, value);
|
|
break;
|
|
|
|
case "number":
|
|
rv = Services.prefs.getIntPref(name, value);
|
|
break;
|
|
|
|
case "string":
|
|
rv = Services.prefs.getCharPref(name, value);
|
|
break;
|
|
|
|
default:
|
|
debug("Unexpected pref type " + value);
|
|
break;
|
|
}
|
|
} catch (ex) {
|
|
}
|
|
|
|
return rv;
|
|
},
|
|
|
|
setPref(name, value) {
|
|
switch (typeof value) {
|
|
case "boolean":
|
|
Services.prefs.setBoolPref(name, value);
|
|
break;
|
|
|
|
case "number":
|
|
Services.prefs.setIntPref(name, value);
|
|
break;
|
|
|
|
case "string":
|
|
Services.prefs.setCharPref(name, value);
|
|
break;
|
|
|
|
default:
|
|
debug("Unexpected pref type " + value);
|
|
break;
|
|
}
|
|
},
|
|
|
|
doEnable: function() {
|
|
return new Promise((resolve, reject) => {
|
|
// Make sure that the API cannot do a new |enable()| call once the
|
|
// feature has been enabled, otherwise we will overwrite the user values.
|
|
if (this._ksState) {
|
|
reject(true);
|
|
return;
|
|
}
|
|
|
|
this.saveUserValues().then(() => {
|
|
DEBUG && debug("Toggling settings: " +
|
|
JSON.stringify(this._enabledValues.settings));
|
|
|
|
let lock = settings.createLock();
|
|
for (let key of Object.keys(this._enabledValues.settings)) {
|
|
lock.set(key, this._enabledValues.settings[key], this);
|
|
}
|
|
|
|
DEBUG && debug("Toggling prefs: " +
|
|
JSON.stringify(this._enabledValues.prefs));
|
|
|
|
for (let key of Object.keys(this._enabledValues.prefs)) {
|
|
this.setPref(key, this._enabledValues.prefs[key]);
|
|
}
|
|
|
|
DEBUG && debug("Toggling properties: " +
|
|
JSON.stringify(this._enabledValues.properties));
|
|
|
|
for (let key of Object.keys(this._enabledValues.properties)) {
|
|
this._libcutils.property_set(key, this._enabledValues.properties[key]);
|
|
}
|
|
|
|
DEBUG && debug("Toggling services: " +
|
|
JSON.stringify(this._enabledValues.services));
|
|
|
|
for (let key of Object.keys(this._enabledValues.services)) {
|
|
let value = this._enabledValues.services[key];
|
|
if (value !== "start" && value !== "stop") {
|
|
debug("Unexpected service " + key + " value:" + value);
|
|
}
|
|
|
|
this._libcutils.property_set("ctl." + value, key);
|
|
}
|
|
|
|
this._ksState = true;
|
|
this.writeStateProperty();
|
|
|
|
resolve(true);
|
|
}).catch(err => {
|
|
DEBUG && debug("doEnable: " + err);
|
|
|
|
reject(false);
|
|
});
|
|
});
|
|
},
|
|
|
|
saveUserValues: function() {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
this.checkLibcUtils();
|
|
} catch (ex) {
|
|
reject("nolibcutils");
|
|
}
|
|
|
|
let _userValues = {
|
|
settings: { },
|
|
prefs: { },
|
|
properties: { }
|
|
};
|
|
|
|
// Those will be sync calls
|
|
for (let key of Object.keys(this._enabledValues.prefs)) {
|
|
_userValues.prefs[key] =
|
|
this.getPref(key, this._enabledValues.prefs[key]);
|
|
}
|
|
|
|
for (let key of Object.keys(this._enabledValues.properties)) {
|
|
_userValues.properties[key] = this._libcutils.property_get(key);
|
|
}
|
|
|
|
let self = this;
|
|
let getCallback = {
|
|
handleAbort: function(m) {
|
|
DEBUG && debug("getCallback: handleAbort: m=" + m);
|
|
reject(m);
|
|
},
|
|
|
|
handleError: function(m) {
|
|
DEBUG && debug("getCallback: handleError: m=" + m);
|
|
reject(m);
|
|
},
|
|
|
|
handle: function(n, v) {
|
|
DEBUG && debug("getCallback: handle: n=" + n + " ; v=" + v);
|
|
|
|
if (self._pendingSettingsGet) {
|
|
// We have received a settings callback value for saving user data
|
|
let pending = self._pendingSettingsGet.indexOf(n);
|
|
if (pending !== -1) {
|
|
_userValues.settings[n] = v;
|
|
self._pendingSettingsGet.splice(pending, 1);
|
|
}
|
|
|
|
if (self._pendingSettingsGet.length === 0) {
|
|
delete self._pendingSettingsGet;
|
|
let payload = JSON.stringify(_userValues);
|
|
DEBUG && debug("Dumping to " + kUserValues + ": " + payload);
|
|
OS.File.writeAtomic(kUserValues, payload).then(
|
|
function writeOk() {
|
|
resolve(true);
|
|
},
|
|
function writeNok(err) {
|
|
reject("write error");
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// For settings we have to wait all the callbacks to come back before
|
|
// we can resolve or reject
|
|
this._pendingSettingsGet = [];
|
|
let lock = settings.createLock();
|
|
for (let key of Object.keys(this._enabledValues.settings)) {
|
|
this._pendingSettingsGet.push(key);
|
|
lock.get(key, getCallback);
|
|
}
|
|
});
|
|
},
|
|
|
|
doDisable: function() {
|
|
return new Promise((resolve, reject) => {
|
|
this.restoreUserValues().then(() => {
|
|
this._ksState = false;
|
|
this.writeStateProperty();
|
|
|
|
resolve(true);
|
|
}).catch(err => {
|
|
DEBUG && debug("doDisable: " + err);
|
|
|
|
reject(false);
|
|
});
|
|
});
|
|
},
|
|
|
|
restoreUserValues: function() {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
this.checkLibcUtils();
|
|
} catch (ex) {
|
|
reject("nolibcutils");
|
|
}
|
|
|
|
OS.File.read(kUserValues, { encoding: "utf-8" }).then(content => {
|
|
let values = JSON.parse(content);
|
|
|
|
for (let key of Object.keys(values.prefs)) {
|
|
this.setPref(key, values.prefs[key]);
|
|
}
|
|
|
|
for (let key of Object.keys(values.properties)) {
|
|
this._libcutils.property_set(key, values.properties[key]);
|
|
}
|
|
|
|
let self = this;
|
|
let saveCallback = {
|
|
handleAbort: function(m) {
|
|
DEBUG && debug("saveCallback: handleAbort: m=" + m);
|
|
reject(m);
|
|
},
|
|
|
|
handleError: function(m) {
|
|
DEBUG && debug("saveCallback: handleError: m=" + m);
|
|
reject(m);
|
|
},
|
|
|
|
handle: function(n, v) {
|
|
DEBUG && debug("saveCallback: handle: n=" + n + " ; v=" + v);
|
|
|
|
if (self._pendingSettingsSet) {
|
|
// We have received a settings callback value for setting user data
|
|
let pending = self._pendingSettingsSet.indexOf(n);
|
|
if (pending !== -1) {
|
|
self._pendingSettingsSet.splice(pending, 1);
|
|
}
|
|
|
|
if (self._pendingSettingsSet.length === 0) {
|
|
delete self._pendingSettingsSet;
|
|
DEBUG && debug("Restored from " + kUserValues + ": " + JSON.stringify(values));
|
|
resolve(values);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// For settings we have to wait all the callbacks to come back before
|
|
// we can resolve or reject
|
|
this._pendingSettingsSet = [];
|
|
let lock = settings.createLock();
|
|
for (let key of Object.keys(values.settings)) {
|
|
this._pendingSettingsSet.push(key);
|
|
lock.set(key, values.settings[key], saveCallback);
|
|
}
|
|
}).catch(err => {
|
|
reject(err);
|
|
});
|
|
});
|
|
},
|
|
|
|
// Settings Callbacks
|
|
handle: function(aName, aValue) {
|
|
DEBUG && debug("handle: aName=" + aName + " ; aValue=" + aValue);
|
|
// We don't have to do anything for now.
|
|
},
|
|
|
|
handleAbort: function(aMessage) {
|
|
debug("handleAbort: " + JSON.stringify(aMessage));
|
|
throw Cr.NS_ERROR_ABORT;
|
|
},
|
|
|
|
handleError: function(aMessage) {
|
|
debug("handleError: " + JSON.stringify(aMessage));
|
|
throw Cr.NS_ERROR_FAILURE;
|
|
},
|
|
|
|
// addObserver
|
|
observe: function(aSubject, aTopic, aData) {
|
|
switch (aTopic) {
|
|
case kXpcomShutdownObserverTopic:
|
|
this.uninit();
|
|
break;
|
|
|
|
default:
|
|
DEBUG && debug("Wrong observer topic: " + aTopic);
|
|
break;
|
|
}
|
|
},
|
|
|
|
// addMessageListener
|
|
receiveMessage: function(aMessage) {
|
|
let hasPermission = aMessage.target.assertPermission("killswitch");
|
|
DEBUG && debug("hasPermission: " + hasPermission);
|
|
|
|
if (!hasPermission) {
|
|
debug("Message " + aMessage.name + " from a process with no killswitch perm.");
|
|
aMessage.target.killChild();
|
|
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
|
return;
|
|
}
|
|
|
|
function returnMessage(name, data) {
|
|
if (aMessage.target) {
|
|
data.requestID = aMessage.data.requestID;
|
|
try {
|
|
aMessage.target.sendAsyncMessage(name, data);
|
|
} catch (e) {
|
|
if (DEBUG) debug("Return message failed, " + name + ": " + e);
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (aMessage.name) {
|
|
case kEnableKillSwitch:
|
|
this.doEnable().then(
|
|
() => {
|
|
returnMessage(kEnableKillSwitchOK, {});
|
|
},
|
|
err => {
|
|
debug("doEnable failed: " + err);
|
|
returnMessage(kEnableKillSwitchKO, {});
|
|
}
|
|
);
|
|
break;
|
|
case kDisableKillSwitch:
|
|
this.doDisable().then(
|
|
() => {
|
|
returnMessage(kDisableKillSwitchOK, {});
|
|
},
|
|
err => {
|
|
debug("doDisable failed: " + err);
|
|
returnMessage(kDisableKillSwitchKO, {});
|
|
}
|
|
);
|
|
break;
|
|
|
|
default:
|
|
debug("Unsupported message: " + aMessage.name);
|
|
aMessage.target && aMessage.target.killChild();
|
|
throw Cr.NS_ERROR_ILLEGAL_VALUE;
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
// This code should ALWAYS be living only on the parent side.
|
|
if (!inParent) {
|
|
debug("KillSwitchMain should only be living on parent side.");
|
|
throw Cr.NS_ERROR_ABORT;
|
|
} else {
|
|
this.KillSwitchMain.init();
|
|
}
|