diff --git a/core/admin/mailu/internal/views/postfix.py b/core/admin/mailu/internal/views/postfix.py index 74e49864..628dbe33 100644 --- a/core/admin/mailu/internal/views/postfix.py +++ b/core/admin/mailu/internal/views/postfix.py @@ -20,37 +20,34 @@ def postfix_mailbox_map(email): @internal.route("/postfix/alias/") def postfix_alias_map(alias): - localpart, domain = alias.split('@', 1) if '@' in alias else (None, alias) - alternative = models.Alternative.query.get(domain) - if alternative: - domain = alternative.domain_name - email = '{}@{}'.format(localpart, domain) + localpart, domain_name = models.Email.resolve_domain(alias) if localpart is None: - return flask.jsonify(domain) - else: - alias_obj = models.Alias.resolve(localpart, domain) - if alias_obj: - return flask.jsonify(",".join(alias_obj.destination)) - user_obj = models.User.query.get(email) - if user_obj: - return flask.jsonify(user_obj.destination) - return flask.abort(404) + return flask.jsonify(domain_name) + destination = models.Email.resolve_destination(localpart, domain_name) + return flask.jsonify(",".join(destination)) if destination else flask.abort(404) @internal.route("/postfix/transport/") def postfix_transport(email): - localpart, domain = email.split('@', 1) if '@' in email else (None, email) - relay = models.Relay.query.get(domain) or flask.abort(404) + if email == '*': + return flask.abort(404) + localpart, domain_name = models.Email.resolve_domain(email) + relay = models.Relay.query.get(domain_name) or flask.abort(404) return flask.jsonify("smtp:[{}]".format(relay.smtp)) -@internal.route("/postfix/sender/") -def postfix_sender(sender): +@internal.route("/postfix/sender/login/") +def postfix_sender_login(sender): + localpart, domain_name = models.Email.resolve_domain(sender) + if localpart is None: + return flask.abort(404) + destination = models.Email.resolve_destination(localpart, domain_name, True) + return flask.jsonify(",".join(destination)) if destination else flask.abort(404) + + +@internal.route("/postfix/sender/access/") +def postfix_sender_access(sender): """ Simply reject any sender that pretends to be from a local domain """ - localpart, domain_name = sender.split('@', 1) if '@' in sender else (None, sender) - domain = models.Domain.query.get(domain_name) - alternative = models.Alternative.query.get(domain_name) - if domain or alternative: - return flask.jsonify("REJECT") - return flask.abort(404) + localpart, domain_name = models.Email.resolve_domain(sender) + return flask.jsonify("REJECT") if models.Domain.query.get(domain_name) else flask.abort(404) diff --git a/core/admin/mailu/models.py b/core/admin/mailu/models.py index 9a19730f..207aa543 100644 --- a/core/admin/mailu/models.py +++ b/core/admin/mailu/models.py @@ -67,7 +67,7 @@ class CommaSeparatedList(db.TypeDecorator): return ",".join(value) def process_result_value(self, value, dialect): - return filter(bool, value.split(",")) + return filter(bool, value.split(",")) if value else [] # Many-to-many association table for domain managers @@ -224,6 +224,28 @@ class Email(object): msg['To'] = to_address smtp.sendmail(from_address, [to_address], msg.as_string()) + @classmethod + def resolve_domain(cls, email): + localpart, domain_name = email.split('@', 1) if '@' in email else (None, email) + alternative = Alternative.query.get(domain_name) + if alternative: + domain_name = alternative.domain_name + return (localpart, domain_name) + + @classmethod + def resolve_destination(cls, localpart, domain_name, ignore_forward_keep=False): + alias = Alias.resolve(localpart, domain_name) + if alias: + return alias.destination + user = User.query.get('{}@{}'.format(localpart, domain_name)) + if user: + if user.forward_enabled: + destination = user.forward_destination + if user.forward_keep or ignore_forward_keep: + destination.append(user.email) + else: + destination = [user.email] + return destination def __str__(self): return self.email @@ -248,7 +270,7 @@ class User(Base, Email): # Filters forward_enabled = db.Column(db.Boolean(), nullable=False, default=False) - forward_destination = db.Column(db.String(255), nullable=True, default=None) + forward_destination = db.Column(CommaSeparatedList(), nullable=True, default=[]) forward_keep = db.Column(db.Boolean(), nullable=False, default=True) reply_enabled = db.Column(db.Boolean(), nullable=False, default=False) reply_subject = db.Column(db.String(255), nullable=True, default=None) diff --git a/core/postfix/conf/main.cf b/core/postfix/conf/main.cf index 7db429bb..a67eb433 100644 --- a/core/postfix/conf/main.cf +++ b/core/postfix/conf/main.cf @@ -78,14 +78,14 @@ lmtp_host_lookup = native smtpd_delay_reject = yes # Allowed senders are: the user or one of the alias destinations -smtpd_sender_login_maps = $virtual_alias_maps +smtpd_sender_login_maps = ${podop}senderlogin # Restrictions for incoming SMTP, other restrictions are applied in master.cf smtpd_helo_required = yes smtpd_client_restrictions = permit_mynetworks, - check_sender_access ${podop}sender, + check_sender_access ${podop}senderaccess, reject_non_fqdn_sender, reject_unknown_sender_domain, reject_unknown_recipient_domain, diff --git a/core/postfix/start.py b/core/postfix/start.py index b3bb328d..86e9a827 100755 --- a/core/postfix/start.py +++ b/core/postfix/start.py @@ -19,7 +19,8 @@ def start_podop(): ("alias", "url", "http://admin/internal/postfix/alias/§"), ("domain", "url", "http://admin/internal/postfix/domain/§"), ("mailbox", "url", "http://admin/internal/postfix/mailbox/§"), - ("sender", "url", "http://admin/internal/postfix/sender/§") + ("senderaccess", "url", "http://admin/internal/postfix/sender/access/§"), + ("senderlogin", "url", "http://admin/internal/postfix/sender/login/§") ]) convert = lambda src, dst: open(dst, "w").write(jinja2.Template(open(src).read()).render(**os.environ))