mirror of
				https://github.com/mail-in-a-box/mailinabox.git
				synced 2025-10-30 18:50:53 +00:00 
			
		
		
		
	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': | 	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\-]' | ||||||
| 	elif mode == 'alias': | 	elif mode in (None, 'alias'): | ||||||
| 		# For aliases, we can allow any valid email address. | 		# 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 addresses. | 		# 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'+)*' | 	DOT_ATOM_TEXT_LOCAL = ATEXT + r'+(?:\.' + ATEXT + r'+)*' | ||||||
| 	if mode == 'alias': | 	if mode == 'alias': | ||||||
| 		# For aliases, Postfix accepts '@domain.tld' format for | 		# 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 + ')?' | 		DOT_ATOM_TEXT_LOCAL = '(?:' + DOT_ATOM_TEXT_LOCAL + ')?' | ||||||
| 
 | 
 | ||||||
| 	# as above, but we can require that the host part have at least | 	# 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'): | 	if not validate_email(source, mode='alias'): | ||||||
| 		return ("Invalid incoming email address (%s)." % source, 400) | 		return ("Invalid incoming email address (%s)." % source, 400) | ||||||
| 
 | 
 | ||||||
| 	# parse comma and \n-separated destination emails & validate | 	# validate destination | ||||||
| 	dests = [] | 	dests = [] | ||||||
|  | 	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 line in destination.split("\n"): | ||||||
| 			for email in line.split(","): | 			for email in line.split(","): | ||||||
| 				email = email.strip() | 				email = email.strip() | ||||||
| 				if email == "": continue | 				if email == "": continue | ||||||
| 			if not validate_email(email, mode='alias'): | 				if not validate_email(email): | ||||||
| 					return ("Invalid destination email address (%s)." % email, 400) | 					return ("Invalid destination email address (%s)." % email, 400) | ||||||
| 				dests.append(email) | 				dests.append(email) | ||||||
| 	if len(destination) == 0: | 	if len(destination) == 0: | ||||||
| 		return ("No destination email address(es) provided.", 400) | 		return ("No destination email address(es) provided.", 400) | ||||||
| 	destination = ",".join(dests) | 	destination = ",".join(dests) | ||||||
| 
 | 
 | ||||||
|  | 	# save to db | ||||||
| 	conn, c = open_database(env, with_connection=True) | 	conn, c = open_database(env, with_connection=True) | ||||||
| 	try: | 	try: | ||||||
| 		c.execute("INSERT INTO aliases (source, destination) VALUES (?, ?)", (source, destination)) | 		c.execute("INSERT INTO aliases (source, destination) VALUES (?, ?)", (source, destination)) | ||||||
|  | |||||||
| @ -13,22 +13,26 @@ | |||||||
|   <div class="form-group"> |   <div class="form-group"> | ||||||
|     <div class="col-sm-offset-1 col-sm-11"> |     <div class="col-sm-offset-1 col-sm-11"> | ||||||
|       <div id="alias_type_buttons" class="btn-group btn-group-xs"> |       <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 active" data-mode="regular">Regular</button> | ||||||
|         <button type="button" class="btn btn-default">Catch-All</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> | ||||||
|       <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> |   </div> | ||||||
|   <div class="form-group"> |   <div class="form-group"> | ||||||
|     <label for="addaliasEmail" class="col-sm-1 control-label">Alias</label> |     <label for="addaliasEmail" class="col-sm-1 control-label">Alias</label> | ||||||
|     <div class="col-sm-10"> |     <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> |   </div> | ||||||
|   <div class="form-group"> |   <div class="form-group"> | ||||||
|     <label for="addaliasTargets" class="col-sm-1 control-label">Forward To</label> |     <label for="addaliasTargets" class="col-sm-1 control-label">Forward To</label> | ||||||
|     <div class="col-sm-10"> |     <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> |   </div> | ||||||
|   <div class="form-group"> |   <div class="form-group"> | ||||||
| @ -106,16 +110,28 @@ function show_aliases() { | |||||||
|     $('#alias_type_buttons button').off('click').click(function() { |     $('#alias_type_buttons button').off('click').click(function() { | ||||||
|       $('#alias_type_buttons button').removeClass('active'); |       $('#alias_type_buttons button').removeClass('active'); | ||||||
|       $(this).addClass('active'); |       $(this).addClass('active'); | ||||||
|       if ($(this).text() == "Regular") { |       if ($(this).attr('data-mode') == "regular") { | ||||||
|         $('#addaliasEmail').attr('type', 'email'); |         $('#addaliasEmail').attr('type', 'email'); | ||||||
|         $('#addaliasEmail').attr('placeholder', 'incoming email address (you@yourdomain.com)'); |         $('#addaliasEmail').attr('placeholder', 'incoming email address (e.g. you@yourdomain.com)'); | ||||||
|         $('#alias_catchall_info').slideUp(); | 	$('#addaliasTargets').attr('placeholder', 'forward to these email addresses (one per line or separated by commas)'); | ||||||
|       } else { |         $('#alias_mode_info').slideUp(); | ||||||
|  |       } else if ($(this).attr('data-mode') == "catchall") { | ||||||
|         $('#addaliasEmail').attr('type', 'text'); |         $('#addaliasEmail').attr('type', 'text'); | ||||||
|         $('#addaliasEmail').attr('placeholder', 'incoming catch-all address (@yourdomain.com)'); |         $('#addaliasEmail').attr('placeholder', 'incoming catch-all address (e.g. @yourdomain.com)'); | ||||||
|         $('#alias_catchall_info').slideDown(); | 	$('#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); |   $("#addaliasEmail").val(email); | ||||||
|   $("#addaliasTargets").val(targets); |   $("#addaliasTargets").val(targets); | ||||||
|   $('#add-alias-button').text('Update'); |   $('#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 }) |   $('body').animate({ scrollTop: 0 }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user