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
19bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport com.android.email.Email;
20bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport com.android.email.mail.Transport;
2131d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.Logging;
222193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.CertificateValidationException;
232193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.MessagingException;
243a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blankimport com.android.emailcommon.utility.SSLUtils;
2596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
26bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport android.util.Log;
27bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
2896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.BufferedInputStream;
2996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.BufferedOutputStream;
3096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.IOException;
3196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStream;
3296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.OutputStream;
33f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onukiimport java.net.InetAddress;
3496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.InetSocketAddress;
3596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.Socket;
3696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.SocketAddress;
3796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.SocketException;
3896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
39fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.HostnameVerifier;
40fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.HttpsURLConnection;
4196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport javax.net.ssl.SSLException;
42fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLPeerUnverifiedException;
43fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLSession;
44fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLSocket;
4596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
46bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook/**
47bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * This class implements the common aspects of "transport", one layer below the
48bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * specific wire protocols such as POP3, IMAP, or SMTP.
49bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook */
50bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookpublic class MailTransport implements Transport {
518d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
5296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    // TODO protected eventually
5396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /*protected*/ public static final int SOCKET_CONNECT_TIMEOUT = 10000;
5496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /*protected*/ public static final int SOCKET_READ_TIMEOUT = 60000;
5596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
56fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    private static final HostnameVerifier HOSTNAME_VERIFIER =
57fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            HttpsURLConnection.getDefaultHostnameVerifier();
58fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
59bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private String mDebugLabel;
60bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
61bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private String mHost;
62bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private int mPort;
63bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private String[] mUserInfoParts;
64bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
65bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /**
66bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * One of the {@code Transport.CONNECTION_SECURITY_*} values.
67bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     */
68bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private int mConnectionSecurity;
69bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
70bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /**
71bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * Whether or not to trust all server certificates (i.e. skip host verification) in SSL
72bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * handshakes
73bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     */
74bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    private boolean mTrustCertificates;
7596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
7696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private Socket mSocket;
7796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private InputStream mIn;
7896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private OutputStream mOut;
7996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
80bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /**
81bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * Simple constructor for starting from scratch.  Call setUri() and setSecurity() to
82bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * complete the configuration.
83bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     * @param debugLabel Label used for Log.d calls
84bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook     */
85bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public MailTransport(String debugLabel) {
8696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        super();
8796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mDebugLabel = debugLabel;
8896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
898d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
90bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    /**
91ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy     * Returns a new transport, using the current transport as a model. The new transport is
92ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy     * configured identically (as if {@link #setSecurity(int, boolean)}, {@link #setPort(int)}
93ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy     * and {@link #setHost(String)} were invoked), but not opened or connected in any way.
9496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
95ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy    @Override
96bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public Transport clone() {
97bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        MailTransport newObject = new MailTransport(mDebugLabel);
98bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
99bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        newObject.mDebugLabel = mDebugLabel;
100bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        newObject.mHost = mHost;
101bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        newObject.mPort = mPort;
102bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (mUserInfoParts != null) {
103bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            newObject.mUserInfoParts = mUserInfoParts.clone();
104bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        }
105bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        newObject.mConnectionSecurity = mConnectionSecurity;
106bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        newObject.mTrustCertificates = mTrustCertificates;
107bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return newObject;
108bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
109bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
110bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
111bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public void setHost(String host) {
112bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        mHost = host;
113bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
114bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
115bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
116bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public void setPort(int port) {
117bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        mPort = port;
118a50fc99b0c433f0cde31ba1c7ab87fb9ea86345dTodd Kennedy    }
119a50fc99b0c433f0cde31ba1c7ab87fb9ea86345dTodd Kennedy
120bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
12196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public String getHost() {
122bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return mHost;
12396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
12496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
125bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
12696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public int getPort() {
127bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return mPort;
128bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
129bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
130bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
131bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public void setSecurity(int connectionSecurity, boolean trustAllCertificates) {
132bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        mConnectionSecurity = connectionSecurity;
133bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        mTrustCertificates = trustAllCertificates;
13496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
13596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
136bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
137bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public int getSecurity() {
138bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return mConnectionSecurity;
139bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    }
140bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook
141bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
14296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean canTrySslSecurity() {
143bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return mConnectionSecurity == Transport.CONNECTION_SECURITY_SSL;
14496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
1458d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
146bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
14796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean canTryTlsSecurity() {
148bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return mConnectionSecurity == Transport.CONNECTION_SECURITY_TLS;
14996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
1508d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
151bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
152e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler    public boolean canTrustAllCertificates() {
153bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        return mTrustCertificates;
154e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler    }
155e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler
15696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
15796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Attempts to open a connection using the Uri supplied for connection parameters.  Will attempt
15896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * an SSL connection if indicated.
15996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
160bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
16196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void open() throws MessagingException, CertificateValidationException {
162bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (Email.DEBUG) {
16331d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.d(Logging.LOG_TAG, "*** " + mDebugLabel + " open " +
16496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    getHost() + ":" + String.valueOf(getPort()));
16596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
16696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
16796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
16896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            SocketAddress socketAddress = new InetSocketAddress(getHost(), getPort());
16996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (canTrySslSecurity()) {
170bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook                mSocket = SSLUtils.getSSLSocketFactory(canTrustAllCertificates()).createSocket();
17196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
17296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                mSocket = new Socket();
17396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
17496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
175fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            // After the socket connects to an SSL server, confirm that the hostname is as expected
176fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            if (canTrySslSecurity() && !canTrustAllCertificates()) {
177fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler                verifyHostname(mSocket, getHost());
178fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            }
17996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
18096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
1818d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
18296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (SSLException e) {
183bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (Email.DEBUG) {
18431d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, e.toString());
18596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
18696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new CertificateValidationException(e.getMessage(), e);
18796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (IOException ioe) {
188bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (Email.DEBUG) {
18931d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, ioe.toString());
19096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
19196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
19296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
19396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
19496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
19596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
19696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Attempts to reopen a TLS connection using the Uri supplied for connection parameters.
19796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     *
198fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * NOTE: No explicit hostname verification is required here, because it's handled automatically
199fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * by the call to createSocket().
200fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
20196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * TODO should we explicitly close the old socket?  This seems funky to abandon it.
20296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
203bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
20496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void reopenTls() throws MessagingException {
20596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
206bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            mSocket = SSLUtils.getSSLSocketFactory(canTrustAllCertificates())
207c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki                    .createSocket(mSocket, getHost(), getPort(), true);
20896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
20996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
21096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
21196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
212e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler        } catch (SSLException e) {
213bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (Email.DEBUG) {
21431d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, e.toString());
215e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler            }
216e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler            throw new CertificateValidationException(e.getMessage(), e);
21796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (IOException ioe) {
218bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (Email.DEBUG) {
21931d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, ioe.toString());
22096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
22196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
22296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
22396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
224fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
225fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    /**
226fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this
227fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * service but is not in the public API.
228fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
229fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * Verify the hostname of the certificate used by the other end of a
230fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * connected socket.  You MUST call this if you did not supply a hostname
231fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * to SSLCertificateSocketFactory.createSocket().  It is harmless to call this method
232fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * redundantly if the hostname has already been verified.
233fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
234fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * <p>Wildcard certificates are allowed to verify any matching hostname,
235fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * so "foo.bar.example.com" is verified if the peer has a certificate
236fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * for "*.example.com".
237fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
238fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @param socket An SSL socket which has been connected to a server
239fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @param hostname The expected hostname of the remote server
240fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @throws IOException if something goes wrong handshaking with the server
241fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @throws SSLPeerUnverifiedException if the server cannot prove its identity
242fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler      */
243fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    private void verifyHostname(Socket socket, String hostname) throws IOException {
244fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // The code at the start of OpenSSLSocketImpl.startHandshake()
245fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // ensures that the call is idempotent, so we can safely call it.
246fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        SSLSocket ssl = (SSLSocket) socket;
247fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        ssl.startHandshake();
248fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
249fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        SSLSession session = ssl.getSession();
250fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        if (session == null) {
251fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            throw new SSLException("Cannot verify SSL socket without session");
252fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        }
253fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // TODO: Instead of reporting the name of the server we think we're connecting to,
254fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // we should be reporting the bad name in the certificate.  Unfortunately this is buried
255fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // in the verifier code and is not available in the verifier API, and extracting the
256fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // CN & alts is beyond the scope of this patch.
257fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
258fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            throw new SSLPeerUnverifiedException(
259fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler                    "Certificate hostname not useable for server: " + hostname);
260fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        }
261fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    }
262fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
26396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
26496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Set the socket timeout.
26596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param timeoutMilliseconds the read timeout value if greater than {@code 0}, or
26696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     *            {@code 0} for an infinite timeout.
26796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
268bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
26996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void setSoTimeout(int timeoutMilliseconds) throws SocketException {
27096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mSocket.setSoTimeout(timeoutMilliseconds);
27196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
27296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
273bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
27496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean isOpen() {
2758d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy        return (mIn != null && mOut != null &&
27696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                mSocket != null && mSocket.isConnected() && !mSocket.isClosed());
27796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
27896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
27996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
28096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Close the connection.  MUST NOT return any exceptions - must be "best effort" and safe.
28196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
282bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
28396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void close() {
28496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
28596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn.close();
28696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
28796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
28896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
28996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
29096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut.close();
29196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
29296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
29396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
29496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
29596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.close();
29696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
29796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
29896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
29996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mIn = null;
30096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mOut = null;
30196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mSocket = null;
30296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
30396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
304bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
30596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public InputStream getInputStream() {
30696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return mIn;
30796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
30896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
309bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
31096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public OutputStream getOutputStream() {
31196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return mOut;
31296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
3138d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
31496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
31596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Writes a single line to the server using \r\n termination.
31696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
317bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
31896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void writeLine(String s, String sensitiveReplacement) throws IOException {
319bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (Email.DEBUG) {
320bfac9f2e8a13f6c719608a6948203bbef921c99fMakoto Onuki            if (sensitiveReplacement != null && !Logging.DEBUG_SENSITIVE) {
32131d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, ">>> " + sensitiveReplacement);
32296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
32331d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, ">>> " + s);
32496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
32596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
3268d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
32796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        OutputStream out = getOutputStream();
32896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write(s.getBytes());
32996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write('\r');
33096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write('\n');
33196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.flush();
33296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
3338d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
33496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
33596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Reads a single line from the server, using either \r\n or \n as the delimiter.  The
33696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * delimiter char(s) are not included in the result.
33796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
338bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
339bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public String readLine() throws IOException {
34096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
34196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        InputStream in = getInputStream();
34296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        int d;
34396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        while ((d = in.read()) != -1) {
34496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (((char)d) == '\r') {
34596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                continue;
34696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else if (((char)d) == '\n') {
34796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                break;
34896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
34996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                sb.append((char)d);
35096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
35196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
352bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (d == -1 && Email.DEBUG) {
35331d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.d(Logging.LOG_TAG, "End of stream reached while trying to read line.");
35496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
35596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        String ret = sb.toString();
356bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (Email.DEBUG) {
35731d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.d(Logging.LOG_TAG, "<<< " + ret);
35896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
35996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return ret;
36096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
36196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
362bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
363f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki    public InetAddress getLocalAddress() {
364f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        if (isOpen()) {
365f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki            return mSocket.getLocalAddress();
366f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        } else {
367f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki            return null;
368f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        }
369f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki    }
37096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project}
371