mailboxdb: merge into mail.js

This commit is contained in:
Girish Ramakrishnan
2021-08-17 15:45:57 -07:00
parent 98ef6dfae9
commit fa9938f50a
12 changed files with 727 additions and 1233 deletions

View File

@@ -18,7 +18,6 @@ const assert = require('assert'),
groups = require('./groups.js'),
ldap = require('ldapjs'),
mail = require('./mail.js'),
mailboxdb = require('./mailboxdb.js'),
safe = require('safetydance'),
services = require('./services.js'),
users = require('./users.js'),
@@ -261,151 +260,33 @@ function groupAdminsCompare(req, res, next) {
});
}
function mailboxSearch(req, res, next) {
async 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);
// if cn is set we only search for one mailbox specifically
if (req.dn.rdns[0].attrs.cn) {
var email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
var parts = email.split('@');
const email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
const parts = email.split('@');
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
mailboxdb.getMailbox(parts[0], parts[1], function (error, mailbox) {
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (error) return next(new ldap.OperationsError(error.toString()));
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
let 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
var 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();
}
});
} else if (req.dn.rdns[0].attrs.domain) { // legacy ldap mailbox search for old sogo
var domain = req.dn.rdns[0].attrs.domain.value.toLowerCase();
mailboxdb.listMailboxes(domain, 1, 1000, function (error, mailboxes) {
if (error) return next(new ldap.OperationsError(error.toString()));
mailboxes = mailboxes.filter(m => m.active);
let results = [];
// send mailbox objects
mailboxes.forEach(function (mailbox) {
var dn = ldap.parseDN(`cn=${mailbox.name}@${domain},domain=${domain},ou=mailboxes,dc=cloudron`);
var obj = {
dn: dn.toString(),
attributes: {
objectclass: ['mailbox'],
objectcategory: 'mailbox',
cn: `${mailbox.name}@${domain}`,
uid: `${mailbox.name}@${domain}`,
mail: `${mailbox.name}@${domain}`
}
};
// 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)) {
results.push(obj);
}
});
finalSend(results, req, res, next);
});
} else { // new sogo
mailboxdb.listAllMailboxes(1, 1000, async function (error, mailboxes) {
if (error) return next(new ldap.OperationsError(error.toString()));
mailboxes = mailboxes.filter(m => m.active);
let results = [];
for (const mailbox of mailboxes) {
const dn = ldap.parseDN(`cn=${mailbox.name}@${mailbox.domain},ou=mailboxes,dc=cloudron`);
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
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}`
}
};
mailbox.aliases.forEach(function (a, idx) {
obj.attributes['mail' + idx] = `${a.name}@${a.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 ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
results.push(obj);
}
}
finalSend(results, req, res, next);
});
}
}
function mailAliasSearch(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);
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
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) {
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
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()));
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
let obj = {
const obj = {
dn: req.dn.toString(),
attributes: {
objectclass: ['nisMailAlias'],
objectcategory: 'nisMailAlias',
cn: `${alias.name}@${alias.domain}`,
rfc822MailMember: `${alias.aliasName}@${alias.aliasDomain}`
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
var lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null);
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)) {
@@ -413,10 +294,123 @@ function mailAliasSearch(req, res, next) {
} else {
res.end();
}
});
} else if (req.dn.rdns[0].attrs.domain) { // legacy ldap mailbox search for old sogo
const domain = req.dn.rdns[0].attrs.domain.value.toLowerCase();
let [error, mailboxes] = await safe(mail.listMailboxes(domain, 1, 1000));
if (error) return next(new ldap.OperationsError(error.toString()));
mailboxes = mailboxes.filter(m => m.active);
let results = [];
// send mailbox objects
mailboxes.forEach(function (mailbox) {
var dn = ldap.parseDN(`cn=${mailbox.name}@${domain},domain=${domain},ou=mailboxes,dc=cloudron`);
var obj = {
dn: dn.toString(),
attributes: {
objectclass: ['mailbox'],
objectcategory: 'mailbox',
cn: `${mailbox.name}@${domain}`,
uid: `${mailbox.name}@${domain}`,
mail: `${mailbox.name}@${domain}`
}
};
// 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)) {
results.push(obj);
}
});
finalSend(results, req, res, next);
} else { // new sogo
let [error, mailboxes] = await safe(mail.listAllMailboxes(1, 1000));
if (error) return next(new ldap.OperationsError(error.toString()));
mailboxes = mailboxes.filter(m => m.active);
let results = [];
for (const mailbox of mailboxes) {
const dn = ldap.parseDN(`cn=${mailbox.name}@${mailbox.domain},ou=mailboxes,dc=cloudron`);
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
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}`
}
};
mailbox.aliases.forEach(function (a, idx) {
obj.attributes['mail' + idx] = `${a.name}@${a.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 ((req.dn.equals(dn) || req.dn.parentOf(dn)) && lowerCaseFilter.matches(obj.attributes)) {
results.push(obj);
}
}
finalSend(results, req, res, next);
}
}
function mailingListSearch(req, res, next) {
async function mailAliasSearch(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);
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('@');
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
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}`
}
};
// 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();
}
}
async 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);
if (!req.dn.rdns[0].attrs.cn) return next(new ldap.NoSuchObjectError(req.dn.toString()));
@@ -426,36 +420,37 @@ function mailingListSearch(req, res, next) {
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
const name = parts[0], domain = parts[1];
mail.resolveList(parts[0], parts[1], function (error, resolvedMembers, list) {
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 [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()));
if (!list.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
const { resolvedMembers, list } = result;
// 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)
var 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
}
};
if (!list.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
// 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 (lowerCaseFilter.matches(obj.attributes)) {
finalSend([ obj ], req, res, next);
} else {
res.end();
// 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
}
});
};
// 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();
}
}
// Will attach req.user if successful
@@ -524,39 +519,34 @@ async function verifyMailboxPassword(mailbox, password) {
return verifiedUser;
}
function authenticateUserMailbox(req, res, next) {
async function authenticateUserMailbox(req, res, next) {
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()));
var email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
var parts = email.split('@');
const email = req.dn.rdns[0].attrs.cn.value.toLowerCase();
const parts = email.split('@');
if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString()));
const getDomainFunc = util.callbackify(mail.getDomain);
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()));
getDomainFunc(parts[1], function (error, domain) {
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (error) return next(new ldap.OperationsError(error.message));
if (!domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (!domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
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()));
mailboxdb.getMailbox(parts[0], parts[1], async function (error, mailbox) {
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (error) return next(new ldap.OperationsError(error.message));
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));
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
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));
eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
res.end();
});
});
res.end();
}
function authenticateSftp(req, res, next) {
@@ -658,7 +648,7 @@ function verifyAppMailboxPassword(addonId, username, password, callback) {
});
}
function authenticateMailAddon(req, res, next) {
async function authenticateMailAddon(req, res, next) {
debug('mail addon 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()));
@@ -670,37 +660,31 @@ function authenticateMailAddon(req, res, next) {
const addonId = req.dn.rdns[1].attrs.ou.value.toLowerCase(); // 'sendmail' or 'recvmail'
if (addonId !== 'sendmail' && addonId !== 'recvmail') return next(new ldap.OperationsError('Invalid DN'));
const getDomainFunc = util.callbackify(mail.getDomain);
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()));
getDomainFunc(parts[1], function (error, domain) {
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (error) return next(new ldap.OperationsError(error.message));
if (addonId === 'recvmail' && !domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (addonId === 'recvmail' && !domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
const [appPasswordError] = await safe(util.promisify(verifyAppMailboxPassword)(addonId, email, req.credentials || ''));
if (!appPasswordError) return res.end(); // validated as app
verifyAppMailboxPassword(addonId, email, req.credentials || '', function (error) {
if (!error) return res.end(); // validated as app
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));
if (error && error.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(req.dn.toString()));
if (error && error.reason !== BoxError.NOT_FOUND) return next(new ldap.OperationsError(error.message));
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()));
mailboxdb.getMailbox(parts[0], parts[1], async function (error, mailbox) {
if (error && error.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(req.dn.toString()));
if (error) return next(new ldap.OperationsError(error.message));
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));
if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString()));
eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
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));
eventlog.upsertLoginEvent(eventlog.ACTION_USER_LOGIN, { authType: 'ldap', mailboxId: email }, { userId: result.id, user: users.removePrivateFields(result) });
res.end();
});
});
});
res.end();
}
function start(callback) {