/* * 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.smtp; import java.io.ByteArrayInputStream; 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.mime.MimeHeader; import org.krakenapps.pcap.decoder.smtp.impl.SmtpDataImpl; import org.krakenapps.pcap.decoder.smtp.impl.SmtpSession; import org.krakenapps.pcap.decoder.tcp.TcpProcessor; import org.krakenapps.pcap.decoder.tcp.TcpSessionKey; import org.krakenapps.pcap.util.Buffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author mindori */ public class SmtpDecoder implements TcpProcessor { private Logger logger = LoggerFactory.getLogger(SmtpDecoder.class.getName()); private Set<SmtpProcessor> callbacks; private Map<TcpSessionKey, SmtpSession> sessionMap; public SmtpDecoder() { callbacks = new HashSet<SmtpProcessor>(); sessionMap = new HashMap<TcpSessionKey, SmtpSession>(); } public void register(SmtpProcessor processor) { callbacks.add(processor); } public void unregister(SmtpProcessor processor) { callbacks.remove(processor); } @Override public void handleTx(TcpSessionKey sessionKey, Buffer data) { SmtpSession session = sessionMap.get(sessionKey); Buffer txBuffer = session.getTxBuffer(); txBuffer.addLast(data); handleTx(session, txBuffer); } @Override public void handleRx(TcpSessionKey sessionKey, Buffer data) { SmtpSession session = sessionMap.get(sessionKey); Buffer rxBuffer = session.getRxBuffer(); rxBuffer.addLast(data); handleRx(session, rxBuffer); } @Override public void onEstablish(TcpSessionKey session) { if (logger.isDebugEnabled()) logger.debug("-> SMTP Session Established: " + (int) session.getClientPort() + " -> " + (int) session.getServerPort()); sessionMap.put(session, new SmtpSession()); } @Override public void onFinish(TcpSessionKey session) { if (logger.isDebugEnabled()) logger.debug("-> SMTP Session Closed: \n" + "Client Port: " + (int) session.getClientPort() + "\nServer Port: " + (int) session.getServerPort()); sessionMap.remove(session); } @Override public void onReset(TcpSessionKey session) { SmtpSession smtpSession = sessionMap.get(session); if (smtpSession != null) { if (logger.isDebugEnabled()) logger.debug("Deallocate tx, rx buffer and remove smtp session."); smtpSession.clear(); sessionMap.remove(session); } } private void handleTx(SmtpSession session, Buffer buf) { if (session.isDataMode()) { handleClientData(session, buf); } else { handleClientCommand(session, buf); session.resetTx(); } } private void handleRx(SmtpSession session, Buffer buf) { handleReply(session, buf); session.resetRx(); } private void handleClientData(SmtpSession session, Buffer txBuffer) { // store until find \r\n.\r\n int length = txBuffer.bytesBefore(new byte[] { 0x0d, 0x0a, 0x2e, 0x0d, 0x0a }); if (length == 0) { return; } session.setDataMode(false); byte[] emailData = new byte[length]; txBuffer.gets(emailData, 0, length); MimeMessage msg = createMimeMessage(emailData); MimeHeader header = new MimeHeader(); Charset headerCharset = header.getHeaderCharset(msg); header.decodeHeader(headerCharset, emailData); SmtpDataImpl smtpData = new SmtpDataImpl(msg); getMessage(header, smtpData); session.reset(); } private void handleClientCommand(SmtpSession session, Buffer txBuffer) { while(true) { int length = txBuffer.bytesBefore(new byte[] { 0x0d, 0x0a }); if (length == 0) { return; } byte[] b = new byte[length]; txBuffer.gets(b, 0, length); /* skip \r\n */ txBuffer.get(); txBuffer.get(); String command = new String(b); if(command.equals("DATA")) { session.setDataMode(true); dispatchCommand(command, ""); break; } else if(command.matches("\\w{4} .+")) { String parameter = command.substring(5); command = command.substring(0, 4); dispatchCommand(command, parameter); } else { /* don't have parameter(ex. QUIT) */ dispatchCommand(command, ""); } } } private void handleReply(SmtpSession session, Buffer rxBuffer) { while(true) { int length = rxBuffer.bytesBefore(new byte[] { 0x0d, 0x0a }); if (length == 0) { return; } byte[] b = new byte[length]; rxBuffer.gets(b, 0, length); /* skip \r\n */ rxBuffer.get(); rxBuffer.get(); String reply = new String(b); if (reply.matches("\\d{3}.+")) { int replyCode = Integer.parseInt(reply.substring(0, 3)); dispatchReply(replyCode, reply.substring(4)); } } } 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("smtp decoder: mime parse error" + e); } return null; } private void getMessage(MimeHeader header, SmtpDataImpl smtpData) { for (SmtpProcessor processor : callbacks) { processor.onSend(header, smtpData); } } private void dispatchCommand(String command, String parameter) { for (SmtpProcessor processor : callbacks) { processor.onCommand(command, parameter); } } private void dispatchReply(int replyCode, String replyMessage) { for (SmtpProcessor processor : callbacks) { processor.onReply(replyCode, replyMessage); } } }