diff --git a/management/mailconfig.py b/management/mailconfig.py index 603934d4..1ddcd473 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -15,7 +15,7 @@ def validate_email(email, mode=None): if mode == 'user': # For Dovecot's benefit, only allow basic characters. ATEXT = r'[\w\-]' - elif mode == 'alias': + elif mode in (None, '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 addresses. @@ -27,7 +27,8 @@ def validate_email(email, mode=None): 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. + # catch-all addresses on the source side and domain aliases + # on the destination side. Make the local part optional. DOT_ATOM_TEXT_LOCAL = '(?:' + DOT_ATOM_TEXT_LOCAL + ')?' # as above, but we can require that the host part have at least @@ -356,19 +357,28 @@ def add_mail_alias(source, destination, env, update_if_exists=False, do_kick=Tru if not validate_email(source, mode='alias'): return ("Invalid incoming email address (%s)." % source, 400) - # parse comma and \n-separated destination emails & validate + # validate destination dests = [] - for line in destination.split("\n"): - for email in line.split(","): - email = email.strip() - if email == "": continue - if not validate_email(email, mode='alias'): - return ("Invalid destination email address (%s)." % email, 400) - dests.append(email) + destination = destination.strip() + if validate_email(destination, mode='alias'): + # Oostfix allows a single @domain.tld as the destination, which means + # the local part on the address is preserved in the rewrite. + dests.append(destination) + else: + # Parse comma and \n-separated destination emails & validate. In this + # case, the recipients must be complete email addresses. + for line in destination.split("\n"): + for email in line.split(","): + email = email.strip() + if email == "": continue + if not validate_email(email): + return ("Invalid destination email address (%s)." % email, 400) + dests.append(email) if len(destination) == 0: return ("No destination email address(es) provided.", 400) destination = ",".join(dests) + # save to db conn, c = open_database(env, with_connection=True) try: c.execute("INSERT INTO aliases (source, destination) VALUES (?, ?)", (source, destination)) diff --git a/management/templates/aliases.html b/management/templates/aliases.html index 69bc62c4..86c8c4d4 100644 --- a/management/templates/aliases.html +++ b/management/templates/aliases.html @@ -13,22 +13,26 @@
- - + + + +
+ -
- +
- +
@@ -106,16 +110,28 @@ function show_aliases() { $('#alias_type_buttons button').off('click').click(function() { $('#alias_type_buttons button').removeClass('active'); $(this).addClass('active'); - if ($(this).text() == "Regular") { + if ($(this).attr('data-mode') == "regular") { $('#addaliasEmail').attr('type', 'email'); - $('#addaliasEmail').attr('placeholder', 'incoming email address (you@yourdomain.com)'); - $('#alias_catchall_info').slideUp(); - } else { + $('#addaliasEmail').attr('placeholder', 'incoming email address (e.g. you@yourdomain.com)'); + $('#addaliasTargets').attr('placeholder', 'forward to these email addresses (one per line or separated by commas)'); + $('#alias_mode_info').slideUp(); + } else if ($(this).attr('data-mode') == "catchall") { $('#addaliasEmail').attr('type', 'text'); - $('#addaliasEmail').attr('placeholder', 'incoming catch-all address (@yourdomain.com)'); - $('#alias_catchall_info').slideDown(); + $('#addaliasEmail').attr('placeholder', 'incoming catch-all address (e.g. @yourdomain.com)'); + $('#addaliasTargets').attr('placeholder', 'forward to these email addresses (one per line or separated by commas)'); + $('#alias_mode_info').slideDown(); + $('#alias_mode_info span').addClass('hidden'); + $('#alias_mode_info span.catchall').removeClass('hidden'); + } else if ($(this).attr('data-mode') == "domainalias") { + $('#addaliasEmail').attr('type', 'text'); + $('#addaliasEmail').attr('placeholder', 'incoming domain (@yourdomain.com)'); + $('#addaliasTargets').attr('placeholder', 'forward to domain (@yourdomain.com)'); + $('#alias_mode_info').slideDown(); + $('#alias_mode_info span').addClass('hidden'); + $('#alias_mode_info span.domainalias').removeClass('hidden'); } }) + $('#alias_type_buttons button[data-mode="regular"]').click(); // init }) } @@ -166,6 +182,12 @@ function aliases_edit(elem) { $("#addaliasEmail").val(email); $("#addaliasTargets").val(targets); $('#add-alias-button').text('Update'); + if (email.charAt(0) == '@' && targets.charAt(0) == '@') + $('#alias_type_buttons button[data-mode="domainalias"]').click(); + else if (email.charAt(0) == '@') + $('#alias_type_buttons button[data-mode="catchall"]').click(); + else + $('#alias_type_buttons button[data-mode="regular"]').click(); $('body').animate({ scrollTop: 0 }) }