Added support of mail only configuration

- added checkboxes to disable web and/or dns on alias or user creation
- added ignore lists for domain not being handled on either web or dns
- added API for managing ignore lists

Signed-off-by: Bernard `Guyzmo` Pratz <guyzmo+github@m0g.net>
This commit is contained in:
Bernard `Guyzmo` Pratz 2016-01-24 14:43:16 +00:00
parent 62b6117638
commit 1d9b678d89
6 changed files with 120 additions and 14 deletions

View File

@ -155,7 +155,12 @@ def mail_users():
@authorized_personnel_only @authorized_personnel_only
def mail_users_add(): def mail_users_add():
try: try:
return add_mail_user(request.form.get('email', ''), request.form.get('password', ''), request.form.get('privileges', ''), env) return add_mail_user(request.form.get('email', ''),
request.form.get('password', ''),
request.form.get('privileges', ''),
env,
dns_enabled=request.form.get('dns_enabled', False),
web_enabled=request.form.get('web_enabled', False))
except ValueError as e: except ValueError as e:
return (str(e), 400) return (str(e), 400)
@ -207,7 +212,9 @@ def mail_aliases_add():
request.form.get('forwards_to', ''), request.form.get('forwards_to', ''),
request.form.get('permitted_senders', ''), request.form.get('permitted_senders', ''),
env, env,
update_if_exists=(request.form.get('update_if_exists', '') == '1') update_if_exists=(request.form.get('update_if_exists', '') == '1'),
dns_enabled=request.form.get('dns_enabled', False),
web_enabled=request.form.get('web_enabled', False)
) )
@app.route('/mail/aliases/remove', methods=['POST']) @app.route('/mail/aliases/remove', methods=['POST'])
@ -335,7 +342,7 @@ def ssl_get_status():
# What domains can we provision certificates for? What unexpected problems do we have? # What domains can we provision certificates for? What unexpected problems do we have?
provision, cant_provision = get_certificates_to_provision(env, show_extended_problems=False) provision, cant_provision = get_certificates_to_provision(env, show_extended_problems=False)
# What's the current status of TLS certificates on all of the domain? # What's the current status of TLS certificates on all of the domain?
domains_status = get_web_domains_info(env) domains_status = get_web_domains_info(env)
domains_status = [{ "domain": d["domain"], "status": d["ssl_certificate"][0], "text": d["ssl_certificate"][1] } for d in domains_status ] domains_status = [{ "domain": d["domain"], "status": d["ssl_certificate"][0], "text": d["ssl_certificate"][1] } for d in domains_status ]

View File

@ -15,8 +15,7 @@ from utils import shell, load_env_vars_from_file, safe_domain_name, sort_domains
def get_dns_domains(env): def get_dns_domains(env):
# Add all domain names in use by email users and mail aliases and ensure # Add all domain names in use by email users and mail aliases and ensure
# PRIMARY_HOSTNAME is in the list. # PRIMARY_HOSTNAME is in the list.
domains = set() domains = get_mail_domains(env, filter_list='dns')
domains |= get_mail_domains(env)
domains.add(env['PRIMARY_HOSTNAME']) domains.add(env['PRIMARY_HOSTNAME'])
return domains return domains
@ -144,7 +143,7 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
# Define ns2.PRIMARY_HOSTNAME or whatever the user overrides. # Define ns2.PRIMARY_HOSTNAME or whatever the user overrides.
# User may provide one or more additional nameservers # User may provide one or more additional nameservers
secondary_ns_list = get_secondary_dns(additional_records, mode="NS") \ secondary_ns_list = get_secondary_dns(additional_records, mode="NS") \
or ["ns2." + env["PRIMARY_HOSTNAME"]] or ["ns2." + env["PRIMARY_HOSTNAME"]]
for secondary_ns in secondary_ns_list: for secondary_ns in secondary_ns_list:
records.append((None, "NS", secondary_ns+'.', False)) records.append((None, "NS", secondary_ns+'.', False))

View File

@ -3,8 +3,35 @@
import subprocess, shutil, os, sqlite3, re import subprocess, shutil, os, sqlite3, re
import utils import utils
from email_validator import validate_email as validate_email_, EmailNotValidError from email_validator import validate_email as validate_email_, EmailNotValidError
import rtyaml
import idna import idna
def load_domain_blacklist(env, service):
try:
return set(rtyaml.load(open(os.path.join(env['STORAGE_ROOT'], '{}/ignored.yaml'.format(service)))))
except:
return set()
def add_domain_blacklist(env, domain, service):
try:
ignored = set(rtyaml.load(open(os.path.join(env['STORAGE_ROOT'], '{}/ignored.yaml'.format(service)))))
ignored.add(domain)
except Exception as err:
# if non-existent or baldy formatted, consider it new
ignored = {domain}
with open(os.path.join(env['STORAGE_ROOT'], '{}/ignored.yaml'.format(service)), "w") as f:
f.write(rtyaml.dump(list(ignored)))
def remove_domain_blacklist(env, domain, service):
try:
ignored = set(rtyaml.load(open(os.path.join(env['STORAGE_ROOT'], '{}/ignored.yaml'.format(service)))))
if domain in ignored:
ignored.remove(domain)
with open(os.path.join(env['STORAGE_ROOT'], '{}/ignored.yaml'.format(service)), "w") as f:
f.write(rtyaml.dump(list(ignored)))
except Exception as err:
pass
def validate_email(email, mode=None): def validate_email(email, mode=None):
# Checks that an email address is syntactically valid. Returns True/False. # Checks that an email address is syntactically valid. Returns True/False.
# Until Postfix supports SMTPUTF8, an email address may contain ASCII # Until Postfix supports SMTPUTF8, an email address may contain ASCII
@ -253,15 +280,20 @@ def get_domain(emailaddr, as_unicode=True):
pass pass
return ret return ret
def get_mail_domains(env, filter_aliases=lambda alias : True): def get_mail_domains(env, filter_aliases=lambda alias : True, filter_list=None):
ignored = set()
if filter_list == 'dns':
ignored |= load_domain_blacklist(env, 'dns')
if filter_list in ('web', 'www'):
ignored |= load_domain_blacklist(env, 'www')
# Returns the domain names (IDNA-encoded) of all of the email addresses # Returns the domain names (IDNA-encoded) of all of the email addresses
# configured on the system. # configured on the system.
return set( return set(
[get_domain(login, as_unicode=False) for login in get_mail_users(env)] [get_domain(login, as_unicode=False) for login in get_mail_users(env)]
+ [get_domain(address, as_unicode=False) for address, *_ in get_mail_aliases(env) if filter_aliases(address) ] + [get_domain(address, as_unicode=False) for address, *_ in get_mail_aliases(env) if filter_aliases(address) ]
) ) - ignored
def add_mail_user(email, pw, privs, env): def add_mail_user(email, pw, privs, env, dns_enabled=True, web_enabled=True):
# validate email # validate email
if email.strip() == "": if email.strip() == "":
return ("No email address provided.", 400) return ("No email address provided.", 400)
@ -303,6 +335,14 @@ def add_mail_user(email, pw, privs, env):
# write databasebefore next step # write databasebefore next step
conn.commit() conn.commit()
# Add non dns enabled domain to the ignored domain list
if dns_enabled == 'false':
add_domain_blacklist(env, get_domain(email), 'dns')
# Add non web enabled domain to the ignored domain list
if web_enabled == 'false':
add_domain_blacklist(env, get_domain(email), 'www')
# Update things in case any new domains are added. # Update things in case any new domains are added.
return kick(env, "mail user added") return kick(env, "mail user added")
@ -347,6 +387,9 @@ def remove_mail_user(email, env):
return ("That's not a user (%s)." % email, 400) return ("That's not a user (%s)." % email, 400)
conn.commit() conn.commit()
remove_domain_blacklist(env, get_domain(address), 'dns')
remove_domain_blacklist(env, get_domain(address), 'www')
# Update things in case any domains are removed. # Update things in case any domains are removed.
return kick(env, "mail user removed") return kick(env, "mail user removed")
@ -395,7 +438,7 @@ def add_remove_mail_user_privilege(email, priv, action, env):
return "OK" return "OK"
def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exists=False, do_kick=True): def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exists=False, dns_enabled=True, web_enabled=True, do_kick=True):
# convert Unicode domain to IDNA # convert Unicode domain to IDNA
address = sanitize_idn_email_address(address) address = sanitize_idn_email_address(address)
@ -484,6 +527,14 @@ def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exist
conn.commit() conn.commit()
# Add non dns enabled domain to the ignored domain list
if dns_enabled == 'false':
add_domain_blacklist(env, get_domain(address), 'dns')
# Add non web enabled domain to the ignored domain list
if web_enabled == 'false':
add_domain_blacklist(env, get_domain(address), 'www')
if do_kick: if do_kick:
# Update things in case any new domains are added. # Update things in case any new domains are added.
return kick(env, return_status) return kick(env, return_status)
@ -499,6 +550,9 @@ def remove_mail_alias(address, env, do_kick=True):
return ("That's not an alias (%s)." % address, 400) return ("That's not an alias (%s)." % address, 400)
conn.commit() conn.commit()
remove_domain_blacklist(env, get_domain(address), 'dns')
remove_domain_blacklist(env, get_domain(address), 'www')
if do_kick: if do_kick:
# Update things in case any domains are removed. # Update things in case any domains are removed.
return kick(env, "alias removed") return kick(env, "alias removed")

View File

@ -64,6 +64,23 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group">
<label for="addaliasDnsHandling" class="col-sm-1 control-label">New domain</label>
<div class="col-sm-10">
<div class="checkbox">
<label>
<input id="addaliasDnsHandling" name="addaliasDnsHandling" type="checkbox" checked>
If the email is of a non-managed domain, create DNS configuration for the domain.
</label>
</div>
<div class="checkbox">
<label>
<input id="addaliasWebHandling" name="addaliasWebHandling" type="checkbox" id="addaliasWebHandling" checked>
If the email is of a non-managed domain, create Web configuration for the domain.
</label>
</div>
</div>
</div>
<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">
<button id="add-alias-button" type="submit" class="btn btn-primary">Add Alias</button> <button id="add-alias-button" type="submit" class="btn btn-primary">Add Alias</button>
@ -174,6 +191,8 @@ function do_add_alias() {
var form_address = $("#addaliasAddress").val(); var form_address = $("#addaliasAddress").val();
var form_forwardsto = $("#addaliasForwardsTo").val(); var form_forwardsto = $("#addaliasForwardsTo").val();
var form_senders = ($('#addaliasForwardsToAdvanced').prop('checked') ? $("#addaliasSenders").val() : ''); var form_senders = ($('#addaliasForwardsToAdvanced').prop('checked') ? $("#addaliasSenders").val() : '');
var form_dns = $('#addaliasDnsHandling').prop('checked');
var form_web = $('#addaliasWebHandling').prop('checked');
if ($('#addaliasForwardsToAdvanced').prop('checked') && !/\S/.exec($("#addaliasSenders").val())) { if ($('#addaliasForwardsToAdvanced').prop('checked') && !/\S/.exec($("#addaliasSenders").val())) {
show_modal_error(title, "You did not enter any permitted senders."); show_modal_error(title, "You did not enter any permitted senders.");
return false; return false;
@ -185,7 +204,9 @@ function do_add_alias() {
update_if_exists: is_alias_add_update ? '1' : '0', update_if_exists: is_alias_add_update ? '1' : '0',
address: form_address, address: form_address,
forwards_to: form_forwardsto, forwards_to: form_forwardsto,
permitted_senders: form_senders permitted_senders: form_senders,
dns_enabled: form_dns,
web_enabled: form_web
}, },
function(r) { function(r) {
// Responses are multiple lines of pre-formatted text. // Responses are multiple lines of pre-formatted text.
@ -204,6 +225,8 @@ function aliases_reset_form() {
$("#addaliasAddress").val('') $("#addaliasAddress").val('')
$("#addaliasForwardsTo").val('') $("#addaliasForwardsTo").val('')
$("#addaliasSenders").val('') $("#addaliasSenders").val('')
$('#addaliasDnsHandling').prop('disabled', false);
$('#addaliasWebHandling').prop('disabled', false);
$('#alias-cancel').addClass('hidden'); $('#alias-cancel').addClass('hidden');
$('#add-alias-button').text('Add Alias'); $('#add-alias-button').text('Add Alias');
is_alias_add_update = false; is_alias_add_update = false;
@ -232,6 +255,8 @@ function aliases_edit(elem) {
$('#addaliasForwardsToAdvanced').prop('checked', senders != ""); $('#addaliasForwardsToAdvanced').prop('checked', senders != "");
$('#addaliasForwardsToNotAdvanced').prop('checked', senders == ""); $('#addaliasForwardsToNotAdvanced').prop('checked', senders == "");
$("#addaliasSenders").val(senders); $("#addaliasSenders").val(senders);
$('#addaliasDnsHandling').prop('disabled', true);
$('#addaliasWebHandling').prop('disabled', true);
$('#add-alias-button').text('Update'); $('#add-alias-button').text('Update');
$('body').animate({ scrollTop: 0 }) $('body').animate({ scrollTop: 0 })
is_alias_add_update = true; is_alias_add_update = true;

View File

@ -28,6 +28,23 @@
<option value="admin">Administrator</option> <option value="admin">Administrator</option>
</select> </select>
</div> </div>
<div class="form-group">
<label for="adduserDnsHandling" class="col-sm-1 control-label">New domain</label>
<div class="col-sm-10">
<div class="checkbox">
<label>
<input id="adduserDnsHandling" name="adduserDnsHandling" type="checkbox" checked>
If the email is of a non-managed domain, create DNS configuration for the domain.
</label>
</div>
<div class="checkbox">
<label>
<input id="adduserWebHandling" name="adduserWebHandling" type="checkbox" id="adduserWebHandling" checked>
If the email is of a non-managed domain, create Web configuration for the domain.
</label>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Add User</button> <button type="submit" class="btn btn-primary">Add User</button>
</form> </form>
<ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;"> <ul style="margin-top: 1em; padding-left: 1.5em; font-size: 90%;">
@ -143,13 +160,17 @@ function do_add_user() {
var email = $("#adduserEmail").val(); var email = $("#adduserEmail").val();
var pw = $("#adduserPassword").val(); var pw = $("#adduserPassword").val();
var privs = $("#adduserPrivs").val(); var privs = $("#adduserPrivs").val();
var form_dns = $('#adduserDnsHandling').prop('checked');
var form_web = $('#adduserWebHandling').prop('checked');
api( api(
"/mail/users/add", "/mail/users/add",
"POST", "POST",
{ {
email: email, email: email,
password: pw, password: pw,
privileges: privs privileges: privs,
dns_enabled: form_dns,
web_enabled: form_web
}, },
function(r) { function(r) {
// Responses are multiple lines of pre-formatted text. // Responses are multiple lines of pre-formatted text.

View File

@ -16,14 +16,14 @@ def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True)
# Serve web for all mail domains so that we might at least # Serve web for all mail domains so that we might at least
# provide auto-discover of email settings, and also a static website # provide auto-discover of email settings, and also a static website
# if the user wants to make one. # if the user wants to make one.
domains |= get_mail_domains(env) domains |= get_mail_domains(env, filter_list='www')
if include_www_redirects: if include_www_redirects:
# Add 'www.' subdomains that we want to provide default redirects # Add 'www.' subdomains that we want to provide default redirects
# to the main domain for. We'll add 'www.' to any DNS zones, i.e. # to the main domain for. We'll add 'www.' to any DNS zones, i.e.
# the topmost of each domain we serve. # the topmost of each domain we serve.
domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env)) domains |= set('www.' + zone for zone, zonefile in get_dns_zones(env))
if exclude_dns_elsewhere: if exclude_dns_elsewhere:
# ...Unless the domain has an A/AAAA record that maps it to a different # ...Unless the domain has an A/AAAA record that maps it to a different
# IP address than this box. Remove those domains from our list. # IP address than this box. Remove those domains from our list.