1 /* 2 * Copyright (C) 2011 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 17 18 package com.android.emailcommon.provider; 19 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.net.Uri; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.text.TextUtils; 27 28 import com.android.emailcommon.provider.EmailContent.HostAuthColumns; 29 import com.android.emailcommon.utility.SSLUtils; 30 import com.android.emailcommon.utility.Utility; 31 32 import java.net.URI; 33 import java.net.URISyntaxException; 34 35 public final class HostAuth extends EmailContent implements HostAuthColumns, Parcelable { 36 public static final String TABLE_NAME = "HostAuth"; 37 @SuppressWarnings("hiding") 38 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth"); 39 // TODO the three following constants duplicate constants in Store.java; remove those and 40 // just reference these. 41 public static final String SCHEME_IMAP = "imap"; 42 public static final String SCHEME_POP3 = "pop3"; 43 public static final String SCHEME_EAS = "eas"; 44 public static final String SCHEME_SMTP = "smtp"; 45 public static final String SCHEME_TRUST_ALL_CERTS = "trustallcerts"; 46 47 public static final int PORT_UNKNOWN = -1; 48 49 public static final int FLAG_NONE = 0x00; // No flags 50 public static final int FLAG_SSL = 0x01; // Use SSL 51 public static final int FLAG_TLS = 0x02; // Use TLS 52 public static final int FLAG_AUTHENTICATE = 0x04; // Use name/password for authentication 53 public static final int FLAG_TRUST_ALL = 0x08; // Trust all certificates 54 // Mask of settings directly configurable by the user 55 public static final int USER_CONFIG_MASK = 0x0b; 56 57 public String mProtocol; 58 public String mAddress; 59 public int mPort; 60 public int mFlags; 61 public String mLogin; 62 public String mPassword; 63 public String mDomain; 64 public String mClientCertAlias = null; 65 66 public static final int CONTENT_ID_COLUMN = 0; 67 public static final int CONTENT_PROTOCOL_COLUMN = 1; 68 public static final int CONTENT_ADDRESS_COLUMN = 2; 69 public static final int CONTENT_PORT_COLUMN = 3; 70 public static final int CONTENT_FLAGS_COLUMN = 4; 71 public static final int CONTENT_LOGIN_COLUMN = 5; 72 public static final int CONTENT_PASSWORD_COLUMN = 6; 73 public static final int CONTENT_DOMAIN_COLUMN = 7; 74 public static final int CONTENT_CLIENT_CERT_ALIAS_COLUMN = 8; 75 76 public static final String[] CONTENT_PROJECTION = new String[] { 77 RECORD_ID, HostAuthColumns.PROTOCOL, HostAuthColumns.ADDRESS, HostAuthColumns.PORT, 78 HostAuthColumns.FLAGS, HostAuthColumns.LOGIN, 79 HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN, HostAuthColumns.CLIENT_CERT_ALIAS 80 }; 81 82 /** 83 * no public constructor since this is a utility class 84 */ 85 public HostAuth() { 86 mBaseUri = CONTENT_URI; 87 88 // other defaults policy) 89 mPort = PORT_UNKNOWN; 90 } 91 92 /** 93 * Restore a HostAuth from the database, given its unique id 94 * @param context 95 * @param id 96 * @return the instantiated HostAuth 97 */ 98 public static HostAuth restoreHostAuthWithId(Context context, long id) { 99 return EmailContent.restoreContentWithId(context, HostAuth.class, 100 HostAuth.CONTENT_URI, HostAuth.CONTENT_PROJECTION, id); 101 } 102 103 104 /** 105 * Returns the scheme for the specified flags. 106 */ 107 public static String getSchemeString(String protocol, int flags) { 108 return getSchemeString(protocol, flags, null); 109 } 110 111 /** 112 * Builds a URI scheme name given the parameters for a {@code HostAuth}. 113 * If a {@code clientAlias} is provided, this indicates that a secure connection must be used. 114 */ 115 public static String getSchemeString(String protocol, int flags, String clientAlias) { 116 String security = ""; 117 switch (flags & USER_CONFIG_MASK) { 118 case FLAG_SSL: 119 security = "+ssl+"; 120 break; 121 case FLAG_SSL | FLAG_TRUST_ALL: 122 security = "+ssl+trustallcerts"; 123 break; 124 case FLAG_TLS: 125 security = "+tls+"; 126 break; 127 case FLAG_TLS | FLAG_TRUST_ALL: 128 security = "+tls+trustallcerts"; 129 break; 130 } 131 132 if (!TextUtils.isEmpty(clientAlias)) { 133 if (TextUtils.isEmpty(security)) { 134 throw new IllegalArgumentException( 135 "Can't specify a certificate alias for a non-secure connection"); 136 } 137 if (!security.endsWith("+")) { 138 security += "+"; 139 } 140 security += SSLUtils.escapeForSchemeName(clientAlias); 141 } 142 143 return protocol + security; 144 } 145 146 /** 147 * Returns the flags for the specified scheme. 148 */ 149 public static int getSchemeFlags(String scheme) { 150 String[] schemeParts = scheme.split("\\+"); 151 int flags = HostAuth.FLAG_NONE; 152 if (schemeParts.length >= 2) { 153 String part1 = schemeParts[1]; 154 if ("ssl".equals(part1)) { 155 flags |= HostAuth.FLAG_SSL; 156 } else if ("tls".equals(part1)) { 157 flags |= HostAuth.FLAG_TLS; 158 } 159 if (schemeParts.length >= 3) { 160 String part2 = schemeParts[2]; 161 if (SCHEME_TRUST_ALL_CERTS.equals(part2)) { 162 flags |= HostAuth.FLAG_TRUST_ALL; 163 } 164 } 165 } 166 return flags; 167 } 168 169 @Override 170 public void restore(Cursor cursor) { 171 mBaseUri = CONTENT_URI; 172 mId = cursor.getLong(CONTENT_ID_COLUMN); 173 mProtocol = cursor.getString(CONTENT_PROTOCOL_COLUMN); 174 mAddress = cursor.getString(CONTENT_ADDRESS_COLUMN); 175 mPort = cursor.getInt(CONTENT_PORT_COLUMN); 176 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 177 mLogin = cursor.getString(CONTENT_LOGIN_COLUMN); 178 mPassword = cursor.getString(CONTENT_PASSWORD_COLUMN); 179 mDomain = cursor.getString(CONTENT_DOMAIN_COLUMN); 180 mClientCertAlias = cursor.getString(CONTENT_CLIENT_CERT_ALIAS_COLUMN); 181 } 182 183 @Override 184 public ContentValues toContentValues() { 185 ContentValues values = new ContentValues(); 186 values.put(HostAuthColumns.PROTOCOL, mProtocol); 187 values.put(HostAuthColumns.ADDRESS, mAddress); 188 values.put(HostAuthColumns.PORT, mPort); 189 values.put(HostAuthColumns.FLAGS, mFlags); 190 values.put(HostAuthColumns.LOGIN, mLogin); 191 values.put(HostAuthColumns.PASSWORD, mPassword); 192 values.put(HostAuthColumns.DOMAIN, mDomain); 193 values.put(HostAuthColumns.CLIENT_CERT_ALIAS, mClientCertAlias); 194 values.put(HostAuthColumns.ACCOUNT_KEY, 0); // Need something to satisfy the DB 195 return values; 196 } 197 198 /** 199 * Sets the user name and password from URI user info string 200 */ 201 public void setLogin(String userInfo) { 202 String userName = null; 203 String userPassword = null; 204 if (!TextUtils.isEmpty(userInfo)) { 205 String[] userInfoParts = userInfo.split(":", 2); 206 userName = userInfoParts[0]; 207 if (userInfoParts.length > 1) { 208 userPassword = userInfoParts[1]; 209 } 210 } 211 setLogin(userName, userPassword); 212 } 213 214 /** 215 * Sets the user name and password 216 */ 217 public void setLogin(String userName, String userPassword) { 218 mLogin = userName; 219 mPassword = userPassword; 220 221 if (mLogin == null) { 222 mFlags &= ~FLAG_AUTHENTICATE; 223 } else { 224 mFlags |= FLAG_AUTHENTICATE; 225 } 226 } 227 228 /** 229 * Returns the login information. [0] is the username and [1] is the password. If 230 * {@link #FLAG_AUTHENTICATE} is not set, {@code null} is returned. 231 */ 232 public String[] getLogin() { 233 if ((mFlags & FLAG_AUTHENTICATE) != 0) { 234 String trimUser = (mLogin != null) ? mLogin.trim() : ""; 235 String password = (mPassword != null) ? mPassword : ""; 236 return new String[] { trimUser, password }; 237 } 238 return null; 239 } 240 241 public void setConnection(String protocol, String address, int port, int flags) { 242 setConnection(protocol, address, port, flags, null); 243 } 244 245 /** 246 * Sets the internal connection parameters based on the specified parameter values. 247 * @param protocol the mail protocol to use (e.g. "eas", "imap"). 248 * @param address the address of the server 249 * @param port the port for the connection 250 * @param flags flags indicating the security and type of the connection 251 * @param clientCertAlias an optional alias to use if a client user certificate is to be 252 * presented during connection establishment. If this is non-empty, it must be the case 253 * that flags indicates use of a secure connection 254 */ 255 public void setConnection(String protocol, String address, 256 int port, int flags, String clientCertAlias) { 257 // Set protocol, security, and additional flags based on uri scheme 258 mProtocol = protocol; 259 260 mFlags &= ~(FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL); 261 mFlags |= (flags & USER_CONFIG_MASK); 262 263 boolean useSecureConnection = (flags & (FLAG_SSL | FLAG_TLS)) != 0; 264 if (!useSecureConnection && !TextUtils.isEmpty(clientCertAlias)) { 265 throw new IllegalArgumentException("Can't use client alias on non-secure connections"); 266 } 267 268 mAddress = address; 269 mPort = port; 270 if (mPort == PORT_UNKNOWN) { 271 boolean useSSL = ((mFlags & FLAG_SSL) != 0); 272 // infer port# from protocol + security 273 // SSL implies a different port - TLS runs in the "regular" port 274 // NOTE: Although the port should be setup in the various setup screens, this 275 // block cannot easily be moved because we get process URIs from other sources 276 // (e.g. for tests, provider templates and account restore) that may or may not 277 // have a port specified. 278 if (SCHEME_POP3.equals(mProtocol)) { 279 mPort = useSSL ? 995 : 110; 280 } else if (SCHEME_IMAP.equals(mProtocol)) { 281 mPort = useSSL ? 993 : 143; 282 } else if (SCHEME_EAS.equals(mProtocol)) { 283 mPort = useSSL ? 443 : 80; 284 } else if (SCHEME_SMTP.equals(mProtocol)) { 285 mPort = useSSL ? 465 : 587; 286 } 287 } 288 289 mClientCertAlias = clientCertAlias; 290 } 291 292 /** Returns {@code true} if this is an EAS connection; otherwise, {@code false}. */ 293 public boolean isEasConnection() { 294 return SCHEME_EAS.equals(mProtocol); 295 } 296 297 /** Convenience method to determine if SSL is used. */ 298 public boolean shouldUseSsl() { 299 return (mFlags & FLAG_SSL) != 0; 300 } 301 302 /** Convenience method to determine if all server certs should be used. */ 303 public boolean shouldTrustAllServerCerts() { 304 return (mFlags & FLAG_TRUST_ALL) != 0; 305 } 306 307 /** 308 * Supports Parcelable 309 */ 310 @Override 311 public int describeContents() { 312 return 0; 313 } 314 315 /** 316 * Supports Parcelable 317 */ 318 public static final Parcelable.Creator<HostAuth> CREATOR 319 = new Parcelable.Creator<HostAuth>() { 320 @Override 321 public HostAuth createFromParcel(Parcel in) { 322 return new HostAuth(in); 323 } 324 325 @Override 326 public HostAuth[] newArray(int size) { 327 return new HostAuth[size]; 328 } 329 }; 330 331 /** 332 * Supports Parcelable 333 */ 334 @Override 335 public void writeToParcel(Parcel dest, int flags) { 336 // mBaseUri is not parceled 337 dest.writeLong(mId); 338 dest.writeString(mProtocol); 339 dest.writeString(mAddress); 340 dest.writeInt(mPort); 341 dest.writeInt(mFlags); 342 dest.writeString(mLogin); 343 dest.writeString(mPassword); 344 dest.writeString(mDomain); 345 dest.writeString(mClientCertAlias); 346 } 347 348 /** 349 * Supports Parcelable 350 */ 351 public HostAuth(Parcel in) { 352 mBaseUri = CONTENT_URI; 353 mId = in.readLong(); 354 mProtocol = in.readString(); 355 mAddress = in.readString(); 356 mPort = in.readInt(); 357 mFlags = in.readInt(); 358 mLogin = in.readString(); 359 mPassword = in.readString(); 360 mDomain = in.readString(); 361 mClientCertAlias = in.readString(); 362 } 363 364 @Override 365 public boolean equals(Object o) { 366 if (!(o instanceof HostAuth)) { 367 return false; 368 } 369 HostAuth that = (HostAuth)o; 370 return mPort == that.mPort 371 && mFlags == that.mFlags 372 && Utility.areStringsEqual(mProtocol, that.mProtocol) 373 && Utility.areStringsEqual(mAddress, that.mAddress) 374 && Utility.areStringsEqual(mLogin, that.mLogin) 375 && Utility.areStringsEqual(mPassword, that.mPassword) 376 && Utility.areStringsEqual(mDomain, that.mDomain) 377 && Utility.areStringsEqual(mClientCertAlias, that.mClientCertAlias); 378 } 379 380 /** 381 * The flag, password, and client cert alias are the only items likely to change after a 382 * HostAuth is created 383 */ 384 @Override 385 public int hashCode() { 386 int hashCode = 29; 387 if (mPassword != null) { 388 hashCode += mPassword.hashCode(); 389 } 390 if (mClientCertAlias != null) { 391 hashCode += (mClientCertAlias.hashCode() << 8); 392 } 393 return (hashCode << 8) + mFlags; 394 } 395 396 /** 397 * Legacy URI parser. Used in parsing template from provider.xml 398 * Example string: 399 * "eas+ssl+trustallcerts://user:password@server/domain:123" 400 * 401 * Note that the use of client certificate is specified in the URI, a secure connection type 402 * must be used. 403 */ 404 public static void setHostAuthFromString(HostAuth auth, String uriString) 405 throws URISyntaxException { 406 URI uri = new URI(uriString); 407 String path = uri.getPath(); 408 String domain = null; 409 if (!TextUtils.isEmpty(path)) { 410 // Strip off the leading slash that begins the path. 411 domain = path.substring(1); 412 } 413 auth.mDomain = domain; 414 auth.setLogin(uri.getUserInfo()); 415 416 String scheme = uri.getScheme(); 417 auth.setConnection(scheme, uri.getHost(), uri.getPort()); 418 } 419 420 /** 421 * Legacy code for setting connection values from a "scheme" (see above) 422 */ 423 public void setConnection(String scheme, String host, int port) { 424 String[] schemeParts = scheme.split("\\+"); 425 String protocol = schemeParts[0]; 426 String clientCertAlias = null; 427 int flags = getSchemeFlags(scheme); 428 429 // Example scheme: "eas+ssl+trustallcerts" or "eas+tls+trustallcerts+client-cert-alias" 430 if (schemeParts.length > 3) { 431 clientCertAlias = schemeParts[3]; 432 } else if (schemeParts.length > 2) { 433 if (!SCHEME_TRUST_ALL_CERTS.equals(schemeParts[2])) { 434 mClientCertAlias = schemeParts[2]; 435 } 436 } 437 438 setConnection(protocol, host, port, flags, clientCertAlias); 439 } 440 441 } 442