diff --git a/CHANGES b/CHANGES index faed875e9..e7f76f929 100644 --- a/CHANGES +++ b/CHANGES @@ -2401,4 +2401,5 @@ [7.1.0] * Add mail manager role +* mailbox: app can be set as owner when recvmail addon enabled diff --git a/src/ldap.js b/src/ldap.js index ec08ec8c9..89ca6a347 100644 --- a/src/ldap.js +++ b/src/ldap.js @@ -298,6 +298,7 @@ async function mailboxSearch(req, res, next) { for (const mailbox of mailboxes) { const dn = ldap.parseDN(`cn=${mailbox.name}@${mailbox.domain},ou=mailboxes,dc=cloudron`); + if (mailbox.ownerType === mail.OWNERTYPE_APP) continue; // cannot login with app mailbox anyway 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 @@ -459,20 +460,24 @@ async function verifyMailboxPassword(mailbox, password) { assert.strictEqual(typeof mailbox, 'object'); assert.strictEqual(typeof password, 'string'); - if (mailbox.ownerType === mail.OWNERTYPE_USER) return await users.verify(mailbox.ownerId, password, users.AP_MAIL /* identifier */); + if (mailbox.ownerType === mail.OWNERTYPE_USER) { + return await users.verify(mailbox.ownerId, password, users.AP_MAIL /* identifier */); + } else if (mailbox.ownerType === mail.OWNERTYPE_GROUP) { + const userIds = await groups.getMembers(mailbox.ownerId); - const userIds = await groups.getMembers(mailbox.ownerId); + let verifiedUser = null; + 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 + } - let verifiedUser = null; - 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; + } else { + throw new BoxError(BoxError.INVALID_CREDENTIALS); } - - if (!verifiedUser) throw new BoxError(BoxError.INVALID_CREDENTIALS); - return verifiedUser; } async function authenticateSftp(req, res, next) { @@ -586,6 +591,7 @@ async function authenticateService(serviceId, dn, req, res, next) { if (appPasswordError.reason !== BoxError.NOT_FOUND) return next(new ldap.OperationsError(appPasswordError.message)); if (!mailbox || !mailbox.active) return next(new ldap.NoSuchObjectError(dn.toString())); // user auth requires active mailbox + if (mailbox.ownerType !== mailbox.OWNERTYPE_USER && mailbox.ownerType !== mailbox.OWNERTYPE_GROUP) return next(new ldap.InvalidCredentialsError(dn.toString())); // app mailboxes don't have a real password const [verifyError, result] = await safe(verifyMailboxPassword(mailbox, req.credentials || '')); if (verifyError && verifyError.reason === BoxError.NOT_FOUND) return next(new ldap.NoSuchObjectError(dn.toString())); if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new ldap.InvalidCredentialsError(dn.toString())); diff --git a/src/mail.js b/src/mail.js index f3d90560b..e94d24586 100644 --- a/src/mail.js +++ b/src/mail.js @@ -57,6 +57,7 @@ exports = module.exports = { OWNERTYPE_USER: 'user', OWNERTYPE_GROUP: 'group', + OWNERTYPE_APP: 'app', DEFAULT_MEMORY_LIMIT: 512 * 1024 * 1024, @@ -106,6 +107,8 @@ const assert = require('assert'), const DNS_OPTIONS = { timeout: 5000 }; const REMOVE_MAILBOX_CMD = path.join(__dirname, 'scripts/rmmailbox.sh'); +const OWNERTYPES = [ exports.OWNERTYPE_USER, exports.OWNERTYPE_GROUP, exports.OWNERTYPE_APP ]; + // if you add a field here, listMailboxes has to be updated const MAILBOX_FIELDS = [ 'name', 'type', 'ownerId', 'ownerType', 'aliasName', 'aliasDomain', 'creationTime', 'membersJson', 'membersOnly', 'domain', 'active', 'enablePop3' ].join(','); const MAILDB_FIELDS = [ 'domain', 'enabled', 'mailFromValidation', 'catchAllJson', 'relayJson', 'dkimKeyJson', 'dkimSelector', 'bannerJson' ].join(','); @@ -1165,7 +1168,7 @@ async function addMailbox(name, domain, data, auditSource) { let error = validateName(name); if (error) throw error; - if (ownerType !== exports.OWNERTYPE_USER && ownerType !== exports.OWNERTYPE_GROUP) throw new BoxError(BoxError.BAD_FIELD, 'bad owner type'); + if (!OWNERTYPES.includes(ownerType)) throw new BoxError(BoxError.BAD_FIELD, 'bad owner type'); [error] = await safe(database.query('INSERT INTO mailboxes (name, type, domain, ownerId, ownerType, active) VALUES (?, ?, ?, ?, ?, ?)', [ name, exports.TYPE_MAILBOX, domain, ownerId, ownerType, active ])); if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'mailbox already exists'); @@ -1188,7 +1191,7 @@ async function updateMailbox(name, domain, data, auditSource) { name = name.toLowerCase(); - if (ownerType !== exports.OWNERTYPE_USER && ownerType !== exports.OWNERTYPE_GROUP) throw new BoxError(BoxError.BAD_FIELD, 'bad owner type'); + if (!OWNERTYPES.includes(ownerType)) throw new BoxError(BoxError.BAD_FIELD, 'bad owner type'); const mailbox = await getMailbox(name, domain); if (!mailbox) throw new BoxError(BoxError.NOT_FOUND, 'No such mailbox');