gecko-dev/b2g/components/KillSwitchMain.jsm

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();
}