package com.inter6.mail.service; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Writer; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.util.HashSet; import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.net.smtp.AuthenticatingSMTPClient; import org.apache.commons.net.smtp.AuthenticatingSMTPClient.AUTH_METHOD; import org.apache.commons.net.smtp.SMTPClient; import org.apache.commons.net.smtp.SMTPReply; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.inter6.mail.model.HeloType; public class SmtpService { private final Logger log = LoggerFactory.getLogger(this.getClass()); private String host; private int port; private String connectType; private String encoding = "UTF-8"; private HeloType heloType; private String heloDomain; private AUTH_METHOD authMethod; private String username; private String password; private String mailFrom; private Set<String> rcptTos; private SmtpService() { } public static SmtpService createInstance(String host, int port, String connectType) { SmtpService smtpService = new SmtpService(); smtpService.setHost(host, port, connectType); return smtpService; } public SmtpService setHost(String host, int port, String connectType) { this.host = host; this.port = port; this.connectType = connectType; return this; } public SmtpService setEncoding(String encoding) { this.encoding = encoding; return this; } public SmtpService setHelo(HeloType heloType, String heloDomain) { this.heloType = heloType; this.heloDomain = heloDomain; return this; } public SmtpService setAuth(AUTH_METHOD authMethod, String username, String password) { this.authMethod = authMethod; this.username = username; this.password = password; return this; } public SmtpService setEnvelope(String mailFrom, Set<String> rcptTos) { this.mailFrom = mailFrom; this.rcptTos = rcptTos; return this; } public Set<String> send(InputStream messageStream) throws IOException { AuthenticatingSMTPClient smtpClient = null; try { smtpClient = this.connect(); this.auth(smtpClient); Set<String> failReceivers = this.processEnvelope(smtpClient); if (this.rcptTos.size() == failReceivers.size()) { throw new IOException("not allowed all rcpt to !"); } this.writeData(smtpClient, messageStream); return failReceivers; } catch (Throwable e) { throw new IOException("send fail ! - " + e.getMessage() + " - RECV:" + this.rcptTos, e); } finally { this.close(smtpClient); } } private AuthenticatingSMTPClient connect() throws IOException { AuthenticatingSMTPClient smtpClient; if ("ssl".equalsIgnoreCase(this.connectType)) { smtpClient = new AuthenticatingSMTPClient("SSL", true, this.encoding); } else if ("tls".equalsIgnoreCase(this.connectType)) { smtpClient = new AuthenticatingSMTPClient("TLS", false, this.encoding); } else { smtpClient = new AuthenticatingSMTPClient("SSL", false, this.encoding); } smtpClient.setConnectTimeout(10 * 1000); smtpClient.connect(this.host, this.port); this.debug(smtpClient); if (heloType != null) { switch (heloType) { case HELO: smtpClient.helo(heloDomain); this.debug(smtpClient); break; case EHLO: smtpClient.ehlo(heloDomain); this.debug(smtpClient); break; case NONE: default: // do nothing } } if ("tls".equalsIgnoreCase(this.connectType)) { smtpClient.execTLS(); this.debug(smtpClient); } return smtpClient; } private void auth(AuthenticatingSMTPClient smtpClient) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, IOException { if (this.authMethod == null) { return; } boolean isSuccess = smtpClient.auth(this.authMethod, this.username, this.password); this.debug(smtpClient); if (!isSuccess) { throw new IOException("auth fail ! - ID:" + this.username); } } private Set<String> processEnvelope(SMTPClient smtpClient) throws IOException { smtpClient.setSender(this.mailFrom); this.debug(smtpClient); Set<String> failReceivers = new HashSet<>(); for (String receiver : this.rcptTos) { if (!smtpClient.addRecipient(receiver)) { failReceivers.add(receiver); } this.debug(smtpClient); } return failReceivers; } private void writeData(SMTPClient smtpClient, InputStream messageStream) throws IOException { Writer smtpWriter = null; try { smtpWriter = smtpClient.sendMessageData(); this.debug(smtpClient); if (smtpWriter == null) { throw new IOException("smtp writer fail !"); } ByteArrayOutputStream bais = new ByteArrayOutputStream(); // no required close() IOUtils.copy(messageStream, bais); ByteArrayInputStream baos = new ByteArrayInputStream(bais.toByteArray()); // no required close() this.writeMessage(smtpWriter, baos); IOUtils.closeQuietly(smtpWriter); // do close() before pending smtpClient.completePendingCommand(); this.debug(smtpClient); if (!SMTPReply.isPositiveCompletion(smtpClient.getReplyCode())) { throw new IOException("server refused connection ! - REPLY:" + smtpClient.getReplyString()); } } finally { IOUtils.closeQuietly(smtpWriter); } } private void writeMessage(Writer smtpWriter, InputStream messageStream) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(messageStream, "UTF-8")); BufferedWriter bw = new BufferedWriter(smtpWriter); this.processHeader(br, bw); this.processBody(br, bw); bw.flush(); } private void processHeader(BufferedReader br, BufferedWriter bw) throws IOException { String line; while ((line = br.readLine()) != null && !this.isBodyStart(line)) { this.writeLine(bw, line); } this.writeLine(bw, StringUtils.defaultString(line)); } private void processBody(BufferedReader br, BufferedWriter bw) throws IOException { char[] readBuffer = new char[1024 * 16]; // 16KB int readSize; while ((readSize = br.read(readBuffer)) != -1) { bw.write(readBuffer, 0, readSize); this.debug(readBuffer); } } private boolean isBodyStart(String line) { return StringUtils.equals(line, ""); } private void writeLine(BufferedWriter bw, String line) throws IOException { bw.write(line); bw.newLine(); this.debug(line + "\n"); } private void close(SMTPClient smtpClient) { if (smtpClient == null || !smtpClient.isConnected()) { return; } try { smtpClient.quit(); this.debug(smtpClient); } catch (IOException e) { log.debug("smtp close fail !", e); } try { smtpClient.disconnect(); } catch (IOException e) { log.debug("smtp disconnect fail !", e); } } private void debug(Object object) { if (object instanceof SMTPClient) { this.log.debug(((SMTPClient) object).getReplyString()); } else if (object instanceof char[]) { this.log.debug(new String((char[]) object)); } else { this.log.debug(object.toString()); } } }