Replace int states with enum and hide IMAP password in logs, refactor IMAP command parsing

git-svn-id: http://svn.code.sf.net/p/davmail/code/trunk@309 3d1905a2-6b24-0410-a738-b14d5a86fcbd
This commit is contained in:
mguessan 2009-01-29 22:18:53 +00:00
parent f92fa85d1a
commit 1389e48831
4 changed files with 71 additions and 70 deletions

View File

@ -2,17 +2,25 @@ package davmail;
import davmail.exchange.ExchangeSession;
import davmail.exchange.ExchangeSessionFactory;
import davmail.smtp.SmtpConnection;
import davmail.tray.DavGatewayTray;
import org.apache.commons.httpclient.util.Base64;
import java.io.*;
import java.net.Socket;
/**
* Generic connection common to pop3 and smtp implementations
*/
public class AbstractConnection extends Thread {
protected enum State {INITIAL, LOGIN, USER, PASSWORD, AUTHENTICATED, STARTMAIL, RECIPIENT, MAILDATA}
/**
* read password state
*/
public static final int PASSWORD = 1;
protected final Socket client;
protected BufferedReader in;
@ -21,7 +29,7 @@ public class AbstractConnection extends Thread {
protected String userName = null;
protected String password = null;
// connection state
protected int state = 0;
protected State state = State.INITIAL;
// Exchange session proxy
protected ExchangeSession session;
@ -102,12 +110,15 @@ public class AbstractConnection extends Thread {
*/
public String readClient() throws IOException {
String line = in.readLine();
// TODO : add basic authorization check
if (line != null) {
if (line.startsWith("PASS")) {
DavGatewayTray.debug("< PASS ********");
} else if (state == SmtpConnection.PASSWORD) {
// IMAP LOGIN
} else if (line.startsWith("LOGIN")) {
DavGatewayTray.debug("< LOGIN ********");
} else if (state == State.PASSWORD) {
DavGatewayTray.debug("< ********");
// HTTP Basic Authentication
} else if (line.startsWith("Authorization:")) {
DavGatewayTray.debug("< Authorization: ********");
} else {

View File

@ -1,31 +1,30 @@
package davmail.imap;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.SocketException;
import java.util.StringTokenizer;
import java.util.List;
import java.util.HashMap;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.OutputStream;
import com.sun.mail.imap.protocol.BASE64MailboxDecoder;
import com.sun.mail.imap.protocol.BASE64MailboxEncoder;
import davmail.AbstractConnection;
import davmail.tray.DavGatewayTray;
import davmail.exchange.ExchangeSession;
import davmail.exchange.ExchangeSessionFactory;
import com.sun.mail.imap.protocol.BASE64MailboxEncoder;
import com.sun.mail.imap.protocol.BASE64MailboxDecoder;
import davmail.tray.DavGatewayTray;
import org.apache.commons.httpclient.HttpException;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
/**
* Dav Gateway smtp connection implementation.
* Still alpha code : need to find a way to handle message ids
*/
public class ImapConnection extends AbstractConnection {
protected static final int INITIAL = 0;
protected static final int AUTHENTICATED = 1;
protected static final int AUTHENTICATED = 2;
ExchangeSession.Folder currentFolder;
List<ExchangeSession.Message> messages;
@ -79,11 +78,11 @@ public class ImapConnection extends AbstractConnection {
try {
session = ExchangeSessionFactory.getInstance(userName, password);
sendClient(commandId + " OK Authenticated");
state = AUTHENTICATED;
state = State.AUTHENTICATED;
} catch (Exception e) {
DavGatewayTray.error(e);
sendClient(commandId + " NO LOGIN failed");
state = INITIAL;
state = State.INITIAL;
}
} else if ("AUTHENTICATE".equalsIgnoreCase(command)) {
if (tokens.hasMoreTokens()) {
@ -96,11 +95,11 @@ public class ImapConnection extends AbstractConnection {
try {
session = ExchangeSessionFactory.getInstance(userName, password);
sendClient(commandId + " OK Authenticated");
state = AUTHENTICATED;
state = State.AUTHENTICATED;
} catch (Exception e) {
DavGatewayTray.error(e);
sendClient(commandId + " NO LOGIN failed");
state = INITIAL;
state = State.INITIAL;
}
} else {
sendClient(commandId + " NO unsupported authentication method");
@ -109,14 +108,14 @@ public class ImapConnection extends AbstractConnection {
sendClient(commandId + " BAD authentication method required");
}
} else {
if (state != AUTHENTICATED) {
if (state != State.AUTHENTICATED) {
sendClient(commandId + " BAD command authentication required");
} else {
if ("lsub".equalsIgnoreCase(command) || "list".equalsIgnoreCase(command)) {
if (tokens.hasMoreTokens()) {
String folderContext = BASE64MailboxDecoder.decode(removeQuotes(tokens.nextToken()));
String folderContext = BASE64MailboxDecoder.decode(tokens.nextToken());
if (tokens.hasMoreTokens()) {
String folderQuery = folderContext + BASE64MailboxDecoder.decode(removeQuotes(tokens.nextToken()));
String folderQuery = folderContext + BASE64MailboxDecoder.decode(tokens.nextToken());
if (folderQuery.endsWith("%/%")) {
folderQuery = folderQuery.substring(0, folderQuery.length() - 2);
}
@ -144,7 +143,7 @@ public class ImapConnection extends AbstractConnection {
}
} else if ("select".equalsIgnoreCase(command) || "examine".equalsIgnoreCase(command)) {
if (tokens.hasMoreTokens()) {
String folderName = BASE64MailboxDecoder.decode(removeQuotes(tokens.nextToken()));
String folderName = BASE64MailboxDecoder.decode(tokens.nextToken());
currentFolder = session.getFolder(folderName);
messages = session.getAllMessages(currentFolder.folderUrl);
sendClient("* " + currentFolder.objectCount + " EXISTS");
@ -164,15 +163,15 @@ public class ImapConnection extends AbstractConnection {
sendClient(commandId + " OK CLOSE completed");
} else if ("create".equalsIgnoreCase(command)) {
if (tokens.hasMoreTokens()) {
String folderName = BASE64MailboxDecoder.decode(removeQuotes(tokens.nextToken()));
String folderName = BASE64MailboxDecoder.decode(tokens.nextToken());
session.createFolder(folderName);
sendClient(commandId + " OK folder created");
} else {
sendClient(commandId + " BAD missing create argument");
}
} else if ("rename".equalsIgnoreCase(command)) {
String folderName = BASE64MailboxDecoder.decode(removeQuotes(tokens.nextToken()));
String targetName = BASE64MailboxDecoder.decode(removeQuotes(tokens.nextToken()));
String folderName = BASE64MailboxDecoder.decode(tokens.nextToken());
String targetName = BASE64MailboxDecoder.decode(tokens.nextToken());
try {
session.moveFolder(folderName, targetName);
sendClient(commandId + " OK rename completed");
@ -214,7 +213,7 @@ public class ImapConnection extends AbstractConnection {
for (int messageIndex = startIndex; messageIndex <= endIndex; messageIndex++) {
ExchangeSession.Message message = messages.get(messageIndex - 1);
if ("(BODYSTRUCTURE)".equals(parameters)) {
if ("BODYSTRUCTURE".equals(parameters)) {
sendClient("* " + messageIndex + " FETCH (BODYSTRUCTURE (\"TEXT\" \"PLAIN\" (\"CHARSET\" \"windows-1252\") NIL NIL \"QUOTED-PRINTABLE\" " + message.size + " 50 NIL NIL NIL NIL))");
} else if (parameters.indexOf("BODY[]") >= 0) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@ -295,12 +294,13 @@ public class ImapConnection extends AbstractConnection {
sendClient(commandId + " BAD command unrecognized");
}
} else if ("fetch".equalsIgnoreCase(command)) {
// TODO : refactor with uid fetch
if (tokens.hasMoreTokens()) {
int messageIndex = Integer.parseInt(tokens.nextToken());
ExchangeSession.Message message = messages.get(messageIndex - 1);
if (tokens.hasMoreTokens()) {
String parameters = tokens.nextToken();
if ("(BODYSTRUCTURE)".equals(parameters)) {
if ("BODYSTRUCTURE".equals(parameters)) {
sendClient("* " + messageIndex + " FETCH (BODYSTRUCTURE (\"TEXT\" \"PLAIN\" (\"CHARSET\" \"windows-1252\") NIL NIL \"QUOTED-PRINTABLE\" " + message.size + " 50 NIL NIL NIL NIL))");
sendClient(commandId + " OK FETCH completed");
} else {
@ -311,9 +311,9 @@ public class ImapConnection extends AbstractConnection {
}
}
} else if ("append".equalsIgnoreCase(command)) {
String folderName = BASE64MailboxDecoder.decode(removeQuotes(tokens.nextToken()));
String folderName = BASE64MailboxDecoder.decode(tokens.nextToken());
String parameters = tokens.nextToken();
int size = Integer.parseInt(removeQuotes(tokens.nextToken()));
int size = Integer.parseInt(tokens.nextToken());
sendClient("+ send literal data");
char[] buffer = new char[size];
int index = 0;
@ -386,13 +386,13 @@ public class ImapConnection extends AbstractConnection {
*/
protected void parseCredentials(StringTokenizer tokens) throws IOException {
if (tokens.hasMoreTokens()) {
userName = removeQuotes(tokens.nextToken());
userName = tokens.nextToken();
} else {
throw new IOException("Invalid credentials");
}
if (tokens.hasMoreTokens()) {
password = removeQuotes(tokens.nextToken());
password = tokens.nextToken();
} else {
throw new IOException("Invalid credentials");
}

View File

@ -18,9 +18,6 @@ import java.util.StringTokenizer;
* Dav Gateway pop connection implementation
*/
public class PopConnection extends AbstractConnection {
protected static final int INITIAL = 0;
protected static final int USER = 1;
protected static final int AUTHENTICATED = 2;
private List<ExchangeSession.Message> messages;
// Initialize the streams and start the thread
@ -93,15 +90,15 @@ public class PopConnection extends AbstractConnection {
if (tokens.hasMoreTokens()) {
userName = tokens.nextToken();
sendOK("USER : " + userName);
state = USER;
state = State.USER;
} else {
sendERR("invalid syntax");
state = INITIAL;
state = State.INITIAL;
}
} else if ("PASS".equalsIgnoreCase(command)) {
if (state != USER) {
if (state != State.USER) {
sendERR("invalid state");
state = INITIAL;
state = State.INITIAL;
} else if (!tokens.hasMoreTokens()) {
sendERR("invalid syntax");
} else {
@ -111,7 +108,7 @@ public class PopConnection extends AbstractConnection {
session = ExchangeSessionFactory.getInstance(userName, password);
messages = session.getAllMessages("INBOX");
sendOK("PASS");
state = AUTHENTICATED;
state = State.AUTHENTICATED;
} catch (SocketException e) {
// can not send error to client after a socket exception
DavGatewayTray.warn("Client closed connection ", e);
@ -123,7 +120,7 @@ public class PopConnection extends AbstractConnection {
} else if ("CAPA".equalsIgnoreCase(command)) {
sendOK("Capability list follows");
printCapabilities();
} else if (state != AUTHENTICATED) {
} else if (state != State.AUTHENTICATED) {
sendERR("Invalid state not authenticated");
} else {
if ("STAT".equalsIgnoreCase(command)) {

View File

@ -18,13 +18,6 @@ import java.util.ArrayList;
* Dav Gateway smtp connection implementation
*/
public class SmtpConnection extends AbstractConnection {
protected static final int INITIAL = 0;
protected static final int AUTHENTICATED = 1;
protected static final int STARTMAIL = 2;
protected static final int RECIPIENT = 3;
protected static final int MAILDATA = 4;
protected static final int LOGIN = 5;
public static final int PASSWORD = 6;
// Initialize the streams and start the thread
public SmtpConnection(Socket clientSocket) {
@ -50,12 +43,12 @@ public class SmtpConnection extends AbstractConnection {
if (tokens.hasMoreTokens()) {
String command = tokens.nextToken();
if (state == LOGIN) {
if (state == State.LOGIN) {
// AUTH LOGIN, read userName
userName = base64Decode(line);
sendClient("334 " + base64Encode("Password:"));
state = PASSWORD;
} else if (state == PASSWORD) {
state = State.PASSWORD;
} else if (state == State.PASSWORD) {
// AUTH LOGIN, read password
password = base64Decode(line);
authenticate();
@ -78,7 +71,7 @@ public class SmtpConnection extends AbstractConnection {
authenticate();
} else if ("LOGIN".equals(authType)) {
sendClient("334 " + base64Encode("Username:"));
state = LOGIN;
state = State.LOGIN;
} else {
sendClient("451 Error : unknown authentication type");
}
@ -86,23 +79,23 @@ public class SmtpConnection extends AbstractConnection {
sendClient("451 Error : authentication type not specified");
}
} else if ("MAIL".equals(command)) {
if (state == AUTHENTICATED) {
state = STARTMAIL;
if (state == State.AUTHENTICATED) {
state = State.STARTMAIL;
recipients.clear();
sendClient("250 Sender OK");
} else {
state = INITIAL;
state = State.INITIAL;
sendClient("503 Bad sequence of commands");
}
} else if ("RCPT".equals(command)) {
if (state == STARTMAIL || state == RECIPIENT) {
if (state == State.STARTMAIL || state == State.RECIPIENT) {
if (line.startsWith("RCPT TO:")) {
state = RECIPIENT;
state = State.RECIPIENT;
try {
InternetAddress internetAddress = new InternetAddress(line.substring("RCPT TO:".length()));
recipients.add(internetAddress.getAddress());
} catch (AddressException e) {
throw new IOException("Invalid recipient: "+line);
throw new IOException("Invalid recipient: " + line);
}
sendClient("250 Recipient OK");
} else {
@ -110,26 +103,26 @@ public class SmtpConnection extends AbstractConnection {
}
} else {
state = AUTHENTICATED;
state = State.AUTHENTICATED;
sendClient("503 Bad sequence of commands");
}
} else if ("DATA".equals(command)) {
if (state == RECIPIENT) {
state = MAILDATA;
if (state == State.RECIPIENT) {
state = State.MAILDATA;
sendClient("354 Start mail input; end with <CRLF>.<CRLF>");
try {
session.sendMessage(recipients, in);
state = AUTHENTICATED;
state = State.AUTHENTICATED;
sendClient("250 Queued mail for delivery");
} catch (Exception e) {
DavGatewayTray.error("Authentication failed", e);
state = AUTHENTICATED;
state = State.AUTHENTICATED;
sendClient("451 Error : " + e + " " + e.getMessage());
}
} else {
state = AUTHENTICATED;
state = State.AUTHENTICATED;
sendClient("503 Bad sequence of commands");
}
}
@ -165,7 +158,7 @@ public class SmtpConnection extends AbstractConnection {
try {
session = ExchangeSessionFactory.getInstance(userName, password);
sendClient("235 OK Authenticated");
state = AUTHENTICATED;
state = State.AUTHENTICATED;
} catch (Exception e) {
DavGatewayTray.error(e);
String message = e.getMessage();
@ -174,7 +167,7 @@ public class SmtpConnection extends AbstractConnection {
}
message = message.replaceAll("\\n", " ");
sendClient("554 Authenticated failed " + message);
state = INITIAL;
state = State.INITIAL;
}
}