/*
* Copyright 2010 NCHOVY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.krakenapps.pcap.decoder.pop3;
import java.io.ByteArrayInputStream;
import java.nio.BufferUnderflowException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.MimeMessage;
import org.krakenapps.pcap.decoder.pop3.impl.Pop3DataImpl;
import org.krakenapps.pcap.decoder.pop3.impl.Pop3Session;
import org.krakenapps.pcap.decoder.pop3.impl.Pop3State;
import org.krakenapps.pcap.decoder.tcp.TcpProcessor;
import org.krakenapps.pcap.decoder.tcp.TcpSessionKey;
import org.krakenapps.pcap.util.Buffer;
import org.krakenapps.mime.MimeHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author mindori
*/
public class Pop3Decoder implements TcpProcessor {
private Logger logger = LoggerFactory.getLogger(Pop3Decoder.class.getName());
private Set<Pop3Processor> callbacks;
private Map<TcpSessionKey, Pop3Session> sessionMap;
public Pop3Decoder() {
callbacks = new HashSet<Pop3Processor>();
sessionMap = new HashMap<TcpSessionKey, Pop3Session>();
}
public void register(Pop3Processor processor) {
callbacks.add(processor);
}
public void unregister(Pop3Processor processor) {
callbacks.remove(processor);
}
@Override
public void onEstablish(TcpSessionKey session) {
if (logger.isDebugEnabled())
logger.debug("-> POP3 Session Established: " + (int) session.getClientPort() + " -> " + (int) session.getServerPort());
sessionMap.put(session, new Pop3Session());
}
@Override
public void onFinish(TcpSessionKey session) {
if (logger.isDebugEnabled())
logger.debug("-> POP3 Session Closed: \n" + "Client Port: " + (int) session.getClientPort() + "\nServer Port: " + (int) session.getServerPort());
sessionMap.remove(session);
}
@Override
public void onReset(TcpSessionKey session) {
Pop3Session pop3Session = sessionMap.get(session);
if (pop3Session != null) {
if (logger.isDebugEnabled())
logger.debug("Deallocate tx, rx buffer and remove pop3 session.");
pop3Session.clear();
sessionMap.remove(session);
}
}
@Override
public void handleTx(TcpSessionKey sessionKey, Buffer data) {
Pop3Session session = sessionMap.get(sessionKey);
Buffer txBuffer = session.getTxBuffer();
txBuffer.addLast(data);
parseTx(session, txBuffer);
}
@Override
public void handleRx(TcpSessionKey sessionKey, Buffer data) {
Pop3Session session = sessionMap.get(sessionKey);
Buffer rxBuffer = session.getRxBuffer();
rxBuffer.addLast(data);
parseRx(session, rxBuffer);
}
private void parseTx(Pop3Session session, Buffer txBuffer) {
try {
int len = txBuffer.bytesBefore(new byte[] { 0x0d, 0x0a });
if (len == 0) {
return;
}
byte[] t = new byte[len];
txBuffer.gets(t, 0, t.length);
/* skip \r\n */
txBuffer.get();
txBuffer.get();
String command = new String(t);
sendCommand(command);
handleCommand(command, session, txBuffer);
} catch (BufferUnderflowException e) {
txBuffer.reset();
return;
}
}
private void parseRx(Pop3Session session, Buffer rxBuffer) {
switch (session.getState()) {
case NONE:
try {
int len = rxBuffer.bytesBefore(new byte[] { 0x0d, 0x0a });
if (len == 0) {
return;
}
byte[] t = new byte[len];
rxBuffer.gets(t, 0, t.length);
/* skip \r\n */
rxBuffer.get();
rxBuffer.get();
String response = new String(t);
sendResponse(response);
} catch (BufferUnderflowException e) {
rxBuffer.reset();
return;
}
break;
case FIND_UIDL:
case FIND_LIST:
try {
int len = rxBuffer.bytesBefore(new byte[] { 0x0d, 0x0a, 0x2e, 0x0d, 0x0a });
if (len == 0) {
return;
}
byte[] t = new byte[len + 5];
rxBuffer.gets(t, 0, t.length);
} catch (BufferUnderflowException e) {
rxBuffer.reset();
return;
}
break;
case FIND_TOP:
try {
while (true) {
rxBuffer.get();
}
} catch (BufferUnderflowException e) {
return;
}
case FIND_RETR:
if (!session.isSkipRETRMessage()) {
/* skip response message */
try {
int len = rxBuffer.bytesBefore(new byte[] { 0x0d, 0x0a });
if (len == 0) {
return;
}
byte[] t = new byte[len + 2];
rxBuffer.gets(t, 0, t.length);
session.setSkipRETRMessage(true);
} catch (BufferUnderflowException e) {
rxBuffer.reset();
return;
}
}
else {
if (!session.isRemarkStart()) {
/* record start point of e-mail */
session.setRemarkStart(true);
}
int length = rxBuffer.bytesBefore(new byte[] { 0x0d, 0x0a, 0x2e, 0x0d, 0x0a });
if (length == 0) {
return;
}
byte[] emailData = new byte[length];
rxBuffer.gets(emailData, 0, length);
MimeMessage msg = createMimeMessage(emailData);
MimeHeader header = new MimeHeader();
Charset headerCharset = header.getHeaderCharset(msg);
header.decodeHeader(headerCharset, emailData);
Pop3DataImpl pop3Data = new Pop3DataImpl(msg);
getMessage(header, pop3Data);
/* skip 'CRLF.CRLF' */
byte[] t = new byte[5];
rxBuffer.gets(t, 0, t.length);
/* initialize e-mail variables */
session.setRemarkStart(false);
session.setSkipRETRMessage(false);
/* deallocate and reallocate */
session.clear();
session.setState(Pop3State.NONE);
}
break;
case FIND_DELE:
/* skip '+OK' */
byte[] msg = new byte[6];
rxBuffer.gets(msg);
break;
}
}
private void handleCommand(String command, Pop3Session session, Buffer txBuffer) {
if (command.equalsIgnoreCase("UIDL")) {
session.setState(Pop3State.FIND_UIDL);
} else if (command.equalsIgnoreCase("LIST")) {
session.setState(Pop3State.FIND_LIST);
} else if (command.length() > 4 && command.substring(0, 3).equalsIgnoreCase("TOP")) {
session.setState(Pop3State.FIND_TOP);
} else if (command.length() > 5 && command.substring(0, 4).equalsIgnoreCase("RETR")) {
session.setState(Pop3State.FIND_RETR);
} else if (command.length() > 5 && command.substring(0, 4).equalsIgnoreCase("DELE")) {
session.setState(Pop3State.FIND_DELE);
} else if (session.getState().compareTo(Pop3State.FIND_TOP) == 0) {
session.setState(Pop3State.NONE);
}
}
private MimeMessage createMimeMessage(byte[] data) {
Session mailSession = Session.getDefaultInstance(new Properties());
InputStream is = new ByteArrayInputStream(data, 0, data.length);
try {
return new MimeMessage(mailSession, is);
} catch (MessagingException e) {
logger.error("pop3 decoder: mime parse error" + e);
}
return null;
}
private void getMessage(MimeHeader header, Pop3DataImpl pop3Data) {
for (Pop3Processor p : callbacks)
p.onReceive(header, pop3Data);
}
private void sendCommand(String command) {
for (Pop3Processor p : callbacks)
p.onCommand(command);
}
private void sendResponse(String response) {
for (Pop3Processor p : callbacks)
p.onResponse(response);
}
}