diff --git a/management/daemon.py b/management/daemon.py index 280efec0..e8e679e4 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -235,7 +235,7 @@ def mail_aliases(): if request.args.get("format", "") == "json": return json_response(get_mail_aliases_ex(env)) else: - return "".join(address+"\t"+receivers+"\t"+(senders or "")+"\n" for address, receivers, senders in get_mail_aliases(env)) + return "".join(address+"\t"+receivers+"\t"+(senders or "")+"\n" for address, receivers, senders, auto in get_mail_aliases(env)) @app.route('/mail/aliases/add', methods=['POST']) @authorized_personnel_only diff --git a/management/mailconfig.py b/management/mailconfig.py index 47faad5f..04a4c8b4 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -186,9 +186,9 @@ def get_admins(env): return users def get_mail_aliases(env): - # Returns a sorted list of tuples of (address, forward-tos, permitted-senders). + # Returns a sorted list of tuples of (address, forward-tos, permitted-senders, auto). c = open_database(env) - c.execute('SELECT source, destination, permitted_senders FROM aliases') + c.execute('SELECT source, destination, permitted_senders, 0 as auto FROM aliases UNION SELECT source, destination, permitted_senders, 1 as auto FROM auto_aliases') aliases = { row[0]: row for row in c.fetchall() } # make dict # put in a canonical order: sort by domain, then by email address lexicographically @@ -208,7 +208,7 @@ def get_mail_aliases_ex(env): # address_display: "name@domain.tld", # full Unicode # forwards_to: ["user1@domain.com", "receiver-only1@domain.com", ...], # permitted_senders: ["user1@domain.com", "sender-only1@domain.com", ...] OR null, - # required: True|False + # auto: True|False # }, # ... # ] @@ -216,12 +216,10 @@ def get_mail_aliases_ex(env): # ... # ] - required_aliases = get_required_aliases(env) domains = {} - for address, forwards_to, permitted_senders in get_mail_aliases(env): + for address, forwards_to, permitted_senders, auto in get_mail_aliases(env): # get alias info domain = get_domain(address) - required = (address in required_aliases) # add to list if not domain in domains: @@ -234,7 +232,7 @@ def get_mail_aliases_ex(env): "address_display": prettify_idn_email_address(address), "forwards_to": [prettify_idn_email_address(r.strip()) for r in forwards_to.split(",")], "permitted_senders": [prettify_idn_email_address(s.strip()) for s in permitted_senders.split(",")] if permitted_senders is not None else None, - "required": required, + "auto": bool(auto), }) # Sort domains. @@ -242,7 +240,7 @@ def get_mail_aliases_ex(env): # Sort aliases within each domain first by required-ness then lexicographically by address. for domain in domains: - domain["aliases"].sort(key = lambda alias : (alias["required"], alias["address"])) + domain["aliases"].sort(key = lambda alias : (alias["auto"], alias["address"])) return domains def get_domain(emailaddr, as_unicode=True): @@ -512,6 +510,13 @@ def remove_mail_alias(address, env, do_kick=True): # Update things in case any domains are removed. return kick(env, "alias removed") +def add_auto_aliases(aliases, env): + conn, c = open_database(env, with_connection=True) + c.execute("DELETE FROM auto_aliases"); + for source, destination in aliases.items(): + c.execute("INSERT INTO auto_aliases (source, destination) VALUES (?, ?)", (source, destination)) + conn.commit() + def get_system_administrator(env): return "administrator@" + env['PRIMARY_HOSTNAME'] @@ -555,39 +560,26 @@ def kick(env, mail_result=None): if mail_result is not None: results.append(mail_result + "\n") - # Ensure every required alias exists. + auto_aliases = { } - existing_users = get_mail_users(env) - existing_alias_records = get_mail_aliases(env) - existing_aliases = set(a for a, *_ in existing_alias_records) # just first entry in tuple + # Mape required aliases to the administrator alias (which should be created manually). + administrator = get_system_administrator(env) required_aliases = get_required_aliases(env) + for alias in required_aliases: + if alias == administrator: continue # don't make an alias from the administrator to itself --- this alias must be created manually + auto_aliases[alias] = administrator - def ensure_admin_alias_exists(address): - # If a user account exists with that address, we're good. - if address in existing_users: - return - # If the alias already exists, we're good. - if address in existing_aliases: - return + add_auto_aliases(auto_aliases, env) - # Doesn't exist. - administrator = get_system_administrator(env) - if address == administrator: return # don't make an alias from the administrator to itself --- this alias must be created manually - add_mail_alias(address, administrator, "", env, do_kick=False) - if administrator not in existing_aliases: return # don't report the alias in output if the administrator alias isn't in yet -- this is a hack to supress confusing output on initial setup - results.append("added alias %s (=> %s)\n" % (address, administrator)) - - for address in required_aliases: - ensure_admin_alias_exists(address) - - # Remove auto-generated postmaster/admin on domains we no - # longer have any other email addresses for. - for address, forwards_to, *_ in existing_alias_records: + # Remove auto-generated postmaster/admin/abuse alises from the main aliases table. + # They are now stored in the auto_aliases table. + for address, forwards_to, permitted_senders, auto in get_mail_aliases(env): user, domain = address.split("@") if user in ("postmaster", "admin", "abuse") \ and address not in required_aliases \ - and forwards_to == get_system_administrator(env): + and forwards_to == get_system_administrator(env) \ + and not auto: remove_mail_alias(address, env, do_kick=False) results.append("removed alias %s (was to %s; domain no longer used for email)\n" % (address, forwards_to)) diff --git a/management/templates/aliases.html b/management/templates/aliases.html index 3d37c4fd..c2a141f7 100644 --- a/management/templates/aliases.html +++ b/management/templates/aliases.html @@ -1,6 +1,6 @@