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
018package org.apache.commons.net.ftp;
019
020import java.io.BufferedReader;
021import java.io.BufferedWriter;
022import java.io.IOException;
023import java.io.InputStreamReader;
024import java.io.OutputStreamWriter;
025import java.net.Socket;
026import javax.net.ssl.KeyManager;
027import javax.net.ssl.SSLContext;
028import javax.net.ssl.SSLException;
029import javax.net.ssl.SSLSocket;
030import javax.net.ssl.SSLSocketFactory;
031import javax.net.ssl.TrustManager;
032
033import org.apache.commons.net.util.Base64;
034import org.apache.commons.net.util.SSLContextUtils;
035import org.apache.commons.net.util.TrustManagerUtils;
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 1230368 2012-01-12 02:00:49Z sebb $
042 * @since 2.0
043 */
044public class FTPSClient extends FTPClient {
045
046// From http://www.iana.org/assignments/port-numbers
047
048//    ftps-data   989/tcp    ftp protocol, data, over TLS/SSL
049//    ftps-data   989/udp    ftp protocol, data, over TLS/SSL
050//    ftps        990/tcp    ftp protocol, control, over TLS/SSL
051//    ftps        990/udp    ftp protocol, control, over TLS/SSL
052
053    public static final int DEFAULT_FTPS_DATA_PORT = 989;
054    public static final int DEFAULT_FTPS_PORT = 990;
055
056    /** The value that I can set in PROT command  (C = Clear, P = Protected) */
057    private static final String[] PROT_COMMAND_VALUE = {"C","E","S","P"};
058    /** Default PROT Command */
059    private static final String DEFAULT_PROT = "C";
060    /** Default secure socket protocol name, i.e. TLS */
061    private static final String DEFAULT_PROTOCOL = "TLS";
062
063    /** The AUTH (Authentication/Security Mechanism) command. */
064    private static final String CMD_AUTH = "AUTH";
065    /**  The ADAT (Authentication/Security Data) command. */
066    private static final String CMD_ADAT = "ADAT";
067    /**  The PROT (Data Channel Protection Level) command. */
068    private static final String CMD_PROT = "PROT";
069    /**  The PBSZ (Protection Buffer Size) command. */
070    private static final String CMD_PBSZ = "PBSZ";
071    /**  The MIC (Integrity Protected Command) command. */
072    private static final String CMD_MIC = "MIC";
073    /**  The CONF (Confidentiality Protected Command) command. */
074    private static final String CMD_CONF = "CONF";
075    /**  The ENC (Privacy Protected Command) command. */
076    private static final String CMD_ENC = "ENC";
077    /**  The CCC (Clear Command Channel) command. */
078    private static final String CMD_CCC = "CCC";
079
080    /** The security mode. (True - Implicit Mode / False - Explicit Mode) */
081    private final boolean isImplicit;
082    /** The secure socket protocol to be used, e.g. SSL/TLS. */
083    private final String protocol;
084    /** The AUTH Command value */
085    private String auth = DEFAULT_PROTOCOL;
086    /** The context object. */
087    private SSLContext context;
088    /** The socket object. */
089    private Socket plainSocket;
090    /** Controls whether a new SSL session may be established by this socket. Default true. */
091    private boolean isCreation = true;
092    /** The use client mode flag. */
093    private boolean isClientMode = true;
094    /** The need client auth flag. */
095    private boolean isNeedClientAuth = false;
096    /** The want client auth flag. */
097    private boolean isWantClientAuth = false;
098    /** The cipher suites */
099    private String[] suites = null;
100    /** The protocol versions */
101    private String[] protocols = null;
102
103    /** The FTPS {@link TrustManager} implementation, default validate only: {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}. */
104    private TrustManager trustManager = TrustManagerUtils.getValidateServerCertificateTrustManager();
105
106    /** The {@link KeyManager}, default null (i.e. use system default). */
107    private KeyManager keyManager = null;
108
109    /**
110     * Constructor for FTPSClient.
111     * Sets protocol to {@link #DEFAULT_PROTOCOL} - i.e. TLS - and security mode to explicit (isImplicit = false)
112     */
113    public FTPSClient() {
114        this(DEFAULT_PROTOCOL, false);
115    }
116
117    /**
118     * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS
119     * @param isImplicit The security mode (Implicit/Explicit).
120     */
121    public FTPSClient(boolean isImplicit) {
122        this(DEFAULT_PROTOCOL, isImplicit);
123    }
124
125    /**
126     * Constructor for FTPSClient, using explict mode
127     * @param protocol the protocol to use
128     */
129    public FTPSClient(String protocol) {
130        this(protocol, false);
131    }
132
133    /**
134     * Constructor for FTPSClient allowing specification of protocol
135     * and security mode. If isImplicit is true, the port is set to
136     * {@link #DEFAULT_FTPS_PORT} i.e. 990.
137     *
138     * @param protocol the protocol
139     * @param isImplicit The security mode(Implicit/Explicit).
140     */
141    public FTPSClient(String protocol, boolean isImplicit) {
142        super();
143        this.protocol = protocol;
144        this.isImplicit = isImplicit;
145        if (isImplicit) {
146            setDefaultPort(DEFAULT_FTPS_PORT);
147        }
148    }
149
150    /**
151     * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS
152     * @param isImplicit The security mode(Implicit/Explicit).
153     * @param context A pre-configured SSL Context
154     */
155    public FTPSClient(boolean isImplicit, SSLContext context) {
156        this(DEFAULT_PROTOCOL, isImplicit);
157        this.context = context;
158    }
159
160    /**
161     * Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS
162     * and isImplicit {@code false}
163     *
164     * @param context A pre-configured SSL Context
165     */
166    public FTPSClient(SSLContext context) {
167        this(false, context);
168    }
169
170
171    /**
172     * Set AUTH command use value.
173     * This processing is done before connected processing.
174     * @param auth AUTH command use value.
175     */
176    public void setAuthValue(String auth) {
177        this.auth = auth;
178    }
179
180    /**
181     * Return AUTH command use value.
182     * @return AUTH command use value.
183     */
184    public String getAuthValue() {
185        return this.auth;
186    }
187
188
189    /**
190     * Because there are so many connect() methods,
191     * the _connectAction_() method is provided as a means of performing
192     * some action immediately after establishing a connection,
193     * rather than reimplementing all of the connect() methods.
194     * @throws IOException If it throw by _connectAction_.
195     * @see org.apache.commons.net.SocketClient#_connectAction_()
196     */
197    @Override
198    protected void _connectAction_() throws IOException {
199        // Implicit mode.
200        if (isImplicit) {
201            sslNegotiation();
202        }
203        super._connectAction_();
204        // Explicit mode.
205        if (!isImplicit) {
206            execAUTH();
207            sslNegotiation();
208        }
209    }
210
211    /**
212     * AUTH command.
213     * @throws SSLException If it server reply code not equal "234" and "334".
214     * @throws IOException If an I/O error occurs while either sending
215     * the command.
216     */
217    protected void execAUTH() throws SSLException, IOException {
218        int replyCode = sendCommand(CMD_AUTH, auth);
219        if (FTPReply.SECURITY_MECHANISM_IS_OK == replyCode) {
220            // replyCode = 334
221            // I carry out an ADAT command.
222        } else if (FTPReply.SECURITY_DATA_EXCHANGE_COMPLETE != replyCode) {
223            throw new SSLException(getReplyString());
224        }
225    }
226
227    /**
228     * Performs a lazy init of the SSL context
229     * @throws IOException
230     */
231    private void initSslContext() throws IOException {
232        if (context == null) {
233            context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager());
234        }
235    }
236
237    /**
238     * SSL/TLS negotiation. Acquires an SSL socket of a control
239     * connection and carries out handshake processing.
240     * @throws IOException If server negotiation fails
241     */
242    protected void sslNegotiation() throws IOException {
243        plainSocket = _socket_;
244        initSslContext();
245
246        SSLSocketFactory ssf = context.getSocketFactory();
247        String ip = _socket_.getInetAddress().getHostAddress();
248        int port = _socket_.getPort();
249        SSLSocket socket =
250            (SSLSocket) ssf.createSocket(_socket_, ip, port, false);
251        socket.setEnableSessionCreation(isCreation);
252        socket.setUseClientMode(isClientMode);
253        // server mode
254        if (!isClientMode) {
255            socket.setNeedClientAuth(isNeedClientAuth);
256            socket.setWantClientAuth(isWantClientAuth);
257        }
258
259        if (protocols != null) {
260            socket.setEnabledProtocols(protocols);
261        }
262        if (suites != null) {
263            socket.setEnabledCipherSuites(suites);
264        }
265        socket.startHandshake();
266
267        _socket_ = socket;
268        _controlInput_ = new BufferedReader(new InputStreamReader(
269                socket .getInputStream(), getControlEncoding()));
270        _controlOutput_ = new BufferedWriter(new OutputStreamWriter(
271                socket.getOutputStream(), getControlEncoding()));
272    }
273
274    /**
275     * Get the {@link KeyManager} instance.
276     * @return The {@link KeyManager} instance
277     */
278    private KeyManager getKeyManager() {
279        return keyManager;
280    }
281
282    /**
283    * Set a {@link KeyManager} to use
284    *
285    * @param keyManager The KeyManager implementation to set.
286    * @see org.apache.commons.net.util.KeyManagerUtils
287    */
288    public void setKeyManager(KeyManager keyManager) {
289        this.keyManager = keyManager;
290    }
291
292    /**
293     * Controls whether a new SSL session may be established by this socket.
294     * @param isCreation The established socket flag.
295     */
296    public void setEnabledSessionCreation(boolean isCreation) {
297        this.isCreation = isCreation;
298    }
299
300    /**
301     * Returns true if new SSL sessions may be established by this socket.
302     * When the underlying {@link Socket} instance is not SSL-enabled (i.e. an
303     * instance of {@link SSLSocket} with {@link SSLSocket}{@link #getEnableSessionCreation()}) enabled,
304     * this returns False.
305     * @return true - Indicates that sessions may be created;
306     * this is the default.
307     * false - indicates that an existing session must be resumed.
308     */
309    public boolean getEnableSessionCreation() {
310        if (_socket_ instanceof SSLSocket) {
311            return ((SSLSocket)_socket_).getEnableSessionCreation();
312        }
313        return false;
314    }
315
316    /**
317     * Configures the socket to require client authentication.
318     * @param isNeedClientAuth The need client auth flag.
319     */
320    public void setNeedClientAuth(boolean isNeedClientAuth) {
321        this.isNeedClientAuth = isNeedClientAuth;
322    }
323
324    /**
325     * Returns true if the socket will require client authentication.
326     * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
327     * @return true - If the server mode socket should request
328     * that the client authenticate itself.
329     */
330    public boolean getNeedClientAuth() {
331        if (_socket_ instanceof SSLSocket) {
332            return ((SSLSocket)_socket_).getNeedClientAuth();
333        }
334        return false;
335    }
336
337    /**
338     * Configures the socket to request client authentication,
339     * but only if such a request is appropriate to the cipher
340     * suite negotiated.
341     * @param isWantClientAuth The want client auth flag.
342     */
343    public void setWantClientAuth(boolean isWantClientAuth) {
344        this.isWantClientAuth = isWantClientAuth;
345    }
346
347    /**
348     * Returns true if the socket will request client authentication.
349     * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
350     * @return true - If the server mode socket should request
351     * that the client authenticate itself.
352     */
353    public boolean getWantClientAuth() {
354        if (_socket_ instanceof SSLSocket) {
355            return ((SSLSocket)_socket_).getWantClientAuth();
356        }
357        return false;
358    }
359
360    /**
361     * Configures the socket to use client (or server) mode in its first
362     * handshake.
363     * @param isClientMode The use client mode flag.
364     */
365    public void setUseClientMode(boolean isClientMode) {
366        this.isClientMode = isClientMode;
367    }
368
369    /**
370     * Returns true if the socket is set to use client mode
371     * in its first handshake.
372     * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns false.
373     * @return true - If the socket should start its first handshake
374     * in "client" mode.
375     */
376    public boolean getUseClientMode() {
377        if (_socket_ instanceof SSLSocket) {
378            return ((SSLSocket)_socket_).getUseClientMode();
379        }
380        return false;
381    }
382
383    /**
384     * Controls which particular cipher suites are enabled for use on this
385     * connection. Called before server negotiation.
386     * @param cipherSuites The cipher suites.
387     */
388    public void setEnabledCipherSuites(String[] cipherSuites) {
389        suites = new String[cipherSuites.length];
390        System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length);
391    }
392
393    /**
394     * Returns the names of the cipher suites which could be enabled
395     * for use on this connection.
396     * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null.
397     * @return An array of cipher suite names, or <code>null</code>
398     */
399    public String[] getEnabledCipherSuites() {
400        if (_socket_ instanceof SSLSocket) {
401            return ((SSLSocket)_socket_).getEnabledCipherSuites();
402        }
403        return null;
404    }
405
406    /**
407     * Controls which particular protocol versions are enabled for use on this
408     * connection. I perform setting before a server negotiation.
409     * @param protocolVersions The protocol versions.
410     */
411    public void setEnabledProtocols(String[] protocolVersions) {
412        protocols = new String[protocolVersions.length];
413        System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length);
414    }
415
416    /**
417     * Returns the names of the protocol versions which are currently
418     * enabled for use on this connection.
419     * When the underlying {@link Socket} is not an {@link SSLSocket} instance, returns null.
420     * @return An array of protocols, or <code>null</code>
421     */
422    public String[] getEnabledProtocols() {
423        if (_socket_ instanceof SSLSocket) {
424            return ((SSLSocket)_socket_).getEnabledProtocols();
425        }
426        return null;
427    }
428
429    /**
430     * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer.
431     * @param pbsz Protection Buffer Size.
432     * @throws SSLException If the server reply code does not equal "200".
433     * @throws IOException If an I/O error occurs while sending
434     * the command.
435     * @see #parsePBSZ(long)
436     */
437    public void execPBSZ(long pbsz) throws SSLException, IOException {
438        if (pbsz < 0 || 4294967295L < pbsz) { // 32-bit unsigned number
439            throw new IllegalArgumentException();
440        }
441        int status = sendCommand(CMD_PBSZ, String.valueOf(pbsz));
442        if (FTPReply.COMMAND_OK != status) {
443            throw new SSLException(getReplyString());
444        }
445    }
446
447    /**
448     * PBSZ command. pbsz value: 0 to (2^32)-1 decimal integer.
449     * Issues the command and parses the response to return the negotiated value.
450     * 
451     * @param pbsz Protection Buffer Size.
452     * @throws SSLException If the server reply code does not equal "200".
453     * @throws IOException If an I/O error occurs while sending
454     * the command.
455     * @return the negotiated value.
456     * @see #execPBSZ(long)
457     * @since 3.0
458     */
459    public long parsePBSZ(long pbsz) throws SSLException, IOException {
460        execPBSZ(pbsz);
461        long minvalue = pbsz;
462        String remainder = extractPrefixedData("PBSZ=", getReplyString());
463        if (remainder != null) {
464            long replysz = Long.parseLong(remainder);
465            if (replysz < minvalue) {
466                minvalue = replysz;
467            }
468        }
469        return minvalue;
470    }
471    
472    /**
473     * PROT command.<br/>
474     * C - Clear<br/>
475     * S - Safe(SSL protocol only)<br/>
476     * E - Confidential(SSL protocol only)<br/>
477     * P - Private
478     * <p>
479     * <b>N.B.</b> the method calls
480     *  {@link #setSocketFactory(javax.net.SocketFactory)} and
481     *  {@link #setServerSocketFactory(javax.net.ServerSocketFactory)}
482     *
483     * @param prot Data Channel Protection Level.
484     * @throws SSLException If the server reply code does not equal "200".
485     * @throws IOException If an I/O error occurs while sending
486     * the command.
487     */
488    public void execPROT(String prot) throws SSLException, IOException {
489        if (prot == null) {
490            prot = DEFAULT_PROT;
491        }
492        if (!checkPROTValue(prot)) {
493            throw new IllegalArgumentException();
494        }
495        if (FTPReply.COMMAND_OK != sendCommand(CMD_PROT, prot)) {
496            throw new SSLException(getReplyString());
497        }
498        if (DEFAULT_PROT.equals(prot)) {
499            setSocketFactory(null);
500            setServerSocketFactory(null);
501        } else {
502            setSocketFactory(new FTPSSocketFactory(context));
503            setServerSocketFactory(new FTPSServerSocketFactory(context));
504            initSslContext();
505        }
506    }
507
508    /**
509     * Check the value that can be set in PROT Command value.
510     * @param prot Data Channel Protection Level.
511     * @return True - A set point is right / False - A set point is not right
512     */
513    private boolean checkPROTValue(String prot) {
514        for (int p = 0; p < PROT_COMMAND_VALUE.length; p++) {
515            if (PROT_COMMAND_VALUE[p].equals(prot)) {
516                return true;
517            }
518        }
519        return false;
520    }
521
522    /**
523     * Send an FTP command.
524     * A successful CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} 
525     * instance to be assigned to a plain {@link Socket}
526     * @param command The FTP command.
527     * @return server reply.
528     * @throws IOException If an I/O error occurs while sending the command.
529     * @throws SSLException if a CCC command fails
530     * @see org.apache.commons.net.ftp.FTP#sendCommand(java.lang.String)
531     */
532    // Would like to remove this method, but that will break any existing clients that are using CCC
533    @Override
534    public int sendCommand(String command, String args) throws IOException {
535        int repCode = super.sendCommand(command, args);
536        /* If CCC is issued, restore socket i/o streams to unsecured versions */
537        if (CMD_CCC.equals(command)) {
538            if (FTPReply.COMMAND_OK == repCode) {
539                _socket_.close();
540                _socket_ = plainSocket;
541                _controlInput_ = new BufferedReader(
542                    new InputStreamReader(
543                        _socket_ .getInputStream(), getControlEncoding()));
544                _controlOutput_ = new BufferedWriter(
545                    new OutputStreamWriter(
546                        _socket_.getOutputStream(), getControlEncoding()));
547            } else {
548                throw new SSLException(getReplyString());
549            }
550        }
551        return repCode;
552    }
553
554    /**
555     * Returns a socket of the data connection.
556     * Wrapped as an {@link SSLSocket}, which carries out handshake processing.
557     * @param command The textual representation of the FTP command to send.
558     * @param arg The arguments to the FTP command.
559     * If this parameter is set to null, then the command is sent with
560     * no arguments.
561     * @return corresponding to the established data connection.
562     * Null is returned if an FTP protocol error is reported at any point
563     * during the establishment and initialization of the connection.
564     * @throws IOException If there is any problem with the connection.
565     * @see FTPClient#_openDataConnection_(int, String)
566     */
567    @Override
568    protected Socket _openDataConnection_(int command, String arg)
569            throws IOException {
570        Socket socket = super._openDataConnection_(command, arg);
571        _prepareDataSocket_(socket);
572        if (socket instanceof SSLSocket) {
573            SSLSocket sslSocket = (SSLSocket)socket;
574
575            sslSocket.setUseClientMode(isClientMode);
576            sslSocket.setEnableSessionCreation(isCreation);
577
578            // server mode
579            if (!isClientMode) {
580                sslSocket.setNeedClientAuth(isNeedClientAuth);
581                sslSocket.setWantClientAuth(isWantClientAuth);
582            }
583            if (suites != null) {
584                sslSocket.setEnabledCipherSuites(suites);
585            }
586            if (protocols != null) {
587                sslSocket.setEnabledProtocols(protocols);
588            }
589            sslSocket.startHandshake();
590        }
591
592        return socket;
593    }
594
595    /**
596    * Performs any custom initialization for a newly created SSLSocket (before
597    * the SSL handshake happens).
598    * Called by {@link #_openDataConnection_(int, String)} immediately 
599    * after creating the socket.
600    * The default implementation is a no-op
601    * @throws IOException 
602    */
603    protected void _prepareDataSocket_(Socket socket)
604            throws IOException {
605    }
606
607    /**
608     * Get the currently configured {@link TrustManager}.
609     *
610     * @return A TrustManager instance.
611     */
612    public TrustManager getTrustManager() {
613        return trustManager;
614    }
615
616    /**
617     * Override the default {@link TrustManager} to use.
618     *
619     * @param trustManager The TrustManager implementation to set.
620    * @see org.apache.commons.net.util.TrustManagerUtils
621     */
622    public void setTrustManager(TrustManager trustManager) {
623        this.trustManager = trustManager;
624    }
625
626    /**
627     * Closes the connection to the FTP server and restores
628     * connection parameters to the default values.
629     * <p>
630     * Calls {@code setSocketFactory(null)} and {@code setServerSocketFactory(null)}
631     * to reset the factories that may have been changed during the session,
632     * e.g. by {@link #execPROT(String)}
633     * @exception IOException If an error occurs while disconnecting.
634     * @since 3.0
635     */
636    @Override
637    public void disconnect() throws IOException
638    {
639        super.disconnect();
640        setSocketFactory(null);
641        setServerSocketFactory(null);
642    }
643
644    /**
645     * Send the AUTH command with the specified mechanism.
646     * @param mechanism The mechanism name to send with the command.
647     * @return server reply.
648     * @throws IOException If an I/O error occurs while sending
649     * the command.
650     * @since 3.0
651     */
652    public int execAUTH(String mechanism) throws IOException
653    {
654        return sendCommand(CMD_AUTH, mechanism);
655    }
656
657    /**
658     * Send the ADAT command with the specified authentication data.
659     * @param data The data to send with the command.
660     * @return server reply.
661     * @throws IOException If an I/O error occurs while sending
662     * the command.
663     * @since 3.0
664     */
665    public int execADAT(byte[] data) throws IOException
666    {
667        if (data != null)
668        {
669            return sendCommand(CMD_ADAT, new String(Base64.encodeBase64(data)));
670        }
671        else
672        {
673            return sendCommand(CMD_ADAT);
674        }
675    }
676
677    /**
678     * Send the CCC command to the server.
679     * The CCC (Clear Command Channel) command causes the underlying {@link SSLSocket} instance  to be assigned
680     * to a plain {@link Socket} instances
681     * @return server reply.
682     * @throws IOException If an I/O error occurs while sending
683     * the command.
684     * @since 3.0
685     */
686    public int execCCC() throws IOException
687    {
688        int repCode = sendCommand(CMD_CCC);
689// This will be performed by sendCommand(String, String)
690//        if (FTPReply.isPositiveCompletion(repCode)) {
691//            _socket_.close();
692//            _socket_ = plainSocket;
693//            _controlInput_ = new BufferedReader(
694//                new InputStreamReader(
695//                    _socket_.getInputStream(), getControlEncoding()));
696//            _controlOutput_ = new BufferedWriter(
697//                new OutputStreamWriter(
698//                    _socket_.getOutputStream(), getControlEncoding()));
699//        }
700        return repCode;
701    }
702
703    /**
704     * Send the MIC command with the specified data.
705     * @param data The data to send with the command.
706     * @return server reply.
707     * @throws IOException If an I/O error occurs while sending
708     * the command.
709     * @since 3.0
710     */
711    public int execMIC(byte[] data) throws IOException
712    {
713        if (data != null)
714        {
715            return sendCommand(CMD_MIC, new String(Base64.encodeBase64(data)));
716        }
717        else
718        {
719            return sendCommand(CMD_MIC, ""); // perhaps "=" or just sendCommand(String)?
720        }
721    }
722
723    /**
724     * Send the CONF command with the specified data.
725     * @param data The data to send with the command.
726     * @return server reply.
727     * @throws IOException If an I/O error occurs while sending
728     * the command.
729     * @since 3.0
730     */
731    public int execCONF(byte[] data) throws IOException
732    {
733        if (data != null)
734        {
735            return sendCommand(CMD_CONF, new String(Base64.encodeBase64(data)));
736        }
737        else
738        {
739            return sendCommand(CMD_CONF, ""); // perhaps "=" or just sendCommand(String)?
740        }
741    }
742
743    /**
744     * Send the ENC command with the specified data.
745     * @param data The data to send with the command.
746     * @return server reply.
747     * @throws IOException If an I/O error occurs while sending
748     * the command.
749     * @since 3.0
750     */
751    public int execENC(byte[] data) throws IOException
752    {
753        if (data != null)
754        {
755            return sendCommand(CMD_ENC, new String(Base64.encodeBase64(data)));
756        }
757        else
758        {
759            return sendCommand(CMD_ENC, ""); // perhaps "=" or just sendCommand(String)?
760        }
761    }
762
763    /**
764     * Parses the given ADAT response line and base64-decodes the data.
765     * @param reply The ADAT reply to parse.
766     * @return the data in the reply, base64-decoded.
767     * @since 3.0
768     */
769    public byte[] parseADATReply(String reply)
770    {
771        if (reply == null) {
772            return null;
773        } else {
774            return Base64.decodeBase64(extractPrefixedData("ADAT=", reply));
775        }
776    }
777
778    /**
779     * Extract the data from a reply with a prefix, e.g. PBSZ=1234 => 1234
780     * @param prefix the prefix to find
781     * @param reply where to find the prefix
782     * @return the remainder of the string after the prefix, or null if the prefix was not present.
783     */
784    private String extractPrefixedData(String prefix, String reply) {
785        int idx = reply.indexOf(prefix);
786        if (idx == -1) { 
787            return null;
788        }
789        // N.B. Cannot use trim before substring as leading space would affect the offset.
790        return reply.substring(idx+prefix.length()).trim();
791    }
792
793    // DEPRECATED - for API compatibility only - DO NOT USE
794
795    /** @deprecated - not used - may be removed in a future release */
796    @Deprecated
797    public static String KEYSTORE_ALGORITHM;
798
799    /** @deprecated - not used - may be removed in a future release */
800    @Deprecated
801    public static String TRUSTSTORE_ALGORITHM;
802
803    /** @deprecated - not used - may be removed in a future release */
804    @Deprecated
805    public static String PROVIDER;
806
807    /** @deprecated - not used - may be removed in a future release */
808    @Deprecated
809    public static String STORE_TYPE;
810
811}
812/* kate: indent-width 4; replace-tabs on; */