/** * Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the * COPYRIGHT.txt in the distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation; either version 3.0 of the * License, or (at your option) any later version. * * This software 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with this * software; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.waarp.gateway.ftp.control; import java.io.File; import java.io.IOException; import io.netty.channel.Channel; import org.waarp.common.command.ReplyCode; import org.waarp.common.command.exception.CommandAbstractException; import org.waarp.common.command.exception.Reply421Exception; import org.waarp.common.command.exception.Reply451Exception; import org.waarp.common.command.exception.Reply502Exception; import org.waarp.common.command.exception.Reply504Exception; import org.waarp.common.database.DbSession; import org.waarp.common.database.data.AbstractDbData.UpdatedInfo; import org.waarp.common.database.exception.WaarpDatabaseNoConnectionException; import org.waarp.common.future.WaarpFuture; import org.waarp.common.logging.WaarpLogger; import org.waarp.common.logging.WaarpLoggerFactory; import org.waarp.ftp.core.command.AbstractCommand; import org.waarp.ftp.core.command.FtpCommandCode; import org.waarp.ftp.core.command.access.QUIT; import org.waarp.ftp.core.control.BusinessHandler; import org.waarp.ftp.core.data.FtpTransfer; import org.waarp.ftp.core.exception.FtpNoFileException; import org.waarp.ftp.core.file.FtpFile; import org.waarp.ftp.core.session.FtpSession; import org.waarp.ftp.filesystembased.FilesystemBasedFtpRestart; import org.waarp.gateway.ftp.config.AUTHUPDATE; import org.waarp.gateway.ftp.config.FileBasedConfiguration; import org.waarp.gateway.ftp.database.DbConstant; import org.waarp.gateway.ftp.file.FileBasedAuth; import org.waarp.gateway.ftp.file.FileBasedDir; import org.waarp.gateway.kernel.exec.AbstractExecutor; import org.waarp.gateway.kernel.exec.R66PreparedTransferExecutor; /** * BusinessHandler implementation that allows pre and post actions on any operations and * specifically on transfer operations * * @author Frederic Bregier * */ public class ExecBusinessHandler extends BusinessHandler { /** * Internal Logger */ private static final WaarpLogger logger = WaarpLoggerFactory .getLogger(ExecBusinessHandler.class); /** * Associated DbFtpSession */ private DbSession dbFtpSession = null; /** * Associated DbR66Session */ private DbSession dbR66Session = null; private boolean internalDb = false; @Override public void afterTransferDoneBeforeAnswer(FtpTransfer transfer) throws CommandAbstractException { // if Admin, do nothing if (getFtpSession() == null || getFtpSession().getAuth() == null) { return; } FileBasedAuth auth = (FileBasedAuth) getFtpSession().getAuth(); if (auth.isAdmin()) { return; } long specialId = auth.getSpecialId(); ReplyCode replyCode = getFtpSession().getReplyCode(); logger.debug("Transfer done but action needed: "+(!(replyCode != ReplyCode.REPLY_250_REQUESTED_FILE_ACTION_OKAY && replyCode != ReplyCode.REPLY_226_CLOSING_DATA_CONNECTION))); if (replyCode != ReplyCode.REPLY_250_REQUESTED_FILE_ACTION_OKAY && replyCode != ReplyCode.REPLY_226_CLOSING_DATA_CONNECTION) { // Do nothing String message = "Transfer done with code: " + getFtpSession().getReplyCode().getMesg(); WaarpActionLogger.logErrorAction(dbFtpSession, specialId, transfer, message, getFtpSession().getReplyCode(), this); return; } // if STOR like: get file (can be STOU) and execute external action FtpCommandCode code = transfer.getCommand(); logger.debug("Checking action vs auth after transfer: {}", code); switch (code) { case RETR: // nothing to do since All done WaarpActionLogger.logAction(dbFtpSession, specialId, "Retrieve executed: OK", this, getFtpSession().getReplyCode(), UpdatedInfo.RUNNING); break; case APPE: case STOR: case STOU: // execute the store command WaarpFuture futureCompletion = new WaarpFuture(true); String[] args = new String[6]; args[0] = auth.getUser(); args[1] = auth.getAccount(); args[2] = auth.getBaseDirectory(); FtpFile file; try { file = transfer.getFtpFile(); } catch (FtpNoFileException e1) { // File cannot be sent String message = "PostExecution in Error for Transfer since No File found: " + transfer.getCommand() + " " + transfer.getStatus() + " " + transfer.getPath(); CommandAbstractException exc = new Reply421Exception( "PostExecution in Error for Transfer since No File found"); WaarpActionLogger.logErrorAction(dbFtpSession, specialId, transfer, message, exc.code, this); throw exc; } try { args[3] = file.getFile(); File newfile = new File(args[2] + args[3]); // Here the transfer is successful. If the file does not exist on disk // We create it : the transfered file was empty. try { newfile.createNewFile(); } catch (IOException e) { throw new Reply421Exception( "PostExecution in Error for Transfer since No File found"); } catch (SecurityException e) { throw new Reply421Exception( "PostExecution in Error for Transfer since No File found"); } if (!newfile.canRead()) { // File cannot be sent String message = "PostExecution in Error for Transfer since File is not readable: " + transfer.getCommand() + " " + newfile.getAbsolutePath() + ":" + newfile.canRead() + " " + transfer.getStatus() + " " + transfer.getPath(); CommandAbstractException exc = new Reply421Exception( "Transfer done but force disconnection since an error occurs on PostOperation"); WaarpActionLogger.logErrorAction(dbFtpSession, specialId, transfer, message, exc.code, this); throw exc; } } catch (CommandAbstractException e1) { // File cannot be sent String message = "PostExecution in Error for Transfer since No File found: " + transfer.getCommand() + " " + transfer.getStatus() + " " + transfer.getPath(); CommandAbstractException exc = new Reply421Exception( "Transfer done but force disconnection since an error occurs on PostOperation"); WaarpActionLogger.logErrorAction(dbFtpSession, specialId, transfer, message, exc.code, this); throw exc; } args[4] = transfer.getCommand().toString(); args[5] = Long.toString(specialId); AbstractExecutor executor = AbstractExecutor.createAbstractExecutor(auth, args, true, futureCompletion); if (executor instanceof R66PreparedTransferExecutor) { ((R66PreparedTransferExecutor) executor).setDbsession(dbR66Session); } executor.run(); try { futureCompletion.await(); } catch (InterruptedException e) { } if (futureCompletion.isSuccess()) { // All done WaarpActionLogger.logAction(dbFtpSession, specialId, "Post-Command executed: OK", this, getFtpSession().getReplyCode(), UpdatedInfo.RUNNING); } else { // File cannot be sent String message = "PostExecution in Error for Transfer: " + transfer.getCommand() + " " + transfer.getStatus() + " " + transfer.getPath() + "\n " + (futureCompletion.getCause() != null ? futureCompletion.getCause().getMessage() : "Internal error of PostExecution"); CommandAbstractException exc = new Reply421Exception( "Transfer done but force disconnection since an error occurs on PostOperation"); WaarpActionLogger.logErrorAction(dbFtpSession, specialId, transfer, message, exc.code, this); throw exc; } break; default: // nothing to do } } @Override public void afterRunCommandKo(CommandAbstractException e) { String message = "ExecHandler: KO: " + getFtpSession() + " " + e.getMessage(); long specialId = ((FileBasedAuth) getFtpSession().getAuth()).getSpecialId(); WaarpActionLogger.logErrorAction(dbFtpSession, specialId, null, message, e.code, this); ((FileBasedAuth) getFtpSession().getAuth()).setSpecialId(DbConstant.ILLEGALVALUE); } @Override public void afterRunCommandOk() throws CommandAbstractException { if (!(this.getFtpSession().getCurrentCommand() instanceof QUIT) && this.dbR66Session != null) { long specialId = ((FileBasedAuth) getFtpSession().getAuth()).getSpecialId(); WaarpActionLogger.logAction(dbFtpSession, specialId, "Transfer Command fully executed: OK", this, getFtpSession().getReplyCode(), UpdatedInfo.DONE); ((FileBasedAuth) getFtpSession().getAuth()).setSpecialId(DbConstant.ILLEGALVALUE); } } @Override public void beforeRunCommand() throws CommandAbstractException { long specialId = DbConstant.ILLEGALVALUE; // if Admin, do nothing if (getFtpSession() == null || getFtpSession().getAuth() == null) { return; } FileBasedAuth auth = (FileBasedAuth) getFtpSession().getAuth(); if (auth.isAdmin()) { logger.debug("Admin user so all actions are allowed"); return; } // Test limits FtpConstraintLimitHandler constraints = ((FileBasedConfiguration) getFtpSession().getConfiguration()) .constraintLimitHandler; if (constraints != null) { if (!auth.isIdentified()) { // ignore test since it can be an Admin connection } else if (auth.isAdmin()) { // ignore test since it is an Admin connection (always valid) } else if (!FtpCommandCode.isSpecialCommand( getFtpSession().getCurrentCommand().getCode())) { // Authenticated, not Admin and not Special Command if (constraints.checkConstraintsSleep(1)) { if (constraints.checkConstraints()) { // Really overload so refuse the command logger.info("Server overloaded. {} Try later... \n" + getFtpSession().toString(), constraints.lastAlert); if (FileBasedConfiguration.fileBasedConfiguration.ftpMib != null) { FileBasedConfiguration.fileBasedConfiguration.ftpMib. notifyOverloaded("Server overloaded", getFtpSession().toString()); } throw new Reply451Exception("Server overloaded. Try later..."); } } } } FtpCommandCode code = getFtpSession().getCurrentCommand().getCode(); logger.debug("Checking action vs auth before command: {}", code); switch (code) { case APPE: case STOR: case STOU: auth.setSpecialId(specialId); if (!auth.getCommandExecutor().isValidOperation(true)) { throw new Reply504Exception("STORe like operations are not allowed"); } // create entry in log specialId = WaarpActionLogger.logCreate(dbFtpSession, "PrepareTransfer: OK", getFtpSession().getCurrentCommand().getArg(), this); auth.setSpecialId(specialId); // nothing to do now break; case RETR: auth.setSpecialId(specialId); if (!auth.getCommandExecutor().isValidOperation(false)) { throw new Reply504Exception("RETRieve like operations are not allowed"); } // create entry in log specialId = WaarpActionLogger.logCreate(dbFtpSession, "PrepareTransfer: OK", getFtpSession().getCurrentCommand().getArg(), this); auth.setSpecialId(specialId); // execute the external retrieve command before the execution of RETR WaarpFuture futureCompletion = new WaarpFuture(true); String[] args = new String[6]; args[0] = auth.getUser(); args[1] = auth.getAccount(); args[2] = auth.getBaseDirectory(); String filename = getFtpSession().getCurrentCommand().getArg(); FtpFile file = getFtpSession().getDir().setFile(filename, false); args[3] = file.getFile(); args[4] = code.toString(); args[5] = Long.toString(specialId); AbstractExecutor executor = AbstractExecutor .createAbstractExecutor(auth, args, false, futureCompletion); if (executor instanceof R66PreparedTransferExecutor) { ((R66PreparedTransferExecutor) executor).setDbsession(dbR66Session); } executor.run(); try { futureCompletion.await(); } catch (InterruptedException e) { } if (futureCompletion.isSuccess()) { // File should be ready if (!file.canRead()) { logger.error("PreExecution in Error for Transfer since " + "File downloaded but not ready to be retrieved: {} " + " {} \n " + (futureCompletion.getCause() != null ? futureCompletion.getCause().getMessage() : "File downloaded but not ready to be retrieved"), args[4], args[3]); throw new Reply421Exception( "File downloaded but not ready to be retrieved"); } WaarpActionLogger.logAction(dbFtpSession, specialId, "Pre-Command executed: OK", this, getFtpSession().getReplyCode(), UpdatedInfo.RUNNING); } else { // File cannot be retrieved logger.error("PreExecution in Error for Transfer since " + "File cannot be prepared to be retrieved: {} " + " {} \n " + (futureCompletion.getCause() != null ? futureCompletion.getCause().getMessage() : "File cannot be prepared to be retrieved"), args[4], args[3]); throw new Reply421Exception( "File cannot be prepared to be retrieved"); } break; default: // nothing to do } } @Override protected void cleanSession() { } @Override public void exceptionLocalCaught(Throwable cause) { if (FileBasedConfiguration.fileBasedConfiguration.ftpMib != null) { String mesg; if (cause != null && cause.getMessage() != null) { mesg = cause.getMessage(); } else { if (this.getFtpSession() != null) { mesg = "Exception while " + this.getFtpSession().getReplyCode().getMesg(); } else { mesg = "Unknown Exception"; } } FileBasedConfiguration.fileBasedConfiguration.ftpMib. notifyError("Exception trapped", mesg); } if (FileBasedConfiguration.fileBasedConfiguration.monitoring != null) { if (this.getFtpSession() != null) { FileBasedConfiguration.fileBasedConfiguration.monitoring. updateCodeNoTransfer(this.getFtpSession().getReplyCode()); } } } @Override public void executeChannelClosed() { if (AbstractExecutor.useDatabase) { if (!internalDb) { if (dbR66Session != null) { dbR66Session.disconnect(); dbR66Session = null; } } } if (dbFtpSession != null) { dbFtpSession.disconnect(); dbFtpSession = null; } } @Override public void executeChannelConnected(Channel channel) { if (AbstractExecutor.useDatabase) { if (org.waarp.openr66.database.DbConstant.admin != null && org.waarp.openr66.database.DbConstant.admin.isActive()) { try { dbR66Session = new DbSession(org.waarp.openr66.database.DbConstant.admin, false); } catch (WaarpDatabaseNoConnectionException e1) { logger.warn("Database not ready due to {}", e1.getMessage()); QUIT command = (QUIT) FtpCommandCode.getFromLine(getFtpSession(), FtpCommandCode.QUIT.name()); this.getFtpSession().setNextCommand(command); dbR66Session = null; internalDb = true; } } } if (DbConstant.gatewayAdmin != null && DbConstant.gatewayAdmin.isActive()) { try { dbFtpSession = new DbSession(DbConstant.gatewayAdmin, false); } catch (WaarpDatabaseNoConnectionException e1) { logger.warn("Database not ready due to {}", e1.getMessage()); QUIT command = (QUIT) FtpCommandCode.getFromLine(getFtpSession(), FtpCommandCode.QUIT.name()); this.getFtpSession().setNextCommand(command); dbFtpSession = null; } } } @Override public FileBasedAuth getBusinessNewAuth() { return new FileBasedAuth(getFtpSession()); } @Override public FileBasedDir getBusinessNewDir() { return new FileBasedDir(getFtpSession()); } @Override public FilesystemBasedFtpRestart getBusinessNewRestart() { return new FilesystemBasedFtpRestart(getFtpSession()); } @Override public String getHelpMessage(String arg) { return "This FTP server is only intend as a Gateway. RETRieve actions may be unallowed.\n" + "This FTP server refers to RFC 959, 775, 2389, 2428, 3659 and supports XCRC, XMD5 and XSHA1 commands.\n" + "XCRC, XMD5 and XSHA1 take a simple filename as argument and return \"250 digest-value is the digest of filename\"."; } @Override public String getFeatMessage() { StringBuilder builder = new StringBuilder("Extensions supported:").append('\n').append(getDefaultFeatMessage()); if (getFtpSession().getConfiguration().getFtpInternalConfiguration().isAcceptAuthProt()) { builder.append('\n').append(getSslFeatMessage()); } builder.append('\n').append(FtpCommandCode.SITE.name()).append(' ').append("AUTHUPDATE").append("\nEnd"); return builder.toString(); } @Override public String getOptsMessage(String[] args) throws CommandAbstractException { if (args.length > 0) { if (args[0].equalsIgnoreCase(FtpCommandCode.MLST.name()) || args[0].equalsIgnoreCase(FtpCommandCode.MLSD.name())) { return getMLSxOptsMessage(args); } throw new Reply502Exception("OPTS not implemented for " + args[0]); } throw new Reply502Exception("OPTS not implemented"); } @Override public AbstractCommand getSpecializedSiteCommand(FtpSession session, String line) { if (getFtpSession() == null || getFtpSession().getAuth() == null) { return null; } if (!session.getAuth().isAdmin()) { return null; } String newline = line; if (newline == null) { return null; } String command = null; String arg = null; if (newline.indexOf(' ') == -1) { command = newline; arg = null; } else { command = newline.substring(0, newline.indexOf(' ')); arg = newline.substring(newline.indexOf(' ') + 1); if (arg.length() == 0) { arg = null; } } String COMMAND = command.toUpperCase(); if (!COMMAND.equals("AUTHUPDATE")) { return null; } AbstractCommand abstractCommand = new AUTHUPDATE(); abstractCommand.setArgs(session, COMMAND, arg, FtpCommandCode.SITE); return abstractCommand; } }