- MailRouter: существующий ящик получает свою почту, всё остальное (и чужие домены) — в ящик MAIL_CATCH_ALL; раньше ловушка забирала и письма между ящиками стенда - DataInitializer: ящик-ловушка создаётся при старте; случайные пароли — в MAIL_PASSWORDS_FILE (права 600, в образе /app/data/initial-passwords.txt) - docker-compose.jambotron.yml: MAIL_CATCH_ALL=trap@mail.test по умолчанию - README: ловушка, файл паролей, что нужно для staging Jambotron (TCP-проброс SMTP) - тесты: MailRouterTest, DataInitializerTest (33 всего) α Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
99 lines
3.8 KiB
Java
99 lines
3.8 KiB
Java
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());
|
|
}
|
|
}
|