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