mirror of
https://github.com/openwrt/luci.git
synced 2025-12-21 19:14:34 +04:00
luci-app-wol: replace fs.stat/exec with safe RPC backend, fix ACLs
fs.stat() and fs.exec() require broad rpcd permissions (ubus file.*) which prevent luci-app-wol from working for restricted users and expose more access than necessary. This introduces a dedicated RPC backend (luci.wol) providing safe stat/exec wrappers for etherwake and wakeonlan, and simplifies ACLs to only allow these two RPC calls. Also adds the missing 'getNetworkDevices' ACL required by DeviceSelect. Signed-off-by: Martin Devolder <martin.devolder2@gmail.com>
This commit is contained in:
@@ -8,9 +8,26 @@
|
||||
'require form';
|
||||
'require tools.widgets as widgets';
|
||||
|
||||
const ETHERWAKE_BIN = '/usr/bin/etherwake';
|
||||
const WAKEONLAN_BIN = '/usr/bin/wakeonlan';
|
||||
|
||||
return view.extend({
|
||||
formdata: { wol: {} },
|
||||
|
||||
callStat: rpc.declare({
|
||||
object: 'luci.wol',
|
||||
method: 'stat',
|
||||
params: [ ],
|
||||
expect: { }
|
||||
}),
|
||||
|
||||
callExec: rpc.declare({
|
||||
object: 'luci.wol',
|
||||
method: 'exec',
|
||||
params: [ 'name', 'args' ],
|
||||
expect: { }
|
||||
}),
|
||||
|
||||
callHostHints: rpc.declare({
|
||||
object: 'luci-rpc',
|
||||
method: 'getHostHints',
|
||||
@@ -19,17 +36,15 @@ return view.extend({
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
L.resolveDefault(fs.stat('/usr/bin/etherwake')),
|
||||
L.resolveDefault(fs.stat('/usr/bin/wol')),
|
||||
L.resolveDefault(this.callStat()),
|
||||
this.callHostHints(),
|
||||
uci.load('etherwake')
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var has_ewk = data[0],
|
||||
has_wol = data[1],
|
||||
hosts = data[2],
|
||||
render([stat, hosts]) {
|
||||
var has_ewk = stat && stat.etherwake,
|
||||
has_wol = stat && stat.wakeonlan,
|
||||
m, s, o;
|
||||
|
||||
this.formdata.has_ewk = has_ewk;
|
||||
@@ -44,8 +59,8 @@ return view.extend({
|
||||
o = s.option(form.ListValue, 'executable', _('WoL program'),
|
||||
_('Sometimes only one of the two tools works. If one fails, try the other one'));
|
||||
|
||||
o.value('/usr/bin/etherwake', 'Etherwake');
|
||||
o.value('/usr/bin/wol', 'WoL');
|
||||
o.value(ETHERWAKE_BIN, 'Etherwake');
|
||||
o.value(WAKEONLAN_BIN, 'Wakeonlan');
|
||||
}
|
||||
|
||||
if (has_ewk) {
|
||||
@@ -67,7 +82,7 @@ return view.extend({
|
||||
});
|
||||
|
||||
if (has_wol)
|
||||
o.depends('executable', '/usr/bin/etherwake');
|
||||
o.depends('executable', ETHERWAKE_BIN);
|
||||
}
|
||||
|
||||
o = s.option(form.Value, 'mac', _('Host to wake up'),
|
||||
@@ -88,7 +103,7 @@ return view.extend({
|
||||
o = s.option(form.Flag, 'broadcast', _('Send to broadcast address'));
|
||||
|
||||
if (has_wol)
|
||||
o.depends('executable', '/usr/bin/etherwake');
|
||||
o.depends('executable', ETHERWAKE_BIN);
|
||||
}
|
||||
|
||||
return m.render();
|
||||
@@ -96,16 +111,17 @@ return view.extend({
|
||||
|
||||
handleWakeup: function(ev) {
|
||||
var map = document.querySelector('#maincontent .cbi-map'),
|
||||
data = this.formdata;
|
||||
data = this.formdata,
|
||||
self = this;
|
||||
|
||||
return dom.callClassMethod(map, 'save').then(function() {
|
||||
if (!data.wol.mac)
|
||||
return alert(_('No target host specified!'));
|
||||
|
||||
var bin = data.executable || (data.has_ewk ? '/usr/bin/etherwake' : '/usr/bin/wol'),
|
||||
var bin = data.wol.executable || (data.has_ewk ? ETHERWAKE_BIN : WAKEONLAN_BIN),
|
||||
args = [];
|
||||
|
||||
if (bin == '/usr/bin/etherwake') {
|
||||
if (bin == ETHERWAKE_BIN) {
|
||||
args.push('-D', '-i', data.wol.iface);
|
||||
|
||||
if (data.wol.broadcast == '1')
|
||||
@@ -114,16 +130,16 @@ return view.extend({
|
||||
args.push(data.wol.mac);
|
||||
}
|
||||
else {
|
||||
args.push('-v', data.wol.mac);
|
||||
args.push(data.wol.mac);
|
||||
}
|
||||
|
||||
ui.showModal(_('Waking host'), [
|
||||
E('p', { 'class': 'spinning' }, [ _('Starting WoL utility…') ])
|
||||
]);
|
||||
|
||||
return fs.exec(bin, args).then(function(res) {
|
||||
return self.callExec(bin, args).then(function(res) {
|
||||
ui.showModal(_('Waking host'), [
|
||||
res.stderr ? E('p', [ res.stdout ]) : '',
|
||||
res.stdout ? E('p', [ res.stdout ]) : '',
|
||||
res.stderr ? E('pre', [ res.stderr ]) : '',
|
||||
E('div', { 'class': 'right' }, [
|
||||
E('button', {
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
"description": "Grant access to wake-on-lan executables",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci-rpc": [ "getHostHints" ]
|
||||
"luci.wol": [ "stat" ],
|
||||
"luci-rpc": [ "getHostHints", "getNetworkDevices" ]
|
||||
},
|
||||
"uci": [ "etherwake" ]
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
"/usr/bin/etherwake": [ "exec" ],
|
||||
"/usr/bin/wol": [ "exec" ]
|
||||
"ubus": {
|
||||
"luci.wol": [ "exec" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
57
applications/luci-app-wol/root/usr/share/rpcd/ucode/luci.wol
Normal file
57
applications/luci-app-wol/root/usr/share/rpcd/ucode/luci.wol
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/ucode
|
||||
|
||||
'use strict';
|
||||
|
||||
import { access, stat, popen } from 'fs';
|
||||
|
||||
const etherwake = '/usr/bin/etherwake';
|
||||
const wakeonlan = '/usr/bin/wakeonlan';
|
||||
|
||||
|
||||
function shellquote(s) {
|
||||
return "'" + replace(s, "'", "'\\''") + "'";
|
||||
}
|
||||
|
||||
|
||||
const methods = {
|
||||
stat: {
|
||||
call: function(request) {
|
||||
const result = {};
|
||||
|
||||
result.etherwake = false;
|
||||
result.wakeonlan = false;
|
||||
|
||||
if (access(etherwake, "x")) {
|
||||
result.etherwake = true;
|
||||
}
|
||||
|
||||
if (access(wakeonlan, "x")) {
|
||||
result.wakeonlan = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
exec: {
|
||||
args: { name: 'string', args: [] },
|
||||
call: function(request) {
|
||||
const result = {};
|
||||
if (request.args.name == etherwake || request.args.name == wakeonlan) {
|
||||
parts = map(request.args.args, shellquote);
|
||||
const fd = popen(request.args.name + ' ' + join(' ', parts));
|
||||
|
||||
result.stdout = fd.read('all');
|
||||
result.stderr = '';
|
||||
result.code = 0;
|
||||
} else {
|
||||
result.stdout = '';
|
||||
result.stderr = 'disallowed';
|
||||
result.code = 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { "luci.wol": methods };
|
||||
Reference in New Issue
Block a user