196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project/*
296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project
396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * you may not use this file except in compliance with the License.
696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * You may obtain a copy of the License at
796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
1096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
1196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
1296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * See the License for the specific language governing permissions and
1496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * limitations under the License.
1596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */
1696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
1796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpackage com.android.email.mail.transport;
1896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
19b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blankimport android.content.Context;
20b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank
2151c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdonimport com.android.email.DebugUtils;
2231d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.Logging;
232193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.CertificateValidationException;
242193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.MessagingException;
257d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport com.android.emailcommon.provider.HostAuth;
263a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blankimport com.android.emailcommon.utility.SSLUtils;
2793a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdonimport com.android.mail.analytics.Analytics;
28560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils;
2996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
3096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.BufferedInputStream;
3196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.BufferedOutputStream;
3296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.IOException;
3396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStream;
3496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.OutputStream;
35f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onukiimport java.net.InetAddress;
3696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.InetSocketAddress;
3796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.Socket;
3896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.SocketAddress;
3996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.SocketException;
4096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
41fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.HostnameVerifier;
42fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.HttpsURLConnection;
4396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport javax.net.ssl.SSLException;
44fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLPeerUnverifiedException;
45fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLSession;
46fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLSocket;
4796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
48b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blankpublic class MailTransport {
498d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
5096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    // TODO protected eventually
5196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /*protected*/ public static final int SOCKET_CONNECT_TIMEOUT = 10000;
5296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /*protected*/ public static final int SOCKET_READ_TIMEOUT = 60000;
5396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
54fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    private static final HostnameVerifier HOSTNAME_VERIFIER =
55fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            HttpsURLConnection.getDefaultHostnameVerifier();
56fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
577d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank    private final String mDebugLabel;
587d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank    private final Context mContext;
5917d3a29c9d8f7a27c463239f190bdcc4e0804527Jerry Xie    protected final HostAuth mHostAuth;
6096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
6196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private Socket mSocket;
6296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private InputStream mIn;
6396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private OutputStream mOut;
6496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
657d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank    public MailTransport(Context context, String debugLabel, HostAuth hostAuth) {
6696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        super();
677d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        mContext = context;
6896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mDebugLabel = debugLabel;
697d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        mHostAuth = hostAuth;
7096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
718d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
727d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank   /**
73ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy     * Returns a new transport, using the current transport as a model. The new transport is
74ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy     * configured identically (as if {@link #setSecurity(int, boolean)}, {@link #setPort(int)}
75ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy     * and {@link #setHost(String)} were invoked), but not opened or connected in any way.
7696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
77ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy    @Override
78b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank    public MailTransport clone() {
797d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return new MailTransport(mContext, mDebugLabel, mHostAuth);
80a50fc99b0c433f0cde31ba1c7ab87fb9ea86345dTodd Kennedy    }
81a50fc99b0c433f0cde31ba1c7ab87fb9ea86345dTodd Kennedy
8296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public String getHost() {
837d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return mHostAuth.mAddress;
8496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
8596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
8696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public int getPort() {
877d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return mHostAuth.mPort;
8896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
8996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
9096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean canTrySslSecurity() {
917d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return (mHostAuth.mFlags & HostAuth.FLAG_SSL) != 0;
9296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
938d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
9496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean canTryTlsSecurity() {
957d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return (mHostAuth.mFlags & HostAuth.FLAG_TLS) != 0;
9696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
978d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
98e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler    public boolean canTrustAllCertificates() {
997d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return (mHostAuth.mFlags & HostAuth.FLAG_TRUST_ALL) != 0;
100e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler    }
101e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler
10296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
10396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Attempts to open a connection using the Uri supplied for connection parameters.  Will attempt
10496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * an SSL connection if indicated.
10596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
10696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void open() throws MessagingException, CertificateValidationException {
10751c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (DebugUtils.DEBUG) {
108560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "*** " + mDebugLabel + " open " +
10996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    getHost() + ":" + String.valueOf(getPort()));
11096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
11196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
11296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
11396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            SocketAddress socketAddress = new InetSocketAddress(getHost(), getPort());
11496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (canTrySslSecurity()) {
1157d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank                mSocket = SSLUtils.getSSLSocketFactory(
116601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon                        mContext, mHostAuth, null, canTrustAllCertificates()).createSocket();
11796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
11896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                mSocket = new Socket();
11996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
12096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
121fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            // After the socket connects to an SSL server, confirm that the hostname is as expected
122fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            if (canTrySslSecurity() && !canTrustAllCertificates()) {
123fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler                verifyHostname(mSocket, getHost());
124fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            }
125f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon            Analytics.getInstance().sendEvent("socket_certificates",
126f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon                    "open", Boolean.toString(canTrustAllCertificates()), 0);
12793a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon            if (mSocket instanceof SSLSocket) {
12893a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon                final SSLSocket sslSocket = (SSLSocket) mSocket;
12993a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon                if (sslSocket.getSession() != null) {
130f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon                    Analytics.getInstance().sendEvent("cipher_suite",
131f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon                            sslSocket.getSession().getProtocol(),
13293a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon                            sslSocket.getSession().getCipherSuite(), 0);
13393a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon                }
13493a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon            }
13596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
13696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
1379a2686afa2e02c62a8e5f9c42c82bd2da70b96afYu Ping Hu            mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
13896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (SSLException e) {
13951c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
140560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, e.toString());
14196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
14296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new CertificateValidationException(e.getMessage(), e);
14396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (IOException ioe) {
14451c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
145560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, ioe.toString());
14696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
14796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
148532bcf4a522f91258c74604b74096c9b1f7c0510Paul Westbrook        } catch (IllegalArgumentException iae) {
14951c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
150560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, iae.toString());
151532bcf4a522f91258c74604b74096c9b1f7c0510Paul Westbrook            }
152532bcf4a522f91258c74604b74096c9b1f7c0510Paul Westbrook            throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION, iae.toString());
15396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
15496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
15596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
15696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
15796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Attempts to reopen a TLS connection using the Uri supplied for connection parameters.
15896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     *
159fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * NOTE: No explicit hostname verification is required here, because it's handled automatically
160fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * by the call to createSocket().
161fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
16296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * TODO should we explicitly close the old socket?  This seems funky to abandon it.
16396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
16496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void reopenTls() throws MessagingException {
16596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
166601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon            mSocket = SSLUtils.getSSLSocketFactory(mContext, mHostAuth, null,
167601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon                    canTrustAllCertificates())
168c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki                    .createSocket(mSocket, getHost(), getPort(), true);
16996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
17096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
17196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
17296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
173f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon            Analytics.getInstance().sendEvent("socket_certificates",
174f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon                    "reopenTls", Boolean.toString(canTrustAllCertificates()), 0);
17593a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon            final SSLSocket sslSocket = (SSLSocket) mSocket;
17693a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon            if (sslSocket.getSession() != null) {
177f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon                Analytics.getInstance().sendEvent("cipher_suite",
178f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon                        sslSocket.getSession().getProtocol(),
17993a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon                        sslSocket.getSession().getCipherSuite(), 0);
18093a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon            }
181e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler        } catch (SSLException e) {
18251c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
183560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, e.toString());
184e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler            }
185e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler            throw new CertificateValidationException(e.getMessage(), e);
18696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (IOException ioe) {
18751c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
188560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, ioe.toString());
18996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
19096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
19196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
19296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
193fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
194fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    /**
195fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this
196fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * service but is not in the public API.
197fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
198fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * Verify the hostname of the certificate used by the other end of a
199fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * connected socket.  You MUST call this if you did not supply a hostname
200fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * to SSLCertificateSocketFactory.createSocket().  It is harmless to call this method
201fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * redundantly if the hostname has already been verified.
202fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
203fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * <p>Wildcard certificates are allowed to verify any matching hostname,
204fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * so "foo.bar.example.com" is verified if the peer has a certificate
205fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * for "*.example.com".
206fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
207fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @param socket An SSL socket which has been connected to a server
208fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @param hostname The expected hostname of the remote server
209fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @throws IOException if something goes wrong handshaking with the server
210fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @throws SSLPeerUnverifiedException if the server cannot prove its identity
211fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler      */
2121b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy    private static void verifyHostname(Socket socket, String hostname) throws IOException {
213fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // The code at the start of OpenSSLSocketImpl.startHandshake()
214fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // ensures that the call is idempotent, so we can safely call it.
215fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        SSLSocket ssl = (SSLSocket) socket;
216fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        ssl.startHandshake();
217fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
218fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        SSLSession session = ssl.getSession();
219fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        if (session == null) {
220fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            throw new SSLException("Cannot verify SSL socket without session");
221fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        }
222fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // TODO: Instead of reporting the name of the server we think we're connecting to,
223fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // we should be reporting the bad name in the certificate.  Unfortunately this is buried
224fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // in the verifier code and is not available in the verifier API, and extracting the
225fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // CN & alts is beyond the scope of this patch.
226fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
227fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            throw new SSLPeerUnverifiedException(
228fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler                    "Certificate hostname not useable for server: " + hostname);
229fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        }
230fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    }
231fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
23296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
2330c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler     * Get the socket timeout.
2340c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler     * @return the read timeout value in milliseconds
2350c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler     * @throws SocketException
2360c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler     */
2370c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler    public int getSoTimeout() throws SocketException {
2380c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler        return mSocket.getSoTimeout();
2390c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler    }
2400c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler
2410c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler    /**
24296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Set the socket timeout.
24396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param timeoutMilliseconds the read timeout value if greater than {@code 0}, or
24496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     *            {@code 0} for an infinite timeout.
24596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
24696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void setSoTimeout(int timeoutMilliseconds) throws SocketException {
24796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mSocket.setSoTimeout(timeoutMilliseconds);
24896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
24996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
25096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean isOpen() {
2518d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy        return (mIn != null && mOut != null &&
25296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                mSocket != null && mSocket.isConnected() && !mSocket.isClosed());
25396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
25496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
25596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
25696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Close the connection.  MUST NOT return any exceptions - must be "best effort" and safe.
25796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
25896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void close() {
25996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
26096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn.close();
26196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
26296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
26396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
26496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
26596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut.close();
26696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
26796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
26896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
26996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
27096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.close();
27196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
27296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
27396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
27496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mIn = null;
27596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mOut = null;
27696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mSocket = null;
27796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
27896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
27996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public InputStream getInputStream() {
28096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return mIn;
28196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
28296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
28396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public OutputStream getOutputStream() {
28496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return mOut;
28596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
2868d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
28796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
28896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Writes a single line to the server using \r\n termination.
28996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
29096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void writeLine(String s, String sensitiveReplacement) throws IOException {
29151c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (DebugUtils.DEBUG) {
292bfac9f2e8a13f6c719608a6948203bbef921c99fMakoto Onuki            if (sensitiveReplacement != null && !Logging.DEBUG_SENSITIVE) {
293560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, ">>> " + sensitiveReplacement);
29496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
295560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, ">>> " + s);
29696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
29796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
2988d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
29996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        OutputStream out = getOutputStream();
30096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write(s.getBytes());
30196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write('\r');
30296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write('\n');
30396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.flush();
30496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
3058d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
30696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
30796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Reads a single line from the server, using either \r\n or \n as the delimiter.  The
30896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * delimiter char(s) are not included in the result.
30996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
310b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank    public String readLine(boolean loggable) throws IOException {
31196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
31296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        InputStream in = getInputStream();
31396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        int d;
31496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        while ((d = in.read()) != -1) {
31596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (((char)d) == '\r') {
31696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                continue;
31796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else if (((char)d) == '\n') {
31896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                break;
31996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
32096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                sb.append((char)d);
32196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
32296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
32351c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (d == -1 && DebugUtils.DEBUG) {
324560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "End of stream reached while trying to read line.");
32596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
32696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        String ret = sb.toString();
32751c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (loggable && DebugUtils.DEBUG) {
328560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "<<< " + ret);
32996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
33096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return ret;
33196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
33296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
333f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki    public InetAddress getLocalAddress() {
334f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        if (isOpen()) {
335f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki            return mSocket.getLocalAddress();
336f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        } else {
337f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki            return null;
338f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        }
339f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki    }
34096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project}
341