diff --git a/management/daemon.py b/management/daemon.py index af15b1c..1b374c1 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -45,7 +45,7 @@ def authorized_personnel_only(viewfunc): # Authorized to access an API view? if "admin" in privs: - # Call view func. + # Call view func. return viewfunc(*args, **kwargs) elif not error: error = "You are not an administrator." @@ -179,7 +179,7 @@ def mail_aliases(): if request.args.get("format", "") == "json": return json_response(get_mail_aliases_ex(env)) else: - return "".join(x+"\t"+y+"\n" for x, y in get_mail_aliases(env)) + return "".join(source+"\t"+destination+"\t"+applies_inbound+"\t"+applies_outbound+"\n" for source, destination, applies_inbound, applies_outbound in get_mail_aliases(env)) @app.route('/mail/aliases/add', methods=['POST']) @authorized_personnel_only @@ -187,6 +187,8 @@ def mail_aliases_add(): return add_mail_alias( request.form.get('source', ''), request.form.get('destination', ''), + request.form.get('applies_inbound', '') == '1', + request.form.get('applies_outbound', '') == '1', env, update_if_exists=(request.form.get('update_if_exists', '') == '1') ) @@ -283,7 +285,7 @@ def dns_set_record(qname, rtype="A"): # make this action set (replace all records for this # qname-rtype pair) rather than add (add a new record). action = "set" - + elif request.method == "DELETE": if value == '': # Delete all records for this qname-type pair. diff --git a/management/mailconfig.py b/management/mailconfig.py index 34cc676..c5ff34e 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -181,13 +181,13 @@ def get_admins(env): return users def get_mail_aliases(env): - # Returns a sorted list of tuples of (alias, forward-to string). + # Returns a sorted list of tuples of (alias, forward-to string, applies-to-inbound-mail, applies-to-outbound-mail). c = open_database(env) - c.execute('SELECT source, destination FROM aliases') - aliases = { row[0]: row[1] for row in c.fetchall() } # make dict + c.execute('SELECT source, destination, applies_inbound, applies_outbound FROM aliases') + aliases = { row[0]: row[1:4] for row in c.fetchall() } # make dict # put in a canonical order: sort by domain, then by email address lexicographically - aliases = [ (source, aliases[source]) for source in utils.sort_email_addresses(aliases.keys(), env) ] + aliases = [ (source,) + aliases[source] for source in utils.sort_email_addresses(aliases.keys(), env) ] return aliases def get_mail_aliases_ex(env): @@ -202,6 +202,8 @@ def get_mail_aliases_ex(env): # source: "name@domain.tld", # IDNA-encoded # source_display: "name@domain.tld", # full Unicode # destination: ["target1@domain.com", "target2@domain.com", ...], + # applies_inbound: True|False + # applies_outbound: True|False # required: True|False # }, # ... @@ -212,7 +214,7 @@ def get_mail_aliases_ex(env): required_aliases = get_required_aliases(env) domains = {} - for source, destination in get_mail_aliases(env): + for source, destination, applies_inbound, applies_outbound in get_mail_aliases(env): # get alias info domain = get_domain(source) required = (source in required_aliases) @@ -227,6 +229,8 @@ def get_mail_aliases_ex(env): "source": source, "source_display": prettify_idn_email_address(source), "destination": [prettify_idn_email_address(d.strip()) for d in destination.split(",")], + "applies_inbound": True if applies_inbound == 1 else False, + "applies_outbound": True if applies_outbound == 1 else False, "required": required, }) @@ -250,7 +254,7 @@ def get_mail_domains(env, filter_aliases=lambda alias : True): # configured on the system. return set( [get_domain(addr, as_unicode=False) for addr in get_mail_users(env)] - + [get_domain(source, as_unicode=False) for source, target in get_mail_aliases(env) if filter_aliases((source, target)) ] + + [get_domain(source, as_unicode=False) for source, *_ in get_mail_aliases(env) if filter_aliases(source) ] ) def add_mail_user(email, pw, privs, env): @@ -406,7 +410,7 @@ def add_remove_mail_user_privilege(email, priv, action, env): return "OK" -def add_mail_alias(source, destination, env, update_if_exists=False, do_kick=True): +def add_mail_alias(source, destination, applies_inbound, applies_outbound, env, update_if_exists=False, do_kick=True): # convert Unicode domain to IDNA source = sanitize_idn_email_address(source) @@ -460,13 +464,13 @@ def add_mail_alias(source, destination, env, update_if_exists=False, do_kick=Tru # save to db conn, c = open_database(env, with_connection=True) try: - c.execute("INSERT INTO aliases (source, destination) VALUES (?, ?)", (source, destination)) + c.execute("INSERT INTO aliases (source, destination, applies_inbound, applies_outbound) VALUES (?, ?, ?, ?)", (source, destination, 1 if applies_inbound else 0, 1 if applies_outbound else 0)) return_status = "alias added" except sqlite3.IntegrityError: if not update_if_exists: return ("Alias already exists (%s)." % source, 400) else: - c.execute("UPDATE aliases SET destination = ? WHERE source = ?", (destination, source)) + c.execute("UPDATE aliases SET destination = ?, applies_inbound = ?, applies_outbound = ? WHERE source = ?", (destination, 1 if applies_inbound else 0, 1 if applies_outbound else 0, source)) return_status = "alias updated" conn.commit() @@ -507,8 +511,8 @@ def get_required_aliases(env): # email on that domain are the required aliases or a catch-all/domain-forwarder. real_mail_domains = get_mail_domains(env, filter_aliases = lambda alias : - not alias[0].startswith("postmaster@") and not alias[0].startswith("admin@") - and not alias[0].startswith("@") + not alias.startswith("postmaster@") and not alias.startswith("admin@") + and not alias.startswith("@") ) # Create postmaster@ and admin@ for all domains we serve mail on. @@ -541,14 +545,14 @@ def kick(env, mail_result=None): return # Does this alias exists? - for s, t in existing_aliases: + for s, *_ in existing_aliases: if s == source: return # Doesn't exist. administrator = get_system_administrator(env) if source == administrator: return # don't make an alias from the administrator to itself --- this alias must be created manually - add_mail_alias(source, administrator, env, do_kick=False) + add_mail_alias(source, administrator, True, True, env, do_kick=False) results.append("added alias %s (=> %s)\n" % (source, administrator)) for alias in required_aliases: @@ -556,7 +560,7 @@ def kick(env, mail_result=None): # Remove auto-generated postmaster/admin on domains we no # longer have any other email addresses for. - for source, target in existing_aliases: + for source, target, *_ in existing_aliases: user, domain = source.split("@") if user in ("postmaster", "admin") \ and source not in required_aliases \ diff --git a/management/status_checks.py b/management/status_checks.py index 7c89b30..2825654 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -33,7 +33,7 @@ def run_checks(rounded_values, env, output, pool): # (ignore errors; if bind9/rndc isn't running we'd already report # that in run_services checks.) shell('check_call', ["/usr/sbin/rndc", "flush"], trap=True) - + run_system_checks(rounded_values, env, output) # perform other checks asynchronously @@ -264,10 +264,10 @@ def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zone if domain == env["PRIMARY_HOSTNAME"]: check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles) - + if domain in dns_domains: check_dns_zone(domain, env, output, dns_zonefiles) - + if domain in mail_domains: check_mail_domain(domain, env, output) @@ -351,11 +351,14 @@ def check_primary_hostname_dns(domain, env, output, dns_domains, dns_zonefiles): check_alias_exists("Hostmaster contact address", "hostmaster@" + domain, env, output) def check_alias_exists(alias_name, alias, env, output): - mail_alises = dict(get_mail_aliases(env)) - if alias in mail_alises: - output.print_ok("%s exists as a mail alias. [%s ↦ %s]" % (alias_name, alias, mail_alises[alias])) + mail_aliases = dict([(source, (destination, applies_inbound)) for source, destination, applies_inbound, *_ in get_mail_aliases(env)]) + if alias in mail_aliases: + if mail_aliases[alias][1]: + output.print_ok("%s exists as an inbound mail alias. [%s ↦ %s]" % (alias_name, alias, mail_aliases[alias][0])) + else: + output.print_error("%s exists as a mail alias [%s ↦ %s] but is not enabled for inbound email." % (alias_name, alias, mail_aliases[alias][0])) else: - output.print_error("""You must add a mail alias for %s and direct email to you or another administrator.""" % alias) + output.print_error("""You must add an inbound mail alias for %s which directs email to you or another administrator.""" % alias) def check_dns_zone(domain, env, output, dns_zonefiles): # If a DS record is set at the registrar, check DNSSEC first because it will affect the NS query. @@ -492,7 +495,7 @@ def check_mail_domain(domain, env, output): # Check that the postmaster@ email address exists. Not required if the domain has a # catch-all address or domain alias. - if "@" + domain not in dict(get_mail_aliases(env)): + if "@" + domain not in [source for source, *_ in get_mail_aliases(env)]: check_alias_exists("Postmaster contact address", "postmaster@" + domain, env, output) # Stop if the domain is listed in the Spamhaus Domain Block List. @@ -884,7 +887,7 @@ def run_and_output_changes(env, pool, send_via_email): if category not in cur_status: out.add_heading(category) out.print_warning("This section was removed.") - + if send_via_email: # If there were changes, send off an email. buf = out.buf.getvalue() @@ -896,7 +899,7 @@ def run_and_output_changes(env, pool, send_via_email): msg['To'] = "administrator@%s" % env['PRIMARY_HOSTNAME'] msg['Subject'] = "[%s] Status Checks Change Notice" % env['PRIMARY_HOSTNAME'] msg.set_payload(buf, "UTF-8") - + # send to administrator@ import smtplib mailserver = smtplib.SMTP('localhost', 25) @@ -906,7 +909,7 @@ def run_and_output_changes(env, pool, send_via_email): "administrator@%s" % env['PRIMARY_HOSTNAME'], # RCPT TO msg.as_string()) mailserver.quit() - + # Store the current status checks output for next time. os.makedirs(os.path.dirname(cache_fn), exist_ok=True) with open(cache_fn, "w") as f: diff --git a/management/templates/aliases.html b/management/templates/aliases.html index 9336672..dceaac5 100644 --- a/management/templates/aliases.html +++ b/management/templates/aliases.html @@ -13,7 +13,7 @@