View Javadoc
1   /**
2    * Copyright 2009, Frederic Bregier, and individual contributors by the @author tags. See the
3    * COPYRIGHT.txt in the distribution for a full listing of individual contributors.
4    * 
5    * This is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
6    * General Public License as published by the Free Software Foundation; either version 3.0 of the
7    * License, or (at your option) any later version.
8    * 
9    * This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10   * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11   * GNU Lesser General Public License for more details.
12   * 
13   * You should have received a copy of the GNU Lesser General Public License along with this
14   * software; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
15   * Boston, MA 02110-1301 USA, or see the FSF site: http://www.fsf.org.
16   */
17  package org.waarp.gateway.ftp.control;
18  
19  import java.io.File;
20  import java.io.IOException;
21  
22  import io.netty.channel.Channel;
23  import org.waarp.common.command.ReplyCode;
24  import org.waarp.common.command.exception.CommandAbstractException;
25  import org.waarp.common.command.exception.Reply421Exception;
26  import org.waarp.common.command.exception.Reply451Exception;
27  import org.waarp.common.command.exception.Reply502Exception;
28  import org.waarp.common.command.exception.Reply504Exception;
29  import org.waarp.common.database.DbSession;
30  import org.waarp.common.database.data.AbstractDbData.UpdatedInfo;
31  import org.waarp.common.database.exception.WaarpDatabaseNoConnectionException;
32  import org.waarp.common.future.WaarpFuture;
33  import org.waarp.common.logging.WaarpLogger;
34  import org.waarp.common.logging.WaarpLoggerFactory;
35  import org.waarp.ftp.core.command.AbstractCommand;
36  import org.waarp.ftp.core.command.FtpCommandCode;
37  import org.waarp.ftp.core.command.access.QUIT;
38  import org.waarp.ftp.core.control.BusinessHandler;
39  import org.waarp.ftp.core.data.FtpTransfer;
40  import org.waarp.ftp.core.exception.FtpNoFileException;
41  import org.waarp.ftp.core.file.FtpFile;
42  import org.waarp.ftp.core.session.FtpSession;
43  import org.waarp.ftp.filesystembased.FilesystemBasedFtpRestart;
44  import org.waarp.gateway.ftp.config.AUTHUPDATE;
45  import org.waarp.gateway.ftp.config.FileBasedConfiguration;
46  import org.waarp.gateway.ftp.database.DbConstant;
47  import org.waarp.gateway.ftp.file.FileBasedAuth;
48  import org.waarp.gateway.ftp.file.FileBasedDir;
49  import org.waarp.gateway.kernel.exec.AbstractExecutor;
50  import org.waarp.gateway.kernel.exec.R66PreparedTransferExecutor;
51  
52  /**
53   * BusinessHandler implementation that allows pre and post actions on any operations and
54   * specifically on transfer operations
55   * 
56   * @author Frederic Bregier
57   * 
58   */
59  public class ExecBusinessHandler extends BusinessHandler {
60      /**
61       * Internal Logger
62       */
63      private static final WaarpLogger logger = WaarpLoggerFactory
64              .getLogger(ExecBusinessHandler.class);
65  
66      /**
67       * Associated DbFtpSession
68       */
69      private DbSession dbFtpSession = null;
70      /**
71       * Associated DbR66Session
72       */
73      private DbSession dbR66Session = null;
74      private boolean internalDb = false;
75  
76      @Override
77      public void afterTransferDoneBeforeAnswer(FtpTransfer transfer)
78              throws CommandAbstractException {
79          // if Admin, do nothing
80          if (getFtpSession() == null || getFtpSession().getAuth() == null) {
81              return;
82          }
83          FileBasedAuth auth = (FileBasedAuth) getFtpSession().getAuth();
84          if (auth.isAdmin()) {
85              return;
86          }
87          long specialId = auth.getSpecialId();
88          ReplyCode replyCode = getFtpSession().getReplyCode();
89          logger.debug("Transfer done but action needed: "+(!(replyCode != ReplyCode.REPLY_250_REQUESTED_FILE_ACTION_OKAY && replyCode != ReplyCode.REPLY_226_CLOSING_DATA_CONNECTION)));
90          if (replyCode != ReplyCode.REPLY_250_REQUESTED_FILE_ACTION_OKAY && replyCode != ReplyCode.REPLY_226_CLOSING_DATA_CONNECTION) {
91              // Do nothing
92              String message = "Transfer done with code: " + getFtpSession().getReplyCode().getMesg();
93              WaarpActionLogger.logErrorAction(dbFtpSession,
94                      specialId, transfer, message, getFtpSession().getReplyCode(), this);
95              return;
96          }
97          // if STOR like: get file (can be STOU) and execute external action
98          FtpCommandCode code = transfer.getCommand();
99          logger.debug("Checking action vs auth after transfer: {}", code);
100         switch (code) {
101             case RETR:
102                 // nothing to do since All done
103                 WaarpActionLogger.logAction(dbFtpSession, specialId,
104                         "Retrieve executed: OK", this, getFtpSession().getReplyCode(),
105                         UpdatedInfo.RUNNING);
106                 break;
107             case APPE:
108             case STOR:
109             case STOU:
110                 // execute the store command
111                 WaarpFuture futureCompletion = new WaarpFuture(true);
112                 String[] args = new String[6];
113                 args[0] = auth.getUser();
114                 args[1] = auth.getAccount();
115                 args[2] = auth.getBaseDirectory();
116                 FtpFile file;
117                 try {
118                     file = transfer.getFtpFile();
119                 } catch (FtpNoFileException e1) {
120                     // File cannot be sent
121                     String message =
122                             "PostExecution in Error for Transfer since No File found: " +
123                                     transfer.getCommand() + " " +
124                                     transfer.getStatus() + " " + transfer.getPath();
125                     CommandAbstractException exc = new Reply421Exception(
126                             "PostExecution in Error for Transfer since No File found");
127                     WaarpActionLogger.logErrorAction(dbFtpSession,
128                             specialId, transfer, message, exc.code, this);
129                     throw exc;
130                 }
131                 try {
132                     args[3] = file.getFile();
133                     File newfile = new File(args[2] + args[3]);
134                     
135                     // Here the transfer is successful. If the file does not exist on disk
136                     // We create it : the transfered file was empty.
137                     try {
138                         newfile.createNewFile();
139                     } catch (IOException e) {
140                         throw new Reply421Exception(
141                             "PostExecution in Error for Transfer since No File found");
142                     }  catch (SecurityException e) {
143                         throw new Reply421Exception(
144                             "PostExecution in Error for Transfer since No File found");
145                     }
146                     
147                     if (!newfile.canRead()) {
148                         // File cannot be sent
149                         String message =
150                                 "PostExecution in Error for Transfer since File is not readable: " +
151                                         transfer.getCommand() + " " +
152                                         newfile.getAbsolutePath() + ":" + newfile.canRead() +
153                                         " " + transfer.getStatus() + " " + transfer.getPath();
154                         CommandAbstractException exc =
155                                 new Reply421Exception(
156                                         "Transfer done but force disconnection since an error occurs on PostOperation");
157                         WaarpActionLogger.logErrorAction(dbFtpSession,
158                                 specialId, transfer, message, exc.code, this);
159                         throw exc;
160                     }
161                 } catch (CommandAbstractException e1) {
162                     // File cannot be sent
163                     String message =
164                             "PostExecution in Error for Transfer since No File found: " +
165                                     transfer.getCommand() + " " +
166                                     transfer.getStatus() + " " + transfer.getPath();
167                     CommandAbstractException exc =
168                             new Reply421Exception(
169                                     "Transfer done but force disconnection since an error occurs on PostOperation");
170                     WaarpActionLogger.logErrorAction(dbFtpSession,
171                             specialId, transfer, message, exc.code, this);
172                     throw exc;
173                 }
174                 args[4] = transfer.getCommand().toString();
175                 args[5] = Long.toString(specialId);
176                 AbstractExecutor executor =
177                         AbstractExecutor.createAbstractExecutor(auth, args, true, futureCompletion);
178                 if (executor instanceof R66PreparedTransferExecutor) {
179                     ((R66PreparedTransferExecutor) executor).setDbsession(dbR66Session);
180                 }
181                 executor.run();
182                 try {
183                     futureCompletion.await();
184                 } catch (InterruptedException e) {
185                 }
186                 if (futureCompletion.isSuccess()) {
187                     // All done
188                     WaarpActionLogger.logAction(dbFtpSession, specialId,
189                             "Post-Command executed: OK", this, getFtpSession().getReplyCode(),
190                             UpdatedInfo.RUNNING);
191                 } else {
192                     // File cannot be sent
193                     String message =
194                             "PostExecution in Error for Transfer: "
195                                     +
196                                     transfer.getCommand()
197                                     + " "
198                                     +
199                                     transfer.getStatus()
200                                     + " "
201                                     + transfer.getPath()
202                                     + "\n   "
203                                     + (futureCompletion.getCause() != null ?
204                                             futureCompletion.getCause().getMessage()
205                                             : "Internal error of PostExecution");
206                     CommandAbstractException exc =
207                             new Reply421Exception(
208                                     "Transfer done but force disconnection since an error occurs on PostOperation");
209                     WaarpActionLogger.logErrorAction(dbFtpSession,
210                             specialId, transfer, message, exc.code, this);
211                     throw exc;
212                 }
213                 break;
214             default:
215                 // nothing to do
216         }
217     }
218 
219     @Override
220     public void afterRunCommandKo(CommandAbstractException e) {
221         String message = "ExecHandler: KO: " + getFtpSession() + " " + e.getMessage();
222         long specialId =
223                 ((FileBasedAuth) getFtpSession().getAuth()).getSpecialId();
224         WaarpActionLogger.logErrorAction(dbFtpSession,
225                 specialId, null, message, e.code, this);
226         ((FileBasedAuth) getFtpSession().getAuth()).setSpecialId(DbConstant.ILLEGALVALUE);
227     }
228 
229     @Override
230     public void afterRunCommandOk() throws CommandAbstractException {
231         if (!(this.getFtpSession().getCurrentCommand() instanceof QUIT)
232                 && this.dbR66Session != null) {
233             long specialId =
234                     ((FileBasedAuth) getFtpSession().getAuth()).getSpecialId();
235             WaarpActionLogger.logAction(dbFtpSession, specialId,
236                     "Transfer Command fully executed: OK", this, getFtpSession().getReplyCode(),
237                     UpdatedInfo.DONE);
238             ((FileBasedAuth) getFtpSession().getAuth()).setSpecialId(DbConstant.ILLEGALVALUE);
239         }
240     }
241 
242     @Override
243     public void beforeRunCommand() throws CommandAbstractException {
244         long specialId = DbConstant.ILLEGALVALUE;
245         // if Admin, do nothing
246         if (getFtpSession() == null || getFtpSession().getAuth() == null) {
247             return;
248         }
249         FileBasedAuth auth = (FileBasedAuth) getFtpSession().getAuth();
250         if (auth.isAdmin()) {
251             logger.debug("Admin user so all actions are allowed");
252             return;
253         }
254         // Test limits
255         FtpConstraintLimitHandler constraints =
256                 ((FileBasedConfiguration) getFtpSession().getConfiguration())
257                 .constraintLimitHandler;
258         if (constraints != null) {
259             if (!auth.isIdentified()) {
260                 // ignore test since it can be an Admin connection
261             } else if (auth.isAdmin()) {
262                 // ignore test since it is an Admin connection (always valid)
263             } else if (!FtpCommandCode.isSpecialCommand(
264                     getFtpSession().getCurrentCommand().getCode())) {
265                 // Authenticated, not Admin and not Special Command
266                 if (constraints.checkConstraintsSleep(1)) {
267                     if (constraints.checkConstraints()) {
268                         // Really overload so refuse the command
269                         logger.info("Server overloaded. {} Try later... \n"
270                                 + getFtpSession().toString(), constraints.lastAlert);
271                         if (FileBasedConfiguration.fileBasedConfiguration.ftpMib != null) {
272                             FileBasedConfiguration.fileBasedConfiguration.ftpMib.
273                                     notifyOverloaded("Server overloaded",
274                                             getFtpSession().toString());
275                         }
276                         throw new Reply451Exception("Server overloaded. Try later...");
277                     }
278                 }
279             }
280         }
281         FtpCommandCode code = getFtpSession().getCurrentCommand().getCode();
282         logger.debug("Checking action vs auth before command: {}", code);
283         switch (code) {
284             case APPE:
285             case STOR:
286             case STOU:
287                 auth.setSpecialId(specialId);
288                 if (!auth.getCommandExecutor().isValidOperation(true)) {
289                     throw new Reply504Exception("STORe like operations are not allowed");
290                 }
291                 // create entry in log
292                 specialId = WaarpActionLogger.logCreate(dbFtpSession,
293                         "PrepareTransfer: OK",
294                         getFtpSession().getCurrentCommand().getArg(),
295                         this);
296                 auth.setSpecialId(specialId);
297                 // nothing to do now
298                 break;
299             case RETR:
300                 auth.setSpecialId(specialId);
301                 if (!auth.getCommandExecutor().isValidOperation(false)) {
302                     throw new Reply504Exception("RETRieve like operations are not allowed");
303                 }
304                 // create entry in log
305                 specialId = WaarpActionLogger.logCreate(dbFtpSession,
306                         "PrepareTransfer: OK",
307                         getFtpSession().getCurrentCommand().getArg(),
308                         this);
309                 auth.setSpecialId(specialId);
310                 // execute the external retrieve command before the execution of RETR
311                 WaarpFuture futureCompletion = new WaarpFuture(true);
312                 String[] args = new String[6];
313                 args[0] = auth.getUser();
314                 args[1] = auth.getAccount();
315                 args[2] = auth.getBaseDirectory();
316                 String filename = getFtpSession().getCurrentCommand().getArg();
317                 FtpFile file = getFtpSession().getDir().setFile(filename, false);
318                 args[3] = file.getFile();
319                 args[4] = code.toString();
320                 args[5] = Long.toString(specialId);
321                 AbstractExecutor executor =
322                         AbstractExecutor
323                                 .createAbstractExecutor(auth, args, false, futureCompletion);
324                 if (executor instanceof R66PreparedTransferExecutor) {
325                     ((R66PreparedTransferExecutor) executor).setDbsession(dbR66Session);
326                 }
327                 executor.run();
328                 try {
329                     futureCompletion.await();
330                 } catch (InterruptedException e) {
331                 }
332                 if (futureCompletion.isSuccess()) {
333                     // File should be ready
334                     if (!file.canRead()) {
335                         logger.error("PreExecution in Error for Transfer since " +
336                                 "File downloaded but not ready to be retrieved: {} " +
337                                 " {} \n   " + (futureCompletion.getCause() != null ?
338                                         futureCompletion.getCause().getMessage() :
339                                         "File downloaded but not ready to be retrieved"),
340                                 args[4], args[3]);
341                         throw new Reply421Exception(
342                                 "File downloaded but not ready to be retrieved");
343                     }
344                     WaarpActionLogger.logAction(dbFtpSession, specialId,
345                             "Pre-Command executed: OK", this, getFtpSession().getReplyCode(),
346                             UpdatedInfo.RUNNING);
347                 } else {
348                     // File cannot be retrieved
349                     logger.error("PreExecution in Error for Transfer since " +
350                             "File cannot be prepared to be retrieved: {} " +
351                             " {} \n   " + (futureCompletion.getCause() != null ?
352                                     futureCompletion.getCause().getMessage() :
353                                     "File cannot be prepared to be retrieved"),
354                             args[4], args[3]);
355                     throw new Reply421Exception(
356                             "File cannot be prepared to be retrieved");
357                 }
358                 break;
359             default:
360                 // nothing to do
361         }
362     }
363 
364     @Override
365     protected void cleanSession() {
366     }
367 
368     @Override
369     public void exceptionLocalCaught(Throwable cause) {
370         if (FileBasedConfiguration.fileBasedConfiguration.ftpMib != null) {
371             String mesg;
372             if (cause != null && cause.getMessage() != null) {
373                 mesg = cause.getMessage();
374             } else {
375                 if (this.getFtpSession() != null) {
376                     mesg = "Exception while " + this.getFtpSession().getReplyCode().getMesg();
377                 } else {
378                     mesg = "Unknown Exception";
379                 }
380             }
381             FileBasedConfiguration.fileBasedConfiguration.ftpMib.
382                     notifyError("Exception trapped", mesg);
383         }
384         if (FileBasedConfiguration.fileBasedConfiguration.monitoring != null) {
385             if (this.getFtpSession() != null) {
386                 FileBasedConfiguration.fileBasedConfiguration.monitoring.
387                         updateCodeNoTransfer(this.getFtpSession().getReplyCode());
388             }
389         }
390     }
391 
392     @Override
393     public void executeChannelClosed() {
394         if (AbstractExecutor.useDatabase) {
395             if (!internalDb) {
396                 if (dbR66Session != null) {
397                     dbR66Session.disconnect();
398                     dbR66Session = null;
399                 }
400             }
401         }
402         if (dbFtpSession != null) {
403             dbFtpSession.disconnect();
404             dbFtpSession = null;
405         }
406     }
407 
408     @Override
409     public void executeChannelConnected(Channel channel) {
410         if (AbstractExecutor.useDatabase) {
411             if (org.waarp.openr66.database.DbConstant.admin != null &&
412                     org.waarp.openr66.database.DbConstant.admin.isActive()) {
413                 try {
414                     dbR66Session = new DbSession(org.waarp.openr66.database.DbConstant.admin, false);
415                 } catch (WaarpDatabaseNoConnectionException e1) {
416                     logger.warn("Database not ready due to {}", e1.getMessage());
417                     QUIT command = (QUIT)
418                             FtpCommandCode.getFromLine(getFtpSession(), FtpCommandCode.QUIT.name());
419                     this.getFtpSession().setNextCommand(command);
420                     dbR66Session = null;
421                     internalDb = true;
422                 }
423             }
424         }
425         if (DbConstant.gatewayAdmin != null && DbConstant.gatewayAdmin.isActive()) {
426             try {
427                 dbFtpSession = new DbSession(DbConstant.gatewayAdmin, false);
428             } catch (WaarpDatabaseNoConnectionException e1) {
429                 logger.warn("Database not ready due to {}", e1.getMessage());
430                 QUIT command = (QUIT)
431                         FtpCommandCode.getFromLine(getFtpSession(), FtpCommandCode.QUIT.name());
432                 this.getFtpSession().setNextCommand(command);
433                 dbFtpSession = null;
434             }
435         }
436     }
437 
438     @Override
439     public FileBasedAuth getBusinessNewAuth() {
440         return new FileBasedAuth(getFtpSession());
441     }
442 
443     @Override
444     public FileBasedDir getBusinessNewDir() {
445         return new FileBasedDir(getFtpSession());
446     }
447 
448     @Override
449     public FilesystemBasedFtpRestart getBusinessNewRestart() {
450         return new FilesystemBasedFtpRestart(getFtpSession());
451     }
452 
453     @Override
454     public String getHelpMessage(String arg) {
455         return "This FTP server is only intend as a Gateway. RETRieve actions may be unallowed.\n"
456                 + "This FTP server refers to RFC 959, 775, 2389, 2428, 3659 and supports XCRC, XMD5 and XSHA1 commands.\n"
457                 + "XCRC, XMD5 and XSHA1 take a simple filename as argument and return \"250 digest-value is the digest of filename\".";
458     }
459 
460     @Override
461     public String getFeatMessage() {
462         StringBuilder builder = new StringBuilder("Extensions supported:").append('\n').append(getDefaultFeatMessage());
463         if (getFtpSession().getConfiguration().getFtpInternalConfiguration().isAcceptAuthProt()) {
464             builder.append('\n').append(getSslFeatMessage());
465         }
466         builder.append('\n').append(FtpCommandCode.SITE.name()).append(' ').append("AUTHUPDATE").append("\nEnd");
467         return builder.toString();
468     }
469 
470     @Override
471     public String getOptsMessage(String[] args) throws CommandAbstractException {
472         if (args.length > 0) {
473             if (args[0].equalsIgnoreCase(FtpCommandCode.MLST.name()) ||
474                     args[0].equalsIgnoreCase(FtpCommandCode.MLSD.name())) {
475                 return getMLSxOptsMessage(args);
476             }
477             throw new Reply502Exception("OPTS not implemented for " + args[0]);
478         }
479         throw new Reply502Exception("OPTS not implemented");
480     }
481 
482     @Override
483     public AbstractCommand getSpecializedSiteCommand(FtpSession session,
484             String line) {
485         if (getFtpSession() == null || getFtpSession().getAuth() == null) {
486             return null;
487         }
488         if (!session.getAuth().isAdmin()) {
489             return null;
490         }
491         String newline = line;
492         if (newline == null) {
493             return null;
494         }
495         String command = null;
496         String arg = null;
497         if (newline.indexOf(' ') == -1) {
498             command = newline;
499             arg = null;
500         } else {
501             command = newline.substring(0, newline.indexOf(' '));
502             arg = newline.substring(newline.indexOf(' ') + 1);
503             if (arg.length() == 0) {
504                 arg = null;
505             }
506         }
507         String COMMAND = command.toUpperCase();
508         if (!COMMAND.equals("AUTHUPDATE")) {
509             return null;
510         }
511         AbstractCommand abstractCommand = new AUTHUPDATE();
512         abstractCommand.setArgs(session, COMMAND, arg, FtpCommandCode.SITE);
513         return abstractCommand;
514     }
515 }