diff --git a/management/daemon.py b/management/daemon.py index 3c712303..ef816c7d 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -156,7 +156,12 @@ def mail_users(): @authorized_personnel_only def mail_users_add(): 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: return (str(e), 400) @@ -208,7 +213,9 @@ def mail_aliases_add(): request.form.get('forwards_to', ''), request.form.get('permitted_senders', ''), 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']) @@ -336,7 +343,7 @@ def ssl_get_status(): # 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) - + # What's the current status of TLS certificates on all of the domain? 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 ] diff --git a/management/dns_update.py b/management/dns_update.py index b3764f7f..da7a6aae 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -15,15 +15,16 @@ from utils import shell, load_env_vars_from_file, safe_domain_name, sort_domains def get_dns_domains(env): # Add all domain names in use by email users and mail aliases and ensure # PRIMARY_HOSTNAME is in the list. - domains = set() - domains |= get_mail_domains(env) + domains = get_mail_domains(env, filter_list='dns') domains.add(env['PRIMARY_HOSTNAME']) return domains def get_dns_zones(env): # What domains should we create DNS zones for? Never create a zone for # a domain & a subdomain of that domain. - domains = get_dns_domains(env) + domains_mail = get_dns_domains(env) + domains_custom = set([n for n, *_ in get_custom_dns_config(env)]) + domains = domains_mail | domains_custom # Exclude domains that are subdomains of other domains we know. Proceed # by looking at shorter domains first. @@ -144,7 +145,7 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en # Define ns2.PRIMARY_HOSTNAME or whatever the user overrides. # User may provide one or more additional nameservers 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: records.append((None, "NS", secondary_ns+'.', False)) @@ -748,16 +749,16 @@ def write_custom_dns_config(config, env): f.write(config_yaml) def set_custom_dns_record(qname, rtype, value, action, env): - # validate qname - for zone, fn in get_dns_zones(env): - # It must match a zone apex or be a subdomain of a zone - # that we are otherwise hosting. - if qname == zone or qname.endswith("."+zone): - break - else: - # No match. - if qname != "_secondary_nameserver": - raise ValueError("%s is not a domain name or a subdomain of a domain name managed by this box." % qname) + # # validate qname + # for zone, fn in get_dns_zones(env): + # # It must match a zone apex or be a subdomain of a zone + # # that we are otherwise hosting. + # if qname == zone or qname.endswith("."+zone): + # break + # else: + # # No match. + # if qname != "_secondary_nameserver": + # raise ValueError("%s is not a domain name or a subdomain of a domain name managed by this box." % qname) # validate rtype rtype = rtype.upper() diff --git a/management/mailconfig.py b/management/mailconfig.py index 4cb57027..815b3369 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -3,8 +3,35 @@ import subprocess, shutil, os, sqlite3, re import utils from email_validator import validate_email as validate_email_, EmailNotValidError +import rtyaml 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): # Checks that an email address is syntactically valid. Returns True/False. # Until Postfix supports SMTPUTF8, an email address may contain ASCII @@ -254,15 +281,20 @@ def get_domain(emailaddr, as_unicode=True): pass 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 # configured on the system. return set( [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) ] - ) + ) - 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 if email.strip() == "": return ("No email address provided.", 400) @@ -304,6 +336,14 @@ def add_mail_user(email, pw, privs, env): # write databasebefore next step 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. return kick(env, "mail user added") @@ -348,6 +388,9 @@ def remove_mail_user(email, env): return ("That's not a user (%s)." % email, 400) 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. return kick(env, "mail user removed") @@ -396,7 +439,7 @@ def add_remove_mail_user_privilege(email, priv, action, env): 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 address = sanitize_idn_email_address(address) @@ -485,6 +528,14 @@ def add_mail_alias(address, forwards_to, permitted_senders, env, update_if_exist 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: # Update things in case any new domains are added. return kick(env, return_status) @@ -500,6 +551,9 @@ def remove_mail_alias(address, env, do_kick=True): return ("That's not an alias (%s)." % address, 400) conn.commit() + remove_domain_blacklist(env, get_domain(address), 'dns') + remove_domain_blacklist(env, get_domain(address), 'www') + if do_kick: # Update things in case any domains are removed. return kick(env, "alias removed") diff --git a/management/templates/aliases.html b/management/templates/aliases.html index 78556df8..0f2bfd6d 100644 --- a/management/templates/aliases.html +++ b/management/templates/aliases.html @@ -26,7 +26,7 @@
- +
@@ -36,7 +36,7 @@
- +
@@ -45,7 +45,7 @@
- +
+
+ +
+
+ + + +
+ +
+
@@ -200,7 +214,33 @@ function show_aliases() { } }) $('#alias_type_buttons button[data-mode="regular"]').click(); // init - }) + + // Service buttons + $('#alias_service_buttons button').off('click').click(function() { + if ($(this).hasClass('active')) { + $(this).removeClass('active'); + $(this).addClass('btn-default'); + $(this).removeClass('btn-success'); + $('#alias_service_info .www').addClass('hidden'); + $('#alias_service_info .dns').addClass('hidden'); + if ($(this).attr('data-mode') == "dns") { + $('#alias_service_info').slideDown(); + $('#addalias-form .dns').removeClass('hidden'); + } else if ($(this).attr('data-mode') == "www") { + $('#alias_service_info').slideDown(); + $('#addalias-form .www').removeClass('hidden'); + } + } else { + $(this).addClass('active'); + $(this).removeClass('btn-default'); + $(this).addClass('btn-success'); + $('#alias_service_info').slideUp(); + $('#addalias-form .www').addClass('hidden'); + $('#addalias-form .dns').addClass('hidden'); + } + return false; + }); + }); } var is_alias_add_update = false; @@ -209,6 +249,8 @@ function do_add_alias() { var form_address = $("#addaliasAddress").val(); var form_forwardsto = $("#addaliasForwardsTo").val(); var form_senders = ($('#addaliasForwardsToAdvanced').prop('checked') ? $("#addaliasSenders").val() : ''); + var form_dns = $('#addaliasDnsHandling').hasClass('active'); + var form_web = $('#addaliasWebHandling').hasClass('active'); if ($('#addaliasForwardsToAdvanced').prop('checked') && !/\S/.exec($("#addaliasSenders").val())) { show_modal_error(title, "You did not enter any permitted senders."); return false; @@ -220,7 +262,9 @@ function do_add_alias() { update_if_exists: is_alias_add_update ? '1' : '0', address: form_address, forwards_to: form_forwardsto, - permitted_senders: form_senders + permitted_senders: form_senders, + dns_enabled: form_dns, + web_enabled: form_web }, function(r) { // Responses are multiple lines of pre-formatted text. @@ -242,8 +286,14 @@ function aliases_reset_form() { $('#alias-cancel').addClass('hidden'); $('#add-alias-button').text('Add Alias'); is_alias_add_update = false; + + $('#addaliasDnsHandling').prop('disabled', false).addClass('btn-success'); + $('#addaliasWebHandling').prop('disabled', false).addClass('btn-success'); + } + + function aliases_edit(elem) { var address = $(elem).parents('tr').attr('data-address'); var receiverdivs = $(elem).parents('tr').find('.forwardsTo div'); @@ -267,6 +317,8 @@ function aliases_edit(elem) { $('#addaliasForwardsToAdvanced').prop('checked', senders != ""); $('#addaliasForwardsToNotAdvanced').prop('checked', senders == ""); $("#addaliasSenders").val(senders); + $('#addaliasDnsHandling').prop('disabled', true); + $('#addaliasWebHandling').prop('disabled', true); $('#add-alias-button').text('Update'); $('body').animate({ scrollTop: 0 }) is_alias_add_update = true; diff --git a/management/templates/custom-dns.html b/management/templates/custom-dns.html index ac530cd2..3fc3523c 100644 --- a/management/templates/custom-dns.html +++ b/management/templates/custom-dns.html @@ -23,6 +23,13 @@ . +
+ + + + +
+
Leave the left field blank to set a record on the chosen domain name, or enter a subdomain.
@@ -166,19 +173,69 @@ function show_custom_dns() { $('#secondarydns-clear-instructions').toggle(data.hostnames.length > 0); }); + $('#customdnsZone').text(''); + var dns_list_domains = $('').attr('label', 'Domains'); + var dns_list_customs = $('').attr('label', 'Custom'); + var dns_list_advanced = $('').attr('label', 'Advanced'); + + $('#customdnsZone').append(dns_list_domains); + $('#customdnsZone').append(dns_list_customs); + $('#customdnsZone').append(dns_list_advanced); + + dns_list_advanced.append($('
+
+
+
+ + + +
+
+
+