001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.net.ftp;
019
020 import java.io.BufferedReader;
021 import java.io.BufferedWriter;
022 import java.io.IOException;
023 import java.io.InputStreamReader;
024 import java.io.OutputStreamWriter;
025 import java.net.Socket;
026 import java.security.KeyManagementException;
027 import java.security.NoSuchAlgorithmException;
028
029 import javax.net.ssl.KeyManager;
030 import javax.net.ssl.SSLContext;
031 import javax.net.ssl.SSLException;
032 import javax.net.ssl.SSLServerSocketFactory;
033 import javax.net.ssl.SSLSocket;
034 import javax.net.ssl.SSLSocketFactory;
035 import javax.net.ssl.TrustManager;
036
037 /**
038 * FTP over SSL processing. If desired, the JVM property -Djavax.net.debug=all can be used to
039 * see wire-level SSL details.
040 *
041 * @version $Id: FTPSClient.java 658520 2008-05-21 01:14:11Z sebb $
042 * @since 2.0
043 */
044 public class FTPSClient extends FTPClient {
045
046 /** keystore algorithm name. */
047 public static String KEYSTORE_ALGORITHM;
048 /** truststore algorithm name. */
049 public static String TRUSTSTORE_ALGORITHM;
050 /** provider name. */
051 public static String PROVIDER;
052 /** truststore type. */
053 public static String STORE_TYPE;
054
055 /** The value that I can set in PROT command */
056 private static final String[] PROT_COMMAND_VALUE = {"C","E","S","P"};
057 /** Default PROT Command */
058 private static final String DEFAULT_PROT = "C";
059 /** Default protocol name */
060 private static final String DEFAULT_PROTOCOL = "TLS";
061
062 /** The security mode. (True - Implicit Mode / False - Explicit Mode) */
063 private boolean isImplicit;
064 /** The use SSL/TLS protocol. */
065 private String protocol = DEFAULT_PROTOCOL;
066 /** The AUTH Command value */
067 private String auth = DEFAULT_PROTOCOL;
068 /** The context object. */
069 private SSLContext context;
070 /** The socket object. */
071 private Socket planeSocket;
072 /** The established socket flag. */
073 private boolean isCreation = true;
074 /** The use client mode flag. */
075 private boolean isClientMode = true;
076 /** The need client auth flag. */
077 private boolean isNeedClientAuth = false;
078 /** The want client auth flag. */
079 private boolean isWantClientAuth = false;
080 /** The cipher suites */
081 private String[] suites = null;
082 /** The protocol versions */
083 private String[] protocols = null;
084
085 /** The FTPS {@link TrustManager} implementation. */
086 private TrustManager trustManager = new FTPSTrustManager();
087
088 /** The {@link KeyManager} */
089 private KeyManager keyManager;
090
091 /**
092 * Constructor for FTPSClient.
093 * @throws NoSuchAlgorithmException A requested cryptographic algorithm
094 * is not available in the environment.
095 */
096 public FTPSClient() throws NoSuchAlgorithmException {
097 this.protocol = DEFAULT_PROTOCOL;
098 this.isImplicit = false;
099 }
100
101 /**
102 * Constructor for FTPSClient.
103 * @param isImplicit The secutiry mode(Implicit/Explicit).
104 * @throws NoSuchAlgorithmException A requested cryptographic algorithm
105 * is not available in the environment.
106 */
107 public FTPSClient(boolean isImplicit) throws NoSuchAlgorithmException {
108 this.protocol = DEFAULT_PROTOCOL;
109 this.isImplicit = isImplicit;
110 }
111
112 /**
113 * Constructor for FTPSClient.
114 * @param protocol the protocol
115 * @throws NoSuchAlgorithmException A requested cryptographic algorithm
116 * is not available in the environment.
117 */
118 public FTPSClient(String protocol) throws NoSuchAlgorithmException {
119 this.protocol = protocol;
120 this.isImplicit = false;
121 }
122
123 /**
124 * Constructor for FTPSClient.
125 * @param protocol the protocol
126 * @param isImplicit The secutiry mode(Implicit/Explicit).
127 * @throws NoSuchAlgorithmException A requested cryptographic algorithm
128 * is not available in the environment.
129 */
130 public FTPSClient(String protocol, boolean isImplicit)
131 throws NoSuchAlgorithmException {
132 this.protocol = protocol;
133 this.isImplicit = isImplicit;
134 }
135
136
137 /**
138 * Set AUTH command use value.
139 * This processing is done before connected processing.
140 * @param auth AUTH command use value.
141 */
142 public void setAuthValue(String auth) {
143 this.auth = auth;
144 }
145
146 /**
147 * Return AUTH command use value.
148 * @return AUTH command use value.
149 */
150 public String getAuthValue() {
151 return this.auth;
152 }
153
154
155 /**
156 * Because there are so many connect() methods,
157 * the _connectAction_() method is provided as a means of performing
158 * some action immediately after establishing a connection,
159 * rather than reimplementing all of the connect() methods.
160 * @throws IOException If it throw by _connectAction_.
161 * @see org.apache.commons.net.SocketClient#_connectAction_()
162 */
163 @Override
164 protected void _connectAction_() throws IOException {
165 // Implicit mode.
166 if (isImplicit) sslNegotiation();
167 super._connectAction_();
168 // Explicit mode.
169 if (!isImplicit) {
170 execAUTH();
171 sslNegotiation();
172 }
173 }
174
175 /**
176 * AUTH command.
177 * @throws SSLException If it server reply code not equal "234" and "334".
178 * @throws IOException If an I/O error occurs while either sending
179 * the command.
180 */
181 private void execAUTH() throws SSLException, IOException {
182 int replyCode = sendCommand(
183 FTPSCommand._commands[FTPSCommand.AUTH], auth);
184 if (FTPReply.SECURITY_MECHANISM_IS_OK == replyCode) {
185 // replyCode = 334
186 // I carry out an ADAT command.
187 } else if (FTPReply.SECURITY_DATA_EXCHANGE_COMPLETE != replyCode) {
188 throw new SSLException(getReplyString());
189 }
190 }
191
192 /**
193 * Performs a lazy init of the SSL context
194 * @throws IOException
195 */
196 private void initSslContext() throws IOException {
197 if(context == null) {
198 try {
199 context = SSLContext.getInstance(protocol);
200
201 context.init(new KeyManager[] { getKeyManager() } , new TrustManager[] { getTrustManager() } , null);
202 } catch (KeyManagementException e) {
203 IOException ioe = new IOException("Could not initialize SSL context");
204 ioe.initCause(e);
205 throw ioe;
206 } catch (NoSuchAlgorithmException e) {
207 IOException ioe = new IOException("Could not initialize SSL context");
208 ioe.initCause(e);
209 throw ioe;
210 }
211 }
212 }
213
214 /**
215 * SSL/TLS negotiation. Acquires an SSL socket of a control
216 * connection and carries out handshake processing.
217 * @throws IOException A handicap breaks out by sever negotiation.
218 */
219 private void sslNegotiation() throws IOException {
220 // Evacuation not ssl socket.
221 planeSocket = _socket_;
222
223 initSslContext();
224
225 SSLSocketFactory ssf = context.getSocketFactory();
226 String ip = _socket_.getInetAddress().getHostAddress();
227 int port = _socket_.getPort();
228 SSLSocket socket =
229 (SSLSocket) ssf.createSocket(_socket_, ip, port, true);
230 socket.setEnableSessionCreation(isCreation);
231 socket.setUseClientMode(isClientMode);
232 // server mode
233 if (!isClientMode) {
234 socket.setNeedClientAuth(isNeedClientAuth);
235 socket.setWantClientAuth(isWantClientAuth);
236 }
237 if (protocols != null) socket.setEnabledProtocols(protocols);
238 if (suites != null) socket.setEnabledCipherSuites(suites);
239
240 socket.startHandshake();
241
242 _socket_ = socket;
243 _controlInput_ = new BufferedReader(new InputStreamReader(
244 socket .getInputStream(), getControlEncoding()));
245 _controlOutput_ = new BufferedWriter(new OutputStreamWriter(
246 socket.getOutputStream(), getControlEncoding()));
247 }
248
249 /**
250 * Get the {@link KeyManager} instance.
251 * @return The {@link KeyManager} instance
252 */
253 private KeyManager getKeyManager() {
254 return keyManager;
255 }
256
257 /**
258 * Set a {@link KeyManager} to use
259 *
260 * @param keyManager The KeyManager implementation to set.
261 */
262 public void setKeyManager(KeyManager keyManager) {
263 this.keyManager = keyManager;
264 }
265
266 /**
267 * Controls whether new a SSL session may be established by this socket.
268 * @param isCreation The established socket flag.
269 */
270 public void setEnabledSessionCreation(boolean isCreation) {
271 this.isCreation = isCreation;
272 }
273
274 /**
275 * Returns true if new SSL sessions may be established by this socket.
276 * When a socket does not have a ssl socket, This return False.
277 * @return true - Indicates that sessions may be created;
278 * this is the default.
279 * false - indicates that an existing session must be resumed.
280 */
281 public boolean getEnableSessionCreation() {
282 if (_socket_ instanceof SSLSocket)
283 return ((SSLSocket)_socket_).getEnableSessionCreation();
284 return false;
285 }
286
287 /**
288 * Configures the socket to require client authentication.
289 * @param isNeedClientAuth The need client auth flag.
290 */
291 public void setNeedClientAuth(boolean isNeedClientAuth) {
292 this.isNeedClientAuth = isNeedClientAuth;
293 }
294
295 /**
296 * Returns true if the socket will require client authentication.
297 * When a socket does not have a ssl socket, This return False.
298 * @return true - If the server mode socket should request
299 * that the client authenticate itself.
300 */
301 public boolean getNeedClientAuth() {
302 if (_socket_ instanceof SSLSocket)
303 return ((SSLSocket)_socket_).getNeedClientAuth();
304 return false;
305 }
306
307 /**
308 * Configures the socket to request client authentication,
309 * but only if such a request is appropriate to the cipher
310 * suite negotiated.
311 * @param isWantClientAuth The want client auth flag.
312 */
313 public void setWantClientAuth(boolean isWantClientAuth) {
314 this.isWantClientAuth = isWantClientAuth;
315 }
316
317 /**
318 * Returns true if the socket will request client authentication.
319 * When a socket does not have a ssl socket, This return False.
320 * @return true - If the server mode socket should request
321 * that the client authenticate itself.
322 */
323 public boolean getWantClientAuth() {
324 if (_socket_ instanceof SSLSocket)
325 return ((SSLSocket)_socket_).getWantClientAuth();
326 return false;
327 }
328
329 /**
330 * Configures the socket to use client (or server) mode in its first
331 * handshake.
332 * @param isClientMode The use client mode flag.
333 */
334 public void setUseClientMode(boolean isClientMode) {
335 this.isClientMode = isClientMode;
336 }
337
338 /**
339 * Returns true if the socket is set to use client mode
340 * in its first handshake.
341 * When a socket does not have a ssl socket, This return False.
342 * @return true - If the socket should start its first handshake
343 * in "client" mode.
344 */
345 public boolean getUseClientMode() {
346 if (_socket_ instanceof SSLSocket)
347 return ((SSLSocket)_socket_).getUseClientMode();
348 return false;
349 }
350
351 /**
352 * Controls which particular cipher suites are enabled for use on this
353 * connection. I perform setting before a server negotiation.
354 * @param cipherSuites The cipher suites.
355 */
356 public void setEnabledCipherSuites(String[] cipherSuites) {
357 suites = new String[cipherSuites.length];
358 System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length);
359 }
360
361 /**
362 * Returns the names of the cipher suites which could be enabled
363 * for use on this connection.
364 * When a socket does not have a ssl socket, This return null.
365 * @return An array of cipher suite names.
366 */
367 public String[] getEnabledCipherSuites() {
368 if (_socket_ instanceof SSLSocket)
369 return ((SSLSocket)_socket_).getEnabledCipherSuites();
370 return null;
371 }
372
373 /**
374 * Controls which particular protocol versions are enabled for use on this
375 * connection. I perform setting before a server negotiation.
376 * @param protocolVersions The protocol versions.
377 */
378 public void setEnabledProtocols(String[] protocolVersions) {
379 protocols = new String[protocolVersions.length];
380 System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length);
381 }
382
383 /**
384 * Returns the names of the protocol versions which are currently
385 * enabled for use on this connection.
386 * When a socket does not have a ssl socket, This return null.
387 * @return An array of protocols.
388 */
389 public String[] getEnabledProtocols() {
390 if (_socket_ instanceof SSLSocket)
391 return ((SSLSocket)_socket_).getEnabledProtocols();
392 return null;
393 }
394
395 /**
396 * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer.
397 * @param pbsz Protection Buffer Size.
398 * @throws SSLException If it server reply code not equal "200".
399 * @throws IOException If an I/O error occurs while either sending
400 * the command.
401 */
402 public void execPBSZ(long pbsz) throws SSLException, IOException {
403 if (pbsz < 0 || 4294967295L < pbsz)
404 throw new IllegalArgumentException();
405 if (FTPReply.COMMAND_OK != sendCommand(
406 FTPSCommand._commands[FTPSCommand.PBSZ],String.valueOf(pbsz)))
407 throw new SSLException(getReplyString());
408 }
409
410 /**
411 * PROT command.</br>
412 * C - Clear</br>
413 * S - Safe(SSL protocol only)</br>
414 * E - Confidential(SSL protocol only)</br>
415 * P - Private
416 * @param prot Data Channel Protection Level.
417 * @throws SSLException If it server reply code not equal "200".
418 * @throws IOException If an I/O error occurs while either sending
419 * the command.
420 */
421 public void execPROT(String prot) throws SSLException, IOException {
422 if (prot == null) prot = DEFAULT_PROT;
423 if (!checkPROTValue(prot)) throw new IllegalArgumentException();
424 if (FTPReply.COMMAND_OK != sendCommand(
425 FTPSCommand._commands[FTPSCommand.PROT], prot))
426 throw new SSLException(getReplyString());
427 if (DEFAULT_PROT.equals(prot)) {
428 setSocketFactory(null);
429 setServerSocketFactory(null);
430 } else {
431 setSocketFactory(new FTPSSocketFactory(context));
432
433 initSslContext();
434
435 SSLServerSocketFactory ssf = context.getServerSocketFactory();
436
437 setServerSocketFactory(ssf);
438 }
439 }
440
441 /**
442 * I check the value that I can set in PROT Command value.
443 * @param prot Data Channel Protection Level.
444 * @return True - A set point is right / False - A set point is not right
445 */
446 private boolean checkPROTValue(String prot) {
447 for (int p = 0; p < PROT_COMMAND_VALUE.length; p++) {
448 if (PROT_COMMAND_VALUE[p].equals(prot)) return true;
449 }
450 return false;
451 }
452
453 /**
454 * I carry out an ftp command.
455 * When a CCC command was carried out, I steep socket and SocketFactory
456 * in a state of not ssl.
457 * @parm command ftp command.
458 * @return server reply.
459 * @throws IOException If an I/O error occurs while either sending
460 * the command.
461 * @see org.apache.commons.net.ftp.FTP#sendCommand(java.lang.String)
462 */
463 @Override
464 public int sendCommand(String command, String args) throws IOException {
465 int repCode = super.sendCommand(command, args);
466 if (FTPSCommand._commands[FTPSCommand.CCC].equals(command)) {
467 if (FTPReply.COMMAND_OK == repCode) {
468 // TODO Check this - is this necessary at all?
469 _socket_ = planeSocket;
470 setSocketFactory(null);
471 } else {
472 throw new SSLException(getReplyString());
473 }
474 }
475 return repCode;
476 }
477
478 /**
479 * Returns a socket of the data connection.
480 * Wrapped as an {@link SSLSocket}, which carries out handshake processing.
481 * @pram command The text representation of the FTP command to send.
482 * @param arg The arguments to the FTP command.
483 * If this parameter is set to null, then the command is sent with
484 * no argument.
485 * @return A Socket corresponding to the established data connection.
486 * Null is returned if an FTP protocol error is reported at any point
487 * during the establishment and initialization of the connection.
488 * @throws IOException If there is any problem with the connection.
489 * @see org.apache.commons.net.ftp.FTPClient#_openDataConnection_(java.lang.String, int)
490 */
491 @Override
492 protected Socket _openDataConnection_(int command, String arg)
493 throws IOException {
494 Socket socket = super._openDataConnection_(command, arg);
495 if (socket != null && socket instanceof SSLSocket) {
496 SSLSocket sslSocket = (SSLSocket)socket;
497 sslSocket.setUseClientMode(isClientMode);
498 sslSocket.setEnableSessionCreation(isCreation);
499 // server mode
500 if (!isClientMode) {
501 sslSocket.setNeedClientAuth(isNeedClientAuth);
502 sslSocket.setWantClientAuth(isWantClientAuth);
503 }
504 if (suites != null)
505 sslSocket.setEnabledCipherSuites(suites);
506 if (protocols != null)
507 sslSocket.setEnabledProtocols(protocols);
508 sslSocket.startHandshake();
509 }
510 return socket;
511 }
512
513 /**
514 * Get the currently configured {@link TrustManager}.
515 *
516 * @return A TrustManager instance.
517 */
518 public TrustManager getTrustManager() {
519 return trustManager;
520 }
521
522 /**
523 * Override the default {@link TrustManager} to use.
524 *
525 * @param trustManager The TrustManager implementation to set.
526 */
527 public void setTrustManager(TrustManager trustManager) {
528 this.trustManager = trustManager;
529 }
530
531
532
533 }