diff --git a/src/ldap.js b/src/ldap.js index 442cdca03..cb6655088 100644 --- a/src/ldap.js +++ b/src/ldap.js @@ -357,7 +357,7 @@ async function mailAliasSearch(req, res, next) { 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])); + const [error, alias] = await safe(mail.searchAlias(parts[0], parts[1])); if (error) return next(new ldap.OperationsError(error.toString())); if (!alias) return next(new ldap.NoSuchObjectError(req.dn.toString())); @@ -371,7 +371,7 @@ async function mailAliasSearch(req, res, next) { attributes: { objectclass: ['nisMailAlias'], objectcategory: 'nisMailAlias', - cn: `${alias.name}@${alias.domain}`, + cn: `${parts[0]}@${alias.domain}`, // alias.name can contain wildcard character rfc822MailMember: `${alias.aliasName}@${alias.aliasDomain}` } }; diff --git a/src/mail.js b/src/mail.js index cae866766..e140b046a 100644 --- a/src/mail.js +++ b/src/mail.js @@ -48,6 +48,7 @@ exports = module.exports = { getAlias, getAliases, setAliases, + searchAlias, getLists, getList, @@ -169,6 +170,18 @@ function validateName(name) { return null; } +function validateAlias(name) { + assert.strictEqual(typeof name, 'string'); + + if (name.length < 1) return new BoxError(BoxError.BAD_FIELD, 'mailbox name must be atleast 1 char'); + if (name.length >= 200) return new BoxError(BoxError.BAD_FIELD, 'mailbox name too long'); + + // also need to consider valid LDAP characters here (e.g '+' is reserved). keep hyphen at the end so it doesn't become a range. + if (/[^a-zA-Z0-9._*-]/.test(name)) return new BoxError(BoxError.BAD_FIELD, 'mailbox name can only contain alphanumerals, dot, hyphen, asterisk or underscore'); + + return null; +} + function validateDisplayName(name) { assert.strictEqual(typeof name, 'string'); @@ -1268,6 +1281,18 @@ async function getAlias(name, domain) { return results[0]; } +async function searchAlias(name, domain) { + assert.strictEqual(typeof name, 'string'); + assert.strictEqual(typeof domain, 'string'); + + const results = await database.query(`SELECT ${MAILBOX_FIELDS} FROM mailboxes WHERE ? LIKE REPLACE(REPLACE(name, '*', '%'), '_', '\\_') AND type = ? AND domain = ?`, [ name, exports.TYPE_ALIAS, domain ]); + if (results.length === 0) return null; + + results.forEach(function (result) { postProcessMailbox(result); }); + + return results[0]; +} + async function getAliases(name, domain) { assert.strictEqual(typeof name, 'string'); assert.strictEqual(typeof domain, 'string'); @@ -1287,7 +1312,7 @@ async function setAliases(name, domain, aliases, auditSource) { const name = aliases[i].name.toLowerCase(); const domain = aliases[i].domain.toLowerCase(); - const error = validateName(name); + const error = validateAlias(name); if (error) throw error; const mailDomain = await getDomain(domain); diff --git a/src/routes/test/mail-test.js b/src/routes/test/mail-test.js index 4703c8dd7..59fd614a9 100644 --- a/src/routes/test/mail-test.js +++ b/src/routes/test/mail-test.js @@ -543,7 +543,7 @@ describe('Mail API', function () { it('set succeeds', async function () { const response = await superagent.put(`${serverUrl}/api/v1/mail/${dashboardDomain}/mailboxes/${MAILBOX_NAME}/aliases`) - .send({ aliases: [{ name: 'hello', domain: dashboardDomain}, {name: 'there', domain: dashboardDomain}] }) + .send({ aliases: [{ name: 'hello*', domain: dashboardDomain}, {name: 'there', domain: dashboardDomain}] }) .query({ access_token: owner.token }); expect(response.statusCode).to.equal(202); @@ -554,7 +554,7 @@ describe('Mail API', function () { .query({ access_token: owner.token }); expect(response.statusCode).to.equal(200); - expect(response.body.aliases).to.eql([{ name: 'hello', domain: dashboardDomain}, {name: 'there', domain: dashboardDomain}]); + expect(response.body.aliases).to.eql([{ name: 'hello*', domain: dashboardDomain}, {name: 'there', domain: dashboardDomain}]); }); it('get fails if mailbox does not exist', async function () { diff --git a/src/test/ldap-test.js b/src/test/ldap-test.js index 248792fba..7caf92521 100644 --- a/src/test/ldap-test.js +++ b/src/test/ldap-test.js @@ -67,12 +67,14 @@ describe('Ldap', function () { const mailbox = `support@${domain.domain}`; const mailAliasName = 'alsosupport'; const mailAlias = `alsosupport@${domain.domain}`; + const mailAliasWildcardName = 'help'; + const mailAliasWildcard = `helpmeplz@${domain.domain}`; before(function (done) { async.series([ setup, async () => await mail.addMailbox(mailboxName, domain.domain, { ownerId: user.id, ownerType: mail.OWNERTYPE_USER, active: true, storageQuota: 0, messagesQuota: 0 }, auditSource), - async () => await mail.setAliases(mailboxName, domain.domain, [ { name: mailAliasName, domain: domain.domain} ], auditSource), + async () => await mail.setAliases(mailboxName, domain.domain, [ { name: mailAliasName, domain: domain.domain}, { name: mailAliasWildcardName + '*', domain: domain.domain } ], auditSource), ldapServer.start.bind(null), async () => { group = await groups.add({ name: 'ldap-test-1' }); @@ -335,6 +337,13 @@ describe('Ldap', function () { expect(entries[0].rfc822MailMember).to.equal(mailbox); }); + it('get alias matching wildcard', async function () { + const entries = await ldapSearch(`cn=${mailAliasWildcard},ou=mailaliases,dc=cloudron`, 'objectclass=nismailalias'); + expect(entries.length).to.equal(1); + expect(entries[0].cn).to.equal(mailAliasWildcard); + expect(entries[0].rfc822MailMember).to.equal(mailbox); + }); + it('cannot get mailbox as alias', async function () { const [error] = await safe(ldapSearch(`cn=${mailbox},ou=mailaliases,dc=cloudron`, 'objectclass=nismailalias')); expect(error).to.be.a(ldap.NoSuchObjectError); diff --git a/src/test/mail-test.js b/src/test/mail-test.js index 74728dbb6..353549a6b 100644 --- a/src/test/mail-test.js +++ b/src/test/mail-test.js @@ -183,6 +183,19 @@ describe('Mail', function () { expect(results[1].domain).to.be(domain.domain); }); + it('can set wildcard alias', async function () { + await mail.setAliases('support', domain.domain, [ { name: 'support*', domain: domain.domain }, { name: 'help', domain: domain.domain } ], auditSource); + }); + + it('can get aliases of name', async function () { + const results = await mail.getAliases('support', domain.domain); + expect(results.length).to.be(2); + expect(results[0].name).to.be('help'); + expect(results[0].domain).to.be(domain.domain); + expect(results[1].name).to.be('support*'); + expect(results[1].domain).to.be(domain.domain); + }); + it('unset aliases', async function () { await mail.setAliases('support', domain.domain, [], auditSource);