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()) {
170397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook                mSocket = SSLUtils.getSSLSocketFactory(
171397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook                        canTrustAllCertificates(), SOCKET_CONNECT_TIMEOUT).createSocket();
17296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
17396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                mSocket = new Socket();
17496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
17596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
176fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            // After the socket connects to an SSL server, confirm that the hostname is as expected
177fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            if (canTrySslSecurity() && !canTrustAllCertificates()) {
178fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler                verifyHostname(mSocket, getHost());
179fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            }
18096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
18196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
1828d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
18396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (SSLException e) {
184bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (Email.DEBUG) {
18531d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, e.toString());
18696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
18796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new CertificateValidationException(e.getMessage(), e);
18896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (IOException ioe) {
189bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (Email.DEBUG) {
19031d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, ioe.toString());
19196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
19296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
19396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
19496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
19596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
19696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
19796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Attempts to reopen a TLS connection using the Uri supplied for connection parameters.
19896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     *
199fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * NOTE: No explicit hostname verification is required here, because it's handled automatically
200fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * by the call to createSocket().
201fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
20296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * TODO should we explicitly close the old socket?  This seems funky to abandon it.
20396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
204bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
20596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void reopenTls() throws MessagingException {
20696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
207397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook            mSocket =
208397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook                    SSLUtils.getSSLSocketFactory(canTrustAllCertificates(), SOCKET_CONNECT_TIMEOUT)
209397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook                            .createSocket(mSocket, getHost(), getPort(), true);
21096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
21196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
21296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
21396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
214e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler        } catch (SSLException e) {
215bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (Email.DEBUG) {
21631d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, e.toString());
217e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler            }
218e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler            throw new CertificateValidationException(e.getMessage(), e);
21996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (IOException ioe) {
220bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook            if (Email.DEBUG) {
22131d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, ioe.toString());
22296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
22396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
22496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
22596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
226fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
227fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    /**
228fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this
229fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * service but is not in the public API.
230fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
231fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * Verify the hostname of the certificate used by the other end of a
232fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * connected socket.  You MUST call this if you did not supply a hostname
233fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * to SSLCertificateSocketFactory.createSocket().  It is harmless to call this method
234fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * redundantly if the hostname has already been verified.
235fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
236fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * <p>Wildcard certificates are allowed to verify any matching hostname,
237fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * so "foo.bar.example.com" is verified if the peer has a certificate
238fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * for "*.example.com".
239fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
240fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @param socket An SSL socket which has been connected to a server
241fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @param hostname The expected hostname of the remote server
242fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @throws IOException if something goes wrong handshaking with the server
243fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @throws SSLPeerUnverifiedException if the server cannot prove its identity
244fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler      */
245fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    private void verifyHostname(Socket socket, String hostname) throws IOException {
246fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // The code at the start of OpenSSLSocketImpl.startHandshake()
247fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // ensures that the call is idempotent, so we can safely call it.
248fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        SSLSocket ssl = (SSLSocket) socket;
249fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        ssl.startHandshake();
250fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
251fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        SSLSession session = ssl.getSession();
252fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        if (session == null) {
253fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            throw new SSLException("Cannot verify SSL socket without session");
254fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        }
255fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // TODO: Instead of reporting the name of the server we think we're connecting to,
256fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // we should be reporting the bad name in the certificate.  Unfortunately this is buried
257fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // in the verifier code and is not available in the verifier API, and extracting the
258fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // CN & alts is beyond the scope of this patch.
259fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
260fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            throw new SSLPeerUnverifiedException(
261fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler                    "Certificate hostname not useable for server: " + hostname);
262fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        }
263fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    }
264fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
26596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
26696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Set the socket timeout.
26796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param timeoutMilliseconds the read timeout value if greater than {@code 0}, or
26896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     *            {@code 0} for an infinite timeout.
26996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
270bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
27196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void setSoTimeout(int timeoutMilliseconds) throws SocketException {
27296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mSocket.setSoTimeout(timeoutMilliseconds);
27396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
27496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
275bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
27696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean isOpen() {
2778d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy        return (mIn != null && mOut != null &&
27896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                mSocket != null && mSocket.isConnected() && !mSocket.isClosed());
27996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
28096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
28196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
28296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Close the connection.  MUST NOT return any exceptions - must be "best effort" and safe.
28396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
284bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
28596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void close() {
286397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook        if (Email.DEBUG) {
287397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook            Log.d(Logging.LOG_TAG, "*** " + mDebugLabel + " close " +
288397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook                    getHost() + ":" + String.valueOf(getPort()));
289397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook        }
290397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook
29196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
29296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn.close();
29396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
29496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
295397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook            if (Email.DEBUG) {
296397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook                Log.d(Logging.LOG_TAG, e.toString());
297397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook            }
29896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
29996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
30096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut.close();
30196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
30296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
303397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook            if (Email.DEBUG) {
304397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook                Log.d(Logging.LOG_TAG, e.toString());
305397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook            }
30696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
30796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
30896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.close();
30996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
31096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
311397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook            if (Email.DEBUG) {
312397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook                Log.d(Logging.LOG_TAG, e.toString());
313397cce4229eb90a6582e1e5b7ce153536a986627Paul Westbrook            }
31496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
31596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mIn = null;
31696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mOut = null;
31796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mSocket = null;
31896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
31996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
320bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
32196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public InputStream getInputStream() {
32296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return mIn;
32396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
32496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
325bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
32696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public OutputStream getOutputStream() {
32796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return mOut;
32896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
3298d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
33096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
33196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Writes a single line to the server using \r\n termination.
33296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
333bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
33496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void writeLine(String s, String sensitiveReplacement) throws IOException {
335bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (Email.DEBUG) {
336bfac9f2e8a13f6c719608a6948203bbef921c99fMakoto Onuki            if (sensitiveReplacement != null && !Logging.DEBUG_SENSITIVE) {
33731d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, ">>> " + sensitiveReplacement);
33896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
33931d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, ">>> " + s);
34096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
34196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
3428d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
34396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        OutputStream out = getOutputStream();
34496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write(s.getBytes());
34596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write('\r');
34696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write('\n');
34796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.flush();
34896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
3498d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
35096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
35196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Reads a single line from the server, using either \r\n or \n as the delimiter.  The
35296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * delimiter char(s) are not included in the result.
35396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
354bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
355bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    public String readLine() throws IOException {
35696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
35796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        InputStream in = getInputStream();
35896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        int d;
35996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        while ((d = in.read()) != -1) {
36096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (((char)d) == '\r') {
36196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                continue;
36296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else if (((char)d) == '\n') {
36396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                break;
36496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
36596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                sb.append((char)d);
36696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
36796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
368bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (d == -1 && Email.DEBUG) {
36931d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.d(Logging.LOG_TAG, "End of stream reached while trying to read line.");
37096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
37196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        String ret = sb.toString();
372bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook        if (Email.DEBUG) {
37331d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.d(Logging.LOG_TAG, "<<< " + ret);
37496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
37596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return ret;
37696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
37796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
378bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook    @Override
379f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki    public InetAddress getLocalAddress() {
380f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        if (isOpen()) {
381f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki            return mSocket.getLocalAddress();
382f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        } else {
383f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki            return null;
384f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        }
385f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki    }
38696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project}
387