View Javadoc

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