diff --git a/management/mailconfig.py b/management/mailconfig.py index e2562bdc..d21069a2 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -43,10 +43,13 @@ def get_mail_aliases(env): c.execute('SELECT source, destination FROM aliases') return [(row[0], row[1]) for row in c.fetchall()] -def get_mail_domains(env): +def get_mail_domains(env, filter_aliases=lambda alias : True): def get_domain(emailaddr): return emailaddr.split('@', 1)[1] - return set([get_domain(addr) for addr in get_mail_users(env)] + [get_domain(addr1) for addr1, addr2 in get_mail_aliases(env)]) + return set( + [get_domain(addr) for addr in get_mail_users(env)] + + [get_domain(source) for source, target in get_mail_aliases(env) if filter_aliases((source, target)) ] + ) def add_mail_user(email, pw, env): if not validate_email(email, True): @@ -91,7 +94,7 @@ def add_mail_user(email, pw, env): shutil.copyfile(utils.CONF_DIR + "/dovecot_sieve.txt", user_mail_dir + "/.dovecot.sieve") os.chown(user_mail_dir + "/.dovecot.sieve", maildirstat.st_uid, maildirstat.st_gid) - # Update DNS/web in case any new domains are added. + # Update things in case any new domains are added. return kick(env, "mail user added") def set_mail_password(email, pw, env): @@ -113,10 +116,10 @@ def remove_mail_user(email, env): return ("That's not a user (%s)." % email, 400) conn.commit() - # Update DNS/web in case any domains are removed. + # Update things in case any domains are removed. return kick(env, "mail user removed") -def add_mail_alias(source, destination, env): +def add_mail_alias(source, destination, env, do_kick=True): if not validate_email(source, False): return ("Invalid email address.", 400) @@ -127,28 +130,84 @@ def add_mail_alias(source, destination, env): return ("Alias already exists (%s)." % source, 400) conn.commit() - # Update DNS/web in case any new domains are added. - return kick(env, "alias added") + if do_kick: + # Update things in case any new domains are added. + return kick(env, "alias added") -def remove_mail_alias(source, env): +def remove_mail_alias(source, env, do_kick=True): conn, c = open_database(env, with_connection=True) c.execute("DELETE FROM aliases WHERE source=?", (source,)) if c.rowcount != 1: return ("That's not an alias (%s)." % source, 400) conn.commit() - # Update DNS and nginx in case any domains are removed. - return kick(env, "alias removed") + if do_kick: + # Update things in case any domains are removed. + return kick(env, "alias removed") + +def kick(env, mail_result=None): + results = [] + + # Inclde the current operation's result in output. + + if mail_result is not None: + results.append(mail_result + "\n") + + # Create hostmaster@ for the primary domain if it does not already exist. + # Default the target to administrator@ which the user is responsible for + # setting and keeping up to date. + + existing_aliases = get_mail_aliases(env) + + administrator = "administrator@" + env['PRIMARY_HOSTNAME'] + + def ensure_admin_alias_exists(source): + # Does this alias exists? + for s, t in existing_aliases: + if s == source: + return + + # Doesn't exist. + add_mail_alias(source, administrator, env, do_kick=False) + results.append("added alias %s (=> %s)\n" % (source, administrator)) + + ensure_admin_alias_exists("hostmaster@" + env['PRIMARY_HOSTNAME']) + + # Get a list of domains we serve mail for, except ones for which the only + # email on that domain is a postmaster/admin alias to the administrator. + + real_mail_domains = get_mail_domains(env, + filter_aliases = lambda alias : \ + (not alias[0].startswith("postmaster@") \ + and not alias[0].startswith("admin@")) \ + or alias[1] != administrator \ + ) + + # Create postmaster@ and admin@ for all domains we serve mail on. + # postmaster@ is assumed to exist by our Postfix configuration. admin@ + # isn't anything, but it might save the user some trouble e.g. when + # buying an SSL certificate. + for domain in real_mail_domains: + ensure_admin_alias_exists("postmaster@" + domain) + ensure_admin_alias_exists("admin@" + domain) + + # Remove auto-generated hostmaster/postmaster/admin on domains we no + # longer have any other email addresses for. + for source, target in existing_aliases: + user, domain = source.split("@") + if user in ("postmaster", "admin") and domain not in real_mail_domains \ + and target == administrator: + remove_mail_alias(source, env, do_kick=False) + results.append("removed alias %s (was to %s; domain no longer used for email)\n" % (source, target)) -def kick(env, mail_result): # Update DNS and nginx in case any domains are added/removed. + from dns_update import do_dns_update + results.append( do_dns_update(env) ) + from web_update import do_web_update - results = [ - do_dns_update(env), - mail_result + "\n", - do_web_update(env), - ] + results.append( do_web_update(env) ) + return "".join(s for s in results if s != "") if __name__ == "__main__": @@ -159,3 +218,7 @@ if __name__ == "__main__": sys.exit(0) else: sys.exit(1) + + if len(sys.argv) > 1 and sys.argv[1] == "update": + from utils import load_environment + print(kick(load_environment())) diff --git a/management/whats_next.py b/management/whats_next.py index 6683a3a2..5afdfbb5 100755 --- a/management/whats_next.py +++ b/management/whats_next.py @@ -55,6 +55,7 @@ def run_domain_checks(env): if domain == env["PRIMARY_HOSTNAME"]: check_primary_hostname_dns(domain, env) + check_alias_exists("administrator@" + domain, env) if domain in dns_domains: check_dns_zone(domain, env, dns_zonefiles) diff --git a/setup/start.sh b/setup/start.sh index 66811ae2..b66377a9 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -222,8 +222,10 @@ if [ -z "`tools/mail.py user`" ]; then echo "Okay. I'm about to set up $EMAIL_ADDR for you." fi - tools/mail.py user add $EMAIL_ADDR $EMAIL_PW # will ask for password if none given - tools/mail.py alias add hostmaster@$PRIMARY_HOSTNAME $EMAIL_ADDR - tools/mail.py alias add postmaster@$PRIMARY_HOSTNAME $EMAIL_ADDR + # Create the user's mail account. This will ask for a password if none was given above. + tools/mail.py user add $EMAIL_ADDR $EMAIL_PW + + # Create an alias to which we'll direct all automatically-created administrative aliases. + tools/mail.py alias add administrator@$PRIMARY_HOSTNAME $EMAIL_ADDR fi