diff --git a/src/ldap.js b/src/ldap.js index b63c0b0c4..e1789ea91 100644 --- a/src/ldap.js +++ b/src/ldap.js @@ -15,7 +15,8 @@ var assert = require('assert'), UserError = user.UserError, ldap = require('ldapjs'), mailboxdb = require('./mailboxdb.js'), - safe = require('safetydance'); + safe = require('safetydance'), + util = require('util'); var gServer = null; @@ -133,24 +134,102 @@ function groupSearch(req, res, next) { function getMailbox(req, res, next) { debug('mailbox get: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id); - if (!req.dn.rdns[0].attrs.cn) return next(new ldap.OperationsError('CN is required')); - - mailboxes.get( - mailboxes.getAll(function (error, result) { + // http://www.zytrax.com/books/ldap/ape/courier.html + // http://www.ldapadmin.org/docs/postfix.schema + // https://wiki.zimbra.com/wiki/5.0.15_Directory_Schema + var get = !!req.dn.rdns[0].attrs.cn; + var func = get ? mailboxdb.getMailbox.bind(null, req.dn.rdns[0].attrs.cn) : mailboxdb.getMailboxes; + func(function (error, result) { if (error) return next(new ldap.OperationsError(error.toString())); - result.forEach(function (entry) { - var dn = ldap.parseDN('cn=' + entry.name + ',ou=mailboxes,dc=cloudron'); + var results = util.isArray(result) ? result : [ result ]; + + results.forEach(function (mailbox) { + var dn = ldap.parseDN('cn=' + mailbox.name + ',ou=mailboxes,dc=cloudron'); - // TODO: send aliases var obj = { - dn: dn.toString(), + dn: req.dn.toString(), attributes: { objectclass: ['mailbox'], objectcategory: 'mailbox', - cn: entry.name, - uid: entry.name, - mail: entry.name + '@' + config.fqdn() + cn: name, + uid: mailbox.ownerId, + mail: name + '@' + config.fqdn() + } + }; + + // ensure all filter values are also lowercase + var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null); + if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString())); + + if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) { + res.send(obj); + } + }); + + res.end(); + }); +} + +function getMailAlias(req, res, next) { + debug('mail alias get: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id); + + // https://wiki.debian.org/LDAP/MigrationTools/Examples + var get = !!req.dn.rdns[0].attrs.cn; + var func = get ? mailboxdb.getAlias.bind(null, req.dn.rdns[0].attrs.cn) : mailboxdb.getAliases; + func(function (error, result) { + if (error) return next(new ldap.OperationsError(error.toString())); + + var results = util.isArray(result) ? result : [ result ]; + + results.forEach(function (alias) { + var dn = ldap.parseDN('cn=' + alias.name + ',ou=mailaliases,dc=cloudron'); + + var obj = { + dn: dn.toString(), + attributes: { + objectclass: ['nisMailAlias'], + objectcategory: 'nisMailAlias', + cn: alias.name, + rfc822MailMember: alias.aliasTarget + } + }; + + // ensure all filter values are also lowercase + var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null); + if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString())); + + if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) { + res.send(obj); + } + }); + + res.end(); + }); +} + +function getMailGroup(req, res, next) { + debug('mailgroup get: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id); + + // https://docs.oracle.com/cd/E19455-01/806-5580/6jej518pp/index.html + var get = !!req.dn.rdns[0].attrs.cn; + var func = get ? mailboxdb.getGroup.bind(null, req.dn.rdns[0].attrs.cn) : mailboxdb.getGroups; + func(function (error, result) { + if (error) return next(new ldap.OperationsError(error.toString())); + + var results = util.isArray(result) ? result : [ result ]; + + results.forEach(function (group) { + var dn = ldap.parseDN('cn=' + group.name + ',ou=mailgroups,dc=cloudron'); + + var obj = { + dn: dn.toString(), + attributes: { + objectclass: ['mailGroup'], + objectcategory: 'mailGroup', + cn: group.name, + mail: group.name, + mgrpRFC822MailMember: group.members } }; @@ -254,7 +333,11 @@ function start(callback) { gServer.search('ou=groups,dc=cloudron', groupSearch); gServer.bind('ou=users,dc=cloudron', authenticateUser, authorizeUserForApp); + // http://www.ietf.org/proceedings/43/I-D/draft-srivastava-ldap-mail-00.txt gServer.search('ou=mailboxes,dc=cloudron', getMailbox); + gServer.search('ou=mailaliases,dc=cloudron', getMailAlias); + gServer.search('ou=mailgroups,dc=cloudron', getMailGroup); + gServer.bind('ou=mailboxes,dc=cloudron', authenticateUser, authorizeUserForMailbox); // this is the bind for addons (after bind, they might search and authenticate) diff --git a/src/mailboxdb.js b/src/mailboxdb.js index 5572a7321..397c413d9 100644 --- a/src/mailboxdb.js +++ b/src/mailboxdb.js @@ -5,8 +5,14 @@ exports = module.exports = { del: del, upsertByOwner: upsertByOwner, get: get, + getMailboxes: getMailboxes, + getMailbox: getMailbox, + getGroup: getGroup, + getGroups: getGroups, getAliases: getAliases, - setAliases: setAliases, + getAlias: getAlias, + getAliasesOf: getAliasesOf, + setAliasesOf: setAliasesOf, getByOwnerId: getByOwnerId, delByOwnerId: delByOwnerId, @@ -99,6 +105,53 @@ function get(name, callback) { }); } +function getMailbox(name, callback) { + assert.strictEqual(typeof name, 'string'); + assert.strictEqual(typeof callback, 'function'); + + database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND (ownerType = ? OR ownerType = ?)', [ name, exports.TYPE_APP, exports.TYPE_USER ], function (error, results) { + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + if (results.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); + + callback(null, results[0]); + }); +} + +function getMailboxes(callback) { + assert.strictEqual(typeof callback, 'function'); + + database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE ownerType = ? OR ownerType = ?', [ exports.TYPE_APP, exports.TYPE_USER ], function (error, results) { + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + + callback(null, results); + }); +} + +function getGroup(name, callback) { + assert.strictEqual(typeof name, 'string'); + assert.strictEqual(typeof callback, 'function'); + + // FIXME: fix the query to return members + database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE name = ? AND ownerType = ?', [ name, exports.TYPE_GROUP ], function (error, results) { + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + if (results.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); + + callback(null, results[0]); + }); +} + +function getGroups(callback) { + assert.strictEqual(typeof callback, 'function'); + + // FIXME: fix the query to return members + database.query('SELECT ' + MAILBOX_FIELDS + ' FROM mailboxes WHERE ownerType = ?', + [ exports.TYPE_GROUP ], function (error, results) { + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + + callback(null, results); + }); +} + function getByOwnerId(ownerId, callback) { assert.strictEqual(typeof ownerId, 'string'); assert.strictEqual(typeof callback, 'function'); @@ -111,7 +164,7 @@ function getByOwnerId(ownerId, callback) { }); } -function setAliases(name, aliases, ownerId, ownerType, callback) { +function setAliasesOf(name, aliases, ownerId, ownerType, callback) { assert.strictEqual(typeof name, 'string'); assert(util.isArray(aliases)); assert.strictEqual(typeof ownerId, 'string'); @@ -133,7 +186,7 @@ function setAliases(name, aliases, ownerId, ownerType, callback) { }); } -function getAliases(name, callback) { +function getAliasesOf(name, callback) { assert.strictEqual(typeof name, 'string'); assert.strictEqual(typeof callback, 'function'); @@ -144,3 +197,25 @@ function getAliases(name, callback) { callback(null, results); }); } + +function getAliases(callback) { + assert.strictEqual(typeof callback, 'function'); + + database.query('SELECT name FROM mailboxes WHERE aliasTarget != null ORDER BY name', function (error, results) { + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + + callback(null, results); + }); +} + +function getAlias(name, callback) { + assert.strictEqual(typeof name, 'string'); + assert.strictEqual(typeof callback, 'function'); + + database.query('SELECT name FROM mailboxes WHERE name = ? AND aliasTarget != null', [ name ], function (error, results) { + if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error)); + if (results.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND)); + + callback(null, results[0]); + }); +} diff --git a/src/test/database-test.js b/src/test/database-test.js index e78a67606..1cdb72f73 100644 --- a/src/test/database-test.js +++ b/src/test/database-test.js @@ -1205,14 +1205,14 @@ describe('database', function () { }); it('can set alias', function (done) { - mailboxdb.setAliases('support', [ 'support2', 'help' ], 'support', 'user', function (error) { + mailboxdb.setAliasesOf('support', [ 'support2', 'help' ], 'support', 'user', function (error) { expect(error).to.be(null); done(); }); }); it('can get alias', function (done) { - mailboxdb.getAliases('support', function (error, results) { + mailboxdb.getAliasesOf('support', function (error, results) { expect(error).to.be(null); expect(results.length).to.be(2); expect(results[0]).to.be('help'); @@ -1222,7 +1222,7 @@ describe('database', function () { }); it('unset aliases', function (done) { - mailboxdb.setAliases('support', [ ], 'support', 'user', function (error) { + mailboxdb.setAliasesOf('support', [ ], 'support', 'user', function (error) { expect(error).to.be(null); mailboxdb.getAliases('support', function (error, results) { diff --git a/src/user.js b/src/user.js index c3845ad79..fe5862dde 100644 --- a/src/user.js +++ b/src/user.js @@ -582,7 +582,7 @@ function setAliases(userId, aliases, callback) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND)); if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error)); - mailboxdb.setAliases(user.username, aliases, userId, mailboxdb.TYPE_USER, function (error) { + mailboxdb.setAliasesOf(user.username, aliases, userId, mailboxdb.TYPE_USER, function (error) { if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(new UserError(UserError.ALREADY_EXISTS, error.message)); if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND)); if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error)); @@ -601,7 +601,7 @@ function getAliases(userId, callback) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND)); if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error)); - mailboxdb.getAliases(user.username, function (error, aliases) { + mailboxdb.getAliasesOf(user.username, function (error, aliases) { if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new UserError(UserError.NOT_FOUND)); if (error) return callback(new UserError(UserError.INTERNAL_ERROR, error));