node-ldapjs
node-ldapjs copied to clipboard
ldapjs acting a proxy for samba does not work
In recently, I want to setup ldap server with the ldapjs
in order to let the samba can use existing users who are in our mysql database. There are some error when samba start. I don't know whether or not the issue should be here, but I want to get some suggestion, at latest it can let me know whether I use the ldapjs
correctly.
First, the error that I met is:
[2020/05/28 04:29:48.107946, 0] ../../source3/lib/smbldap.c:1052(smbldap_connect_system)
failed to bind to server ldap://192.168.1.107:389 with dn="cn=Manager,dc=ngbox,dc=com" Error: No such object
cn=Manager, dc=ngbox, dc=com
[2020/05/28 04:30:04.189994, 0] ../../source3/passdb/pdb_ldap.c:6680(pdb_ldapsam_init_common)
pdb_init_ldapsam: WARNING: Could not get domain info, nor add one to the domain. We cannot work reliably without it.
[2020/05/28 04:30:04.190120, 0] ../../source3/passdb/pdb_interface.c:180(make_pdb_method_name)
pdb backend ldapsam:ldap://192.168.1.107:389 did not correctly init (error was NT_STATUS_CANT_ACCESS_DOMAIN_INFO)
My ldapjs server is verify simple, In fact it is the examples/inmemory.js, just the port
and SUFFIX
are different:
var ldap = require('../lib/index')
/// --- Shared handlers
function authorize (req, res, next) {
/* Any user may search after bind, only cn=root has full power */
var isSearch = (req instanceof ldap.SearchRequest)
if (!req.connection.ldap.bindDN.equals('cn=root') && !isSearch) { return next(new ldap.InsufficientAccessRightsError()) }
return next()
}
/// --- Globals
var SUFFIX = 'dc=ngbox, dc=com'
var db = {}
var server = ldap.createServer()
server.bind('cn=root', function (req, res, next) {
if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret') { return next(new ldap.InvalidCredentialsError()) }
res.end()
return next()
})
server.add(SUFFIX, authorize, function (req, res, next) {
var dn = req.dn.toString()
if (db[dn]) { return next(new ldap.EntryAlreadyExistsError(dn)) }
db[dn] = req.toObject().attributes
res.end()
return next()
})
server.bind(SUFFIX, function (req, res, next) {
var dn = req.dn.toString()
if (!db[dn]) { return next(new ldap.NoSuchObjectError(dn)) }
if (!db[dn].userpassword) { return next(new ldap.NoSuchAttributeError('userPassword')) }
if (db[dn].userpassword.indexOf(req.credentials) === -1) { return next(new ldap.InvalidCredentialsError()) }
res.end()
return next()
})
server.compare(SUFFIX, authorize, function (req, res, next) {
var dn = req.dn.toString()
if (!db[dn]) { return next(new ldap.NoSuchObjectError(dn)) }
if (!db[dn][req.attribute]) { return next(new ldap.NoSuchAttributeError(req.attribute)) }
var matches = false
var vals = db[dn][req.attribute]
for (var i = 0; i < vals.length; i++) {
if (vals[i] === req.value) {
matches = true
break
}
}
res.end(matches)
return next()
})
server.del(SUFFIX, authorize, function (req, res, next) {
var dn = req.dn.toString()
if (!db[dn]) { return next(new ldap.NoSuchObjectError(dn)) }
delete db[dn]
res.end()
return next()
})
server.modify(SUFFIX, authorize, function (req, res, next) {
var dn = req.dn.toString()
if (!req.changes.length) { return next(new ldap.ProtocolError('changes required')) }
if (!db[dn]) { return next(new ldap.NoSuchObjectError(dn)) }
var entry = db[dn]
let mod
for (var i = 0; i < req.changes.length; i++) {
mod = req.changes[i].modification
switch (req.changes[i].operation) {
case 'replace':
if (!entry[mod.type]) { return next(new ldap.NoSuchAttributeError(mod.type)) }
if (!mod.vals || !mod.vals.length) {
delete entry[mod.type]
} else {
entry[mod.type] = mod.vals
}
break
case 'add':
if (!entry[mod.type]) {
entry[mod.type] = mod.vals
} else {
mod.vals.forEach(function (v) {
if (entry[mod.type].indexOf(v) === -1) { entry[mod.type].push(v) }
})
}
break
case 'delete':
if (!entry[mod.type]) { return next(new ldap.NoSuchAttributeError(mod.type)) }
delete entry[mod.type]
break
}
}
res.end()
return next()
})
server.search(SUFFIX, authorize, function (req, res, next) {
var dn = req.dn.toString()
if (!db[dn]) { return next(new ldap.NoSuchObjectError(dn)) }
var scopeCheck
switch (req.scope) {
case 'base':
if (req.filter.matches(db[dn])) {
res.send({
dn: dn,
attributes: db[dn]
})
}
res.end()
return next()
case 'one':
scopeCheck = function (k) {
if (req.dn.equals(k)) { return true }
var parent = ldap.parseDN(k).parent()
return (parent ? parent.equals(req.dn) : false)
}
break
case 'sub':
scopeCheck = function (k) {
return (req.dn.equals(k) || req.dn.parentOf(k))
}
break
}
Object.keys(db).forEach(function (key) {
if (!scopeCheck(key)) { return }
if (req.filter.matches(db[key])) {
res.send({
dn: key,
attributes: db[key]
})
}
})
res.end()
return next()
})
/// --- Fire it up
server.listen(389, function () {
console.log('LDAP server up at: %s', server.url)
})
The samba(ldap client) configuration looks like:
[global]
workgroup = MYGROUP
server string = Samba Server Version %v
log file = /var/log/samba/log
security = user
passdb backend = ldapsam:ldap://192.168.1.107:389
ldap suffix = dc=ngbox,dc=com
ldap user suffix = ou=People
ldap group suffix = ou=Group
ldap admin dn = cn=Manager,dc=ngbox,dc=com
ldap ssl = no
load printers = no
[homes]
comment = Home Directories
browseable = no
writable = yes
create mask = 0600
directory mask = 700
Ok, all steps is done, when I run the shell command: systemctl start smb
, the error will be found in the samba log file.
I am not sure that the error is caused by my ldapjs or samba, please give me some suggestion at latest in the ldapjs view.
The error is stating that it cannot find the cn=Manager,dc=ngbox,dc=com
user. Your LDAP server needs to return an entry when queried for that user.
The error is stating that it cannot find the
cn=Manager,dc=ngbox,dc=com
user. Your LDAP server needs to return an entry when queried for that user.
Thank you, I also find the problem. From the office document and my test, the bind function callback object res
does not have the send
function. So I append a search function for this:
server.search(`cn=Manager, ${SUFFIX}`, function (req, res, next) {
console.log('search Manager');
var dn = req.dn.toString();
if (!db[dn])
return next(new ldap.NoSuchObjectError(dn));
var scopeCheck;
... ...
}
But the search is not executed because I do not find the console.log
output. I am sure only the bind function is executed, the logs like:
Oh, the bind cn=Manager, dc=ngbox, dc=com 123456
comes from the second bind function, it looks like:
After too much test, I am thinking that if I do not how samba to communicate with ldap, for example: what to bind and what to search, I can not implement my job, right?
LDAP connections are two parts:
- A TCP connection is established to the server.
- A
bind
is performed to authenticate. This bind can either be an anonymous bind or a credentialed bind as a known user in the LDAP tree.
Your SAMBA server is attempting a credentialed bind to the server. It does this so that it then has the permissions necessary to query the LDAP tree for other entries.
You should read about how LDAP works before attempting to implement a server for it. Providing such support is outside of this project's scope.
@jsumners Thanks for your reply. I have read some wiki about LDAP, for example: LDAP Guide and others. Of course, this is basic knowledge. The another knowledge is the samba how to work with LDAP.
About the bind for authenticate, yes, here is a detail introduction. I also write some code use the real logic and connect to real database to check the credential, the code look like the our office example Address Book. If the credential is ok we do next, if not we do next and give a LDAPError
. But the test result is same. In another words, if the credential is ok, how can I tell the client(samba) have the right and how to let client know the LDAP tree. I think this is key to resolve the problem. If you have the experience, please give some advice.
👋
On February 22, 2023, we released version 3 of this library. As a result, we are closing this issue/pull request.
Please see issue #839 for more information, including how to proceed if you feel this closure is in error.