2015-07-20 00:09:47 -07:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
exports = module.exports = {
|
2021-01-21 11:31:35 -08:00
|
|
|
start,
|
2021-07-07 12:59:17 -07:00
|
|
|
stop,
|
|
|
|
|
|
|
|
|
|
_MOCK_APP: null
|
2015-07-20 00:09:47 -07:00
|
|
|
};
|
|
|
|
|
|
2021-08-19 21:39:27 -07:00
|
|
|
const addonConfigs = require('./addonconfigs.js'),
|
|
|
|
|
assert = require('assert'),
|
2016-02-18 16:04:53 +01:00
|
|
|
apps = require('./apps.js'),
|
2019-10-24 11:13:48 -07:00
|
|
|
BoxError = require('./boxerror.js'),
|
2019-10-24 13:41:41 -07:00
|
|
|
constants = require('./constants.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'),
|
2020-11-12 23:25:33 -08:00
|
|
|
groups = require('./groups.js'),
|
2016-05-29 17:25:23 -07:00
|
|
|
ldap = require('ldapjs'),
|
2018-01-22 20:35:08 +01:00
|
|
|
mail = require('./mail.js'),
|
2019-03-22 15:42:16 -07:00
|
|
|
safe = require('safetydance'),
|
2021-09-07 09:57:49 -07:00
|
|
|
users = require('./users.js'),
|
|
|
|
|
util = require('util');
|
2015-07-20 00:09:47 -07:00
|
|
|
|
|
|
|
|
var gServer = null;
|
|
|
|
|
|
2021-07-07 12:59:17 -07:00
|
|
|
const NOOP = function () {};
|
2015-07-20 00:09:47 -07:00
|
|
|
|
2021-07-07 12:59:17 -07:00
|
|
|
const GROUP_USERS_DN = 'cn=users,ou=groups,dc=cloudron';
|
|
|
|
|
const GROUP_ADMINS_DN = 'cn=admins,ou=groups,dc=cloudron';
|
2015-08-12 15:31:44 +02:00
|
|
|
|
2018-09-03 15:38:50 +02:00
|
|
|
// Will attach req.app if successful
|
2021-08-20 09:19:44 -07:00
|
|
|
async function authenticateApp(req, res, next) {
|
2021-07-07 12:59:17 -07:00
|
|
|
const sourceIp = req.connection.ldap.id.split(':')[0];
|
2018-09-03 15:38:50 +02:00
|
|
|
if (sourceIp.split('.').length !== 4) return next(new ldap.InsufficientAccessRightsError('Missing source identifier'));
|
2016-02-18 16:04:53 +01:00
|
|
|
|
2021-07-07 12:59:17 -07:00
|
|
|
// this is only used by the ldap test. the apps tests still uses proper docker
|
|
|
|
|
if (constants.TEST && sourceIp === '127.0.0.1') {
|
|
|
|
|
req.app = exports._MOCK_APP;
|
|
|
|
|
return next();
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const [error, app] = await safe(apps.getByIpAddress(sourceIp));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.message));
|
|
|
|
|
if (!app) return next(new ldap.OperationsError('Could not detect app source'));
|
2016-06-17 10:08:41 -05:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
req.app = app;
|
2016-06-17 10:08:41 -05:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
next();
|
2016-02-18 16:04:53 +01:00
|
|
|
}
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
async function getUsersWithAccessToApp(req) {
|
2018-09-03 15:38:50 +02:00
|
|
|
assert.strictEqual(typeof req.app, 'object');
|
2021-07-15 09:50:11 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const result = await users.list();
|
2021-09-21 10:00:47 -07:00
|
|
|
const allowedUsers = result.filter((user) => apps.canAccess(req.app, user));
|
2021-08-20 09:19:44 -07:00
|
|
|
return allowedUsers;
|
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) {
|
2021-08-20 09:19:44 -07:00
|
|
|
let min = 0;
|
|
|
|
|
let max = results.length;
|
|
|
|
|
let cookie = null;
|
|
|
|
|
let pageSize = 0;
|
2017-10-27 01:25:07 +02:00
|
|
|
|
|
|
|
|
// 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;
|
2021-08-20 09:19:44 -07:00
|
|
|
let i;
|
2017-10-27 01:25:07 +02:00
|
|
|
|
|
|
|
|
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) {
|
2019-03-21 20:06:14 -07:00
|
|
|
resultCookie = Buffer.from(last.toString());
|
2017-10-27 01:25:07 +02:00
|
|
|
} else {
|
2019-03-21 20:06:14 -07:00
|
|
|
resultCookie = Buffer.from('');
|
2017-10-27 01:25:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
async function userSearch(req, res, next) {
|
2017-03-13 11:09:12 +01:00
|
|
|
debug('user search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const [error, result] = await safe(getUsersWithAccessToApp(req));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
2017-10-27 01:25:07 +02:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
let results = [];
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
// send user objects
|
|
|
|
|
result.forEach(function (user) {
|
|
|
|
|
// skip entries with empty username. Some apps like owncloud can't deal with this
|
|
|
|
|
if (!user.username) return;
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const dn = ldap.parseDN('cn=' + user.id + ',ou=users,dc=cloudron');
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const memberof = [ GROUP_USERS_DN ];
|
|
|
|
|
if (users.compareRoles(user.role, users.ROLE_ADMIN) >= 0) memberof.push(GROUP_ADMINS_DN);
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const displayName = user.displayName || user.username || ''; // displayName can be empty and username can be null
|
|
|
|
|
const nameParts = displayName.split(' ');
|
|
|
|
|
const firstName = nameParts[0];
|
|
|
|
|
const lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : ''; // choose last part, if it exists
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const obj = {
|
|
|
|
|
dn: dn.toString(),
|
|
|
|
|
attributes: {
|
|
|
|
|
objectclass: ['user', 'inetorgperson', 'person' ],
|
|
|
|
|
objectcategory: 'person',
|
|
|
|
|
cn: user.id,
|
|
|
|
|
uid: user.id,
|
|
|
|
|
entryuuid: user.id, // to support OpenLDAP clients
|
|
|
|
|
mail: user.email,
|
|
|
|
|
mailAlternateAddress: user.fallbackEmail,
|
|
|
|
|
displayname: displayName,
|
|
|
|
|
givenName: firstName,
|
|
|
|
|
username: user.username,
|
|
|
|
|
samaccountname: user.username, // to support ActiveDirectory clients
|
|
|
|
|
memberof: memberof
|
|
|
|
|
}
|
|
|
|
|
};
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
// 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;
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
// ensure all filter values are also lowercase
|
|
|
|
|
const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
|
|
|
|
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
|
|
|
|
|
results.push(obj);
|
|
|
|
|
}
|
2017-03-13 11:09:12 +01:00
|
|
|
});
|
2021-08-20 09:19:44 -07:00
|
|
|
|
|
|
|
|
finalSend(results, req, res, next);
|
2017-03-13 11:09:12 +01:00
|
|
|
}
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
async 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
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const [error, result] = await safe(getUsersWithAccessToApp(req));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const results = [];
|
2017-10-27 01:25:07 +02:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const groups = [{
|
|
|
|
|
name: 'users',
|
|
|
|
|
admin: false
|
|
|
|
|
}, {
|
|
|
|
|
name: 'admins',
|
|
|
|
|
admin: true
|
|
|
|
|
}];
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
groups.forEach(function (group) {
|
|
|
|
|
const dn = ldap.parseDN('cn=' + group.name + ',ou=groups,dc=cloudron');
|
|
|
|
|
const members = group.admin ? result.filter(function (user) { return users.compareRoles(user.role, users.ROLE_ADMIN) >= 0; }) : result;
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const obj = {
|
|
|
|
|
dn: dn.toString(),
|
|
|
|
|
attributes: {
|
|
|
|
|
objectclass: ['group'],
|
|
|
|
|
cn: group.name,
|
|
|
|
|
memberuid: members.map(function(entry) { return entry.id; })
|
2017-03-13 11:09:12 +01:00
|
|
|
}
|
2021-08-20 09:19:44 -07:00
|
|
|
};
|
2017-03-13 11:09:12 +01:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
// ensure all filter values are also lowercase
|
|
|
|
|
const 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)) {
|
|
|
|
|
results.push(obj);
|
|
|
|
|
}
|
2015-07-20 00:09:47 -07:00
|
|
|
});
|
2021-08-20 09:19:44 -07:00
|
|
|
|
|
|
|
|
finalSend(results, req, res, next);
|
2016-05-12 13:36:53 -07:00
|
|
|
}
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
async function groupUsersCompare(req, res, next) {
|
2017-10-24 01:35:35 +02:00
|
|
|
debug('group users compare: dn %s, attribute %s, value %s (from %s)', req.dn.toString(), req.attribute, req.value, req.connection.ldap.id);
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const [error, result] = await safe(getUsersWithAccessToApp(req));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
2017-10-24 01:35:35 +02:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
// we only support memberuid here, if we add new group attributes later add them here
|
|
|
|
|
if (req.attribute === 'memberuid') {
|
|
|
|
|
const found = result.find(function (u) { return u.id === req.value; });
|
|
|
|
|
if (found) return res.end(true);
|
|
|
|
|
}
|
2017-10-24 01:35:35 +02:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
res.end(false);
|
2017-10-24 01:35:35 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
async function groupAdminsCompare(req, res, next) {
|
2017-10-24 01:35:35 +02:00
|
|
|
debug('group admins compare: dn %s, attribute %s, value %s (from %s)', req.dn.toString(), req.attribute, req.value, req.connection.ldap.id);
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const [error, result] = await safe(getUsersWithAccessToApp(req));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
2017-10-24 01:35:35 +02:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
// we only support memberuid here, if we add new group attributes later add them here
|
|
|
|
|
if (req.attribute === 'memberuid') {
|
|
|
|
|
var user = result.find(function (u) { return u.id === req.value; });
|
|
|
|
|
if (user && users.compareRoles(user.role, users.ROLE_ADMIN) >= 0) return res.end(true);
|
|
|
|
|
}
|
2017-10-24 01:35:35 +02:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
res.end(false);
|
2017-10-24 01:35:35 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
async function mailboxSearch(req, res, next) {
|
2016-09-26 10:18:58 -07:00
|
|
|
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
|
|
|
|
2018-05-03 18:05:32 +02:00
|
|
|
// if cn is set we only search for one mailbox specifically
|
|
|
|
|
if (req.dn.rdns[0].attrs.cn) {
|
2021-08-17 15:45:57 -07:00
|
|
|
const email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
|
|
|
|
const parts = email.split('@');
|
2018-05-03 18:05:32 +02:00
|
|
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2017-10-27 01:25:07 +02:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const [error, mailbox] = await safe(mail.getMailbox(parts[0], parts[1]));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
|
|
|
|
if (!mailbox) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
|
|
|
|
const obj = {
|
|
|
|
|
dn: req.dn.toString(),
|
|
|
|
|
attributes: {
|
|
|
|
|
objectclass: ['mailbox'],
|
|
|
|
|
objectcategory: 'mailbox',
|
|
|
|
|
cn: `${mailbox.name}@${mailbox.domain}`,
|
|
|
|
|
uid: `${mailbox.name}@${mailbox.domain}`,
|
|
|
|
|
mail: `${mailbox.name}@${mailbox.domain}`
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ensure all filter values are also lowercase
|
|
|
|
|
const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
|
|
|
|
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
|
|
|
|
|
|
|
|
|
if (lowerCaseFilter.matches(obj.attributes)) {
|
|
|
|
|
finalSend([ obj ], req, res, next);
|
|
|
|
|
} else {
|
|
|
|
|
res.end();
|
|
|
|
|
}
|
2020-03-06 13:05:31 -08:00
|
|
|
} else { // new sogo
|
2021-08-17 15:45:57 -07:00
|
|
|
let [error, mailboxes] = await safe(mail.listAllMailboxes(1, 1000));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
2018-05-03 18:05:32 +02:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
mailboxes = mailboxes.filter(m => m.active);
|
2021-04-14 22:37:01 -07:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
let results = [];
|
2018-05-03 18:05:32 +02:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
for (const mailbox of mailboxes) {
|
|
|
|
|
const dn = ldap.parseDN(`cn=${mailbox.name}@${mailbox.domain},ou=mailboxes,dc=cloudron`);
|
2021-07-15 09:50:11 -07:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const [error, ownerObject] = await safe(mailbox.ownerType === mail.OWNERTYPE_USER ? users.get(mailbox.ownerId) : groups.get(mailbox.ownerId));
|
|
|
|
|
if (error || !ownerObject) continue; // skip mailboxes with unknown user
|
2021-07-15 09:50:11 -07:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const obj = {
|
|
|
|
|
dn: dn.toString(),
|
|
|
|
|
attributes: {
|
|
|
|
|
objectclass: ['mailbox'],
|
|
|
|
|
objectcategory: 'mailbox',
|
|
|
|
|
displayname: mailbox.ownerType === mail.OWNERTYPE_USER ? ownerObject.displayName : ownerObject.name,
|
|
|
|
|
cn: `${mailbox.name}@${mailbox.domain}`,
|
|
|
|
|
uid: `${mailbox.name}@${mailbox.domain}`,
|
|
|
|
|
mail: `${mailbox.name}@${mailbox.domain}`
|
|
|
|
|
}
|
|
|
|
|
};
|
2020-03-05 22:40:25 -08:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
mailbox.aliases.forEach(function (a, idx) {
|
|
|
|
|
obj.attributes['mail' + idx] = `${a.name}@${a.domain}`;
|
|
|
|
|
});
|
2020-03-05 22:40:25 -08:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
// ensure all filter values are also lowercase
|
|
|
|
|
const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
|
|
|
|
|
if (!lowerCaseFilter) return next(new ldap.OperationsError(safe.error.toString()));
|
2021-07-15 09:50:11 -07:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
if ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
|
|
|
|
|
results.push(obj);
|
2021-07-15 09:50:11 -07:00
|
|
|
}
|
2021-08-17 15:45:57 -07:00
|
|
|
}
|
2021-07-15 09:50:11 -07:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
finalSend(results, req, res, next);
|
2018-05-03 18:05:32 +02:00
|
|
|
}
|
2016-09-25 18:59:11 -07:00
|
|
|
}
|
|
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
async 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
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
|
|
|
|
const parts = email.split('@');
|
2018-01-18 18:14:31 -08:00
|
|
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const [error, alias] = await safe(mail.getAlias(parts[0], parts[1]));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
|
|
|
|
if (!alias) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
|
|
|
|
if (!alias.active) return next(new ldap.NoSuchObjectError(req.dn.toString())); // there is no way to disable an alias. this is just here for completeness
|
|
|
|
|
|
|
|
|
|
// https://wiki.debian.org/LDAP/MigrationTools/Examples
|
|
|
|
|
// https://docs.oracle.com/cd/E19455-01/806-5580/6jej518pp/index.html
|
|
|
|
|
// member is fully qualified - https://docs.oracle.com/cd/E19957-01/816-6082-10/chap4.doc.html#43314
|
|
|
|
|
const obj = {
|
|
|
|
|
dn: req.dn.toString(),
|
|
|
|
|
attributes: {
|
|
|
|
|
objectclass: ['nisMailAlias'],
|
|
|
|
|
objectcategory: 'nisMailAlias',
|
|
|
|
|
cn: `${alias.name}@${alias.domain}`,
|
|
|
|
|
rfc822MailMember: `${alias.aliasName}@${alias.aliasDomain}`
|
|
|
|
|
}
|
|
|
|
|
};
|
2016-09-25 18:59:11 -07:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
// ensure all filter values are also lowercase
|
|
|
|
|
const 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
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
if (lowerCaseFilter.matches(obj.attributes)) {
|
|
|
|
|
finalSend([ obj ], req, res, next);
|
|
|
|
|
} else {
|
|
|
|
|
res.end();
|
|
|
|
|
}
|
2016-09-25 18:59:11 -07:00
|
|
|
}
|
|
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
async function mailingListSearch(req, res, next) {
|
2016-09-27 12:20:20 -07:00
|
|
|
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
|
|
|
|
2019-11-06 16:45:44 -08:00
|
|
|
let email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
|
|
|
|
let parts = email.split('@');
|
2018-01-18 18:14:31 -08:00
|
|
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2019-11-06 16:45:44 -08:00
|
|
|
const name = parts[0], domain = parts[1];
|
2018-01-18 18:14:31 -08:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const [error, result] = await safe(mail.resolveList(parts[0], parts[1]));
|
|
|
|
|
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
|
|
|
|
|
|
|
|
|
const { resolvedMembers, list } = result;
|
|
|
|
|
|
|
|
|
|
if (!list.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
|
|
|
|
// http://ldapwiki.willeke.com/wiki/Original%20Mailgroup%20Schema%20From%20Netscape
|
|
|
|
|
// members are fully qualified (https://docs.oracle.com/cd/E19444-01/816-6018-10/groups.htm#13356)
|
|
|
|
|
const obj = {
|
|
|
|
|
dn: req.dn.toString(),
|
|
|
|
|
attributes: {
|
|
|
|
|
objectclass: ['mailGroup'],
|
|
|
|
|
objectcategory: 'mailGroup',
|
|
|
|
|
cn: `${name}@${domain}`, // fully qualified
|
|
|
|
|
mail: `${name}@${domain}`,
|
|
|
|
|
membersOnly: list.membersOnly, // ldapjs only supports strings and string array. so this is not a bool!
|
|
|
|
|
mgrpRFC822MailMember: resolvedMembers // fully qualified
|
|
|
|
|
}
|
|
|
|
|
};
|
2021-04-14 22:37:01 -07:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
// ensure all filter values are also lowercase
|
|
|
|
|
const 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
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
if (lowerCaseFilter.matches(obj.attributes)) {
|
|
|
|
|
finalSend([ obj ], req, res, next);
|
|
|
|
|
} else {
|
|
|
|
|
res.end();
|
|
|
|
|
}
|
2016-05-29 18:24:54 -07:00
|
|
|
}
|
|
|
|
|
|
2018-09-03 15:38:50 +02:00
|
|
|
// Will attach req.user if successful
|
2021-07-15 09:50:11 -07:00
|
|
|
async 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
|
2021-07-15 09:50:11 -07:00
|
|
|
const attributeName = Object.keys(req.dn.rdns[0].attrs)[0];
|
|
|
|
|
const 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()));
|
|
|
|
|
|
2021-07-15 09:50:11 -07:00
|
|
|
let verifyFunc;
|
2016-05-16 12:21:15 -07:00
|
|
|
if (attributeName === 'mail') {
|
2021-07-15 09:50:11 -07:00
|
|
|
verifyFunc = users.verifyWithEmail;
|
2016-05-16 12:21:15 -07:00
|
|
|
} else if (commonName.indexOf('@') !== -1) { // if mail is specified, enforce mail check
|
2021-07-15 09:50:11 -07:00
|
|
|
verifyFunc = users.verifyWithEmail;
|
2016-05-12 13:36:53 -07:00
|
|
|
} else if (commonName.indexOf('uid-') === 0) {
|
2021-07-15 09:50:11 -07:00
|
|
|
verifyFunc = users.verify;
|
2016-05-12 13:36:53 -07:00
|
|
|
} else {
|
2021-07-15 09:50:11 -07:00
|
|
|
verifyFunc = users.verifyWithUsername;
|
2016-05-12 13:36:53 -07:00
|
|
|
}
|
|
|
|
|
|
2021-07-15 09:50:11 -07:00
|
|
|
const [error, user] = await safe(verifyFunc(commonName, req.credentials || '', req.app.id));
|
|
|
|
|
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.message));
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2021-07-15 09:50:11 -07:00
|
|
|
req.user = user;
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2021-07-15 09:50:11 -07:00
|
|
|
next();
|
2016-05-29 17:16:52 -07:00
|
|
|
}
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
async function authorizeUserForApp(req, res, next) {
|
2018-09-03 15:38:50 +02:00
|
|
|
assert.strictEqual(typeof req.user, 'object');
|
|
|
|
|
assert.strictEqual(typeof req.app, 'object');
|
2016-05-29 17:16:52 -07:00
|
|
|
|
2021-09-21 10:00:47 -07:00
|
|
|
const canAccess = apps.canAccess(req.app, req.user);
|
2021-08-20 09:19:44 -07:00
|
|
|
// we return no such object, to avoid leakage of a users existence
|
2021-09-21 10:00:47 -07:00
|
|
|
if (!canAccess) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
await eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', appId: req.app.id }, { userId: req.user.id, user: users.removePrivateFields(req.user) });
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
res.end();
|
2016-05-12 13:36:53 -07:00
|
|
|
}
|
|
|
|
|
|
2021-07-15 09:50:11 -07:00
|
|
|
async function verifyMailboxPassword(mailbox, password) {
|
2020-11-12 23:25:33 -08:00
|
|
|
assert.strictEqual(typeof mailbox, 'object');
|
|
|
|
|
assert.strictEqual(typeof password, 'string');
|
|
|
|
|
|
2021-07-15 09:50:11 -07:00
|
|
|
if (mailbox.ownerType === mail.OWNERTYPE_USER) return await users.verify(mailbox.ownerId, password, users.AP_MAIL /* identifier */);
|
2020-11-12 23:25:33 -08:00
|
|
|
|
2021-07-15 09:50:11 -07:00
|
|
|
const userIds = await groups.getMembers(mailbox.ownerId);
|
2020-11-12 23:25:33 -08:00
|
|
|
|
2021-06-28 15:15:28 -07:00
|
|
|
let verifiedUser = null;
|
2021-07-15 09:50:11 -07:00
|
|
|
for (const userId of userIds) {
|
|
|
|
|
const [error, result] = await safe(users.verify(userId, password, users.AP_MAIL /* identifier */));
|
|
|
|
|
if (error) continue; // try the next user
|
|
|
|
|
verifiedUser = result;
|
|
|
|
|
break; // found a matching validated user
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!verifiedUser) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
|
|
|
|
return verifiedUser;
|
2020-11-12 23:25:33 -08:00
|
|
|
}
|
|
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
async function authenticateUserMailbox(req, res, next) {
|
2018-12-16 18:04:30 -08:00
|
|
|
debug('user mailbox auth: %s (from %s)', req.dn.toString(), req.connection.ldap.id);
|
|
|
|
|
|
|
|
|
|
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
|
|
|
|
const parts = email.split('@');
|
2018-12-16 18:04:30 -08:00
|
|
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const [error, domain] = await safe(mail.getDomain(parts[1]));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.message));
|
|
|
|
|
if (!domain) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2018-12-16 18:04:30 -08:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
if (!domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2018-12-16 18:04:30 -08:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const [getMailboxError, mailbox] = await safe(mail.getMailbox(parts[0], parts[1]));
|
|
|
|
|
if (getMailboxError) return next(new ldap.OperationsError(getMailboxError.message));
|
|
|
|
|
if (!mailbox) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2021-04-14 22:37:01 -07:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const [verifyError, result] = await safe(verifyMailboxPassword(mailbox, req.credentials || ''));
|
|
|
|
|
if (verifyError && verifyError.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
|
|
|
|
if (verifyError) return next(new ldap.OperationsError(verifyError.message));
|
2018-12-16 18:04:30 -08:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
|
2021-06-01 09:35:20 -07:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
res.end();
|
2018-12-16 18:04:30 -08:00
|
|
|
}
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
async function authenticateSftp(req, res, next) {
|
2019-04-04 20:46:01 -07:00
|
|
|
debug('sftp auth: %s (from %s)', req.dn.toString(), req.connection.ldap.id);
|
2019-03-19 11:59:51 -07:00
|
|
|
|
2019-03-18 21:15:50 -07:00
|
|
|
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
2021-07-15 09:50:11 -07:00
|
|
|
const email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
|
|
|
|
const parts = email.split('@');
|
2019-03-18 21:15:50 -07:00
|
|
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
let [error, app] = await safe(apps.getByFqdn(parts[1]));
|
|
|
|
|
if (error || !app) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
2019-03-18 21:15:50 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
[error] = await safe(users.verifyWithUsername(parts[0], req.credentials, app.id));
|
|
|
|
|
if (error) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
2019-03-18 21:15:50 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
debug('sftp auth: success');
|
2020-03-26 21:50:25 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
res.end();
|
2019-03-18 21:15:50 -07:00
|
|
|
}
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
async function userSearchSftp(req, res, next) {
|
2019-04-04 20:46:01 -07:00
|
|
|
debug('sftp user search: dn %s, scope %s, filter %s (from %s)', req.dn.toString(), req.scope, req.filter.toString(), req.connection.ldap.id);
|
2019-03-19 11:59:51 -07:00
|
|
|
|
2019-03-18 21:15:50 -07:00
|
|
|
if (req.filter.attribute !== 'username' || !req.filter.value) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const parts = req.filter.value.split('@');
|
2019-03-18 21:15:50 -07:00
|
|
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const username = parts[0];
|
|
|
|
|
const appFqdn = parts[1];
|
2019-03-18 21:15:50 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const [error, app] = await safe(apps.getByFqdn(appFqdn));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.toString()));
|
2019-03-18 21:15:50 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
// only allow apps which specify "ftp" support in the localstorage addon
|
|
|
|
|
if (!safe.query(app.manifest.addons, 'localstorage.ftp.uid')) return next(new ldap.UnavailableError('Not supported'));
|
|
|
|
|
if (typeof app.manifest.addons.localstorage.ftp.uid !== 'number') return next(new ldap.UnavailableError('Bad uid, must be a number'));
|
2019-04-04 22:38:40 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const uidNumber = app.manifest.addons.localstorage.ftp.uid;
|
2019-03-19 21:17:23 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const [userGetError, user] = await safe(users.getByUsername(username));
|
|
|
|
|
if (userGetError) return next(new ldap.OperationsError(userGetError.toString()));
|
|
|
|
|
if (!user) return next(new ldap.OperationsError('Invalid username'));
|
2019-03-18 21:15:50 -07:00
|
|
|
|
2021-09-21 22:42:32 -07:00
|
|
|
if (!apps.isOperator(app, user)) return next(new ldap.InsufficientAccessRightsError('Not authorized'));
|
2019-03-18 21:15:50 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
const obj = {
|
|
|
|
|
dn: ldap.parseDN(`cn=${username}@${appFqdn},ou=sftp,dc=cloudron`).toString(),
|
|
|
|
|
attributes: {
|
|
|
|
|
homeDirectory: app.dataDir ? `/mnt/${app.id}` : `/mnt/appsdata/${app.id}/data`,
|
|
|
|
|
objectclass: ['user'],
|
|
|
|
|
objectcategory: 'person',
|
|
|
|
|
cn: user.id,
|
|
|
|
|
uid: `${username}@${appFqdn}`, // for bind after search
|
|
|
|
|
uidNumber: uidNumber, // unix uid for ftp access
|
|
|
|
|
gidNumber: uidNumber // unix gid for ftp access
|
|
|
|
|
}
|
|
|
|
|
};
|
2019-03-18 21:15:50 -07:00
|
|
|
|
2021-08-20 09:19:44 -07:00
|
|
|
finalSend([ obj ], req, res, next);
|
2019-03-18 21:15:50 -07:00
|
|
|
}
|
|
|
|
|
|
2021-09-20 19:30:00 -07:00
|
|
|
async function verifyAppMailboxPassword(serviceId, username, password) {
|
|
|
|
|
assert.strictEqual(typeof serviceId, 'string');
|
2020-12-03 12:14:04 -08:00
|
|
|
assert.strictEqual(typeof username, 'string');
|
|
|
|
|
assert.strictEqual(typeof password, 'string');
|
|
|
|
|
|
2021-09-20 19:30:00 -07:00
|
|
|
const pattern = serviceId === 'msa' ? 'MAIL_SMTP' : 'MAIL_IMAP';
|
2021-10-08 09:59:44 -07:00
|
|
|
const addonId = serviceId === 'msa' ? 'sendmail' : 'recvmail';
|
|
|
|
|
const appId = await addonConfigs.getAppIdByValue(addonId, `%${pattern}_PASSWORD`, password); // search by password because this is unique for each app
|
2021-08-19 21:39:27 -07:00
|
|
|
if (!appId) throw new BoxError(BoxError.NOT_FOUND);
|
2020-12-03 12:14:04 -08:00
|
|
|
|
2021-10-08 09:59:44 -07:00
|
|
|
const result = await addonConfigs.get(appId, addonId);
|
2020-12-03 12:14:04 -08:00
|
|
|
|
2021-08-19 21:39:27 -07:00
|
|
|
if (!result.some(r => r.name.endsWith(`${pattern}_USERNAME`) && r.value === username)) throw new BoxError(BoxError.INVALID_CREDENTIALS);
|
2020-12-03 12:14:04 -08:00
|
|
|
}
|
|
|
|
|
|
2021-09-20 19:30:00 -07:00
|
|
|
async function authenticateMail(req, res, next) {
|
|
|
|
|
debug('authenticateMail: %s (from %s)', req.dn.toString(), req.connection.ldap.id);
|
2018-02-08 18:49:27 -08:00
|
|
|
|
2020-12-03 13:35:50 -08:00
|
|
|
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
|
|
|
|
|
const email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
|
|
|
|
|
const parts = email.split('@');
|
2018-01-18 18:14:31 -08:00
|
|
|
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2016-09-26 11:50:32 -07:00
|
|
|
|
2021-10-08 10:15:48 -07:00
|
|
|
const knownServices = [ 'msa', 'imap', 'pop3', 'sieve' ];
|
|
|
|
|
const serviceId = req.dn.rdns[1].attrs.ou.value.toLowerCase();
|
|
|
|
|
if (!knownServices.includes(serviceId)) return next(new ldap.OperationsError('Invalid DN. Unknown service'));
|
2018-12-06 21:08:19 -08:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const [error, domain] = await safe(mail.getDomain(parts[1]));
|
|
|
|
|
if (error) return next(new ldap.OperationsError(error.message));
|
|
|
|
|
if (!domain) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2018-01-22 20:35:08 +01:00
|
|
|
|
2021-10-08 10:15:48 -07:00
|
|
|
const serviceNeedsMailbox = serviceId === 'imap' || serviceId === 'sieve' || serviceId === 'pop3';
|
2021-09-20 19:30:00 -07:00
|
|
|
if (serviceNeedsMailbox && !domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2018-01-22 20:35:08 +01:00
|
|
|
|
2021-10-03 23:59:06 -07:00
|
|
|
const [getMailboxError, mailbox] = await safe(mail.getMailbox(parts[0], parts[1]));
|
|
|
|
|
if (getMailboxError) return next(new ldap.OperationsError(getMailboxError.message));
|
2021-10-08 10:15:48 -07:00
|
|
|
if (serviceId === 'pop3' && !mailbox.enablePop3) return next(new ldap.OperationsError('POP3 is not enabled'));
|
2021-10-03 23:59:06 -07:00
|
|
|
|
2021-09-20 19:30:00 -07:00
|
|
|
const [appPasswordError] = await safe(verifyAppMailboxPassword(serviceId, email, req.credentials || ''));
|
2021-10-03 23:59:06 -07:00
|
|
|
if (!appPasswordError) { // validated as app
|
2021-09-20 19:30:00 -07:00
|
|
|
if (serviceNeedsMailbox && (!mailbox || !mailbox.active)) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2021-10-03 23:59:06 -07:00
|
|
|
return res.end();
|
|
|
|
|
}
|
2018-01-22 20:35:08 +01:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
if (appPasswordError && appPasswordError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
|
|
|
|
if (appPasswordError && appPasswordError.reason !== BoxError.NOT_FOUND) return next(new ldap.OperationsError(appPasswordError.message));
|
2018-01-29 19:29:04 +01:00
|
|
|
|
2021-09-20 19:30:00 -07:00
|
|
|
// user password check requires an active mailbox
|
2021-08-17 15:45:57 -07:00
|
|
|
if (!mailbox) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
2021-04-14 22:37:01 -07:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
const [verifyError, result] = await safe(verifyMailboxPassword(mailbox, req.credentials || ''));
|
|
|
|
|
if (verifyError && verifyError.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
|
|
|
|
|
if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
|
|
|
|
|
if (verifyError) return next(new ldap.OperationsError(verifyError.message));
|
2018-01-22 20:35:08 +01:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
|
2021-06-01 09:35:20 -07:00
|
|
|
|
2021-08-17 15:45:57 -07:00
|
|
|
res.end();
|
2016-05-29 17:25:23 -07:00
|
|
|
}
|
|
|
|
|
|
2021-09-07 09:57:49 -07:00
|
|
|
async function start() {
|
2021-08-20 09:19:44 -07:00
|
|
|
const logger = {
|
2016-09-25 16:11:54 -07:00
|
|
|
trace: NOOP,
|
|
|
|
|
debug: NOOP,
|
|
|
|
|
info: debug,
|
|
|
|
|
warn: debug,
|
2020-08-02 11:43:18 -07:00
|
|
|
error: debug,
|
|
|
|
|
fatal: debug
|
2016-09-25 16:11:54 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
gServer = ldap.createServer({ log: logger });
|
2016-05-12 13:36:53 -07:00
|
|
|
|
2019-04-25 13:10:52 +02:00
|
|
|
gServer.on('error', function (error) {
|
2020-08-02 11:43:18 -07:00
|
|
|
debug('start: server error ', error);
|
2019-04-25 13:10:52 +02:00
|
|
|
});
|
|
|
|
|
|
2018-09-03 15:38:50 +02:00
|
|
|
gServer.search('ou=users,dc=cloudron', authenticateApp, userSearch);
|
|
|
|
|
gServer.search('ou=groups,dc=cloudron', authenticateApp, groupSearch);
|
|
|
|
|
gServer.bind('ou=users,dc=cloudron', authenticateApp, 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
|
2020-11-12 23:25:33 -08:00
|
|
|
gServer.search('ou=mailboxes,dc=cloudron', mailboxSearch); // haraka (address translation), dovecot (LMTP), sogo (mailbox search)
|
2018-12-16 18:04:30 -08:00
|
|
|
gServer.bind('ou=mailboxes,dc=cloudron', authenticateUserMailbox); // apps like sogo can use domain=${domain} to authenticate a mailbox
|
|
|
|
|
gServer.search('ou=mailaliases,dc=cloudron', mailAliasSearch); // haraka
|
|
|
|
|
gServer.search('ou=mailinglists,dc=cloudron', mailingListSearch); // haraka
|
2016-09-25 18:59:11 -07:00
|
|
|
|
2021-09-20 19:30:00 -07:00
|
|
|
gServer.bind('ou=imap,dc=cloudron', authenticateMail); // dovecot (IMAP auth)
|
|
|
|
|
gServer.bind('ou=msa,dc=cloudron', authenticateMail); // haraka (MSA auth)
|
|
|
|
|
gServer.bind('ou=sieve,dc=cloudron', authenticateMail); // dovecot (sieve auth)
|
2021-10-08 10:15:48 -07:00
|
|
|
gServer.bind('ou=pop3,dc=cloudron', authenticateMail); // dovecot (pop3 auth)
|
2016-05-29 17:25:23 -07:00
|
|
|
|
2019-04-04 20:46:01 -07:00
|
|
|
gServer.bind('ou=sftp,dc=cloudron', authenticateSftp); // sftp
|
2021-09-21 22:42:32 -07:00
|
|
|
gServer.search('ou=sftp,dc=cloudron', userSearchSftp);
|
2019-03-18 21:15:50 -07:00
|
|
|
|
2018-09-03 15:38:50 +02:00
|
|
|
gServer.compare('cn=users,ou=groups,dc=cloudron', authenticateApp, groupUsersCompare);
|
|
|
|
|
gServer.compare('cn=admins,ou=groups,dc=cloudron', authenticateApp, groupAdminsCompare);
|
2017-10-24 01:35:35 +02:00
|
|
|
|
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();
|
|
|
|
|
});
|
|
|
|
|
|
2021-02-13 18:56:36 +01:00
|
|
|
// just log that an attempt was made to unknown route, this helps a lot during app packaging
|
|
|
|
|
gServer.use(function(req, res, next) {
|
|
|
|
|
debug('not handled: dn %s, scope %s, filter %s (from %s)', req.dn ? req.dn.toString() : '-', req.scope, req.filter ? req.filter.toString() : '-', req.connection.ldap.id);
|
|
|
|
|
return next();
|
|
|
|
|
});
|
|
|
|
|
|
2021-09-07 09:57:49 -07:00
|
|
|
await util.promisify(gServer.listen.bind(gServer))(constants.LDAP_PORT, '0.0.0.0');
|
2015-07-20 00:09:47 -07:00
|
|
|
}
|
2015-09-14 10:59:05 -07:00
|
|
|
|
2021-09-07 09:57:49 -07:00
|
|
|
async function stop() {
|
2016-09-15 11:53:28 -07:00
|
|
|
if (gServer) gServer.close();
|
2015-09-14 10:59:05 -07:00
|
|
|
}
|