package com.mailserver.smtp; import com.mailserver.config.MailDomains; import com.mailserver.delivery.OutboundQueue; import com.mailserver.delivery.SmtpDeliveryClient; import com.mailserver.domain.User; import com.mailserver.repository.UserRepository; import com.mailserver.service.DkimService; import com.mailserver.service.LocalDelivery; import com.mailserver.service.MailLists; import com.mailserver.service.MailboxAccess; import com.mailserver.service.UserService; import org.springframework.stereotype.Service; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Set; /** * Where mail goes — one rule for SMTP and for the web client: * */ @Service public class MailRouter implements SmtpBackend { private final MailDomains domains; private final UserRepository users; private final UserService userService; private final LocalDelivery localDelivery; private final OutboundQueue queue; private final DkimService dkim; private final SmtpDeliveryClient client; private final MailLists lists; private final MailboxAccess access; public MailRouter(MailDomains domains, UserRepository users, UserService userService, LocalDelivery localDelivery, OutboundQueue queue, DkimService dkim, SmtpDeliveryClient client, MailLists lists, MailboxAccess access) { this.access = access; this.domains = domains; this.users = users; this.userService = userService; this.localDelivery = localDelivery; this.queue = queue; this.dkim = dkim; this.client = client; this.lists = lists; } @Override public String authenticate(String username, String password) { User mailbox = access.login(username, password); return mailbox == null ? null : mailbox.getEmail(); } @Override public Route route(String recipient, String authenticatedUser) { if (domains.isLocal(recipient)) { String base = MailDomains.baseOf(recipient); if (mailboxExists(recipient) || mailboxExists(base)) { return Route.LOCAL; } MailLists.MailList list = lists.find(base).block(); if (list != null) { return list.openToOutside() || authenticatedUser != null ? Route.LOCAL : Route.LIST_CLOSED; } } if (isTrap(authenticatedUser)) { return Route.TRAP; } if (domains.catchAll() != null) { return Route.LOCAL; } if (domains.isLocal(recipient)) { return Route.UNKNOWN_USER; } if (authenticatedUser == null) { return Route.RELAY_DENIED; } return client.mode() == SmtpDeliveryClient.Mode.OFF ? Route.OUTBOUND_DISABLED : Route.OUTBOUND; } @Override public void accept(Envelope envelope, byte[] message) throws Exception { boolean trusted = envelope.authenticatedUser() != null; // one copy per mailbox + folder, even when an address and a list both lead there Set targets = new LinkedHashSet<>(); byte[] signed = null; for (Recipient recipient : envelope.recipients()) { switch (recipient.route()) { case LOCAL -> targets.addAll(targets(recipient.address())); case TRAP -> targets.add(new Target(envelope.authenticatedUser().toLowerCase(Locale.ROOT), null)); case OUTBOUND -> { if (signed == null) { signed = dkim.sign(message); } queue.enqueue(envelope.mailFrom(), recipient.address(), signed).block(); } default -> throw new IllegalStateException("Recipient was not accepted: " + recipient); } } for (Target target : targets) { localDelivery.deliver(message, target.mailbox(), target.folder(), envelope.remoteIp(), envelope.mailFrom(), trusted); } } /** Mailbox + folder (null — INBOX) that store mail to a local address. */ record Target(String mailbox, String folder) { } Set targets(String address) { Set result = new LinkedHashSet<>(); String lower = address.toLowerCase(Locale.ROOT); if (domains.isLocal(lower)) { if (mailboxExists(lower)) { result.add(new Target(lower, null)); return result; } String base = MailDomains.baseOf(lower); String tag = MailDomains.tagOf(address); if (mailboxExists(base)) { result.add(new Target(base, tag)); return result; } MailLists.MailList list = lists.find(base).block(); if (list != null) { for (String member : list.members()) { result.add(new Target(member, tag != null ? tag : list.folder())); } return result; } } if (domains.catchAll() != null) { result.add(new Target(domains.catchAll(), null)); } return result; } /** Mailbox that stores mail to this address: its own (without "+tag") if it exists here, otherwise the catch-all one. */ String mailbox(String address) { Set found = targets(address); return found.isEmpty() ? address : found.iterator().next().mailbox(); } /** The logged-in sender is a trap mailbox. */ boolean isTrap(String authenticatedUser) { if (authenticatedUser == null) { return false; } User user = users.findByEmail(authenticatedUser.toLowerCase(Locale.ROOT)).block(); return user != null && user.isTrap(); } private boolean mailboxExists(String address) { return Boolean.TRUE.equals(users.existsByEmail(address.toLowerCase(Locale.ROOT)).block()); } }