diff --git a/src/infra_version.js b/src/infra_version.js index 19b31c596..8b484f430 100644 --- a/src/infra_version.js +++ b/src/infra_version.js @@ -20,7 +20,7 @@ exports = module.exports = { 'postgresql': { repo: 'cloudron/postgresql', tag: 'cloudron/postgresql:4.1.1@sha256:86e4e2f4fd43809efca7c9cb1def4d7608cf36cb9ea27052f9b64da4481db43a' }, 'mongodb': { repo: 'cloudron/mongodb', tag: 'cloudron/mongodb:4.0.2@sha256:9df297ccc3370f38c54f8d614e214e082b363777cd1c6c9522e29663cc8f5362' }, 'redis': { repo: 'cloudron/redis', tag: 'cloudron/redis:3.0.4@sha256:5c60de75d078ae609da5565f32dcd91030f45907e945756cc976ff207b8c6199' }, - 'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:3.3.3@sha256:b1093e6f38bebf4a9ae903ca385aea3a32e7cccae5ede7f2e01a34681e361a5f' }, + 'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:3.4.0@sha256:19934adb89499942c0ae81580be71442f2482401dbf0618e13a04ff478bbfdb7' }, 'graphite': { repo: 'cloudron/graphite', tag: 'cloudron/graphite:3.0.1@sha256:bed9f6b5d06fe2c5289e895e806cfa5b74ad62993d705be55d4554a67d128029' }, 'sftp': { repo: 'cloudron/sftp', tag: 'cloudron/sftp:3.4.0@sha256:2804a2ad5645a771c30ce56b3fbbec05f5b9d9284a5e402450a7e1c1d9a10eab' } } diff --git a/src/ldap.js b/src/ldap.js index 41dac1ae6..b330e6def 100644 --- a/src/ldap.js +++ b/src/ldap.js @@ -592,22 +592,22 @@ async function userSearchSftp(req, res, next) { finalSend([ obj ], req, res, next); } -async function verifyAppMailboxPassword(addonId, username, password) { - assert.strictEqual(typeof addonId, 'string'); +async function verifyAppMailboxPassword(serviceId, username, password) { + assert.strictEqual(typeof serviceId, 'string'); assert.strictEqual(typeof username, 'string'); assert.strictEqual(typeof password, 'string'); - const pattern = addonId === 'sendmail' ? 'MAIL_SMTP' : 'MAIL_IMAP'; - const appId = await addonConfigs.getAppIdByValue(addonId, `%${pattern}_PASSWORD`, password); // search by password because this is unique for each app + const pattern = serviceId === 'msa' ? 'MAIL_SMTP' : 'MAIL_IMAP'; + const appId = await addonConfigs.getAppIdByValue(serviceId, `%${pattern}_PASSWORD`, password); // search by password because this is unique for each app if (!appId) throw new BoxError(BoxError.NOT_FOUND); - const result = await addonConfigs.get(appId, addonId); + const result = await addonConfigs.get(appId, serviceId); if (!result.some(r => r.name.endsWith(`${pattern}_USERNAME`) && r.value === username)) throw new BoxError(BoxError.INVALID_CREDENTIALS); } -async function authenticateMailAddon(req, res, next) { - debug('mail addon auth: %s (from %s)', req.dn.toString(), req.connection.ldap.id); +async function authenticateMail(req, res, next) { + debug('authenticateMail: %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())); @@ -615,28 +615,29 @@ async function authenticateMailAddon(req, res, next) { const parts = email.split('@'); if (parts.length !== 2) return next(new ldap.NoSuchObjectError(req.dn.toString())); - 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 serviceId = req.dn.rdns[1].attrs.ou.value.toLowerCase(); // msa, imap, sieve + if (serviceId !== 'msa' && serviceId !== 'imap' && serviceId !== 'sieve') return next(new ldap.OperationsError('Invalid DN')); 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())); - if (addonId === 'recvmail' && !domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString())); + const serviceNeedsMailbox = serviceId === 'imap' || serviceId === 'sieve'; + if (serviceNeedsMailbox && !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)); - const [appPasswordError] = await safe(verifyAppMailboxPassword(addonId, email, req.credentials || '')); + const [appPasswordError] = await safe(verifyAppMailboxPassword(serviceId, email, req.credentials || '')); if (!appPasswordError) { // validated as app - if (addonId === 'recvmail' && (!mailbox || !mailbox.active)) return next(new ldap.NoSuchObjectError(req.dn.toString())); // recvmail requires active mailbox + if (serviceNeedsMailbox && (!mailbox || !mailbox.active)) return next(new ldap.NoSuchObjectError(req.dn.toString())); return res.end(); } 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)); - // user password check requires an active mailbox for recvmail and sendmail addon + // user password check requires an active mailbox if (!mailbox) return next(new ldap.NoSuchObjectError(req.dn.toString())); if (!mailbox.active) return next(new ldap.NoSuchObjectError(req.dn.toString())); @@ -676,8 +677,9 @@ async function start() { gServer.search('ou=mailaliases,dc=cloudron', mailAliasSearch); // haraka gServer.search('ou=mailinglists,dc=cloudron', mailingListSearch); // haraka - gServer.bind('ou=recvmail,dc=cloudron', authenticateMailAddon); // dovecot (IMAP auth) - gServer.bind('ou=sendmail,dc=cloudron', authenticateMailAddon); // haraka (MSA auth) + 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) gServer.bind('ou=sftp,dc=cloudron', authenticateSftp); // sftp gServer.search('ou=sftp,dc=cloudron', userSearchSftp); diff --git a/src/test/ldap-test.js b/src/test/ldap-test.js index 661edc4d0..cf260addc 100644 --- a/src/test/ldap-test.js +++ b/src/test/ldap-test.js @@ -423,52 +423,52 @@ describe('Ldap', function () { }); }); - describe('user recvmail bind', function () { + describe('user imap bind', function () { it('email disabled - cannot find domain email', async function () { await mail._updateDomain(domain.domain, { enabled: false }); - const [error] = await safe(ldapBind(`cn=${mailbox},ou=recvmail,dc=cloudron`, 'badpassword')); + const [error] = await safe(ldapBind(`cn=${mailbox},ou=imap,dc=cloudron`, 'badpassword')); expect(error).to.be.a(ldap.NoSuchObjectError); }); it('email enabled - allows with valid email', async function () { await mail._updateDomain(domain.domain, { enabled: true }); - await ldapBind(`cn=${mailbox},ou=recvmail,dc=cloudron`, user.password); + await ldapBind(`cn=${mailbox},ou=imap,dc=cloudron`, user.password); }); it('email enabled - does not allow with invalid password', async function () { await mail._updateDomain(domain.domain, { enabled: true }); - const [error] = await safe(ldapBind(`cn=${mailbox},ou=recvmail,dc=cloudron`, 'badpassword')); + const [error] = await safe(ldapBind(`cn=${mailbox},ou=imap,dc=cloudron`, 'badpassword')); expect(error).to.be.a(ldap.InvalidCredentialsError); }); it('does not allow for inactive mailbox', async function () { await mail._updateDomain(domain.domain, { enabled: true }); await mail.updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: false }, auditSource); - const [error] = await safe(ldapBind(`cn=${mailbox},ou=recvmail,dc=cloudron`, 'badpassword')); + const [error] = await safe(ldapBind(`cn=${mailbox},ou=imap,dc=cloudron`, 'badpassword')); expect(error).to.be.a(ldap.NoSuchObjectError); await mail._updateDomain(domain.domain, { enabled: false }); await mail.updateMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: true }, auditSource); }); }); - describe('app recvmail bind', function () { + describe('app imap bind', function () { before(async function () { await mail._updateDomain(domain.domain, { enabled: true }); }); it('does not allow with invalid app', async function () { - const [error] = await safe(ldapBind(`cn=hacker.app@${domain.domain},ou=recvmail,dc=cloudron`, 'nope')); + const [error] = await safe(ldapBind(`cn=hacker.app@${domain.domain},ou=imap,dc=cloudron`, 'nope')); expect(error).to.be.a(ldap.NoSuchObjectError); }); it('does not allow with invalid password', async function () { - const [error] = await safe(ldapBind(`cn=${app.location}.app@${domain.domain},ou=recvmail,dc=cloudron`, 'nope')); + const [error] = await safe(ldapBind(`cn=${app.location}.app@${domain.domain},ou=imap,dc=cloudron`, 'nope')); expect(error).to.be.a(ldap.NoSuchObjectError); }); it('allows with valid password', async function () { - await addonConfigs.set(app.id, 'recvmail', [{ name: 'MAIL_IMAP_USERNAME', value : `${app.location}.app@${domain.domain}` }, { name: 'MAIL_IMAP_PASSWORD', value : 'recvmailpassword' }]), - await ldapBind(`cn=${app.location}.app@${domain.domain},ou=recvmail,dc=cloudron`, 'recvmailpassword'); + await addonConfigs.set(app.id, 'imap', [{ name: 'MAIL_IMAP_USERNAME', value : `${app.location}.app@${domain.domain}` }, { name: 'MAIL_IMAP_PASSWORD', value : 'imappassword' }]), + await ldapBind(`cn=${app.location}.app@${domain.domain},ou=imap,dc=cloudron`, 'imappassword'); }); }); });