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.ftp.core.session;
21  
22  import io.netty.channel.Channel;
23  import org.waarp.common.command.CommandInterface;
24  import org.waarp.common.command.ReplyCode;
25  import org.waarp.common.command.exception.CommandAbstractException;
26  import org.waarp.common.command.exception.Reply425Exception;
27  import org.waarp.common.file.FileParameterInterface;
28  import org.waarp.common.file.Restart;
29  import org.waarp.common.file.SessionInterface;
30  import org.waarp.common.future.WaarpFuture;
31  import org.waarp.common.logging.WaarpLogger;
32  import org.waarp.common.logging.WaarpLoggerFactory;
33  import org.waarp.ftp.core.command.AbstractCommand;
34  import org.waarp.ftp.core.command.FtpArgumentCode;
35  import org.waarp.ftp.core.command.FtpArgumentCode.TransferSubType;
36  import org.waarp.ftp.core.command.internal.ConnectionCommand;
37  import org.waarp.ftp.core.config.FtpConfiguration;
38  import org.waarp.ftp.core.control.BusinessHandler;
39  import org.waarp.ftp.core.control.NetworkHandler;
40  import org.waarp.ftp.core.data.FtpDataAsyncConn;
41  import org.waarp.ftp.core.exception.FtpNoConnectionException;
42  import org.waarp.ftp.core.file.FtpAuth;
43  import org.waarp.ftp.core.file.FtpDir;
44  
45  import java.io.File;
46  import java.net.InetAddress;
47  import java.net.InetSocketAddress;
48  import java.util.concurrent.atomic.AtomicBoolean;
49  
50  /**
51   * Main class that stores any information that must be accessible from anywhere
52   * during the connection of one
53   * user.
54   */
55  public class FtpSession implements SessionInterface {
56    /**
57     * Internal Logger
58     */
59    private static final WaarpLogger logger =
60        WaarpLoggerFactory.getLogger(FtpSession.class);
61    /**
62     * Business Handler
63     */
64    private final BusinessHandler businessHandler;
65  
66    /**
67     * Associated global configuration
68     */
69    private final FtpConfiguration configuration;
70  
71    /**
72     * Associated Binary connection
73     */
74    private FtpDataAsyncConn dataConn;
75  
76    /**
77     * Ftp Authentication
78     */
79    private FtpAuth ftpAuth;
80  
81    /**
82     * Ftp DirInterface configuration and access
83     */
84    private FtpDir ftpDir;
85  
86    /**
87     * Previous Command
88     */
89    private AbstractCommand previousCommand;
90  
91    /**
92     * Current Command
93     */
94    private AbstractCommand currentCommand;
95    /**
96     * Is the current command finished
97     */
98    private final AtomicBoolean isCurrentCommandFinished =
99        new AtomicBoolean(true);
100 
101   /**
102    * Associated Reply Code
103    */
104   private ReplyCode replyCode;
105 
106   /**
107    * Real text for answer
108    */
109   private String answer;
110 
111   /**
112    * Current Restart information
113    */
114   private Restart restart;
115 
116   /**
117    * Is the control ready to accept command
118    */
119   private final WaarpFuture isReady = new WaarpFuture(true);
120 
121   /**
122    * Is the current session using SSL on Control
123    */
124   private final AtomicBoolean isSsl = new AtomicBoolean(false);
125   /**
126    * Is the current session will using SSL on Control
127    */
128   private WaarpFuture waitForSsl;
129   /**
130    * WIll all data be using SSL
131    */
132   private final AtomicBoolean isDataSsl = new AtomicBoolean(false);
133 
134   /**
135    * Constructor
136    *
137    * @param configuration
138    * @param handler
139    */
140   public FtpSession(final FtpConfiguration configuration,
141                     final BusinessHandler handler) {
142     this.configuration = configuration;
143     businessHandler = handler;
144   }
145 
146   /**
147    * @return the businessHandler
148    */
149   public final BusinessHandler getBusinessHandler() {
150     return businessHandler;
151   }
152 
153   /**
154    * Get the configuration
155    *
156    * @return the configuration
157    */
158   public final FtpConfiguration getConfiguration() {
159     return configuration;
160   }
161 
162   @Override
163   public final FtpDir getDir() {
164     return ftpDir;
165   }
166 
167   /**
168    * @return the Data Connection
169    */
170   public final FtpDataAsyncConn getDataConn() {
171     return dataConn;
172   }
173 
174   @Override
175   public final FtpAuth getAuth() {
176     return ftpAuth;
177   }
178 
179   @Override
180   public final Restart getRestart() {
181     return restart;
182   }
183 
184   /**
185    * This function is called when the Command Channel is connected (from
186    * channelConnected of the NetworkHandler)
187    */
188   public final synchronized void setControlConnected() {
189     dataConn = new FtpDataAsyncConn(this);
190     // AuthInterface must be done before FtpFile
191     ftpAuth = businessHandler.getBusinessNewAuth();
192     ftpDir = businessHandler.getBusinessNewDir();
193     restart = businessHandler.getBusinessNewRestart();
194   }
195 
196   /**
197    * Special initialization (FtpExec with Https session)
198    *
199    * @param auth
200    * @param dir
201    * @param restart
202    */
203   public final void setSpecialInit(final FtpAuth auth, final FtpDir dir,
204                                    final Restart restart) {
205     ftpAuth = auth;
206     ftpDir = dir;
207     this.restart = restart;
208   }
209 
210   /**
211    * @return the Control channel
212    */
213   public final Channel getControlChannel() {
214     final NetworkHandler networkHandler = getNetworkHandler();
215     if (networkHandler != null) {
216       return networkHandler.getControlChannel();
217     }
218     return null;
219   }
220 
221   /**
222    * @return The network handler associated with control
223    */
224   public final NetworkHandler getNetworkHandler() {
225     if (businessHandler != null) {
226       return businessHandler.getNetworkHandler();
227     }
228     return null;
229   }
230 
231   /**
232    * Set the new current command
233    *
234    * @param command
235    */
236   public final void setNextCommand(final CommandInterface command) {
237     previousCommand = currentCommand;
238     currentCommand = (AbstractCommand) command;
239     isCurrentCommandFinished.set(false);
240   }
241 
242   /**
243    * @return the currentCommand
244    */
245   public final AbstractCommand getCurrentCommand() {
246     return currentCommand;
247   }
248 
249   /**
250    * @return the previousCommand
251    */
252   public final AbstractCommand getPreviousCommand() {
253     return previousCommand;
254   }
255 
256   /**
257    * Set the previous command as the new current command (used after a
258    * incorrect
259    * sequence of commands or unknown
260    * command)
261    */
262   public final void setPreviousAsCurrentCommand() {
263     currentCommand = previousCommand;
264     isCurrentCommandFinished.set(true);
265   }
266 
267   /**
268    * @return True if the Current Command is already Finished (ready to accept
269    *     a
270    *     new one)
271    */
272   public final boolean isCurrentCommandFinished() {
273     return isCurrentCommandFinished.get();
274   }
275 
276   /**
277    * Set the Current Command as finished
278    */
279   public final void setCurrentCommandFinished() {
280     isCurrentCommandFinished.set(true);
281   }
282 
283   /**
284    * @return the answer
285    */
286   public final String getAnswer() {
287     if (answer == null) {
288       if (replyCode == null) {
289         answer = ReplyCode.REPLY_000_SPECIAL_NOSTATUS.getMesg();
290       } else {
291         answer = replyCode.getMesg();
292       }
293     }
294     return answer;
295   }
296 
297   /**
298    * @param replyCode the replyCode to set
299    * @param answer
300    */
301   public final void setReplyCode(final ReplyCode replyCode,
302                                  final String answer) {
303     this.replyCode = replyCode;
304     if (answer != null) {
305       this.answer = ReplyCode.getFinalMsg(replyCode.getCode(), answer);
306     } else {
307       this.answer = replyCode.getMesg();
308     }
309   }
310 
311   /**
312    * @param exception
313    */
314   public final void setReplyCode(final CommandAbstractException exception) {
315     setReplyCode(exception.code, exception.message);
316   }
317 
318   /**
319    * Set Exit code after an error
320    *
321    * @param answer
322    */
323   public final void setExitErrorCode(final String answer) {
324     setReplyCode(
325         ReplyCode.REPLY_421_SERVICE_NOT_AVAILABLE_CLOSING_CONTROL_CONNECTION,
326         answer);
327   }
328 
329   /**
330    * Set Exit normal code
331    *
332    * @param answer
333    */
334   public final void setExitNormalCode(final String answer) {
335     setReplyCode(ReplyCode.REPLY_221_CLOSING_CONTROL_CONNECTION, answer);
336   }
337 
338   /**
339    * @return the replyCode
340    */
341   public final ReplyCode getReplyCode() {
342     return replyCode;
343   }
344 
345   @Override
346   public final void clear() {
347     if (dataConn != null) {
348       dataConn.clear();
349     }
350     if (ftpDir != null) {
351       ftpDir.clear();
352     }
353     if (ftpAuth != null) {
354       ftpAuth.clear();
355     }
356     previousCommand = null;
357     replyCode = null;
358     answer = null;
359     isReady.cancel();
360   }
361 
362   /**
363    * @return True if the Control is ready to accept command
364    */
365   public final boolean isReady() {
366     isReady.awaitOrInterruptible();
367     return isReady.isSuccess();
368   }
369 
370   /**
371    * @param isReady the isReady to set
372    */
373   public final void setReady(final boolean isReady) {
374     if (isReady) {
375       this.isReady.setSuccess();
376     } else {
377       this.isReady.cancel();
378     }
379   }
380 
381   @Override
382   public String toString() {
383     final StringBuilder builder = new StringBuilder("FtpSession: ");
384     if (ftpAuth != null) {
385       builder.append("User: ").append(ftpAuth.getUser()).append('/')
386              .append(ftpAuth.getAccount()).append(' ');
387     }
388     if (previousCommand != null) {
389       builder.append("PRVCMD: ").append(previousCommand.getCommand())
390              .append(' ').append(previousCommand.getArg()).append(' ');
391     }
392     if (currentCommand != null) {
393       builder.append("CMD: ").append(currentCommand.getCommand()).append(' ')
394              .append(currentCommand.getArg()).append(' ');
395     }
396     if (replyCode != null) {
397       builder.append("Reply: ")
398              .append(answer != null? answer : replyCode.getMesg()).append(' ');
399     }
400     if (dataConn != null) {
401       builder.append(dataConn);
402     }
403     if (ftpDir != null) {
404       try {
405         builder.append(" PWD: ").append(ftpDir.getPwd());
406       } catch (final CommandAbstractException ignored) {
407         // nothing
408       }
409     }
410     if (getControlChannel() != null) {
411       builder.append(" Control: ").append(getControlChannel());
412     }
413     try {
414       if (getDataConn().getCurrentDataChannel() != null) {
415         builder.append(" Data: ").append(getDataConn().getCurrentDataChannel());
416       }
417     } catch (final FtpNoConnectionException ignored) {
418       // nothing
419     }
420     return builder.append('\n').toString();
421   }
422 
423   @Override
424   public final int getBlockSize() {
425     return restart.getMaxSize(configuration.getBlocksize());
426   }
427 
428   @Override
429   public final FileParameterInterface getFileParameter() {
430     return configuration.getFileParameter();
431   }
432 
433   /**
434    * @param path
435    *
436    * @return the basename from the given path
437    */
438   public static String getBasename(final String path) {
439     final File file = new File(path);
440     return file.getName();
441   }
442 
443   /**
444    * Reinitialize the authentication to the connection step
445    */
446   public final void reinitFtpAuth() {
447     final AbstractCommand connectioncommand = new ConnectionCommand(this);
448     setNextCommand(connectioncommand);
449     getAuth().clear();
450     getDataConn().clear();
451     getDataConn().getFtpTransferControl().resetWaitForOpenedDataChannel();
452   }
453 
454   /**
455    * Reinitialize all connection parameters, including authentification
456    */
457   public final void rein() {
458     // reset to default
459     // Previous mode could be Passive so remove the current configuration
460     final InetSocketAddress local = getDataConn().getLocalAddress();
461     final InetAddress remote = getDataConn().getRemoteAddress().getAddress();
462     getConfiguration().delFtpSession(remote, local);
463     getDataConn().unbindData();
464     getDataConn().setMode(FtpArgumentCode.TransferMode.STREAM);
465     getDataConn().setStructure(FtpArgumentCode.TransferStructure.FILE);
466     getDataConn().setType(FtpArgumentCode.TransferType.ASCII);
467     getDataConn().setSubType(TransferSubType.NONPRINT);
468     reinitFtpAuth();
469   }
470 
471   /**
472    * Try to open a connection. Do the intermediate reply if any (150) and the
473    * final one (125)
474    *
475    * @throws Reply425Exception if the connection cannot be opened
476    */
477   public final void openDataConnection() throws Reply425Exception {
478     getDataConn().getFtpTransferControl().openDataConnection();
479     final NetworkHandler networkHandler = getNetworkHandler();
480     if (networkHandler != null) {
481       networkHandler.writeIntermediateAnswer();
482     }
483   }
484 
485   @Override
486   public final String getUniqueExtension() {
487     return configuration.getUniqueExtension();
488   }
489 
490   public final boolean isSsl() {
491     return isSsl.get();
492   }
493 
494   public final void setSsl(final boolean isSsl) {
495     this.isSsl.set(isSsl);
496     if (waitForSsl != null) {
497       if (isSsl) {
498         waitForSsl.setSuccess();
499       } else {
500         waitForSsl.cancel();
501       }
502     }
503   }
504 
505   public synchronized void prepareSsl() {
506     waitForSsl = new WaarpFuture(true);
507   }
508 
509   public final boolean isSslReady() {
510     if (waitForSsl != null) {
511       for (int i = 0; i < 20; i++) {
512         if (waitForSsl.awaitOrInterruptible(100)) {
513           break;
514         }
515         Thread.yield();
516       }
517       logger.debug("DEBUG : {}:{}:{}", waitForSsl.isDone(), isSsl.get(),
518                    getControlChannel());
519     }
520     return isSsl.get();
521   }
522 
523   public final boolean isDataSsl() {
524     return isDataSsl.get();
525   }
526 
527   public final void setDataSsl(final boolean isDataSsl) {
528     this.isDataSsl.set(isDataSsl);
529   }
530 
531 }