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