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