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.UserService; import org.springframework.stereotype.Service; import java.util.Locale; /** * Where mail goes — one rule for SMTP and for the web client: * an existing mailbox of our domain → that mailbox; catch-all set → everything else into the catch-all mailbox * (nothing goes outside, existing mailboxes still get their own mail); otherwise our domain without such a mailbox → * "no such user", someone else's domain → the delivery queue, only for a logged-in sender (never an open relay). */ @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; public MailRouter(MailDomains domains, UserRepository users, UserService userService, LocalDelivery localDelivery, OutboundQueue queue, DkimService dkim, SmtpDeliveryClient client) { this.domains = domains; this.users = users; this.userService = userService; this.localDelivery = localDelivery; this.queue = queue; this.dkim = dkim; this.client = client; } @Override public boolean authenticate(String username, String password) { User user = users.findByEmail(username.toLowerCase(Locale.ROOT)).block(); return user != null && user.isActive() && userService.checkPassword(user, password); } @Override public Route route(String recipient, String authenticatedUser) { if (domains.isLocal(recipient) && mailboxExists(recipient)) { return Route.LOCAL; } 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; byte[] signed = null; for (Recipient recipient : envelope.recipients()) { switch (recipient.route()) { case LOCAL -> { String mailbox = mailbox(recipient.address()); localDelivery.deliver(message, mailbox, envelope.remoteIp(), envelope.mailFrom(), trusted); } 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); } } } /** Mailbox that stores mail to this address: its own if it exists here, otherwise the catch-all one. */ String mailbox(String address) { if (domains.catchAll() == null || (domains.isLocal(address) && mailboxExists(address))) { return address; } return domains.catchAll(); } private boolean mailboxExists(String address) { return Boolean.TRUE.equals(users.existsByEmail(address.toLowerCase(Locale.ROOT)).block()); } }