1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.waarp.ftp.client;
21
22 import it.sauronsoftware.ftp4j.FTPAbortedException;
23 import it.sauronsoftware.ftp4j.FTPClient;
24 import it.sauronsoftware.ftp4j.FTPCommunicationListener;
25 import it.sauronsoftware.ftp4j.FTPConnector;
26 import it.sauronsoftware.ftp4j.FTPDataTransferException;
27 import it.sauronsoftware.ftp4j.FTPException;
28 import it.sauronsoftware.ftp4j.FTPFile;
29 import it.sauronsoftware.ftp4j.FTPIllegalReplyException;
30 import it.sauronsoftware.ftp4j.FTPListParseException;
31 import it.sauronsoftware.ftp4j.FTPReply;
32 import org.waarp.common.file.FileUtils;
33 import org.waarp.common.logging.SysErrLogger;
34 import org.waarp.common.logging.WaarpLogger;
35 import org.waarp.common.logging.WaarpLoggerFactory;
36 import org.waarp.common.utility.SystemPropertyUtil;
37
38 import javax.net.ssl.SSLContext;
39 import javax.net.ssl.SSLSocketFactory;
40 import javax.net.ssl.TrustManager;
41 import javax.net.ssl.X509TrustManager;
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.FileNotFoundException;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.OutputStream;
49 import java.net.SocketException;
50 import java.security.KeyManagementException;
51 import java.security.NoSuchAlgorithmException;
52 import java.security.cert.X509Certificate;
53
54 import static org.waarp.common.digest.WaarpBC.*;
55
56
57
58
59 public class WaarpFtp4jClient implements WaarpFtpClientInterface {
60
61
62
63 private static final WaarpLogger logger =
64 WaarpLoggerFactory.getLogger(WaarpFtp4jClient.class);
65 private static final int DEFAULT_WAIT = 1;
66
67 static {
68 initializedTlsContext();
69 }
70
71 final String server;
72 int port = 21;
73 final String user;
74 final String pwd;
75 final String acct;
76 final int timeout;
77 final int keepalive;
78 boolean isPassive;
79 final int ssl;
80 protected FTPClient ftpClient;
81 protected String result;
82 protected String directory = null;
83
84
85
86
87
88
89
90
91
92
93
94 public WaarpFtp4jClient(final String server, final int port,
95 final String user, final String pwd,
96 final String acct, final boolean isPassive,
97 final int ssl, final int keepalive,
98 final int timeout) {
99 this.server = server;
100 this.port = port;
101 this.user = user;
102 this.pwd = pwd;
103 this.acct = acct;
104 this.isPassive = isPassive;
105 this.ssl = ssl;
106 this.keepalive = keepalive;
107 this.timeout = timeout;
108 ftpClient = new FTPClient();
109 if (this.ssl != 0) {
110
111 final TrustManager[] trustManager = {
112 new X509TrustManager() {
113 @Override
114 public X509Certificate[] getAcceptedIssuers() {
115 return null;
116 }
117
118 @Override
119 public final void checkClientTrusted(final X509Certificate[] certs,
120
121 final String authType) {
122
123 }
124
125 @Override
126 public final void checkServerTrusted(final X509Certificate[] certs,
127
128 final String authType) {
129
130 }
131 }
132 };
133 final SSLContext sslContext;
134 try {
135 sslContext = getInstanceJDK();
136 sslContext.init(null, trustManager, getSecureRandom());
137 } catch (final NoSuchAlgorithmException e) {
138 throw new IllegalArgumentException("Bad algorithm", e);
139 } catch (final KeyManagementException e) {
140 throw new IllegalArgumentException("Bad KeyManagement", e);
141 } catch (final Exception e) {
142 throw new IllegalArgumentException("Bad Provider", e);
143 }
144 final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
145 ftpClient.setSSLSocketFactory(sslSocketFactory);
146 if (this.ssl < 0) {
147 ftpClient.setSecurity(FTPClient.SECURITY_FTPS);
148 } else {
149 ftpClient.setSecurity(FTPClient.SECURITY_FTPES);
150 }
151 } else {
152 ftpClient = new FTPClient();
153 }
154 if (timeout > 0) {
155 System.setProperty("ftp4j.activeDataTransfer.acceptTimeout",
156 String.valueOf(timeout));
157 }
158 System.setProperty("ftp4j.activeDataTransfer.hostAddress", "127.0.0.1");
159
160 ftpClient.addCommunicationListener(new FTPCommunicationListener() {
161 @Override
162 public final void sent(final String arg0) {
163 logger.debug("Command: {}", arg0);
164 }
165
166 @Override
167 public final void received(final String arg0) {
168 logger.debug("Answer: {}", arg0);
169 }
170 });
171 final FTPConnector connector = ftpClient.getConnector();
172 int timeoutDefault = timeout > 0? timeout / 1000 : 30;
173 connector.setCloseTimeout(timeoutDefault);
174 connector.setReadTimeout(timeoutDefault);
175 connector.setConnectionTimeout(timeoutDefault);
176 connector.setUseSuggestedAddressForDataConnections(true);
177 }
178
179 @Override
180 public void setReportActiveExternalIPAddress(final String ipAddress) {
181 if (ipAddress != null) {
182 SystemPropertyUtil.set("ftp4j.activeDataTransfer.hostAddress", ipAddress);
183 } else {
184 SystemPropertyUtil.clear("ftp4j.activeDataTransfer.hostAddress");
185 }
186 }
187
188 @Override
189 public void setActiveDataTransferPortRange(final int from, final int to) {
190 if (from <= 0 || to <= 0) {
191 SystemPropertyUtil.clear("ftp4j.activeDataTransfer.portRange");
192 } else {
193 SystemPropertyUtil.set("ftp4j.activeDataTransfer.portRange",
194 from + "-" + to);
195 }
196 }
197
198 @Override
199 public final String getResult() {
200 return result;
201 }
202
203 private void reconnect() {
204 ftpClient.setAutoNoopTimeout(0);
205 try {
206 ftpClient.logout();
207 } catch (final Exception e) {
208
209 } finally {
210 disconnect();
211 }
212 waitAfterDataCommand();
213 connect();
214 if (directory != null) {
215 changeDir(directory);
216 }
217 }
218
219 @Override
220 public final boolean connect() {
221 result = null;
222 boolean isActive = false;
223 try {
224 waitAfterDataCommand();
225 Exception lastExcemption = null;
226 for (int i = 0; i < 5; i++) {
227 try {
228 ftpClient.connect(server, port);
229 lastExcemption = null;
230 break;
231 } catch (final SocketException e) {
232 result = CONNECTION_IN_ERROR;
233 lastExcemption = e;
234 } catch (final Exception e) {
235 result = CONNECTION_IN_ERROR;
236 lastExcemption = e;
237 }
238 waitAfterDataCommand();
239 }
240 if (lastExcemption != null) {
241 logger.error(result + ": {}", lastExcemption.getMessage());
242 return false;
243 }
244 try {
245 if (acct == null) {
246
247 ftpClient.login(user, pwd);
248 } else {
249 ftpClient.login(user, pwd, acct);
250 }
251 } catch (final Exception e) {
252 logout();
253 result = LOGIN_IN_ERROR;
254 logger.error(result);
255 return false;
256 }
257 try {
258 ftpClient.setType(FTPClient.TYPE_BINARY);
259 } catch (final IllegalArgumentException e1) {
260 result = SET_BINARY_IN_ERROR;
261 logger.error(result + ": {}", e1.getMessage());
262 return false;
263 }
264 changeMode(isPassive);
265 if (keepalive > 0) {
266 ftpClient.setAutoNoopTimeout(keepalive);
267 }
268 isActive = true;
269 return true;
270 } finally {
271 if (!isActive && !ftpClient.isPassive()) {
272 disconnect();
273 }
274 }
275 }
276
277 @Override
278 public final void logout() {
279 result = null;
280 ftpClient.setAutoNoopTimeout(0);
281 logger.debug("QUIT");
282 if (executeCommand("QUIT") == null) {
283 try {
284 ftpClient.logout();
285 } catch (final Exception e) {
286
287 } finally {
288 disconnect();
289 }
290 }
291 }
292
293 @Override
294 public final void disconnect() {
295 result = null;
296 ftpClient.setAutoNoopTimeout(0);
297 try {
298 ftpClient.disconnect(false);
299 } catch (final Exception e) {
300 logger.debug(DISCONNECTION_IN_ERROR, e);
301 }
302 }
303
304 @Override
305 public final boolean makeDir(final String newDir) {
306 result = null;
307 try {
308 ftpClient.createDirectory(newDir);
309 return true;
310 } catch (final Exception e) {
311 try {
312 reconnect();
313 ftpClient.createDirectory(newDir);
314 return true;
315 } catch (final Exception e1) {
316 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
317 }
318 result = MKDIR_IN_ERROR;
319 logger.error(result + ": {}", e.getMessage());
320 waitAfterDataCommand();
321 return false;
322 }
323 }
324
325 @Override
326 public final boolean changeDir(final String newDir) {
327 result = null;
328 try {
329 directory = newDir;
330 ftpClient.changeDirectory(newDir);
331 return true;
332 } catch (final IOException e) {
333 try {
334 reconnect();
335 ftpClient.changeDirectory(newDir);
336 return true;
337 } catch (final Exception e1) {
338 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
339 }
340 result = CHDIR_IN_ERROR;
341 logger.error(result + ": {}", e.getMessage());
342 return false;
343 } catch (final Exception e) {
344 try {
345 reconnect();
346 ftpClient.changeDirectory(newDir);
347 return true;
348 } catch (final Exception e1) {
349 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
350 }
351 result = CHDIR_IN_ERROR;
352 logger.error(result + ": {}", e.getMessage());
353 return false;
354 }
355 }
356
357 @Override
358 public final boolean changeFileType(final boolean binaryTransfer) {
359 result = null;
360 try {
361 if (binaryTransfer) {
362 ftpClient.setType(FTPClient.TYPE_BINARY);
363 } else {
364 ftpClient.setType(FTPClient.TYPE_TEXTUAL);
365 }
366 return true;
367 } catch (final IllegalArgumentException e) {
368 result = FILE_TYPE_IN_ERROR;
369 logger.error(result + ": {}", e.getMessage());
370 return false;
371 }
372 }
373
374 @Override
375 public final void changeMode(final boolean passive) {
376 result = null;
377 isPassive = passive;
378 ftpClient.setPassive(passive);
379 waitAfterDataCommand();
380 }
381
382 @Override
383 public void compressionMode(final boolean compression) {
384 if (compression) {
385 if (ftpClient.isCompressionSupported()) {
386 try {
387 ftpClient.setType(FTPClient.TYPE_BINARY);
388 } catch (final IllegalArgumentException e1) {
389 result = SET_BINARY_IN_ERROR;
390 logger.error(result + ": {}", e1.getMessage());
391 }
392 ftpClient.setCompressionEnabled(true);
393 } else {
394 logger.warn("Z Compression not supported by Server");
395 ftpClient.setCompressionEnabled(false);
396 }
397 } else {
398 ftpClient.setCompressionEnabled(false);
399 }
400 }
401
402 @Override
403 public final boolean store(final String local, final String remote) {
404 return transferFile(local, remote, 1);
405 }
406
407 @Override
408 public final boolean store(final InputStream local, final String remote) {
409 return transferFile(local, remote, 1);
410 }
411
412 @Override
413 public final boolean append(final String local, final String remote) {
414 return transferFile(local, remote, 2);
415 }
416
417 @Override
418 public final boolean append(final InputStream local, final String remote) {
419 return transferFile(local, remote, 2);
420 }
421
422 @Override
423 public final boolean retrieve(final String local, final String remote) {
424 return transferFile(local, remote, -1);
425 }
426
427 @Override
428 public final boolean retrieve(final OutputStream local, final String remote) {
429 return transferFile(local, remote);
430 }
431
432 @Override
433 public final boolean transferFile(final String local, final String remote,
434 final int getStoreOrAppend) {
435 result = null;
436 try {
437 if (!internalTransferFile(local, remote, getStoreOrAppend)) {
438 reconnect();
439 return internalTransferFile(local, remote, getStoreOrAppend);
440 }
441 return true;
442 } catch (final IOException e) {
443 try {
444 reconnect();
445 return internalTransferFile(local, remote, getStoreOrAppend);
446 } catch (final Exception e1) {
447 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
448 }
449 result = CANNOT_FINALIZE_TRANSFER_OPERATION;
450 logger.error(result + ": {}", e.getMessage());
451 return false;
452 }
453 }
454
455 private boolean internalTransferFile(final String local, final String remote,
456 final int getStoreOrAppend)
457 throws FileNotFoundException {
458 if (getStoreOrAppend > 0) {
459 final File from = new File(local);
460 logger.debug("Will STOR: {}", from);
461 FileInputStream stream = null;
462 try {
463 stream = new FileInputStream(local);
464 return transferFile(stream, remote, getStoreOrAppend);
465 } finally {
466 FileUtils.close(stream);
467 }
468 } else {
469 OutputStream outputStream = null;
470 if (local == null) {
471
472 logger.debug("Will DLD nullStream: {}", remote);
473 outputStream = new NullOutputStream();
474 } else {
475 logger.debug("Will DLD to local: {} into {}", remote, local);
476 outputStream = new FileOutputStream(local);
477 }
478 try {
479 return transferFile(outputStream, remote);
480 } finally {
481 FileUtils.close(outputStream);
482 }
483 }
484 }
485
486 @Override
487 public final boolean transferFile(final InputStream local,
488 final String remote,
489 final int getStoreOrAppend) {
490 result = null;
491 result = CANNOT_FINALIZE_STORE_LIKE_OPERATION;
492 logger.debug("Will STOR to: {}", remote);
493 try {
494 internalTransferFile(local, remote, getStoreOrAppend);
495 return true;
496 } catch (final Exception e) {
497 try {
498 reconnect();
499 return internalTransferFile(local, remote, getStoreOrAppend);
500 } catch (final Exception e1) {
501 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
502 }
503 logger.error(result + ": {}", e.getMessage());
504 return false;
505 } finally {
506 waitAfterDataCommand();
507 }
508 }
509
510 private boolean internalTransferFile(final InputStream local,
511 final String remote,
512 final int getStoreOrAppend)
513 throws IOException, FTPIllegalReplyException, FTPException,
514 FTPDataTransferException, FTPAbortedException {
515 if (getStoreOrAppend == 1) {
516 ftpClient.upload(remote, local, 0, 0, null);
517 } else {
518
519 ftpClient.append(remote, local, 0, null);
520 }
521 result = null;
522 return true;
523 }
524
525 @Override
526 public final boolean transferFile(final OutputStream local,
527 final String remote) {
528 result = null;
529 result = CANNOT_FINALIZE_RETRIEVE_LIKE_OPERATION;
530 logger.debug("Will DLD nullStream: {}", remote);
531 try {
532 ftpClient.download(remote, local, 0, null);
533 result = null;
534 return true;
535 } catch (final Exception e) {
536 try {
537 reconnect();
538 ftpClient.download(remote, local, 0, null);
539 result = null;
540 return true;
541 } catch (final Exception e1) {
542 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
543 }
544 logger.error(result + ": {}", e.getMessage(), e);
545 return false;
546 } finally {
547 waitAfterDataCommand();
548 }
549 }
550
551 @Override
552 public final String[] listFiles(final String remote) {
553 result = null;
554 ftpClient.setMLSDPolicy(FTPClient.MLSD_NEVER);
555 try {
556 return internalListFiles(remote);
557 } catch (final Exception e) {
558 try {
559 reconnect();
560 return internalListFiles(remote);
561 } catch (final Exception e1) {
562 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
563 }
564 result = CANNOT_FINALIZE_TRANSFER_OPERATION;
565 logger.error(result + ": {}", e.getMessage());
566 return null;
567 } finally {
568 waitAfterDataCommand();
569 }
570 }
571
572 private String[] internalListFiles(final String remote)
573 throws IOException, FTPIllegalReplyException, FTPException,
574 FTPDataTransferException, FTPAbortedException,
575 FTPListParseException {
576 final FTPFile[] list = ftpClient.list(remote);
577 final String[] results = new String[list.length];
578 int i = 0;
579 for (final FTPFile file : list) {
580 results[i] = file.toString();
581 i++;
582 }
583 return results;
584 }
585
586 @Override
587 public final String[] listFiles() {
588 return listFiles((String) null);
589 }
590
591 @Override
592 public final String[] mlistFiles(final String remote) {
593 ftpClient.setMLSDPolicy(FTPClient.MLSD_ALWAYS);
594 return listFiles(remote);
595 }
596
597 @Override
598 public final String[] mlistFiles() {
599 return mlistFiles((String) null);
600 }
601
602
603 @Override
604 public final String[] features() {
605 result = null;
606 try {
607 final FTPReply reply = ftpClient.sendCustomCommand("FEAT");
608 return reply.getMessages();
609 } catch (final IOException e) {
610 try {
611 reconnect();
612 final FTPReply reply = ftpClient.sendCustomCommand("FEAT");
613 return reply.getMessages();
614 } catch (final Exception e1) {
615 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
616 }
617 result = CANNOT_EXECUTE_OPERATION_FEATURE;
618 logger.error(result + ": {}", e.getMessage());
619 return null;
620 } catch (final Exception e) {
621 try {
622 reconnect();
623 final FTPReply reply = ftpClient.sendCustomCommand("FEAT");
624 return reply.getMessages();
625 } catch (final Exception e1) {
626 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
627 }
628 result = CANNOT_EXECUTE_OPERATION_FEATURE;
629 logger.error(result + ": {}", e.getMessage());
630 return null;
631 }
632 }
633
634 @Override
635 public final boolean featureEnabled(final String feature) {
636 result = null;
637 try {
638 return internalFeatureEnabled(feature);
639 } catch (final Exception e) {
640 try {
641 reconnect();
642 return internalFeatureEnabled(feature);
643 } catch (final Exception e1) {
644 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
645 }
646 result = CANNOT_EXECUTE_OPERATION_FEATURE;
647 logger.error(result + ": {}", e.getMessage());
648 return false;
649 }
650 }
651
652 private boolean internalFeatureEnabled(final String feature)
653 throws IOException, FTPIllegalReplyException {
654 final FTPReply reply = ftpClient.sendCustomCommand("FEAT");
655 final String[] msg = reply.getMessages();
656 for (final String string : msg) {
657 if (string.contains(feature.toUpperCase())) {
658 return true;
659 }
660 }
661 return false;
662 }
663
664 @Override
665 public boolean deleteFile(final String remote) {
666 result = null;
667 try {
668 logger.debug("DELE {}", remote);
669 ftpClient.deleteFile(remote);
670 return true;
671 } catch (final Exception e) {
672 try {
673 reconnect();
674 ftpClient.deleteFile(remote);
675 return true;
676 } catch (final Exception e1) {
677 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
678 }
679 result = CANNOT_EXECUTE_OPERATION_SITE;
680 logger.error(result + ": {}", e.getMessage());
681 return false;
682 }
683 }
684
685 @Override
686 public final String[] executeCommand(final String params) {
687 result = null;
688 try {
689 logger.debug(params);
690 FTPReply reply = ftpClient.sendCustomCommand(params);
691 if (!reply.isSuccessCode()) {
692 reconnect();
693 reply = ftpClient.sendCustomCommand(params);
694 if (!reply.isSuccessCode()) {
695 result = reply.toString();
696 return null;
697 }
698 }
699 return reply.getMessages();
700 } catch (final Exception e) {
701 try {
702 reconnect();
703 final FTPReply reply = ftpClient.sendCustomCommand(params);
704 if (!reply.isSuccessCode()) {
705 result = reply.toString();
706 return null;
707 }
708 return reply.getMessages();
709 } catch (final Exception e1) {
710 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
711 }
712 result = CANNOT_EXECUTE_OPERATION_SITE;
713 logger.error(result + ": {}", e.getMessage());
714 return null;
715 }
716 }
717
718 @Override
719 public final String[] executeSiteCommand(final String params) {
720 result = null;
721 try {
722 logger.debug("SITE {}", params);
723 FTPReply reply = ftpClient.sendSiteCommand(params);
724 if (!reply.isSuccessCode()) {
725 reconnect();
726 reply = ftpClient.sendSiteCommand(params);
727 if (!reply.isSuccessCode()) {
728 result = reply.toString();
729 return null;
730 }
731 }
732 return reply.getMessages();
733 } catch (final Exception e) {
734 try {
735 reconnect();
736 final FTPReply reply = ftpClient.sendSiteCommand(params);
737 if (!reply.isSuccessCode()) {
738 result = reply.toString();
739 return null;
740 }
741 return reply.getMessages();
742 } catch (final Exception e1) {
743 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
744 }
745 result = CANNOT_EXECUTE_OPERATION_SITE;
746 logger.error(result + ": {}", e.getMessage());
747 return null;
748 }
749 }
750
751 @Override
752 public final void noop() {
753 try {
754 ftpClient.noop();
755 } catch (final Exception e) {
756 try {
757 reconnect();
758 ftpClient.noop();
759 } catch (final Exception e1) {
760 SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
761 }
762 result = NOOP_ERROR;
763 logger.error(result + ": {}", e.getMessage());
764 }
765 }
766
767
768
769
770 static void waitAfterDataCommand() {
771 if (DEFAULT_WAIT > 0) {
772 try {
773 Thread.sleep(DEFAULT_WAIT);
774 } catch (final InterruptedException e) {
775 SysErrLogger.FAKE_LOGGER.ignoreLog(e);
776 }
777 } else {
778 Thread.yield();
779 }
780 }
781 }