/* * 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.ftp; import java.io.InputStream; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.krakenapps.pcap.Protocol; import org.krakenapps.pcap.decoder.tcp.TcpProtocolMapper; import org.krakenapps.pcap.decoder.tcp.TcpProcessor; import org.krakenapps.pcap.decoder.tcp.TcpSessionKey; import org.krakenapps.pcap.util.Buffer; import org.krakenapps.pcap.util.BufferInputStream; import org.krakenapps.pcap.util.ChainBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author mindori */ public class FtpDecoder implements TcpProcessor { private Logger logger = LoggerFactory.getLogger(FtpDecoder.class.getName()); private Set<FtpProcessor> callbacks; private Map<TcpSessionKey, FtpSession> sessionMap; private final TcpProtocolMapper mapper; /* multi-session variables */ private FtpDataSession dataSession; private TcpSessionKey key; private boolean isDownload = false; private String fileName = ""; /* list variables */ private boolean isViewList = false; private Buffer list; public FtpDecoder(TcpProtocolMapper mapper) { callbacks = new HashSet<FtpProcessor>(); sessionMap = new HashMap<TcpSessionKey, FtpSession>(); this.mapper = mapper; } public void register(FtpProcessor processor) { callbacks.add(processor); } public void unregister(FtpProcessor processor) { callbacks.remove(processor); } @Override public void handleTx(TcpSessionKey sessionKey, Buffer data) { FtpSession session = sessionMap.get(sessionKey); if (isDownload && !(sessionKey.equals(key))) dataSession.putData(data); else handleTxBuffer(sessionKey, session, data); } @Override public void handleRx(TcpSessionKey sessionKey, Buffer data) { FtpSession session = sessionMap.get(sessionKey); if (isDownload && !(sessionKey.equals(key))) dataSession.putData(data); else handleRxBuffer(sessionKey, session, data); } @Override public void onEstablish(TcpSessionKey session) { if (logger.isDebugEnabled()) logger.debug("-> ftp Session Established: " + (int) session.getClientPort() + " -> " + (int) session.getServerPort()); sessionMap.put(session, new FtpSession()); } @Override public void onFinish(TcpSessionKey session) { if (logger.isDebugEnabled()) logger.debug("-> ftp Session Closed: \n" + "Client Port: " + (int) session.getClientPort() + "\nServer Port: " + (int) session.getServerPort()); sessionMap.remove(session); } @Override public void onReset(TcpSessionKey session) { FtpSession ftpSession = sessionMap.get(session); if (ftpSession != null) { if (logger.isDebugEnabled()) logger.debug("Deallocate tx, rx buffer and remove ftp session."); ftpSession.clear(); sessionMap.remove(session); } } private void handleTxBuffer(TcpSessionKey key, FtpSession session, Buffer data) { Buffer txBuffer = session.getTxBuffer(); txBuffer.addLast(data); handleCommandSession(key, session, txBuffer); } private void handleRxBuffer(TcpSessionKey key, FtpSession session, Buffer data) { Buffer rxBuffer = session.getRxBuffer(); rxBuffer.addLast(data); handleCommandSession(key, session, rxBuffer); } private void handleCommandSession(TcpSessionKey key, FtpSession session, Buffer buffer) { while (true) { if (buffer.isEOB()) { break; } int length = buffer.bytesBefore(new byte[] { 0x0d, 0x0a }); if (length == 0) return; byte[] codes = new byte[4]; buffer.mark(); buffer.gets(codes, 0, 4); buffer.reset(); String code = new String(codes); if (code.matches("\\d{3} ")) { byte[] reply = new byte[length]; buffer.gets(reply, 0, length); /* skip \r\n */ buffer.get(); buffer.get(); dispatchReply(new String(reply)); if (code.equals("227 ")) { /* passive mode */ String replyStr = new String(reply); String[] token = replyStr.split(" "); InetSocketAddress sockAddr = new InetSocketAddress(key.getServerIp(), getPort(token)); mapper.register(sockAddr, Protocol.FTP); } else if (code.equals("226 ") || code.equals("250 ")) { /* get attached file */ if (fileName != "") { Buffer data = dataSession.getData(); BufferInputStream is = new BufferInputStream(data); dispatchFile(is, fileName); initMultiSession(); } /* view directory list */ else if (isViewList) { int remain = list.readableBytes(); if (remain <= 0) { /* get directory list. But, list is empty. */ list = null; isViewList = false; } else { byte[] b = new byte[remain]; list.gets(b, 0, remain); dispatchList(b); list = null; isViewList = false; } } } } else { byte[] command = new byte[length]; buffer.gets(command, 0, length); /* skip \r\n */ buffer.get(); buffer.get(); String commandStr = new String(command); if (isViewList) { list.addLast(command); list.addLast(new byte[] { 0x0d, 0x0a }); } else if (code.equalsIgnoreCase("LIST") || code.equalsIgnoreCase("NLST")) { fileName = ""; list = new ChainBuffer(); isViewList = true; dispatchCommand(commandStr); } else if (code.equalsIgnoreCase("STOR") || code.equalsIgnoreCase("RETR")) { dataSession = new FtpDataSession(); fileName = commandStr.split(" ")[1].replaceAll("\r\n", ""); isDownload = true; this.key = key; dispatchCommand(commandStr); } else if (code.equalsIgnoreCase("PORT")) { /* active mode - REGISTER PORT */ String[] token = commandStr.split(" "); InetSocketAddress sockAddr = new InetSocketAddress(key.getClientIp(), getPort(token)); mapper.register(sockAddr, Protocol.FTP); dispatchCommand(commandStr); } else { dispatchCommand(commandStr); } } } session.reset(); } private int getPort(String[] token) { String[] portCommand = token[token.length - 1].replaceAll("[()]\\.", "").split(","); int port = (Integer.parseInt(portCommand[4]) * 256) + Integer.parseInt(portCommand[5].replaceAll("\r\n", "")); return port; } private void initMultiSession() { dataSession = null; key = null; isDownload = false; fileName = ""; } private void dispatchCommand(String command) { for (FtpProcessor processor : callbacks) { processor.onCommand(command); } } private void dispatchReply(String reply) { for (FtpProcessor processor : callbacks) { processor.onReply(reply); } } private void dispatchList(byte[] list) { for (FtpProcessor processor : callbacks) { processor.viewList(list); } } private void dispatchFile(InputStream is, String fileName) { for (FtpProcessor processor : callbacks) { processor.onExtractFile(is, fileName); } } }