MailTransport.java revision c5912e4920bb4fa1979d63d47e7f430e87e3820f
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.mail.transport; 18 19import com.android.email.Email; 20import com.android.email.mail.CertificateValidationException; 21import com.android.email.mail.MessagingException; 22import com.android.email.mail.Transport; 23 24import android.util.Config; 25import android.util.Log; 26 27import java.io.BufferedInputStream; 28import java.io.BufferedOutputStream; 29import java.io.IOException; 30import java.io.InputStream; 31import java.io.OutputStream; 32import java.net.InetSocketAddress; 33import java.net.Socket; 34import java.net.SocketAddress; 35import java.net.SocketException; 36import java.net.URI; 37import java.security.GeneralSecurityException; 38import java.security.SecureRandom; 39 40import javax.net.ssl.SSLContext; 41import javax.net.ssl.SSLException; 42import javax.net.ssl.TrustManager; 43 44/** 45 * This class implements the common aspects of "transport", one layer below the 46 * specific wire protocols such as POP3, IMAP, or SMTP. 47 */ 48public class MailTransport implements Transport { 49 50 // TODO protected eventually 51 /*protected*/ public static final int SOCKET_CONNECT_TIMEOUT = 10000; 52 /*protected*/ public static final int SOCKET_READ_TIMEOUT = 60000; 53 54 private String mDebugLabel; 55 56 private String mHost; 57 private int mPort; 58 private String[] mUserInfoParts; 59 private int mConnectionSecurity; 60 private boolean mTrustCertificates; 61 62 private Socket mSocket; 63 private InputStream mIn; 64 private OutputStream mOut; 65 66 /** 67 * Simple constructor for starting from scratch. Call setUri() and setSecurity() to 68 * complete the configuration. 69 * @param debugLabel Label used for Log.d calls 70 */ 71 public MailTransport(String debugLabel) { 72 super(); 73 mDebugLabel = debugLabel; 74 } 75 76 /** 77 * Get a new transport, using an existing one as a model. The new transport is configured as if 78 * setUri() and setSecurity() have been called, but not opened or connected in any way. 79 * @return a new Transport ready to open() 80 */ 81 public Transport newInstanceWithConfiguration() { 82 MailTransport newObject = new MailTransport(mDebugLabel); 83 84 newObject.mDebugLabel = mDebugLabel; 85 newObject.mHost = mHost; 86 newObject.mPort = mPort; 87 if (mUserInfoParts != null) { 88 newObject.mUserInfoParts = mUserInfoParts.clone(); 89 } 90 newObject.mConnectionSecurity = mConnectionSecurity; 91 newObject.mTrustCertificates = mTrustCertificates; 92 return newObject; 93 } 94 95 public void setUri(URI uri, int defaultPort) { 96 mHost = uri.getHost(); 97 98 mPort = defaultPort; 99 if (uri.getPort() != -1) { 100 mPort = uri.getPort(); 101 } 102 103 if (uri.getUserInfo() != null) { 104 mUserInfoParts = uri.getUserInfo().split(":", 2); 105 } 106 107 } 108 109 public String[] getUserInfoParts() { 110 return mUserInfoParts; 111 } 112 113 public String getHost() { 114 return mHost; 115 } 116 117 public int getPort() { 118 return mPort; 119 } 120 121 public void setSecurity(int connectionSecurity, boolean trustAllCertificates) { 122 mConnectionSecurity = connectionSecurity; 123 mTrustCertificates = trustAllCertificates; 124 } 125 126 public int getSecurity() { 127 return mConnectionSecurity; 128 } 129 130 public boolean canTrySslSecurity() { 131 return mConnectionSecurity == CONNECTION_SECURITY_SSL; 132 } 133 134 public boolean canTryTlsSecurity() { 135 return mConnectionSecurity == Transport.CONNECTION_SECURITY_TLS; 136 } 137 138 public boolean canTrustAllCertificates() { 139 return mTrustCertificates; 140 } 141 142 /** 143 * Attempts to open a connection using the Uri supplied for connection parameters. Will attempt 144 * an SSL connection if indicated. 145 */ 146 public void open() throws MessagingException, CertificateValidationException { 147 if (Config.LOGD && Email.DEBUG) { 148 Log.d(Email.LOG_TAG, "*** " + mDebugLabel + " open " + 149 getHost() + ":" + String.valueOf(getPort())); 150 } 151 152 try { 153 SocketAddress socketAddress = new InetSocketAddress(getHost(), getPort()); 154 if (canTrySslSecurity()) { 155 mSocket = SSLUtils.getSSLSocketFactory(canTrustAllCertificates()).createSocket(); 156 } else { 157 mSocket = new Socket(); 158 } 159 mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); 160 mIn = new BufferedInputStream(mSocket.getInputStream(), 1024); 161 mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512); 162 163 } catch (SSLException e) { 164 if (Config.LOGD && Email.DEBUG) { 165 Log.d(Email.LOG_TAG, e.toString()); 166 } 167 throw new CertificateValidationException(e.getMessage(), e); 168 } catch (IOException ioe) { 169 if (Config.LOGD && Email.DEBUG) { 170 Log.d(Email.LOG_TAG, ioe.toString()); 171 } 172 throw new MessagingException(MessagingException.IOERROR, ioe.toString()); 173 } 174 } 175 176 /** 177 * Attempts to reopen a TLS connection using the Uri supplied for connection parameters. 178 * 179 * TODO should we explicitly close the old socket? This seems funky to abandon it. 180 */ 181 public void reopenTls() throws MessagingException { 182 try { 183 mSocket = SSLUtils.getSSLSocketFactory(canTrustAllCertificates()) 184 .createSocket(mSocket, getHost(), getPort(), true); 185 mSocket.setSoTimeout(SOCKET_READ_TIMEOUT); 186 mIn = new BufferedInputStream(mSocket.getInputStream(), 1024); 187 mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512); 188 189 } catch (SSLException e) { 190 if (Config.LOGD && Email.DEBUG) { 191 Log.d(Email.LOG_TAG, e.toString()); 192 } 193 throw new CertificateValidationException(e.getMessage(), e); 194 } catch (IOException ioe) { 195 if (Config.LOGD && Email.DEBUG) { 196 Log.d(Email.LOG_TAG, ioe.toString()); 197 } 198 throw new MessagingException(MessagingException.IOERROR, ioe.toString()); 199 } 200 } 201 202 /** 203 * Set the socket timeout. 204 * @param timeoutMilliseconds the read timeout value if greater than {@code 0}, or 205 * {@code 0} for an infinite timeout. 206 */ 207 public void setSoTimeout(int timeoutMilliseconds) throws SocketException { 208 mSocket.setSoTimeout(timeoutMilliseconds); 209 } 210 211 public boolean isOpen() { 212 return (mIn != null && mOut != null && 213 mSocket != null && mSocket.isConnected() && !mSocket.isClosed()); 214 } 215 216 /** 217 * Close the connection. MUST NOT return any exceptions - must be "best effort" and safe. 218 */ 219 public void close() { 220 try { 221 mIn.close(); 222 } catch (Exception e) { 223 // May fail if the connection is already closed. 224 } 225 try { 226 mOut.close(); 227 } catch (Exception e) { 228 // May fail if the connection is already closed. 229 } 230 try { 231 mSocket.close(); 232 } catch (Exception e) { 233 // May fail if the connection is already closed. 234 } 235 mIn = null; 236 mOut = null; 237 mSocket = null; 238 } 239 240 public InputStream getInputStream() { 241 return mIn; 242 } 243 244 public OutputStream getOutputStream() { 245 return mOut; 246 } 247 248 /** 249 * Writes a single line to the server using \r\n termination. 250 */ 251 public void writeLine(String s, String sensitiveReplacement) throws IOException { 252 if (Config.LOGD && Email.DEBUG) { 253 if (sensitiveReplacement != null && !Email.DEBUG_SENSITIVE) { 254 Log.d(Email.LOG_TAG, ">>> " + sensitiveReplacement); 255 } else { 256 Log.d(Email.LOG_TAG, ">>> " + s); 257 } 258 } 259 260 OutputStream out = getOutputStream(); 261 out.write(s.getBytes()); 262 out.write('\r'); 263 out.write('\n'); 264 out.flush(); 265 } 266 267 /** 268 * Reads a single line from the server, using either \r\n or \n as the delimiter. The 269 * delimiter char(s) are not included in the result. 270 */ 271 public String readLine() throws IOException { 272 StringBuffer sb = new StringBuffer(); 273 InputStream in = getInputStream(); 274 int d; 275 while ((d = in.read()) != -1) { 276 if (((char)d) == '\r') { 277 continue; 278 } else if (((char)d) == '\n') { 279 break; 280 } else { 281 sb.append((char)d); 282 } 283 } 284 if (d == -1 && Config.LOGD && Email.DEBUG) { 285 Log.d(Email.LOG_TAG, "End of stream reached while trying to read line."); 286 } 287 String ret = sb.toString(); 288 if (Config.LOGD) { 289 if (Email.DEBUG) { 290 Log.d(Email.LOG_TAG, "<<< " + ret); 291 } 292 } 293 return ret; 294 } 295 296 297} 298