/* Copyright 2009 David Revell This file is part of SwiFTP. SwiFTP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SwiFTP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SwiFTP. If not, see <http://www.gnu.org/licenses/>. */ package org.swiftp; import java.io.File; import java.lang.reflect.Constructor; import android.util.Log; public abstract class FtpCmd implements Runnable { protected SessionThread sessionThread; protected MyLog myLog; protected static MyLog staticLog = new MyLog(FtpCmd.class.toString()); private static FtpCmd getCmd(String cmd, SessionThread sessionThread, String input) { if ("SYST".equals(cmd)) return new CmdSYST(sessionThread, input); if ("USER".equals(cmd)) return new CmdUSER(sessionThread, input); if ("PASS".equals(cmd)) return new CmdPASS(sessionThread, input); if ("TYPE".equals(cmd)) return new CmdTYPE(sessionThread, input); if ("CWD".equals(cmd)) return new CmdCWD(sessionThread, input); if ("PWD".equals(cmd)) return new CmdPWD(sessionThread, input); if ("LIST".equals(cmd)) return new CmdLIST(sessionThread, input); if ("PASV".equals(cmd)) return new CmdPASV(sessionThread, input); if ("RETR".equals(cmd)) return new CmdRETR(sessionThread, input); if ("NLST".equals(cmd)) return new CmdNLST(sessionThread, input); if ("NOOP".equals(cmd)) return new CmdNOOP(sessionThread, input); if ("STOR".equals(cmd)) return new CmdSTOR(sessionThread, input); if ("DELE".equals(cmd)) return new CmdDELE(sessionThread, input); if ("RNFR".equals(cmd)) return new CmdRNFR(sessionThread, input); if ("RNTO".equals(cmd)) return new CmdRNTO(sessionThread, input); if ("RMD".equals(cmd)) return new CmdRMD(sessionThread, input); if ("MKD".equals(cmd)) return new CmdMKD(sessionThread, input); if ("OPTS".equals(cmd)) return new CmdOPTS(sessionThread, input); if ("PORT".equals(cmd)) return new CmdPORT(sessionThread, input); if ("QUIT".equals(cmd)) return new CmdQUIT(sessionThread, input); if ("FEAT".equals(cmd)) return new CmdFEAT(sessionThread, input); if ("SIZE".equals(cmd)) return new CmdSIZE(sessionThread, input); if ("CDUP".equals(cmd)) return new CmdCDUP(sessionThread, input); if ("APPE".equals(cmd)) return new CmdAPPE(sessionThread, input); if ("XCUP".equals(cmd)) return new CmdCDUP(sessionThread, input); // synonym if ("XPWD".equals(cmd)) return new CmdPWD(sessionThread, input); // synonym if ("XMKD".equals(cmd)) return new CmdMKD(sessionThread, input); // synonym if ("XRMD".equals(cmd)) return new CmdRMD(sessionThread, input); // synonym return null; }; public FtpCmd(SessionThread sessionThread, String logName) { this.sessionThread = sessionThread; myLog = new MyLog(logName); } abstract public void run(); protected static void dispatchCommand(SessionThread session, String inputString) { String[] strings = inputString.split(" "); String unrecognizedCmdMsg = "502 Command not recognized\r\n"; if(strings == null) { // There was some egregious sort of parsing error String errString = "502 Command parse error\r\n"; staticLog.l(Log.INFO, errString); session.writeString(errString); return; } if(strings.length < 1) { staticLog.l(Log.INFO, "No strings parsed"); session.writeString(unrecognizedCmdMsg); return; } String verb = strings[0]; if(verb.length() < 1) { staticLog.l(Log.INFO, "Invalid command verb"); session.writeString(unrecognizedCmdMsg); return; } verb = verb.trim(); verb = verb.toUpperCase(); FtpCmd cmdInstance = getCmd(verb, session, inputString); if(cmdInstance == null) { // If we couldn't find a matching command, staticLog.l(Log.DEBUG, "Ignoring unrecognized FTP verb: " + verb); session.writeString(unrecognizedCmdMsg); return; } else if(session.isAuthenticated() || cmdInstance.getClass().equals(CmdUSER.class) || cmdInstance.getClass().equals(CmdPASS.class) || cmdInstance.getClass().equals(CmdUSER.class)) { // Unauthenticated users can run only USER, PASS and QUIT cmdInstance.run(); } else { session.writeString("530 Login first with USER and PASS\r\n"); } } /** * An FTP parameter is that part of the input string that occurs * after the first space, including any subsequent spaces. Also, * we want to chop off the trailing '\r\n', if present. * * Some parameters shouldn't be logged or output (e.g. passwords), * so the caller can use silent==true in that case. */ static public String getParameter(String input, boolean silent) { if(input == null) { return ""; } int firstSpacePosition = input.indexOf(' '); if(firstSpacePosition == -1) { return ""; } String retString = input.substring(firstSpacePosition+1); // Remove trailing whitespace // todo: trailing whitespace may be significant, just remove \r\n retString = retString.replaceAll("\\s+$", ""); if(!silent) { staticLog.l(Log.DEBUG, "Parsed argument: " + retString); } return retString; } /** * A wrapper around getParameter, for when we don't want it to be silent. */ static public String getParameter(String input) { return getParameter(input, false); } public static File inputPathToChrootedFile(File existingPrefix, String param) { try { if(param.charAt(0) == '/') { // The STOR contained an absolute path File chroot = Globals.getChrootDir(); return new File(chroot, param); } } catch (Exception e) {} // The STOR contained a relative path return new File(existingPrefix, param); } public boolean violatesChroot(File file) { File chroot = Globals.getChrootDir(); try { String canonicalPath = file.getCanonicalPath(); if(!canonicalPath.startsWith(chroot.toString())) { myLog.l(Log.INFO, "Path violated folder restriction, denying"); myLog.l(Log.DEBUG, "path: " + canonicalPath); myLog.l(Log.DEBUG, "chroot: " + chroot.toString()); return true; // the path must begin with the chroot path } return false; } catch(Exception e) { myLog.l(Log.INFO, "Path canonicalization problem: " + e.toString()); myLog.l(Log.INFO, "When checking file: " + file.getAbsolutePath()); return true; // for security, assume violation } } }