node-ldapjs icon indicating copy to clipboard operation
node-ldapjs copied to clipboard

ldapjs acting a proxy for samba does not work

Open jianwang-beijing opened this issue 4 years ago • 4 comments

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.

jianwang-beijing avatar May 28 '20 08:05 jianwang-beijing

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.

jsumners avatar May 28 '20 11:05 jsumners

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: image

Oh, the bind cn=Manager, dc=ngbox, dc=com 123456 comes from the second bind function, it looks like: image

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?

jianwang-beijing avatar May 28 '20 11:05 jianwang-beijing

LDAP connections are two parts:

  1. A TCP connection is established to the server.
  2. 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 avatar May 28 '20 12:05 jsumners

@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.

jianwang-beijing avatar May 28 '20 12:05 jianwang-beijing

👋

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.

jsumners avatar Feb 22 '23 19:02 jsumners