src/org/sonews/mlgw/SMTPTransport.java
author cli
Wed, 14 Sep 2011 12:54:40 +0200
changeset 60 39f1fadf50a0
parent 59 68a6825a4f4d
child 61 da0c30d28e22
permissions -rwxr-xr-x
Add libcommons-codec-java to Debian dependencies.
     1 /*
     2  *   SONEWS News Server
     3  *   see AUTHORS for the list of contributors
     4  *
     5  *   This program is free software: you can redistribute it and/or modify
     6  *   it under the terms of the GNU General Public License as published by
     7  *   the Free Software Foundation, either version 3 of the License, or
     8  *   (at your option) any later version.
     9  *
    10  *   This program is distributed in the hope that it will be useful,
    11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  *   GNU General Public License for more details.
    14  *
    15  *   You should have received a copy of the GNU General Public License
    16  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  */
    18 package org.sonews.mlgw;
    19 
    20 import java.io.BufferedOutputStream;
    21 import java.io.BufferedReader;
    22 import java.io.IOException;
    23 import java.io.InputStreamReader;
    24 import java.net.Socket;
    25 import java.net.UnknownHostException;
    26 import java.util.ArrayList;
    27 import java.util.List;
    28 import org.sonews.config.Config;
    29 import org.sonews.storage.Article;
    30 import org.sonews.util.io.ArticleInputStream;
    31 
    32 /**
    33  * Connects to a SMTP server and sends a given Article to it.
    34  * @author Christian Lins
    35  * @since sonews/1.0
    36  */
    37 class SMTPTransport {
    38 
    39 	public static final String NEWLINE = "\r\n";
    40 
    41 	protected BufferedReader in;
    42 	protected BufferedOutputStream out;
    43 	protected Socket socket;
    44 
    45 	public SMTPTransport(String host, int port)
    46 			throws IOException, UnknownHostException {
    47 		this.socket = new Socket(host, port);
    48 		this.in = new BufferedReader(
    49 				new InputStreamReader(socket.getInputStream()));
    50 		this.out = new BufferedOutputStream(socket.getOutputStream());
    51 
    52 		// Read HELO from server
    53 		String line = this.in.readLine();
    54 		if (line == null || !line.startsWith("220 ")) {
    55 			throw new IOException("Invalid HELO from server: " + line);
    56 		}
    57 	}
    58 
    59 	public void close()
    60 			throws IOException {
    61 		this.out.write("QUIT".getBytes("UTF-8"));
    62 		this.out.flush();
    63 		this.in.readLine();
    64 
    65 		this.socket.close();
    66 	}
    67 
    68 	private void ehlo(String hostname) throws IOException {
    69 		StringBuilder strBuf = new StringBuilder();
    70 		strBuf.append("EHLO ");
    71 		strBuf.append(hostname);
    72 		strBuf.append(NEWLINE);
    73 
    74 		// Send EHLO to server
    75 		this.out.write(strBuf.toString().getBytes("UTF-8"));
    76 		this.out.flush();
    77 
    78 		List<String> ehloReplies = readReply("250");
    79 
    80 		// TODO: Check for supported methods
    81 
    82 		// Do a PLAIN login
    83 		strBuf = new StringBuilder();
    84 		strBuf.append("AUTH PLAIN");
    85 		strBuf.append(NEWLINE);
    86 
    87 		// Send AUTH to server
    88 		this.out.write(strBuf.toString().getBytes("UTF-8"));
    89 		this.out.flush();
    90 
    91 		readReply("334");
    92 
    93 		// Send PLAIN credentials to server
    94 		
    95 
    96 		// Read reply
    97 		String line = this.in.readLine();
    98 		if (line == null || !line.startsWith("250 ")) {
    99 			throw new IOException("Unexpected reply: " + line);
   100 		}
   101 	}
   102 
   103 	private void helo(String hostname) throws IOException {
   104 		StringBuilder heloStr = new StringBuilder();
   105 		heloStr.append("HELO ");
   106 		heloStr.append(hostname);
   107 		heloStr.append(NEWLINE);
   108 
   109 		// Send HELO to server
   110 		this.out.write(heloStr.toString().getBytes("UTF-8"));
   111 		this.out.flush();
   112 
   113 		// Read reply
   114 		readReply("250");
   115 	}
   116 
   117 	public void login() throws IOException {
   118 		String hostname = Config.inst().get(Config.HOSTNAME, "localhost");
   119 		String auth = Config.inst().get(Config.MLSEND_AUTH, "none");
   120 		if(auth.equals("none")) {
   121 			helo(hostname);
   122 		} else {
   123 			ehlo(hostname);
   124 		}
   125 	}
   126 
   127 	/**
   128 	 * Read one or more exspected reply lines.
   129 	 * @param expectedReply
   130 	 * @return
   131 	 * @throws IOException If the reply of the server does not fit the exspected
   132 	 * reply code.
   133 	 */
   134 	private List<String> readReply(String expectedReply) throws IOException {
   135 		List<String> replyStrings = new ArrayList<String>();
   136 
   137 		for(;;) {
   138 			String line = this.in.readLine();
   139 			if (line == null || !line.startsWith(expectedReply)) {
   140 				throw new IOException("Unexpected reply: " + line);
   141 			}
   142 
   143 			replyStrings.add(line);
   144 
   145 			if(line.charAt(3) == ' ') { // Last reply line
   146 				break;
   147 			}
   148 		}
   149 
   150 		return replyStrings;
   151 	}
   152 
   153 	public void send(Article article, String mailFrom, String rcptTo)
   154 			throws IOException {
   155 		assert (article != null);
   156 		assert (mailFrom != null);
   157 		assert (rcptTo != null);
   158 
   159 		this.out.write(("MAIL FROM: " + mailFrom).getBytes("UTF-8"));
   160 		this.out.flush();
   161 		String line = this.in.readLine();
   162 		if (line == null || !line.startsWith("250 ")) {
   163 			throw new IOException("Unexpected reply: " + line);
   164 		}
   165 
   166 		this.out.write(("RCPT TO: " + rcptTo).getBytes("UTF-8"));
   167 		this.out.flush();
   168 		line = this.in.readLine();
   169 		if (line == null || !line.startsWith("250 ")) {
   170 			throw new IOException("Unexpected reply: " + line);
   171 		}
   172 
   173 		this.out.write("DATA".getBytes("UTF-8"));
   174 		this.out.flush();
   175 		line = this.in.readLine();
   176 		if (line == null || !line.startsWith("354 ")) {
   177 			throw new IOException("Unexpected reply: " + line);
   178 		}
   179 
   180 		ArticleInputStream artStream = new ArticleInputStream(article);
   181 		for (int b = artStream.read(); b >= 0; b = artStream.read()) {
   182 			this.out.write(b);
   183 		}
   184 
   185 		// Flush the binary stream; important because otherwise the output
   186 		// will be mixed with the PrintWriter.
   187 		this.out.flush();
   188 		this.out.write("\r\n.\r\n".getBytes("UTF-8"));
   189 		this.out.flush();
   190 		line = this.in.readLine();
   191 		if (line == null || !line.startsWith("250 ")) {
   192 			throw new IOException("Unexpected reply: " + line);
   193 		}
   194 	}
   195 }