package com.mailserver.config; import com.mailserver.repository.UserRepository; import com.mailserver.service.UserService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.PosixFilePermissions; import java.security.SecureRandom; import java.time.Instant; import java.util.Base64; import java.util.Locale; /** * Mailboxes the server needs on start: * * A generated password is appended to {@code mailserver.passwords-file} (mode 600, in the data volume) — read it once * and change the password. Without the file setting it is printed to the log once. */ @Configuration public class DataInitializer { private static final Logger log = LoggerFactory.getLogger(DataInitializer.class); @Bean public CommandLineRunner initDatabase(UserService userService, UserRepository userRepository, MailDomains domains, @Value("${mailserver.admin.email:}") String email, @Value("${mailserver.admin.password:}") String password, @Value("${mailserver.passwords-file:}") String passwordsFile) { return args -> { Path file = passwordsFile.isBlank() ? null : Path.of(passwordsFile); if (userRepository.count().block() == 0) { String address = email.isBlank() ? "admin@" + domains.domains().iterator().next() : email.trim().toLowerCase(Locale.ROOT); create(userService, address, password, "Administrator", file); } String catchAll = domains.catchAll(); if (catchAll != null && !Boolean.TRUE.equals(userRepository.existsByEmail(catchAll).block())) { create(userService, catchAll, "", "Mail trap", file); } }; } private static void create(UserService userService, String address, String password, String name, Path file) throws IOException { boolean generated = password.isBlank(); String secret = generated ? randomPassword() : password; userService.register(address, secret, name).block(); if (!generated) { log.info("Mailbox created: {}", address); } else if (file != null) { appendPassword(file, address, secret); log.warn("Mailbox created: {} — password in {} (change it)", address, file); } else { log.warn("Mailbox created: {} — password: {} (shown once, change it)", address, secret); } } static void appendPassword(Path file, String address, String password) throws IOException { if (file.getParent() != null) { Files.createDirectories(file.getParent()); } if (!Files.exists(file)) { Files.createFile(file); try { Files.setPosixFilePermissions(file, PosixFilePermissions.fromString("rw-------")); } catch (UnsupportedOperationException ignored) { // not a POSIX file system (Windows) — nothing to restrict } } Files.writeString(file, Instant.now() + " " + address + " " + password + System.lineSeparator(), StandardCharsets.UTF_8, StandardOpenOption.APPEND); } private static String randomPassword() { byte[] bytes = new byte[18]; new SecureRandom().nextBytes(bytes); return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); } }