Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion applications/luci-app-acl/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk
PKG_LICENSE:=Apache-2.0

LUCI_TITLE:=LuCI account management module
LUCI_DEPENDS:=+luci-base
LUCI_DEPENDS:=+luci-base +luci-mod-system

PKG_MAINTAINER:=Jo-Philipp Wich <jo@mein.io>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,46 @@
'require dom';
'require fs';
'require ui';
'require rpc';
'require uci';
'require form';
'require tools.widgets as widgets';
'require tools.password as pwtool';

const aclList = {};

const callSetPassword = rpc.declare({
object: 'luci',
method: 'setPassword',
params: [ 'username', 'password', 'oldpassword', 'rpcd' ],
expect: { result: 1 }
});

function checkPassword(value) {
const pw_enabled = uci.get('rpcd', 'policy', 'enabled');
const pw_length = uci.get('rpcd', 'policy', 'pw_length');
const pw_digits = uci.get('rpcd', 'policy', 'digits');
const pw_ul = uci.get('rpcd', 'policy', 'uc_lc');
const special = uci.get('rpcd', 'policy', 'special_characters');

if (!pw_enabled)
return true;

if (pw_length && !pwtool.checkLength(value, pw_length))
return _('Policy: min. length of %s characters').format(pw_length);

if (pw_digits && !pwtool.checkDigits(value))
return _('Policy: contain digits');

if (pw_ul && !pwtool.checkUpperLower(value))
return _('Policy: contain uppercase/lowercase');

if (special && !pwtool.checkSpecialChars(value))
return _('Policy: contain special characters');

return true;
}

function globListToRegExp(section_id, option) {
const list = L.toArray(uci.get('rpcd', section_id, option));
const positivePatterns = [];
Expand Down Expand Up @@ -162,7 +196,8 @@ return view.extend({
return L.resolveDefault(fs.list('/usr/share/rpcd/acl.d'), []).then(function(entries) {
const tasks = [
L.resolveDefault(fs.stat('/usr/sbin/uhttpd'), null),
fs.lines('/etc/passwd')
fs.lines('/etc/passwd'),
uci.load('rpcd')
];

for (let e of entries)
Expand All @@ -173,7 +208,7 @@ return view.extend({
});
},

render([has_uhttpd, passwd, ...acls]) {
render([has_uhttpd, passwd, uci_rpcd, ...acls]) {
ui.addNotification(null, E('p', [
_('The LuCI ACL management is in an experimental stage! It does not yet work reliably with all applications')
]), 'warning');
Expand Down Expand Up @@ -267,22 +302,17 @@ return view.extend({
return _('Cannot encrypt plaintext password since uhttpd is not installed.');
}

return true;
return checkPassword(value);
};
o.write = function(section_id, value) {
const variant = this.map.lookupOption('_variant', section_id)[0];
const user = this.map.lookupOption('username', section_id)[0].formvalue(section_id);

if (variant.formvalue(section_id) == 'crypted' && value.substring(0, 3) != '$1$')
return fs.exec('/usr/sbin/uhttpd', [ '-m', value ]).then(function(res) {
if (res.code == 0 && res.stdout)
uci.set('rpcd', section_id, 'password', res.stdout.trim());
else
throw new Error(res.stderr);
}).catch(function(err) {
throw new Error(_('Unable to encrypt plaintext password: %s').format(err.message));
return callSetPassword(user, value, '', true).then(function(success) {
if (!success)
throw new Error('Failed to create password');
});

uci.set('rpcd', section_id, 'password', value);
};
o.remove = function() {};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
"uci": [ "rpcd" ]
},
"write": {
"uci": [ "rpcd" ]
"uci": [ "rpcd" ],
"ubus": {
"luci": [ "setPassword" ]
}
}
}
}
116 changes: 110 additions & 6 deletions modules/luci-base/root/usr/share/rpcd/ucode/luci
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,67 @@ function callPackageVersionCheck(pkg) {
return version;
}

function set_new_pwd(user, pwd) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two functions differ only slightly - can they be combined?

Also, it's a good idea to check for uhttpd and bail early if absent - not everyone uses uhttpd, IIUC. Not sure what to do in that case....

Copy link
Copy Markdown
Contributor Author

@ckorber ckorber May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They differ only slightly, but the first sets a password, while the other simply prompts for one.
There is little point in combining them, as this would eliminate the separation of tasks.

Your point with uhttpd is valid. But in that case the user relies on cli not luci, right? I mean it is necessary for luci-mod-system.

I could exit early if uhttpd is not available.

const ctx = cursor();
let cmd, fd, value;

cmd = sprintf('/usr/sbin/uhttpd -m %s', pwd);
fd = popen(cmd);
value = trim(fd.read('line'));

fd.close();

ctx.foreach('rpcd', 'login', s => {
if (s.username == user) {
ctx.set('rpcd', s['.name'], 'password', value);
ctx.commit();
}
});
}

function check_oldpwd(user, pwd) {
let cmd, fd, value;
const ctx = cursor();
let valid = false;

cmd = sprintf('/usr/sbin/uhttpd -m %s', pwd);
fd = popen(cmd);
value = trim(fd.read('line'));
fd.close();

ctx.foreach('rpcd', 'login', s => {
if (s.username == user && s.password == value)
valid = true;
});

return valid;
}

function check_user(source, entry) {
let found = false;

switch (source) {
case 'unix': {
const fd = open('/etc/passwd');

for (let line = fd.read('line'); length(line); line = fd.read('line')) {
let user = split(line, /:/)[0];
if (user == entry)
found = true;
}
}

case 'rpcd': {
const ctx = cursor();
ctx.foreach('rpcd', 'login', s => {
if (s.username == entry)
found = true;
});
}
}
return found;
}

const methods = {
getVersion: {
call: function(request) {
Expand Down Expand Up @@ -465,14 +526,57 @@ const methods = {
},

setPassword: {
args: { username: 'root', password: 'password' },
args: {
username: 'root',
password: 'password',
oldpassword: '',
rpcd: false,
},
call: function(request) {
const u = shellquote(request.args.username);
const p = shellquote(request.args.password);
const user = request.args.username;
const pwd = request.args.password;
const oldpwd = request.args.oldpassword;
const type = request.args.rpcd == true ? 'rpcd' : 'unix';
const known_user = check_user(type, user);
const uci = cursor();

return {
result: system(`(echo ${p}; sleep 1; echo ${p}) | /bin/busybox passwd ${u} >/dev/null 2>&1`) == 0
};
if (type == 'unix') {
const u = shellquote(user);
const p = shellquote(pwd);

return {
result: system(`(echo ${p}; sleep 1; echo ${p}) | /bin/busybox passwd ${u} >/dev/null 2>&1`) == 0
};
}

if (!access('/usr/sbin/uhttpd', 'x'))
return {
result: 0 ,
msg: "uhttpd not installed"
};

if (known_user) {
if (!oldpwd)
return { result: 0 };

/*
* check if login is valid
*/
if (!check_oldpwd(user, oldpwd))
return { result: 0 };
} else {
/*
* create user to keep logic of luci-app-acl
*/
const sid = uci.add('rpcd', 'login');
uci.set('rpcd', sid, 'username', user);
}

/*
* encrypt password and assign it to rpcd login
*/
set_new_pwd(user, pwd);
return { result: 1 };
}
},

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

'require baseclass';
'require uci';

return baseclass.extend({
checkLength(p, required) {
let enough = p.length >= parseInt(required);

if (required && !enough)
return false;

return true;
},

checkDigits(p) {
let m = p.match(/\d/);

return m ? true : false;
},

checkUpperLower(p) {

return /[a-z]/.test(p) && /[A-Z]/.test(p);
},

checkSpecialChars(p) {
let m = p.match(/[^a-zA-Z0-9]/);

return m ? true : false;
}
});
Loading