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.data;
21  
22  import io.netty.channel.Channel;
23  import io.netty.channel.ChannelFuture;
24  import org.waarp.common.command.exception.Reply425Exception;
25  import org.waarp.common.crypto.ssl.WaarpSslUtility;
26  import org.waarp.common.logging.SysErrLogger;
27  import org.waarp.common.logging.WaarpLogger;
28  import org.waarp.common.logging.WaarpLoggerFactory;
29  import org.waarp.common.utility.WaarpNettyUtil;
30  import org.waarp.ftp.core.command.FtpArgumentCode;
31  import org.waarp.ftp.core.command.FtpArgumentCode.TransferMode;
32  import org.waarp.ftp.core.command.FtpArgumentCode.TransferStructure;
33  import org.waarp.ftp.core.command.FtpArgumentCode.TransferType;
34  import org.waarp.ftp.core.config.FtpConfiguration;
35  import org.waarp.ftp.core.config.FtpInternalConfiguration;
36  import org.waarp.ftp.core.data.handler.DataNetworkHandler;
37  import org.waarp.ftp.core.exception.FtpNoConnectionException;
38  import org.waarp.ftp.core.session.FtpSession;
39  import org.waarp.ftp.core.utils.FtpChannelUtils;
40  
41  import java.net.InetAddress;
42  import java.net.InetSocketAddress;
43  import java.util.concurrent.atomic.AtomicBoolean;
44  
45  /**
46   * Main class that handles Data connection using asynchronous connection with
47   * Netty
48   */
49  public class FtpDataAsyncConn {
50    /**
51     * Internal Logger
52     */
53    private static final WaarpLogger logger =
54        WaarpLoggerFactory.getLogger(FtpDataAsyncConn.class);
55    /**
56     * SessionInterface
57     */
58    private final FtpSession session;
59  
60    /**
61     * Current Data Network Handler
62     */
63    private DataNetworkHandler dataNetworkHandler;
64  
65    /**
66     * Data Channel with the client
67     */
68    private Channel dataChannel;
69  
70    /**
71     * External address of the client (active)
72     */
73    private InetSocketAddress remoteAddress;
74  
75    /**
76     * Local listening address for the server (passive)
77     */
78    private InetSocketAddress localAddress;
79  
80    /**
81     * Active: the connection is done from the Server to the Client on this
82     * remotePort Passive: not used
83     */
84    private int remotePort = -1;
85  
86    /**
87     * Active: the connection is done from the Server from this localPort to the
88     * Client Passive: the connection is
89     * done from the Client to the Server on this localPort
90     */
91    private int localPort = -1;
92  
93    /**
94     * Is the connection passive
95     */
96    private final AtomicBoolean passiveMode = new AtomicBoolean(false);
97  
98    /**
99     * Is the server binded (active or passive, but mainly passive)
100    */
101   private final AtomicBoolean isBind = new AtomicBoolean(false);
102 
103   /**
104    * The FtpTransferControl
105    */
106   private final FtpTransferControl transferControl;
107 
108   /**
109    * Current TransferType. Default ASCII
110    */
111   private FtpArgumentCode.TransferType transferType =
112       FtpArgumentCode.TransferType.ASCII;
113 
114   /**
115    * Current TransferSubType. Default NONPRINT
116    */
117   private FtpArgumentCode.TransferSubType transferSubType =
118       FtpArgumentCode.TransferSubType.NONPRINT;
119 
120   /**
121    * Current TransferStructure. Default FILE
122    */
123   private FtpArgumentCode.TransferStructure transferStructure =
124       FtpArgumentCode.TransferStructure.FILE;
125 
126   /**
127    * Current TransferMode. Default Stream
128    */
129   private FtpArgumentCode.TransferMode transferMode =
130       FtpArgumentCode.TransferMode.STREAM;
131 
132   /**
133    * Constructor for Active session by default
134    *
135    * @param session
136    */
137   public FtpDataAsyncConn(final FtpSession session) {
138     this.session = session;
139     dataChannel = null;
140     remoteAddress = FtpChannelUtils.getRemoteInetSocketAddress(
141         this.session.getControlChannel());
142     remotePort = remoteAddress.getPort();
143     setDefaultLocalPort();
144     resetLocalAddress();
145     passiveMode.set(false);
146     isBind.set(false);
147     transferControl = new FtpTransferControl(session);
148   }
149 
150   /**
151    * @param channel
152    *
153    * @return True if the given channel is the same as the one currently
154    *     registered
155    */
156   public final boolean checkCorrectChannel(final Channel channel) {
157     if (dataChannel == null || channel == null) {
158       return false;
159     }
160     return dataChannel.compareTo(channel) == 0;
161   }
162 
163   /**
164    * Clear the Data Connection
165    */
166   public final synchronized void clear() {
167     unbindData();
168     transferControl.clear();
169     passiveMode.set(false);
170     remotePort = -1;
171     localPort = -1;
172   }
173 
174   /**
175    * Set the local port to the default (0 meaning any available)
176    */
177   private void setDefaultLocalPort() {
178     setLocalPort(0);
179   }
180 
181   /**
182    * Set the Local Port (Active or Passive)
183    *
184    * @param localPort
185    */
186   public final synchronized void setLocalPort(final int localPort) {
187     this.localPort = localPort;
188   }
189 
190   /**
191    * @return the local address
192    */
193   public final InetSocketAddress getLocalAddress() {
194     return localAddress;
195   }
196 
197   /**
198    * @return the remote address
199    */
200   public final InetSocketAddress getRemoteAddress() {
201     return remoteAddress;
202   }
203 
204   /**
205    * @return the remotePort
206    */
207   public final int getRemotePort() {
208     return remotePort;
209   }
210 
211   /**
212    * @return the localPort
213    */
214   public final synchronized int getLocalPort() {
215     return localPort;
216   }
217 
218   private synchronized void resetLocalAddress() {
219     localAddress = new InetSocketAddress(
220         FtpChannelUtils.getLocalInetAddress(session.getControlChannel()),
221         localPort);
222   }
223 
224   /**
225    * Change to active connection (reset localPort to default)
226    *
227    * @param address remote address
228    */
229   public final synchronized void setActive(final InetSocketAddress address) {
230     unbindData();
231     setDefaultLocalPort();
232     resetLocalAddress();
233     remoteAddress = address;
234     passiveMode.set(false);
235     isBind.set(false);
236     remotePort = remoteAddress.getPort();
237     logger.debug("SetActive: {}", this);
238   }
239 
240   /**
241    * Change to passive connection (all necessaries informations like local
242    * port
243    * should have been set)
244    */
245   public final void setPassive() {
246     unbindData();
247     resetLocalAddress();
248     passiveMode.set(true);
249     isBind.set(false);
250     logger.debug("SetPassive: {}", this);
251   }
252 
253   /**
254    * @return the passiveMode
255    */
256   public final boolean isPassiveMode() {
257     return passiveMode.get();
258   }
259 
260   /**
261    * @return True if the connection is bind (active = connected, passive = not
262    *     necessarily connected)
263    */
264   public final boolean isBind() {
265     return isBind.get();
266   }
267 
268   /**
269    * Is the Data dataChannel connected
270    *
271    * @return True if the dataChannel is connected
272    */
273   public final synchronized boolean isActive() {
274     return dataChannel != null && dataChannel.isActive();
275   }
276 
277   /**
278    * @return the transferMode
279    */
280   public final synchronized FtpArgumentCode.TransferMode getMode() {
281     return transferMode;
282   }
283 
284   /**
285    * @param transferMode the transferMode to set
286    */
287   public final synchronized void setMode(
288       final FtpArgumentCode.TransferMode transferMode) {
289     this.transferMode = transferMode;
290     setCorrectCodec();
291   }
292 
293   /**
294    * @return the transferStructure
295    */
296   public final synchronized FtpArgumentCode.TransferStructure getStructure() {
297     return transferStructure;
298   }
299 
300   /**
301    * @param transferStructure the transferStructure to set
302    */
303   public final synchronized void setStructure(
304       final FtpArgumentCode.TransferStructure transferStructure) {
305     this.transferStructure = transferStructure;
306     setCorrectCodec();
307   }
308 
309   /**
310    * @return the transferSubType
311    */
312   public final synchronized FtpArgumentCode.TransferSubType getSubType() {
313     return transferSubType;
314   }
315 
316   /**
317    * @param transferSubType the transferSubType to set
318    */
319   public final synchronized void setSubType(
320       final FtpArgumentCode.TransferSubType transferSubType) {
321     this.transferSubType = transferSubType;
322     setCorrectCodec();
323   }
324 
325   /**
326    * @return the transferType
327    */
328   public final synchronized FtpArgumentCode.TransferType getType() {
329     return transferType;
330   }
331 
332   /**
333    * @param transferType the transferType to set
334    */
335   public final synchronized void setType(
336       final FtpArgumentCode.TransferType transferType) {
337     this.transferType = transferType;
338     setCorrectCodec();
339   }
340 
341   /**
342    * @return True if the current mode for data connection is FileInterface +
343    *     (Stream or Block) + (Ascii or
344    *     Image)
345    */
346   public final boolean isFileStreamBlockAsciiImage() {
347     return transferStructure == TransferStructure.FILE &&
348            (transferMode == TransferMode.STREAM ||
349             transferMode == TransferMode.ZLIB ||
350             transferMode == TransferMode.BLOCK) &&
351            (transferType == TransferType.ASCII ||
352             transferType == TransferType.IMAGE);
353   }
354 
355   /**
356    * @return True if the current mode for data connection is Stream
357    */
358   public final boolean isStreamFile() {
359     return (transferMode == TransferMode.STREAM ||
360             transferMode == TransferMode.ZLIB) &&
361            transferStructure == TransferStructure.FILE;
362   }
363 
364   /**
365    * This function must be called after any changes of parameters, ie after
366    * MODE, STRU, TYPE
367    */
368   private void setCorrectCodec() {
369     try {
370       getDataNetworkHandler().setCorrectCodec();
371     } catch (final FtpNoConnectionException ignored) {
372       // nothing
373     }
374   }
375 
376   /**
377    * Unbind connection when close the Data Channel
378    */
379   public final synchronized void unbindData() {
380     if (isBind.get()) {
381       if (passiveMode.get()) {
382         isBind.set(false);
383         final InetSocketAddress local = getLocalAddress();
384         ChannelFuture future = null;
385         if (dataChannel != null && dataChannel.isActive()) {
386           future = WaarpSslUtility.closingSslChannel(dataChannel);
387         }
388         session.getConfiguration().getFtpInternalConfiguration()
389                .unbindPassive(local);
390         // Previous mode was Passive so remove the current configuration if
391         // any
392         final InetAddress remote = remoteAddress.getAddress();
393         session.getConfiguration().delFtpSession(remote, local);
394         // prepare the validation of the next connection
395         getFtpTransferControl().resetWaitForOpenedDataChannel();
396         if (future != null) {
397           future.awaitUninterruptibly(FtpConfiguration.getDataTimeoutCon());
398         }
399       } else {
400         isBind.set(false);
401         ChannelFuture future = null;
402         if (dataChannel != null && dataChannel.isActive()) {
403           future = WaarpSslUtility.closingSslChannel(dataChannel);
404         }
405         getFtpTransferControl().resetWaitForOpenedDataChannel();
406         if (future != null) {
407           future.awaitUninterruptibly(FtpConfiguration.getDataTimeoutCon());
408         }
409       }
410     }
411     dataChannel = null;
412     dataNetworkHandler = null;
413   }
414 
415   /**
416    * Initialize the socket from Server side (only used in Passive)
417    *
418    * @return True if OK
419    *
420    * @throws Reply425Exception
421    */
422   public final boolean initPassiveConnection() throws Reply425Exception {
423     unbindData();
424     if (passiveMode.get()) {
425       // Connection is enable but the client will do the real connection
426       session.getConfiguration().getFtpInternalConfiguration()
427              .bindPassive(getLocalAddress(), session.isDataSsl());
428       isBind.set(true);
429       return true;
430     }
431     // Connection is already prepared
432     return true;
433   }
434 
435   /**
436    * Return the current Data Channel
437    *
438    * @return the current Data Channel
439    *
440    * @throws FtpNoConnectionException
441    */
442   public final Channel getCurrentDataChannel() throws FtpNoConnectionException {
443     if (dataChannel == null) {
444       throw new FtpNoConnectionException("No Data Connection active");
445     }
446     return dataChannel;
447   }
448 
449   /**
450    * @return the DataNetworkHandler
451    *
452    * @throws FtpNoConnectionException
453    */
454   public final synchronized DataNetworkHandler getDataNetworkHandler()
455       throws FtpNoConnectionException {
456     if (dataNetworkHandler == null) {
457       throw new FtpNoConnectionException("No Data Connection active");
458     }
459     return dataNetworkHandler;
460   }
461 
462   /**
463    * @param dataNetworkHandler the {@link DataNetworkHandler} to set
464    */
465   public final synchronized void setDataNetworkHandler(
466       final DataNetworkHandler dataNetworkHandler) {
467     this.dataNetworkHandler = dataNetworkHandler;
468   }
469 
470   /**
471    * @param configuration
472    *
473    * @return a new Passive Port
474    */
475   public static int getNewPassivePort(final FtpConfiguration configuration) {
476     synchronized (FtpConfiguration.ftpConfiguration) {
477       int port = -1;
478       for (int i = 0; i < 1000; i++) {
479         port = configuration.getNextRangePort();
480         if (port > 0 && WaarpNettyUtil.availablePort(port)) {
481           return port;
482         }
483         try {
484           Thread.sleep(FtpInternalConfiguration.RETRYINMS);// NOSONAR
485         } catch (final InterruptedException e) {// NOSONAR
486           SysErrLogger.FAKE_LOGGER.ignoreLog(e);
487         }
488       }
489       return -1;
490     }
491   }
492 
493   /**
494    * @return The current status in String of the different parameters
495    */
496   public final String getStatus() {
497     final StringBuilder builder = new StringBuilder("Data connection: ").append(
498         isActive()? "connected " : "not connected ").append(
499         isBind()? "bind " : "not bind ").append(
500         isPassiveMode()? "passive mode" : "active mode").append('\n').append(
501         "Mode: ").append(transferMode.name()).append(" localPort: ").append(
502         getLocalPort()).append(" remotePort: ").append(getRemotePort()).append(
503         '\n').append("Structure: ").append(transferStructure.name()).append(
504         '\n').append("Type: ").append(transferType.name()).append(' ').append(
505         transferSubType.name());
506     return builder.toString();
507   }
508 
509   /**
510    *
511    */
512   @Override
513   public String toString() {
514     return getStatus().replace('\n', ' ');
515   }
516 
517   /**
518    * @return the FtpTransferControl
519    */
520   public final FtpTransferControl getFtpTransferControl() {
521     return transferControl;
522   }
523 
524   /**
525    * Set the new connected Data Channel
526    *
527    * @param dataChannel the new Data Channel
528    *
529    * @throws Reply425Exception
530    */
531   public final synchronized void setNewOpenedDataChannel(
532       final Channel dataChannel) throws Reply425Exception {
533     this.dataChannel = dataChannel;
534     if (dataChannel == null) {
535       final String curmode;
536       if (isPassiveMode()) {
537         curmode = "passive";
538       } else {
539         curmode = "active";
540       }
541       // Cannot open connection
542       throw new Reply425Exception(
543           "Cannot open " + curmode + " data connection");
544     }
545     isBind.set(true);
546   }
547 }