support "domain aliases" (@domain => @domain aliases)
This seemed to already be technically supported but the validation is now stricter and the admin is more helpful: * Postfix seems to allow @domain.tld as an alias destination address but only if it is the only destination address (see the virtual man page). * Allow @domain.tld if it is the whole destination address string. * Otherwise, do not allow email addresses without local parts in the destination. * In the admin, add a third tab for making it clear how to add a domain alias. closes #265
This commit is contained in:
parent
9b9f5abf8f
commit
7e7abf3b53
|
@ -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))
|
||||
|
|
|
@ -13,22 +13,26 @@
|
|||
<div class="form-group">
|
||||
<div class="col-sm-offset-1 col-sm-11">
|
||||
<div id="alias_type_buttons" class="btn-group btn-group-xs">
|
||||
<button type="button" class="btn btn-default active">Regular</button>
|
||||
<button type="button" class="btn btn-default">Catch-All</button>
|
||||
<button type="button" class="btn btn-default active" data-mode="regular">Regular</button>
|
||||
<button type="button" class="btn btn-default" data-mode="catchall">Catch-All</button>
|
||||
<button type="button" class="btn btn-default" data-mode="domainalias">Domain Alias</button>
|
||||
</div>
|
||||
<div id="alias_mode_info" class="text-info small" style="display: none; margin: .5em 0 0 0;">
|
||||
<span class="catchall hidden">A catch-all alias captures all otherwise unmatched email to a domain. Enter just a part of an email address starting with the @-sign.</span>
|
||||
<span class="domainalias hidden">A domain alias forwards all otherwise unmatched mail from one domain to another domain, preserving the part before the @-sign.</span>
|
||||
</div>
|
||||
<div id="alias_catchall_info" class="text-info small" style="display: none; margin: .5em 0 0 0;">A catch-all alias captures all otherwise unmatched email to a domain. Enter just a part of an email address starting with the @-sign.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="addaliasEmail" class="col-sm-1 control-label">Alias</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="email" class="form-control" id="addaliasEmail" placeholder="incoming email address (you@yourdomain.com)">
|
||||
<input type="email" class="form-control" id="addaliasEmail">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="addaliasTargets" class="col-sm-1 control-label">Forward To</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" rows="3" id="addaliasTargets" placeholder="forward to these email addresses (one per line or separated by commas)"></textarea>
|
||||
<textarea class="form-control" rows="3" id="addaliasTargets"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@ -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 })
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue