From 9c7d4769154cd61be80d6e4889a008126725b8e6 Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Sun, 13 Jul 2014 12:13:41 +0000 Subject: [PATCH] re-do catch-all aliases, fixes #107 (originally #104) This reverts pull request #105 from jonessen96/master (84d2023f94a6b1f6d9b761ff0550789d629a665d) which was incorrect because it lost the "+" in DOT_ATOM_TEXT and so was not accepting any email addresses. Am taking the opportunity to make the code cleaner while I'm here. --- management/mailconfig.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/management/mailconfig.py b/management/mailconfig.py index 94342acb..b6fdb2df 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -3,7 +3,7 @@ import subprocess, shutil, os, sqlite3, re import utils -def validate_email(email, strict): +def validate_email(email, mode=None): # There are a lot of characters permitted in email addresses, but # Dovecot's sqlite driver seems to get confused if there are any # unusual characters in the address. Bah. Also note that since @@ -12,20 +12,30 @@ def validate_email(email, strict): if len(email) > 255: return False - if strict: + if mode == 'user': # For Dovecot's benefit, only allow basic characters. ATEXT = r'[\w\-]' - else: + elif mode == 'alias': + # For aliases, we can allow any valid email address. # Based on RFC 2822 and https://github.com/SyrusAkbary/validate_email/blob/master/validate_email.py, - # these characters are permitted in email address. + # these characters are permitted in email addresses. ATEXT = r'[\w!#$%&\'\*\+\-/=\?\^`\{\|\}~]' # see 3.2.4 + else: + raise ValueError(mode) - DOT_ATOM_TEXT = r'(' + ATEXT + r'(?:\.' + ATEXT + r'+)*)' # see 3.2.4 - if not strict: - DOT_ATOM_TEXT += r'?' # allow an empty local part for catchalls + # per RFC 2822 3.2.4 + DOT_ATOM_TEXT_LOCAL = ATEXT + r'+(?:\.' + ATEXT + r'+)*' + if mode == 'alias': + # For aliases, Postfix accepts '@domain.tld' format for + # catch-all addresses. Make the local part optional. + DOT_ATOM_TEXT_LOCAL = '(?:' + DOT_ATOM_TEXT_LOCAL + ')?' - DOT_ATOM_TEXT2 = ATEXT + r'+(?:\.' + ATEXT + r'+)+' # as above, but with a "+" since the host part must be under some TLD - ADDR_SPEC = '^%s@%s$' % (DOT_ATOM_TEXT, DOT_ATOM_TEXT2) # see 3.4.1 + # as above, but we can require that the host part have at least + # one period in it, so use a "+" rather than a "*" at the end + DOT_ATOM_TEXT_HOST = ATEXT + r'+(?:\.' + ATEXT + r'+)+' + + # per RFC 2822 3.4.1 + ADDR_SPEC = '^%s@%s$' % (DOT_ATOM_TEXT_LOCAL, DOT_ATOM_TEXT_HOST) return re.match(ADDR_SPEC, email) @@ -55,7 +65,7 @@ def get_mail_domains(env, filter_aliases=lambda alias : True): ) def add_mail_user(email, pw, env): - if not validate_email(email, True): + if not validate_email(email, mode='user'): return ("Invalid email address.", 400) # get the database @@ -113,7 +123,7 @@ def remove_mail_user(email, env): return kick(env, "mail user removed") def add_mail_alias(source, destination, env, do_kick=True): - if not validate_email(source, False): + if not validate_email(source, mode='alias'): return ("Invalid email address.", 400) conn, c = open_database(env, with_connection=True) @@ -207,7 +217,7 @@ if __name__ == "__main__": import sys if len(sys.argv) > 2 and sys.argv[1] == "validate-email": # Validate that we can create a Dovecot account for a given string. - if validate_email(sys.argv[2], True): + if validate_email(sys.argv[2], mode='user'): sys.exit(0) else: sys.exit(1)