re-do catch-all aliases, fixes #107 (originally #104)

This reverts pull request #105 from jonessen96/master (84d2023f94) 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.
This commit is contained in:
Joshua Tauberer 2014-07-13 12:13:41 +00:00
parent 84d2023f94
commit 9c7d476915
1 changed files with 22 additions and 12 deletions

View File

@ -3,7 +3,7 @@
import subprocess, shutil, os, sqlite3, re import subprocess, shutil, os, sqlite3, re
import utils import utils
def validate_email(email, strict): def validate_email(email, mode=None):
# There are a lot of characters permitted in email addresses, but # There are a lot of characters permitted in email addresses, but
# Dovecot's sqlite driver seems to get confused if there are any # Dovecot's sqlite driver seems to get confused if there are any
# unusual characters in the address. Bah. Also note that since # 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 len(email) > 255: return False
if strict: if mode == 'user':
# For Dovecot's benefit, only allow basic characters. # For Dovecot's benefit, only allow basic characters.
ATEXT = r'[\w\-]' 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, # 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 ATEXT = r'[\w!#$%&\'\*\+\-/=\?\^`\{\|\}~]' # see 3.2.4
else:
raise ValueError(mode)
DOT_ATOM_TEXT = r'(' + ATEXT + r'(?:\.' + ATEXT + r'+)*)' # see 3.2.4 # per RFC 2822 3.2.4
if not strict: DOT_ATOM_TEXT_LOCAL = ATEXT + r'+(?:\.' + ATEXT + r'+)*'
DOT_ATOM_TEXT += r'?' # allow an empty local part for catchalls 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 # as above, but we can require that the host part have at least
ADDR_SPEC = '^%s@%s$' % (DOT_ATOM_TEXT, DOT_ATOM_TEXT2) # see 3.4.1 # 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) 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): 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) return ("Invalid email address.", 400)
# get the database # get the database
@ -113,7 +123,7 @@ def remove_mail_user(email, env):
return kick(env, "mail user removed") return kick(env, "mail user removed")
def add_mail_alias(source, destination, env, do_kick=True): 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) return ("Invalid email address.", 400)
conn, c = open_database(env, with_connection=True) conn, c = open_database(env, with_connection=True)
@ -207,7 +217,7 @@ if __name__ == "__main__":
import sys import sys
if len(sys.argv) > 2 and sys.argv[1] == "validate-email": if len(sys.argv) > 2 and sys.argv[1] == "validate-email":
# Validate that we can create a Dovecot account for a given string. # 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) sys.exit(0)
else: else:
sys.exit(1) sys.exit(1)