/**
*
*/
package fr.cedrik.email.pop3;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.StringTokenizer;
import javax.net.ssl.SSLSocket;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import fr.cedrik.email.pop3.commands.PASS;
import fr.cedrik.email.spi.PropertiesFileSupplier;
import fr.cedrik.util.Charsets;
/**
* @author Cédrik LIME
*/
class POP3Session implements Runnable {
private static final String CR_LF = "\r\n";//$NON-NLS-1$
private static final String END_OF_COMMAND_RESULT = ".";//$NON-NLS-1$
/* MDC keys */
private static final String MDC_IP = "";
private static final String MDC_USER = "";
private static final String MDC_COMMAND = "";
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected final Socket clientSocket;
protected final boolean secure;
protected final BufferedReader in;
protected final Writer out;
protected final Context context ;
protected final Map<String, POP3Command> commands = new HashMap<String, POP3Command>();
{
ServiceLoader<POP3Command> services = ServiceLoader.load(POP3Command.class);
for (POP3Command command : services) {
logger.trace("Discovered new POP3 command: {}", command.getClass().getSimpleName());
commands.put(command.getClass().getSimpleName(), command);
}
}
public POP3Session(Socket clientSocket) throws IOException {
this.clientSocket = clientSocket;
this.secure = (clientSocket instanceof SSLSocket);
// don't inherit from server pop3Properties, as the properties will be changed per session/connected user
this.context = new Context(clientSocket.getInetAddress(), new POP3Properties(PropertiesFileSupplier.Util.get()));
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), DEFAULT_ENCODING));
out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream(), DEFAULT_ENCODING));
}
private static final Charset DEFAULT_ENCODING = Charsets.ISO_8859_1;
protected String filterMultiLineResponseLine(String line) {
if (line.startsWith(END_OF_COMMAND_RESULT)) {
return END_OF_COMMAND_RESULT + line;
} else {
return line;
}
}
@Override
public void run() throws RuntimeException {
try {
Thread.currentThread().setName("POP3"+ (secure ? "S" : "") + " client " + clientSocket.getRemoteSocketAddress());
MDC.put(MDC_IP, clientSocket.getRemoteSocketAddress().toString());
out.append(ResponseStatus.POSITIVE.toString("POP3"+ (secure ? "S" : "") + " server ready")).append(CR_LF).flush();
context.inputArgs = "";
while (context.inputArgs != null
&& ! clientSocket.isClosed()
&& clientSocket.isConnected()
&& ! clientSocket.isInputShutdown()
&& ! clientSocket.isOutputShutdown()) {
String inputLine = in.readLine();
if (inputLine == null) {
// POP3 client has broken the socket connection
context.inputArgs = null;
continue;
}
StringTokenizer tokenizer = new StringTokenizer(inputLine.trim());
if (tokenizer.countTokens() == 0) {
continue;
}
String requestedCommand = tokenizer.nextToken();
POP3Command pop3Command = commands.get(requestedCommand.toUpperCase());
if (pop3Command == null) {
out.append(ResponseStatus.NEGATIVE.toString("[SYS/TEMP] " + requestedCommand)).append(CR_LF).flush();
continue;
}
if (! pop3Command.isValid(context)) {
out.append(ResponseStatus.NEGATIVE.toString("[SYS/TEMP] invalid state")).append(CR_LF).flush();
continue;
}
if (context.state == State.TRANSACTION) {
MDC.put(MDC_USER, context.userName);
}
if (pop3Command instanceof PASS) {
// don't echo password in logs!
Thread.currentThread().setName("POP3"+ (secure ? "S" : "") + " client " + clientSocket.getRemoteSocketAddress() + ' ' + requestedCommand);
MDC.put(MDC_COMMAND, requestedCommand);
logger.debug(requestedCommand);
} else {
Thread.currentThread().setName("POP3"+ (secure ? "S" : "") + " client " + clientSocket.getRemoteSocketAddress() + ' ' + inputLine);
MDC.put(MDC_COMMAND, inputLine);
logger.debug(inputLine);
}
context.inputArgs = inputLine.substring(requestedCommand.length()).trim();
Iterator<String> responseLines;
try {
responseLines = pop3Command.call(context);
} catch (IOException ioe) {
//out.append(ResponseStatus.NEGATIVE.toString("[SYS/TEMP] " + ioe.getMessage()));
out.append(ResponseStatus.NEGATIVE.toString("[SYS/PERM] " + ioe.getMessage()));
throw ioe;
}
if (pop3Command instanceof PASS && context.state == State.TRANSACTION) {
MDC.put(MDC_USER, context.userName);
}
int nLines = 0;
while (responseLines.hasNext()) {
String line = responseLines.next();
assert ! line.contains("\r") : line;
assert ! line.contains("\n") : line;
if (nLines == 0) {
// log only response status
logger.debug(line);
} else {
logger.trace(line);
}
out.append(filterMultiLineResponseLine(line)).append(CR_LF);
++nLines;
}
if (nLines > 1) {
logger.trace(END_OF_COMMAND_RESULT);
out.append(END_OF_COMMAND_RESULT).append(CR_LF);
}
out.flush();
Thread.currentThread().setName("POP3"+ (secure ? "S" : "") + " client " + clientSocket.getRemoteSocketAddress());
MDC.remove(MDC_COMMAND);
}
} catch (SocketTimeoutException ste) {
logger.info("Closing socket for POP3{} client {}: {}", (secure ? "S" : ""), clientSocket.getRemoteSocketAddress(), ste.getMessage());
} catch (IOException ioe) {
throw new RuntimeException(ioe);
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(clientSocket);
try {
context.remoteSession.logout();
} catch (IOException ignore) {
logger.info("{} while logging out POP3{} client {}: {}", ignore.getClass().getName(), (secure ? "S" : ""), clientSocket.getRemoteSocketAddress(), ignore.getMessage());
}
MDC.clear();
}
}
}