2015-07-20 00:09:47 -07:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
exports = module.exports = {
|
2015-09-14 10:59:05 -07:00
|
|
|
start: start,
|
|
|
|
|
stop: stop
|
2015-07-20 00:09:47 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var assert = require('assert'),
|
2017-03-26 19:14:50 -07:00
|
|
|
appdb = require('./appdb.js'),
|
2016-02-18 16:04:53 +01:00
|
|
|
apps = require('./apps.js'),
|
2017-03-13 11:01:11 +01:00
|
|
|
async = require('async'),
|
2015-07-20 00:09:47 -07:00
|
|
|
config = require('./config.js'),
|
2016-09-21 15:34:58 -07:00
|
|
|
DatabaseError = require('./databaseerror.js'),
|
2015-07-20 00:09:47 -07:00
|
|
|
debug = require('debug')('box:ldap'),
|
2016-04-30 23:16:37 -07:00
|
|
|
eventlog = require('./eventlog.js'),
|
2015-07-20 00:09:47 -07:00
|
|
|
user = require('./user.js'),
|
|
|
|
|
UserError = user.UserError,
|
2016-05-29 17:25:23 -07:00
|
|
|
ldap = require('ldapjs'),
|
2016-09-21 15:34:58 -07:00
|
|
|
mailboxdb = require('./mailboxdb.js'),
|
2017-03-10 14:51:12 -08:00
|
|
|
safe = require('safetydance');
|
2015-07-20 00:09:47 -07:00
|
|
|
|
|
|
|
|
var gServer = null;
|
|
|
|
|
|
|
|
|
|
var NOOP = function () {};
|
|
|
|
|
|
2015-08-12 15:31:44 +02:00
|
|
|
var GROUP_USERS_DN = 'cn=users,ou=groups,dc=cloudron';
|
2015-08-18 16:35:52 -07:00
|
|
|
var GROUP_ADMINS_DN = 'cn=admins,ou=groups,dc=cloudron';
|
2015-08-12 15:31:44 +02:00
|
|
|
|
2016-02-18 16:04:53 +01:00
|
|
|
function getAppByRequest(req, callback) {
|
2017-03-13 11:10:08 +01:00
|
|
|
assert.strictEqual(typeof req, 'object');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2016-02-18 16:40:30 +01:00
|
|
|
var sourceIp = req.connection.ldap.id.split(':')[0];
|
2016-02-18 16:04:53 +01:00
|
|
|
if (sourceIp.split('.').length !== 4) return callback(new ldap.InsufficientAccessRightsError('Missing source identifier'));
|
|
|
|
|
|
|
|
|
|
apps.getByIpAddress(sourceIp, function (error, app) {
|
2016-06-17 10:08:41 -05:00
|
|
|
if (error) return callback(new ldap.OperationsError(error.message));
|
|
|
|
|
|
|
|
|
|
if (!app) return callback(new ldap.OperationsError('Could not detect app source'));
|
|
|
|
|
|
|
|
|
|
callback(null, app);
|
2016-02-18 16:04:53 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-13 11:09:12 +01:00
|
|
|
function getUsersWithAccessToApp(req, callback) {
|
2017-03-13 11:10:08 +01:00
|
|
|
assert.strictEqual(typeof req, 'object');
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2017-03-13 11:01:11 +01:00
|
|
|
getAppByRequest(req, function (error, app) {
|
2017-03-13 11:09:12 +01:00
|
|
|
if (error) return callback(error);
|
2017-03-13 11:01:11 +01:00
|
|
|
|
2017-03-13 11:09:12 +01:00
|
|
|
user.list(function (error, result){
|
|
|
|
|
if (error) return callback(new ldap.OperationsError(error.toString()));
|
2017-03-13 11:01:11 +01:00
|
|
|
|
|
|
|
|
async.filter(result, apps.hasAccessTo.bind(null, app), function (error, result) {
|
2017-03-13 11:09:12 +01:00
|
|
|
if (error) return callback(new ldap.OperationsError(error.toString()));
|
|
|
|
|
|
|
|
|
|
callback(null, result);
|
2017-03-13 11:01:11 +01:00
|
|
|
});
|
2015-07-20 00:09:47 -07:00
|
|
|
});
|
|
|
|
|
});
|
2016-05-12 13:36:53 -07:00
|
|
|
}
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2017-10-27 01:25:07 +02:00
|
|
|
// helper function to deal with pagination
|
|
|
|
|
function finalSend(results, req, res, next) {
|
|
|
|
|
var min = 0;
|
|
|
|
|
var max = results.length;
|
|
|
|
|
var cookie = null;
|
|
|
|
|
var pageSize = 0;
|
|
|
|
|
|
|
|
|
|
// check if this is a paging request, if so get the cookie for session info
|
|
|
|
|
req.controls.forEach(function (control) {
|
|
|
|
|
if (control.type === ldap.PagedResultsControl.OID) {
|
|
|
|
|
pageSize = control.value.size;
|
|
|
|
|
cookie = control.value.cookie;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function sendPagedResults(start, end) {
|
|
|
|
|
start = (start < min) ? min : start;
|
|
|
|
|
end = (end > max || end < min) ? max : end;
|
|
|
|
|
var i;
|
|
|
|
|
|
|
|
|
|
for (i = start; i < end; i++) {
|
|
|
|
|
res.send(results[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cookie && Buffer.isBuffer(cookie)) {
|
|
|
|
|
// we have pagination
|
|
|
|
|
var first = min;
|
|
|
|
|
if (cookie.length !== 0) {
|
|
|
|
|
first = parseInt(cookie.toString(), 10);
|
|
|
|
|
}
|
|
|
|
|
var last = sendPagedResults(first, first + pageSize);
|
|
|
|
|
|
|
|
|
|
var resultCookie;
|
|
|
|
|
if (last < max) {
|
|
|
|
|
resultCookie = new Buffer(last.toString());
|
|
|
|
|
} else {
|
|
|
|
|
resultCookie = new Buffer('');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.controls.push(new ldap.PagedResultsControl({
|
|
|
|
|
value: {
|
|
|
|
|
size: pageSize, // correctness not required here
|
|
|
|
|
cookie: resultCookie
|
|
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
} else {
|
|
|
|
|
// no pagination simply send all
|
|
|
|
|
results.forEach(function (result) {
|
|
|
|
|
res.send(result);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// all done
|
|
|
|
|
res.end();
|
|
|
|
|
next();
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-13 11:09:12 +01:00
|
|
|
function userSearch(req, res, next) {
|
|
|
|
|
debug('user search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
|
|
|
|
|
|
|
|
|
getUsersWithAccessToApp(req, function (error, result) {
|
|
|
|
|
if (error) return next(error);
|
|
|
|
|
|
2017-10-27 01:25:07 +02:00
|
|
|
var results = [];
|
|
|
|
|
|
2017-03-13 11:09:12 +01:00
|
|
|
// send user objects
|
|
|
|
|
result.forEach(function (entry) {
|
|
|
|
|
// skip entries with empty username. Some apps like owncloud can't deal with this
|
|
|
|
|
if (!entry.username) return;
|
|
|
|
|
|
|
|
|
|
var dn = ldap.parseDN('cn=' + entry.id + ',ou=users,dc=cloudron');
|
|
|
|
|
|
|
|
|
|
var groups = [ GROUP_USERS_DN ];
|
|
|
|
|
if (entry.admin) groups.push(GROUP_ADMINS_DN);
|
|
|
|
|
|
|
|
|
|
var displayName = entry.displayName || entry.username || ''; // displayName can be empty and username can be null
|
|
|
|
|
var nameParts = displayName.split(' ');
|
|
|
|
|
var firstName = nameParts[0];
|
|
|
|
|
var lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : ''; // choose last part, if it exists
|
|
|
|
|
|
|
|
|
|
var obj = {
|
|
|
|
|
dn: dn.toString(),
|
|
|
|
|
attributes: {
|
|
|
|
|
objectclass: ['user'],
|
|
|
|
|
objectcategory: 'person',
|
|
|
|
|
cn: entry.id,
|
|
|
|
|
uid: entry.id,
|
|
|
|
|
mail: entry.email,
|
2018-01-21 14:50:24 +01:00
|
|
|
mailAlternateAddress: entry.fallbackEmail,
|
2017-03-13 11:09:12 +01:00
|
|
|
displayname: displayName,
|
|
|
|
|
givenName: firstName,
|
|
|
|
|
username: entry.username,
|
|
|
|
|
samaccountname: entry.username, // to support ActiveDirectory clients
|
|
|
|
|
isadmin: entry.admin ? 1 : 0,
|
|
|
|
|
memberof: groups
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// http://www.zytrax.com/books/ldap/ape/core-schema.html#sn has 'name' as SUP which is a DirectoryString
|
|
|
|
|
// which is required to have atleast one character if present
|
|
|
|
|
if (lastName.length !== 0) obj.attributes.sn = lastName;
|
|
|
|
|
|
|
|
|
|
// 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)) {
|
2017-10-27 01:25:07 +02:00
|
|
|
results.push(obj);
|
2017-03-13 11:09:12 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2017-10-27 01:25:07 +02:00
|
|
|
finalSend(results, req, res, next);
|
2017-03-13 11:09:12 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-12 13:36:53 -07:00
|
|
|
function groupSearch(req, res, next) {
|
2016-05-16 14:14:58 -07:00
|
|
|
debug('group search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2017-03-13 11:09:12 +01:00
|
|
|
getUsersWithAccessToApp(req, function (error, result) {
|
2017-03-13 11:06:27 +01:00
|
|
|
if (error) return next(error);
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2017-10-27 01:25:07 +02:00
|
|
|
var results = [];
|
|
|
|
|
|
2017-03-13 11:09:12 +01:00
|
|
|
var groups = [{
|
|
|
|
|
name: 'users',
|
|
|
|
|
admin: false
|
|
|
|
|
}, {
|
|
|
|
|
name: 'admins',
|
|
|
|
|
admin: true
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
groups.forEach(function (group) {
|
|
|
|
|
var dn = ldap.parseDN('cn=' + group.name + ',ou=groups,dc=cloudron');
|
|
|
|
|
var members = group.admin ? result.filter(function (entry) { return entry.admin; }) : result;
|
|
|
|
|
|
|
|
|
|
var obj = {
|
|
|
|
|
dn: dn.toString(),
|
|
|
|
|
attributes: {
|
|
|
|
|
objectclass: ['group'],
|
|
|
|
|
cn: group.name,
|
|
|
|
|
memberuid: members.map(function(entry) { return entry.id; })
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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)) {
|
2017-10-27 01:25:07 +02:00
|
|
|
results.push(obj);
|
2017-03-13 11:09:12 +01:00
|
|
|
}
|
2017-03-13 11:06:27 +01:00
|
|
|
});
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2017-10-27 01:25:07 +02:00
|
|
|
finalSend(results, req, res, next);
|
2015-07-20 00:09:47 -07:00
|
|
|
});
|
2016-05-12 13:36:53 -07:00
|
|
|
}
|
|
|
|
|
|
2017-10-24 01:35:35 +02:00
|
|
|
function groupUsersCompare(req, res, next) {
|
|
|
|
|
debug('group users compare: dn %s, attribute %s, value %s (from %s)', req.dn.toString(), req.attribute, req.value, req.connection.ldap.id);
|
|
|
|
|
|
|
|
|
|
getUsersWithAccessToApp(req, function (error, result) {
|
|
|
|
|
if (error) return next(error);
|
|
|
|
|
|
|
|
|
|
// we only support memberuid here, if we add new group attributes later add them here
|
|
|
|
|
if (req.attribute === 'memberuid') {
|
|
|
|
|
var found = result.find(function (u) { return u.id === req.value; });
|
|
|
|
|
if (found) return res.end(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.end(false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function groupAdminsCompare(req, res, next) {
|
|
|
|
|
debug('group admins compare: dn %s, attribute %s, value %s (from %s)', req.dn.toString(), req.attribute, req.value, req.connection.ldap.id);
|
|
|
|
|
|
|
|
|
|
getUsersWithAccessToApp(req, function (error, result) {
|
|
|
|
|
if (error) return next(error);
|
|
|
|
|
|
|
|
|
|
// we only support memberuid here, if we add new group attributes later add them here
|
|
|
|
|
if (req.attribute === 'memberuid') {
|
|
|
|
|
var found = result.find(function (u) { return u.id === req.value; });
|
|
|
|
|
if (found && found.admin) return res.end(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.end(false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-26 10:18:58 -07:00
|
|
|
function mailboxSearch(req, res, next) {
|
|
|
|
|
debug('mailbox search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
2016-05-29 18:24:54 -07:00
|
|
|
|
2016-09-27 11:45:49 -07:00
|
|
|
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2017-10-27 01:25:07 +02:00
|
|
|
|
2018-01-18 18:14:31 -08:00
|
|
|
var email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
|
|
|
|
var parts = email.split('@');
|
|
|
|
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2016-09-26 21:03:07 -07:00
|
|
|
|
2018-01-18 18:14:31 -08:00
|
|
|
mailboxdb.getMailbox(parts[0], parts[1], function (error, mailbox) {
|
2016-09-26 10:18:58 -07:00
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2016-05-29 18:24:54 -07:00
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
|
|
|
|
|
2016-09-27 11:45:49 -07:00
|
|
|
var obj = {
|
2016-09-27 11:58:02 -07:00
|
|
|
dn: req.dn.toString(),
|
2016-09-27 11:45:49 -07:00
|
|
|
attributes: {
|
|
|
|
|
objectclass: ['mailbox'],
|
|
|
|
|
objectcategory: 'mailbox',
|
2018-01-19 12:13:09 -08:00
|
|
|
cn: `${mailbox.name}@${mailbox.domain}`,
|
|
|
|
|
uid: `${mailbox.name}@${mailbox.domain}`,
|
|
|
|
|
mail: `${mailbox.name}@${mailbox.domain}`,
|
2017-01-13 19:43:01 -08:00
|
|
|
ownerType: mailbox.ownerType
|
2016-09-27 11:45:49 -07:00
|
|
|
}
|
|
|
|
|
};
|
2016-09-25 18:59:11 -07:00
|
|
|
|
2016-09-27 11:45:49 -07:00
|
|
|
// 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()));
|
2016-09-25 18:59:11 -07:00
|
|
|
|
2017-10-27 01:25:07 +02:00
|
|
|
if (lowerCaseFilter.matches(obj.attributes)) {
|
|
|
|
|
finalSend([ obj ], req, res, next);
|
|
|
|
|
} else {
|
|
|
|
|
res.end();
|
|
|
|
|
}
|
2016-09-25 18:59:11 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-26 14:38:23 -07:00
|
|
|
function mailAliasSearch(req, res, next) {
|
2016-09-25 18:59:11 -07:00
|
|
|
debug('mail alias get: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
|
|
|
|
|
2016-09-27 11:45:49 -07:00
|
|
|
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2017-10-27 01:25:07 +02:00
|
|
|
|
2018-01-18 18:14:31 -08:00
|
|
|
var email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
|
|
|
|
var parts = email.split('@');
|
|
|
|
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
|
|
|
|
mailboxdb.getAlias(parts[0], parts[1], function (error, alias) {
|
2016-09-26 14:38:23 -07:00
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2016-09-25 18:59:11 -07:00
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
|
|
|
|
|
2016-09-27 11:45:49 -07:00
|
|
|
// https://wiki.debian.org/LDAP/MigrationTools/Examples
|
|
|
|
|
// https://docs.oracle.com/cd/E19455-01/806-5580/6jej518pp/index.html
|
2018-01-19 12:10:24 -08:00
|
|
|
// member is fully qualified - https://docs.oracle.com/cd/E19957-01/816-6082-10/chap4.doc.html#43314
|
2016-09-27 11:45:49 -07:00
|
|
|
var obj = {
|
2016-09-27 11:58:02 -07:00
|
|
|
dn: req.dn.toString(),
|
2016-09-27 11:45:49 -07:00
|
|
|
attributes: {
|
|
|
|
|
objectclass: ['nisMailAlias'],
|
|
|
|
|
objectcategory: 'nisMailAlias',
|
2018-01-19 12:10:24 -08:00
|
|
|
cn: `${alias.name}@${alias.domain}`,
|
|
|
|
|
rfc822MailMember: `${alias.aliasTarget}@${alias.domain}`
|
2016-09-27 11:45:49 -07:00
|
|
|
}
|
|
|
|
|
};
|
2016-09-25 18:59:11 -07:00
|
|
|
|
2016-09-27 11:45:49 -07:00
|
|
|
// 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()));
|
2016-09-25 18:59:11 -07:00
|
|
|
|
2017-10-27 01:25:07 +02:00
|
|
|
if (lowerCaseFilter.matches(obj.attributes)) {
|
|
|
|
|
finalSend([ obj ], req, res, next);
|
|
|
|
|
} else {
|
|
|
|
|
res.end();
|
|
|
|
|
}
|
2016-09-25 18:59:11 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-27 12:20:20 -07:00
|
|
|
function mailingListSearch(req, res, next) {
|
|
|
|
|
debug('mailing list get: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
2016-09-25 18:59:11 -07:00
|
|
|
|
2016-09-27 12:20:20 -07:00
|
|
|
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2017-10-27 01:25:07 +02:00
|
|
|
|
2018-01-18 18:14:31 -08:00
|
|
|
var email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
|
|
|
|
var parts = email.split('@');
|
|
|
|
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
|
|
|
|
mailboxdb.getGroup(parts[0], parts[1], function (error, group) {
|
2016-09-27 12:20:20 -07:00
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2016-09-25 18:59:11 -07:00
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
|
|
|
|
|
2016-09-27 16:27:22 -07:00
|
|
|
// http://ldapwiki.willeke.com/wiki/Original%20Mailgroup%20Schema%20From%20Netscape
|
2018-01-19 11:35:02 -08:00
|
|
|
// members are fully qualified (https://docs.oracle.com/cd/E19444-01/816-6018-10/groups.htm#13356)
|
2016-09-27 12:20:20 -07:00
|
|
|
var obj = {
|
|
|
|
|
dn: req.dn.toString(),
|
|
|
|
|
attributes: {
|
|
|
|
|
objectclass: ['mailGroup'],
|
|
|
|
|
objectcategory: 'mailGroup',
|
2018-01-19 11:35:02 -08:00
|
|
|
cn: `${group.name}@${group.domain}`, // fully qualified
|
|
|
|
|
mail: `${group.name}@${group.domain}`,
|
|
|
|
|
mgrpRFC822MailMember: group.members.map(function (m) { return `${m}@${group.domain}`; })
|
2016-09-27 12:20:20 -07:00
|
|
|
}
|
|
|
|
|
};
|
2016-05-29 18:24:54 -07:00
|
|
|
|
2016-09-27 12:20:20 -07:00
|
|
|
// 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()));
|
2016-05-29 18:24:54 -07:00
|
|
|
|
2017-10-27 01:25:07 +02:00
|
|
|
if (lowerCaseFilter.matches(obj.attributes)) {
|
|
|
|
|
finalSend([ obj ], req, res, next);
|
|
|
|
|
} else {
|
|
|
|
|
res.end();
|
|
|
|
|
}
|
2016-05-29 18:24:54 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-29 17:16:52 -07:00
|
|
|
function authenticateUser(req, res, next) {
|
2016-05-16 14:14:58 -07:00
|
|
|
debug('user bind: %s (from %s)', req.dn.toString(), req.connection.ldap.id);
|
2016-05-12 13:36:53 -07:00
|
|
|
|
|
|
|
|
// extract the common name which might have different attribute names
|
2016-09-26 09:08:04 -07:00
|
|
|
var attributeName = Object.keys(req.dn.rdns[0].attrs)[0];
|
|
|
|
|
var commonName = req.dn.rdns[0].attrs[attributeName].value;
|
2016-05-12 13:36:53 -07:00
|
|
|
if (!commonName) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
|
|
|
|
var api;
|
2016-05-16 12:21:15 -07:00
|
|
|
if (attributeName === 'mail') {
|
2016-05-12 13:36:53 -07:00
|
|
|
api = user.verifyWithEmail;
|
2016-05-16 12:21:15 -07:00
|
|
|
} else if (commonName.indexOf('@') !== -1) { // if mail is specified, enforce mail check
|
2016-09-27 16:21:58 +02:00
|
|
|
api = user.verifyWithEmail;
|
2016-05-12 13:36:53 -07:00
|
|
|
} else if (commonName.indexOf('uid-') === 0) {
|
|
|
|
|
api = user.verify;
|
|
|
|
|
} else {
|
|
|
|
|
api = user.verifyWithUsername;
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-29 17:16:52 -07:00
|
|
|
api(commonName, req.credentials || '', function (error, user) {
|
2016-05-12 13:36:53 -07:00
|
|
|
if (error && error.reason === UserError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
if (error && error.reason === UserError.WRONG_PASSWORD) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
2016-06-17 10:08:41 -05:00
|
|
|
if (error) return next(new ldap.OperationsError(error.message));
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2016-05-29 17:16:52 -07:00
|
|
|
req.user = user;
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2016-05-29 17:16:52 -07:00
|
|
|
next();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function authorizeUserForApp(req, res, next) {
|
|
|
|
|
assert(req.user);
|
|
|
|
|
|
|
|
|
|
getAppByRequest(req, function (error, app) {
|
|
|
|
|
if (error) return next(error);
|
|
|
|
|
|
|
|
|
|
apps.hasAccessTo(app, req.user, function (error, result) {
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2016-05-29 17:16:52 -07:00
|
|
|
// we return no such object, to avoid leakage of a users existence
|
|
|
|
|
if (!result) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2016-05-29 17:16:52 -07:00
|
|
|
eventlog.add(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', appId: app.id }, { userId: req.user.id });
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2016-05-29 17:16:52 -07:00
|
|
|
res.end();
|
2016-05-12 13:36:53 -07:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-26 11:50:32 -07:00
|
|
|
function authenticateMailbox(req, res, next) {
|
|
|
|
|
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
2018-01-18 18:14:31 -08:00
|
|
|
var email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
|
|
|
|
var parts = email.split('@');
|
|
|
|
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2016-09-26 11:50:32 -07:00
|
|
|
|
2018-01-18 18:14:31 -08:00
|
|
|
mailboxdb.getMailbox(parts[0], parts[1], function (error, mailbox) {
|
2016-09-26 11:50:32 -07:00
|
|
|
if (error && error.reason === DatabaseError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.message));
|
|
|
|
|
|
|
|
|
|
if (mailbox.ownerType === mailboxdb.TYPE_APP) {
|
2017-03-26 19:14:50 -07:00
|
|
|
var addonId = req.dn.rdns[1].attrs.ou.value.toLowerCase(); // 'sendmail' or 'recvmail'
|
|
|
|
|
var name;
|
|
|
|
|
if (addonId === 'sendmail') name = 'MAIL_SMTP_PASSWORD';
|
|
|
|
|
else if (addonId === 'recvmail') name = 'MAIL_IMAP_PASSWORD';
|
|
|
|
|
else return next(new ldap.OperationsError('Invalid DN'));
|
2016-09-26 11:50:32 -07:00
|
|
|
|
2017-03-26 19:14:50 -07:00
|
|
|
appdb.getAddonConfigByName(mailbox.ownerId, addonId, name, function (error, value) {
|
2017-03-26 20:42:46 -07:00
|
|
|
if (error) return next(new ldap.OperationsError(error.message));
|
|
|
|
|
if (req.credentials !== value) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
2016-05-29 17:25:23 -07:00
|
|
|
|
2017-03-26 19:14:50 -07:00
|
|
|
eventlog.add(eventlog.ACTION_APP_LOGIN, { authType: 'ldap', mailboxId: name }, { appId: mailbox.ownerId, addonId: addonId });
|
|
|
|
|
return res.end();
|
|
|
|
|
});
|
|
|
|
|
} else if (mailbox.ownerType === mailboxdb.TYPE_USER) {
|
2018-01-22 20:05:09 +01:00
|
|
|
user.verifyWithUsername(parts[0], req.credentials || '', function (error, user) {
|
2018-01-18 18:14:31 -08:00
|
|
|
if (error && error.reason === UserError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
if (error && error.reason === UserError.WRONG_PASSWORD) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.message));
|
|
|
|
|
|
|
|
|
|
eventlog.add(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: user.username });
|
2017-03-26 19:14:50 -07:00
|
|
|
res.end();
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
return next(new ldap.OperationsError('Unknown ownerType for mailbox'));
|
|
|
|
|
}
|
2016-09-26 11:50:32 -07:00
|
|
|
});
|
2016-05-29 17:25:23 -07:00
|
|
|
}
|
|
|
|
|
|
2016-05-12 13:36:53 -07:00
|
|
|
function start(callback) {
|
|
|
|
|
assert.strictEqual(typeof callback, 'function');
|
|
|
|
|
|
2016-09-25 16:11:54 -07:00
|
|
|
var logger = {
|
|
|
|
|
trace: NOOP,
|
|
|
|
|
debug: NOOP,
|
|
|
|
|
info: debug,
|
|
|
|
|
warn: debug,
|
|
|
|
|
error: console.error,
|
|
|
|
|
fatal: console.error
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
gServer = ldap.createServer({ log: logger });
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2017-03-22 19:01:13 +01:00
|
|
|
gServer.search('ou=users,dc=cloudron', userSearch);
|
|
|
|
|
gServer.search('ou=groups,dc=cloudron', groupSearch);
|
|
|
|
|
gServer.bind('ou=users,dc=cloudron', authenticateUser, authorizeUserForApp);
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2016-09-25 18:59:11 -07:00
|
|
|
// http://www.ietf.org/proceedings/43/I-D/draft-srivastava-ldap-mail-00.txt
|
2017-03-22 19:01:13 +01:00
|
|
|
gServer.search('ou=mailboxes,dc=cloudron', mailboxSearch);
|
|
|
|
|
gServer.search('ou=mailaliases,dc=cloudron', mailAliasSearch);
|
|
|
|
|
gServer.search('ou=mailinglists,dc=cloudron', mailingListSearch);
|
2016-09-25 18:59:11 -07:00
|
|
|
|
2017-03-26 19:14:50 -07:00
|
|
|
gServer.bind('ou=recvmail,dc=cloudron', authenticateMailbox);
|
|
|
|
|
gServer.bind('ou=sendmail,dc=cloudron', authenticateMailbox);
|
2016-05-29 17:25:23 -07:00
|
|
|
|
2017-10-24 01:35:35 +02:00
|
|
|
gServer.compare('cn=users,ou=groups,dc=cloudron', groupUsersCompare);
|
|
|
|
|
gServer.compare('cn=admins,ou=groups,dc=cloudron', groupAdminsCompare);
|
|
|
|
|
|
2016-05-12 13:20:57 -07:00
|
|
|
// this is the bind for addons (after bind, they might search and authenticate)
|
2017-11-15 18:07:10 -08:00
|
|
|
gServer.bind('ou=addons,dc=cloudron', function(req, res /*, next */) {
|
2016-05-12 13:20:57 -07:00
|
|
|
debug('addons bind: %s', req.dn.toString()); // note: cn can be email or id
|
2016-05-11 14:26:34 -07:00
|
|
|
res.end();
|
|
|
|
|
});
|
|
|
|
|
|
2016-05-12 13:20:57 -07:00
|
|
|
// this is the bind for apps (after bind, they might search and authenticate user)
|
2017-11-15 18:07:10 -08:00
|
|
|
gServer.bind('ou=apps,dc=cloudron', function(req, res /*, next */) {
|
2015-09-25 21:17:48 -07:00
|
|
|
// TODO: validate password
|
2016-03-04 17:50:48 -08:00
|
|
|
debug('application bind: %s', req.dn.toString());
|
2015-09-25 21:17:48 -07:00
|
|
|
res.end();
|
|
|
|
|
});
|
|
|
|
|
|
2016-06-14 20:46:29 -07:00
|
|
|
gServer.listen(config.get('ldapPort'), '0.0.0.0', callback);
|
2015-07-20 00:09:47 -07:00
|
|
|
}
|
2015-09-14 10:59:05 -07:00
|
|
|
|
|
|
|
|
function stop(callback) {
|
2015-09-14 11:09:37 -07:00
|
|
|
assert.strictEqual(typeof callback, 'function');
|
2015-09-14 10:59:05 -07:00
|
|
|
|
2016-09-15 11:53:28 -07:00
|
|
|
if (gServer) gServer.close();
|
2015-09-14 10:59:05 -07:00
|
|
|
|
|
|
|
|
callback();
|
|
|
|
|
}
|