diff --git a/lib/server.js b/lib/server.js index 74f2a67e..c9f6aae6 100644 --- a/lib/server.js +++ b/lib/server.js @@ -406,7 +406,20 @@ function Server (options) { const next = messageIIFE if (chain.handlers[i]) { return chain.handlers[i++].call(chain.backend, req, res, next) } - if (req.protocolOp === Protocol.LDAP_REQ_BIND && res.status === 0) { conn.ldap.bindDN = req.dn } + if (req.protocolOp === Protocol.LDAP_REQ_BIND && res.status === 0) { + // 0 length == anonymous bind + if (req.dn.length === 0 && req.credentials === '') { + conn.ldap.bindDN = new DN([new dn.RDN({ cn: 'anonymous' })]) + } else { + conn.ldap.bindDN = req.dn + } + } + + // unbind clear bindDN for safety + // conn should terminate on unbind (RFC4511 4.3) + if (req.protocolOp === Protocol.LDAP_REQ_UNBIND && res.status === 0) { + conn.ldap.bindDN = new DN([new dn.RDN({ cn: 'anonymous' })]) + } return after() } catch (e) { diff --git a/test/server.test.js b/test/server.test.js index 459cfa36..a9fa6e47 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -204,6 +204,99 @@ tap.test('route unbind', function (t) { }) }) +tap.test('bind/unbind identity anonymous', function (t) { + const server = ldap.createServer({ + connectionRouter: function (c) { + server.newConnection(c) + server.emit('testconnection', c) + } + }) + + server.unbind(function (req, res, next) { + t.ok(true, 'server unbind successful') + res.end() + return next() + }) + + server.bind('', function (req, res, next) { + t.ok(true, 'server bind successful') + res.end() + return next() + }) + + const anonDN = ldap.dn.parse('cn=anonymous') + + server.listen(t.context.sock, function () { + t.ok(true, 'server startup') + + const client = ldap.createClient({ socketPath: t.context.sock }) + server.once('testconnection', (c) => { + t.ok(anonDN.equals(c.ldap.bindDN), 'pre bind dn is correct') + client.bind('', '', function (err) { + t.error(err, 'client anon bind error') + t.ok(anonDN.equals(c.ldap.bindDN), 'anon bind dn is correct') + client.unbind(function (err) { + t.error(err, 'client anon unbind error') + t.ok(anonDN.equals(c.ldap.bindDN), 'anon unbind dn is correct') + server.close(() => t.end()) + }) + }) + }) + }) +}) + +tap.test('bind/unbind identity user', function (t) { + const server = ldap.createServer({ + connectionRouter: function (c) { + server.newConnection(c) + server.emit('testconnection', c) + } + }) + + server.unbind(function (req, res, next) { + t.ok(true, 'server unbind successful') + res.end() + return next() + }) + + server.bind('', function (req, res, next) { + t.ok(true, 'server bind successful') + res.end() + return next() + }) + + const anonDN = ldap.dn.parse('cn=anonymous') + const testDN = ldap.dn.parse('cn=anotheruser') + + server.listen(t.context.sock, function () { + t.ok(true, 'server startup') + + const client = ldap.createClient({ socketPath: t.context.sock }) + server.once('testconnection', (c) => { + t.ok(anonDN.equals(c.ldap.bindDN), 'pre bind dn is correct') + client.bind(testDN.toString(), 'somesecret', function (err) { + t.error(err, 'user bind error') + t.ok(testDN.equals(c.ldap.bindDN), 'user bind dn is correct') + // check rebinds too + client.bind('', '', function (err) { + t.error(err, 'client anon bind error') + t.ok(anonDN.equals(c.ldap.bindDN), 'anon bind dn is correct') + // user rebind + client.bind(testDN.toString(), 'somesecret', function (err) { + t.error(err, 'user bind error') + t.ok(testDN.equals(c.ldap.bindDN), 'user rebind dn is correct') + client.unbind(function (err) { + t.error(err, 'user unbind error') + t.ok(anonDN.equals(c.ldap.bindDN), 'user unbind dn is correct') + server.close(() => t.end()) + }) + }) + }) + }) + }) + }) +}) + tap.test('strict routing', function (t) { const testDN = 'cn=valid' let clt