View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.waarp.gateway.ftp.exec;
21  
22  import org.waarp.common.command.exception.CommandAbstractException;
23  import org.waarp.common.future.WaarpFuture;
24  import org.waarp.common.guid.GUID;
25  import org.waarp.common.logging.WaarpLogger;
26  import org.waarp.common.logging.WaarpLoggerFactory;
27  import org.waarp.common.utility.WaarpStringUtils;
28  import org.waarp.gateway.kernel.session.CommandExecutorInterface;
29  import org.waarp.gateway.kernel.session.HttpAuthInterface;
30  
31  import java.util.regex.Pattern;
32  
33  /**
34   * Abstract Executor class. If the command starts with "REFUSED", the command
35   * will be refused for execution.
36   * If "REFUSED" is set, the command "RETR" or "STOR" like operations will be
37   * stopped at starting of
38   * command.<br>
39   * If the command starts with "EXECUTE", the following will be a command to be
40   * executed.<br>
41   * If the command starts with "JAVAEXECUTE", the following will be a command
42   * through Java class to be
43   * executed.<br>
44   * If the command starts with "R66PREPARETRANSFER", the following will be a r66
45   * prepare transfer execution
46   * (asynchronous operation only).<br>
47   * <p>
48   * <p>
49   * The following replacement are done dynamically before the command is
50   * executed:<br>
51   * - #BASEPATH# is replaced by the full path for the root of FTP Directory<br>
52   * - #FILE# is replaced by the current file path relative to FTP Directory (so
53   * #BASEPATH##FILE# is the full
54   * path of the file)<br>
55   * - #USER# is replaced by the username<br>
56   * - #ACCOUNT# is replaced by the account<br>
57   * - #COMMAND# is replaced by the command issued for the file<br>
58   * - #SPECIALID# is replaced by the FTP id of the transfer (whatever in or
59   * out)<br>
60   * - #UUID# is replaced by a special UUID globally unique for the transfer, in
61   * general to be placed in -info
62   * part (for instance ##UUID## giving #uuid#)<br>
63   */
64  public abstract class AbstractExecutor {
65    /**
66     * Internal Logger
67     */
68    private static final WaarpLogger logger =
69        WaarpLoggerFactory.getLogger(AbstractExecutor.class);
70    protected static final Pattern BLANK = WaarpStringUtils.BLANK;
71  
72    protected static final String USER = "#USER#";
73    protected static final String ACCOUNT = "#ACCOUNT#";
74    protected static final String BASEPATH = "#BASEPATH#";
75    protected static final String FILE = "#FILE#";
76    protected static final String COMMAND = "#COMMAND#";
77    protected static final String SPECIALID = "#SPECIALID#";
78    protected static final String S_UUID = "#UUID#";
79  
80    protected static final String REFUSED = "REFUSED";
81    protected static final String NONE = "NONE";
82    protected static final String EXECUTE = "EXECUTE";
83    protected static final String JAVAEXECUTE = "JAVAEXECUTE";
84    protected static final String R66PREPARETRANSFER = "R66PREPARETRANSFER";
85  
86    protected static final int T_REFUSED = -1;
87    protected static final int T_NONE = 0;
88    protected static final int T_EXECUTE = 1;
89    protected static final int T_R_66_PREPARETRANSFER = 2;
90    protected static final int T_JAVAEXECUTE = 3;
91  
92    protected static CommandExecutor commandExecutor;
93  
94    /**
95     * For OpenR66 access
96     */
97    public static boolean useDatabase;
98  
99    /**
100    * Local Exec Daemon is used or not for execution of external commands
101    */
102   public static boolean useLocalExec;
103 
104   public static class CommandExecutor implements CommandExecutorInterface {
105     /**
106      * Retrieve External Command
107      */
108     public final String pretrCMD;
109     public final int pretrType;
110     private boolean pretrRefused;
111     /**
112      * Retrieve Delay (0 = unlimited)
113      */
114     private long pretrDelay;
115     /**
116      * Store External Command
117      */
118     public final String pstorCMD;
119     public final int pstorType;
120     private boolean pstorRefused;
121     /**
122      * Store Delay (0 = unlimited)
123      */
124     private long pstorDelay;
125 
126     /**
127      * @param retrieve
128      * @param retrDelay
129      * @param store
130      * @param storDelay
131      */
132     public CommandExecutor(final String retrieve, final long retrDelay,
133                            final String store, final long storDelay) {
134       if (retrieve == null || retrieve.trim().length() == 0) {
135         pretrCMD = commandExecutor.pretrCMD;
136         pretrType = commandExecutor.pretrType;
137         setPretrRefused(commandExecutor.isPretrRefused());
138       } else if (isRefused(retrieve)) {
139         pretrCMD = REFUSED;
140         pretrType = T_REFUSED;
141         setPretrRefused(true);
142       } else {
143         if (isExecute(retrieve)) {
144           pretrCMD = getExecuteCmd(retrieve);
145           pretrType = T_EXECUTE;
146         } else if (isR66PrepareTransfer(retrieve)) {
147           pretrCMD = getR66PrepareTransferCmd(retrieve);
148           pretrType = T_R_66_PREPARETRANSFER;
149           useDatabase = true;
150         } else if (isJavaExecute(retrieve)) {
151           pretrCMD = getJavaExecuteCmd(retrieve);
152           pretrType = T_JAVAEXECUTE;
153         } else {
154           // Default NONE
155           pretrCMD = getNone(retrieve);
156           pretrType = T_NONE;
157         }
158       }
159       setPretrDelay(retrDelay);
160       if (store == null || store.trim().length() == 0) {
161         pstorCMD = commandExecutor.pstorCMD;
162         setPstorRefused(commandExecutor.isPstorRefused());
163         pstorType = commandExecutor.pstorType;
164       } else if (isRefused(store)) {
165         pstorCMD = REFUSED;
166         setPstorRefused(true);
167         pstorType = T_REFUSED;
168       } else {
169         if (isExecute(store)) {
170           pstorCMD = getExecuteCmd(store);
171           pstorType = T_EXECUTE;
172         } else if (isR66PrepareTransfer(store)) {
173           pstorCMD = getR66PrepareTransferCmd(store);
174           pstorType = T_R_66_PREPARETRANSFER;
175           useDatabase = true;
176         } else if (isJavaExecute(store)) {
177           pstorCMD = getJavaExecuteCmd(store);
178           pstorType = T_JAVAEXECUTE;
179         } else {
180           // Default NONE
181           pstorCMD = getNone(store);
182           pstorType = T_NONE;
183         }
184       }
185       setPstorDelay(storDelay);
186     }
187 
188     private static String getNone(final String cmd) {
189       return cmd.substring(NONE.length()).trim();
190     }
191 
192     private static String getExecuteCmd(final String cmd) {
193       return cmd.substring(EXECUTE.length()).trim();
194     }
195 
196     private static String getJavaExecuteCmd(final String cmd) {
197       return cmd.substring(JAVAEXECUTE.length()).trim();
198     }
199 
200     private static String getR66PrepareTransferCmd(final String cmd) {
201       return cmd.substring(R66PREPARETRANSFER.length()).trim();
202     }
203 
204     private static boolean isRefused(final String cmd) {
205       return cmd.startsWith(REFUSED);
206     }
207 
208     private static boolean isExecute(final String cmd) {
209       return cmd.startsWith(EXECUTE);
210     }
211 
212     private static boolean isJavaExecute(final String cmd) {
213       return cmd.startsWith(JAVAEXECUTE);
214     }
215 
216     private static boolean isR66PrepareTransfer(final String cmd) {
217       return cmd.startsWith(R66PREPARETRANSFER);
218     }
219 
220     @Override
221     public final boolean isValidOperation(final boolean isStore) {
222       if (isStore && isPstorRefused()) {
223         logger.info("STORe like operations REFUSED");
224         return false;
225       } else if (!isStore && isPretrRefused()) {
226         logger.info("RETRieve operations REFUSED");
227         return false;
228       }
229       return true;
230     }
231 
232     @Override
233     public final String getRetrType() {
234       switch (pretrType) {
235         case T_REFUSED:
236           return REFUSED;
237         case T_EXECUTE:
238           return EXECUTE;
239         case T_R_66_PREPARETRANSFER:
240           return R66PREPARETRANSFER;
241         case T_JAVAEXECUTE:
242           return JAVAEXECUTE;
243         default:
244           return NONE;
245       }
246     }
247 
248     @Override
249     public final String getStorType() {
250       switch (pstorType) {
251         case T_REFUSED:
252           return REFUSED;
253         case T_EXECUTE:
254           return EXECUTE;
255         case T_R_66_PREPARETRANSFER:
256           return R66PREPARETRANSFER;
257         case T_JAVAEXECUTE:
258           return JAVAEXECUTE;
259         default:
260           return NONE;
261       }
262     }
263 
264     /**
265      * @return the pretrRefused
266      */
267     public final boolean isPretrRefused() {
268       return pretrRefused;
269     }
270 
271     /**
272      * @param pretrRefused the pretrRefused to set
273      */
274     public final void setPretrRefused(final boolean pretrRefused) {
275       this.pretrRefused = pretrRefused;
276     }
277 
278     /**
279      * @return the pretrDelay
280      */
281     public final long getPretrDelay() {
282       return pretrDelay;
283     }
284 
285     /**
286      * @param pretrDelay the pretrDelay to set
287      */
288     public final void setPretrDelay(final long pretrDelay) {
289       this.pretrDelay = pretrDelay;
290     }
291 
292     /**
293      * @return the pstorRefused
294      */
295     public final boolean isPstorRefused() {
296       return pstorRefused;
297     }
298 
299     /**
300      * @param pstorRefused the pstorRefused to set
301      */
302     public final void setPstorRefused(final boolean pstorRefused) {
303       this.pstorRefused = pstorRefused;
304     }
305 
306     /**
307      * @return the pstorDelay
308      */
309     public final long getPstorDelay() {
310       return pstorDelay;
311     }
312 
313     /**
314      * @param pstorDelay the pstorDelay to set
315      */
316     public final void setPstorDelay(final long pstorDelay) {
317       this.pstorDelay = pstorDelay;
318     }
319   }
320 
321   /**
322    * Initialize the Executor with the correct command and delay
323    *
324    * @param retrieve
325    * @param retrDelay
326    * @param store
327    * @param storDelay
328    */
329   public static void initializeExecutor(final String retrieve,
330                                         final long retrDelay,
331                                         final String store,
332                                         final long storDelay) {
333     commandExecutor =
334         new CommandExecutor(retrieve, retrDelay, store, storDelay);
335     if (logger.isInfoEnabled()) {
336       logger.info(
337           "Executor configured as [RETR: " + commandExecutor.getRetrType() +
338           ':' + commandExecutor.pretrCMD + ':' +
339           commandExecutor.getPretrDelay() + ':' +
340           commandExecutor.isPretrRefused() + "] [STOR: " +
341           commandExecutor.getStorType() + ':' + commandExecutor.pstorCMD + ':' +
342           commandExecutor.getPstorDelay() + ':' +
343           commandExecutor.isPstorRefused() + ']');
344     }
345   }
346 
347   /**
348    * Check if the given operation is allowed Globally
349    *
350    * @param isStore
351    *
352    * @return True if allowed, else False
353    */
354   public static boolean isValidOperation(final boolean isStore) {
355     return commandExecutor.isValidOperation(isStore);
356   }
357 
358   /**
359    * @param auth the current Authentication
360    * @param args containing in that order "User Account BaseDir
361    *     FilePath(relative to BaseDir)
362    *     Command"
363    * @param isStore True for a STORE like operation, else False
364    * @param futureCompletion
365    */
366   public static AbstractExecutor createAbstractExecutor(
367       final HttpAuthInterface auth, final String[] args, final boolean isStore,
368       final WaarpFuture futureCompletion) {
369     if (isStore) {
370       CommandExecutor executor = (CommandExecutor) auth.getCommandExecutor();
371       if (executor == null) {
372         executor = commandExecutor;
373       } else if (executor.pstorType == T_NONE) {
374         final String replaced = getPreparedCommand(executor.pstorCMD, args);
375         return new NoTaskExecutor(replaced, executor.getPstorDelay(),
376                                   futureCompletion);
377       }
378       if (executor.isPstorRefused()) {
379         logger.error("STORe like operation REFUSED");
380         futureCompletion.cancel();
381         return null;
382       }
383       final String replaced = getPreparedCommand(executor.pstorCMD, args);
384       switch (executor.pstorType) {
385         case T_REFUSED:
386           logger.error("STORe like operation REFUSED");
387           futureCompletion.cancel();
388           return null;
389         case T_EXECUTE:
390           return new ExecuteExecutor(replaced, executor.getPstorDelay(),
391                                      futureCompletion);
392         case T_JAVAEXECUTE:
393           return new JavaExecutor(replaced, executor.getPstorDelay(),
394                                   futureCompletion);
395         case T_R_66_PREPARETRANSFER:
396           return new R66PreparedTransferExecutor(replaced,
397                                                  executor.getPstorDelay(),
398                                                  futureCompletion);
399         default:
400           return new NoTaskExecutor(replaced, executor.getPstorDelay(),
401                                     futureCompletion);
402       }
403     } else {
404       CommandExecutor executor = (CommandExecutor) auth.getCommandExecutor();
405       if (executor == null) {
406         executor = commandExecutor;
407       } else if (executor.pretrType == T_NONE) {
408         final String replaced = getPreparedCommand(executor.pretrCMD, args);
409         return new NoTaskExecutor(replaced, executor.getPretrDelay(),
410                                   futureCompletion);
411       }
412       if (executor.isPretrRefused()) {
413         logger.error("RETRieve operation REFUSED");
414         futureCompletion.cancel();
415         return null;
416       }
417       final String replaced = getPreparedCommand(executor.pretrCMD, args);
418       switch (executor.pretrType) {
419         case T_REFUSED:
420           logger.error("RETRieve operation REFUSED");
421           futureCompletion.cancel();
422           return null;
423         case T_EXECUTE:
424           return new ExecuteExecutor(replaced, executor.getPretrDelay(),
425                                      futureCompletion);
426         case T_JAVAEXECUTE:
427           return new JavaExecutor(replaced, executor.getPretrDelay(),
428                                   futureCompletion);
429         case T_R_66_PREPARETRANSFER:
430           return new R66PreparedTransferExecutor(replaced,
431                                                  executor.getPretrDelay(),
432                                                  futureCompletion);
433         default:
434           return new NoTaskExecutor(replaced, executor.getPretrDelay(),
435                                     futureCompletion);
436       }
437     }
438   }
439 
440   /**
441    * @param command
442    * @param args as {User, Account, BaseDir, FilePath(relative to
443    *     BaseDir),
444    *     Command}
445    *
446    * @return the prepared command
447    */
448   public static String getPreparedCommand(final String command,
449                                           final String[] args) {
450     final StringBuilder builder = new StringBuilder(command);
451     logger.debug(
452         "Will replace value in {} with User={}:Acct={}:Base={}:File={}:Cmd={}",
453         command, args[0], args[1], args[2], args[3], args[4]);
454     replaceAll(builder, USER, args[0]);
455     replaceAll(builder, ACCOUNT, args[1]);
456     replaceAll(builder, BASEPATH, args[2]);
457     replaceAll(builder, FILE, args[3]);
458     replaceAll(builder, COMMAND, args[4]);
459     replaceAll(builder, SPECIALID, args[5]);
460     if (builder.indexOf(S_UUID) > 0) {
461       replaceAll(builder, S_UUID, new GUID().toString());
462     }
463     logger.debug("Result: {}", builder);
464     return builder.toString();
465   }
466 
467   /**
468    * Make a replacement of first "find" string by "replace" string into the
469    * StringBuilder
470    *
471    * @param builder
472    * @param find
473    * @param replace
474    */
475   public static boolean replace(final StringBuilder builder, final String find,
476                                 final String replace) {
477     final int start = builder.indexOf(find);
478     if (start == -1) {
479       return false;
480     }
481     final int end = start + find.length();
482     builder.replace(start, end, replace);
483     return true;
484   }
485 
486   /**
487    * Make replacement of all "find" string by "replace" string into the
488    * StringBuilder
489    *
490    * @param builder
491    * @param find
492    * @param replace
493    */
494   public static void replaceAll(final StringBuilder builder, final String find,
495                                 final String replace) {
496     while (replace(builder, find, replace)) {
497       // nothing
498     }
499   }
500 
501   public static CommandExecutor getCommandExecutor() {
502     return commandExecutor;
503   }
504 
505   public abstract void run() throws CommandAbstractException;
506 }