/*
* SONEWS News Server
* Copyright (C) 2009-2015 Christian Lins <christian@lins.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sonews.mlgw;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.codec.binary.Base64;
import org.sonews.config.Config;
import org.sonews.storage.Article;
import org.sonews.util.io.ArticleInputStream;
/**
* Connects to a SMTP server and sends a given Article to it.
* @author Christian Lins
* @since sonews/1.0
*/
class SMTPTransport {
public static final String NEWLINE = "\r\n";
protected BufferedReader in;
protected BufferedOutputStream out;
protected Socket socket;
public SMTPTransport(String host, int port)
throws IOException, UnknownHostException {
this.socket = new Socket(host, port);
this.in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
this.out = new BufferedOutputStream(socket.getOutputStream());
// Read HELO from server
String line = this.in.readLine();
if (line == null || !line.startsWith("220 ")) {
throw new IOException("Invalid HELO from server: " + line);
}
}
public void close()
throws IOException {
this.out.write("QUIT".getBytes("UTF-8"));
this.out.flush();
this.in.readLine();
this.socket.close();
}
private byte[] createCredentials() throws UnsupportedEncodingException {
String user = Config.inst().get(Config.MLSEND_USER, "");
String pass = Config.inst().get(Config.MLSEND_PASSWORD, "");
StringBuilder credBuf = new StringBuilder();
credBuf.append(user);
credBuf.append("\u0000");
credBuf.append(pass);
return Base64.encodeBase64(credBuf.toString().getBytes("UTF-8"));
}
private void ehlo(String hostname) throws IOException {
StringBuilder strBuf = new StringBuilder();
strBuf.append("EHLO ");
strBuf.append(hostname);
strBuf.append(NEWLINE);
// Send EHLO to server
this.out.write(strBuf.toString().getBytes("UTF-8"));
this.out.flush();
/*List<String> ehloReplies = */ readReply("250");
// TODO: Check for supported methods
// Do a PLAIN login
strBuf = new StringBuilder();
strBuf.append("AUTH PLAIN");
strBuf.append(NEWLINE);
// Send AUTH to server
this.out.write(strBuf.toString().getBytes("UTF-8"));
this.out.flush();
readReply("334");
// Send PLAIN credentials to server
this.out.write(createCredentials());
this.out.flush();
// Read reply of successful login
readReply("235");
}
private void helo(String hostname) throws IOException {
StringBuilder heloStr = new StringBuilder();
heloStr.append("HELO ");
heloStr.append(hostname);
heloStr.append(NEWLINE);
// Send HELO to server
this.out.write(heloStr.toString().getBytes("UTF-8"));
this.out.flush();
// Read reply
readReply("250");
}
public void login() throws IOException {
String hostname = Config.inst().get(Config.HOSTNAME, "localhost");
String auth = Config.inst().get(Config.MLSEND_AUTH, "none");
if (auth.equals("none")) {
helo(hostname);
} else {
ehlo(hostname);
}
}
/**
* Read one or more exspected reply lines.
*
* @param expectedReply
* @return
* @throws IOException If the reply of the server does not fit the exspected
* reply code.
*/
private List<String> readReply(String expectedReply) throws IOException {
List<String> replyStrings = new ArrayList<>();
for (;;) {
String line = this.in.readLine();
if (line == null || !line.startsWith(expectedReply)) {
throw new IOException("Unexpected reply: " + line);
}
replyStrings.add(line);
if (line.charAt(3) == ' ') { // Last reply line
break;
}
}
return replyStrings;
}
public void send(Article article, String mailFrom, String rcptTo)
throws IOException {
assert (article != null);
assert (mailFrom != null);
assert (rcptTo != null);
this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
this.out.flush();
String line = this.in.readLine();
if (line == null || !line.startsWith("250 ")) {
throw new IOException("Unexpected reply: " + line);
}
this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
this.out.flush();
line = this.in.readLine();
if (line == null || !line.startsWith("250 ")) {
throw new IOException("Unexpected reply: " + line);
}
this.out.write("DATA".getBytes("UTF-8"));
this.out.flush();
line = this.in.readLine();
if (line == null || !line.startsWith("354 ")) {
throw new IOException("Unexpected reply: " + line);
}
ArticleInputStream artStream = new ArticleInputStream(article);
for (int b = artStream.read(); b >= 0; b = artStream.read()) {
this.out.write(b);
}
// Flush the binary stream; important because otherwise the output
// will be mixed with the PrintWriter.
this.out.flush();
this.out.write("\r\n.\r\n".getBytes("UTF-8"));
this.out.flush();
line = this.in.readLine();
if (line == null || !line.startsWith("250 ")) {
throw new IOException("Unexpected reply: " + line);
}
}
}