| package com.android.exchange.service; |
| |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.os.Build; |
| import android.os.Bundle; |
| |
| import com.android.emailcommon.mail.MessagingException; |
| import com.android.emailcommon.provider.Account; |
| import com.android.emailcommon.provider.EmailContent.AccountColumns; |
| import com.android.emailcommon.provider.HostAuth; |
| import com.android.emailcommon.provider.Policy; |
| import com.android.emailcommon.service.EmailServiceProxy; |
| import com.android.emailcommon.service.PolicyServiceProxy; |
| import com.android.exchange.CommandStatusException; |
| import com.android.exchange.CommandStatusException.CommandStatus; |
| import com.android.exchange.Eas; |
| import com.android.exchange.EasResponse; |
| import com.android.exchange.adapter.FolderSyncParser; |
| import com.android.exchange.adapter.ProvisionParser; |
| import com.android.exchange.adapter.Serializer; |
| import com.android.exchange.adapter.SettingsParser; |
| import com.android.exchange.adapter.Tags; |
| import com.android.mail.utils.LogUtils; |
| import com.google.common.collect.Sets; |
| |
| import org.apache.http.Header; |
| import org.apache.http.HttpStatus; |
| |
| import java.io.IOException; |
| import java.security.cert.CertificateException; |
| import java.util.HashSet; |
| |
| /** |
| * Base class to perform the various requests needed to validate or sync an account. |
| * "Account sync" consists primarily of syncing all folders for this account, but also includes |
| * handling the protocol version, security policies, and other authentication issues. |
| */ |
| public class EasAccountValidator extends EasServerConnection { |
| /** Logging tag. */ |
| private static final String TAG = "EasAccountValidator"; |
| |
| /** |
| * The maximum number of redirects we permit before giving up. Ideally the server should not |
| * send us on a chase like this, so this is here to prevent infinite recursion in a bad case. |
| */ |
| private static final int MAX_REDIRECTS = 3; |
| |
| public static final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML"; |
| public static final String EAS_2_POLICY_TYPE = "MS-WAP-Provisioning-XML"; |
| |
| /** The EAS protocol Provision status for "we implement all of the policies" */ |
| private static final String PROVISION_STATUS_OK = "1"; |
| /** The EAS protocol Provision status meaning "we partially implement the policies" */ |
| private static final String PROVISION_STATUS_PARTIAL = "2"; |
| |
| /** Set of Exchange protocol versions we understand. */ |
| private static final HashSet<String> SUPPORTED_PROTOCOL_VERSIONS = Sets.newHashSet( |
| Eas.SUPPORTED_PROTOCOL_EX2003, |
| Eas.SUPPORTED_PROTOCOL_EX2007, Eas.SUPPORTED_PROTOCOL_EX2007_SP1, |
| Eas.SUPPORTED_PROTOCOL_EX2010, Eas.SUPPORTED_PROTOCOL_EX2010_SP1); |
| |
| /** The number of times we've been redirected so far. */ |
| private int mRedirectCount; |
| |
| /** |
| * An exception type used exclusively within this class -- some sub-functions throw this to |
| * signal that a response from the Exchange server indicated that we should be using a different |
| * host. This exception is caught |
| */ |
| private static class RedirectException extends Exception { |
| public final String mRedirectAddress; |
| public RedirectException(final EasResponse resp) { |
| mRedirectAddress = resp.getRedirectAddress(); |
| } |
| } |
| |
| public EasAccountValidator(final Context context, final Account account, |
| final HostAuth hostAuth) { |
| super(context, account, hostAuth); |
| mRedirectCount = 0; |
| } |
| |
| public EasAccountValidator(final Context context, final HostAuth hostAuth) { |
| this(context, new Account(), hostAuth); |
| mAccount.mEmailAddress = mHostAuth.mLogin; |
| } |
| |
| /** |
| * Update our account's protocol version based on the server's supported versions. |
| * @param versionHeader The {@link Header} for the server's supported versions. |
| * @return Whether we found a suitable protocol version. |
| */ |
| private boolean setProtocolVersion(final Header versionHeader) { |
| // The string is a comma separated list of EAS versions in ascending order |
| // e.g. 1.0,2.0,2.5,12.0,12.1,14.0,14.1 |
| final String supportedVersions = versionHeader.getValue(); |
| LogUtils.i(TAG, "Server supports versions: %s", supportedVersions); |
| final String[] supportedVersionsArray = supportedVersions.split(","); |
| // Find the most recent version we support |
| String newProtocolVersion = null; |
| for (final String version: supportedVersionsArray) { |
| if (SUPPORTED_PROTOCOL_VERSIONS.contains(version)) { |
| newProtocolVersion = version; |
| } |
| } |
| if (newProtocolVersion == null) { |
| LogUtils.w(TAG, "No supported EAS versions: %s", supportedVersions); |
| // TODO: if mAccount.isSaved(), we should delete the account. |
| return false; |
| } |
| |
| // Update our account with the new protocol version. |
| final boolean protocolChanged = !newProtocolVersion.equals(mAccount.mProtocolVersion); |
| if (protocolChanged) { |
| mAccount.mProtocolVersion = newProtocolVersion; |
| uncacheProtocolVersion(); |
| } |
| |
| // Fixup search flags, if they're not set. |
| final boolean flagsChanged; |
| if (getProtocolVersion() >= 12.0) { |
| int oldFlags = mAccount.mFlags; |
| mAccount.mFlags |= Account.FLAGS_SUPPORTS_GLOBAL_SEARCH + Account.FLAGS_SUPPORTS_SEARCH; |
| flagsChanged = (oldFlags != mAccount.mFlags); |
| } else { |
| flagsChanged = false; |
| } |
| |
| // Write account back to DB if needed. |
| if ((protocolChanged || flagsChanged) && mAccount.isSaved()) { |
| final ContentValues cv = new ContentValues(); |
| if (protocolChanged) { |
| cv.put(AccountColumns.PROTOCOL_VERSION, mAccount.mProtocolVersion); |
| } |
| if (flagsChanged) { |
| cv.put(AccountColumns.FLAGS, mAccount.mFlags); |
| } |
| mAccount.update(mContext, cv); |
| } |
| return true; |
| } |
| |
| /** |
| * Make an OPTIONS request to determine the protocol version to use, and update our account to |
| * use the most recent protocol that both we and the server understand. |
| * @return A status code for getting the protocol version. If NO_ERROR, then mAccount will be |
| * updated to the best version we mutually understand. |
| */ |
| private int getServerProtocolVersion() throws IOException, RedirectException { |
| final EasResponse resp = sendHttpClientOptions(); |
| try { |
| final int code = resp.getStatus(); |
| LogUtils.d(TAG, "Validation (OPTIONS) response: %d", code); |
| if (code == HttpStatus.SC_OK) { |
| // No exception means successful validation |
| final Header commands = resp.getHeader("MS-ASProtocolCommands"); |
| final Header versions = resp.getHeader("ms-asprotocolversions"); |
| final boolean hasProtocolVersion; |
| if (commands == null || versions == null) { |
| LogUtils.e(TAG, "OPTIONS response without commands or versions"); |
| hasProtocolVersion = false; |
| } else { |
| hasProtocolVersion = setProtocolVersion(versions); |
| } |
| if (!hasProtocolVersion) { |
| return MessagingException.PROTOCOL_VERSION_UNSUPPORTED; |
| } |
| return MessagingException.NO_ERROR; |
| } |
| if (EasResponse.isAuthError(code)) { |
| return resp.isMissingCertificate() |
| ? MessagingException.CLIENT_CERTIFICATE_REQUIRED |
| : MessagingException.AUTHENTICATION_FAILED; |
| } |
| if (code == HttpStatus.SC_INTERNAL_SERVER_ERROR) { |
| return MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR; |
| } |
| if (EasResponse.isRedirectError(code)) { |
| throw new RedirectException(resp); |
| } |
| // TODO Need to catch other kinds of errors (e.g. policy) For now, report code. |
| LogUtils.w(TAG, "Validation failed, reporting I/O error: %d", code); |
| return MessagingException.IOERROR; |
| } finally { |
| resp.close(); |
| } |
| } |
| |
| /** |
| * Send a FolderSync request and handle the response. Depending on isStatusOnly, this either |
| * simply verifies that the response is valid, or it will also actually sync the folders. |
| * @param isStatusOnly If true, this is only a validation, otherwise it's a full sync. |
| * @return A status code indicating the result of this check. |
| * @throws IOException |
| * @throws CommandStatusException |
| * @throws RedirectException |
| */ |
| private int doFolderSync(final boolean isStatusOnly) |
| throws IOException, CommandStatusException, RedirectException { |
| LogUtils.i(TAG, "FolderSync (%s) for %s, %s, ssl = %s", isStatusOnly ? "validate" : "sync", |
| mHostAuth.mAddress, mHostAuth.mLogin, mHostAuth.shouldUseSsl() ? "1" : "0"); |
| |
| // Send "0" as the sync key for new accounts; otherwise, use the current key |
| final String syncKey = mAccount.mSyncKey != null ? mAccount.mSyncKey : "0"; |
| final Serializer s = new Serializer(); |
| s.start(Tags.FOLDER_FOLDER_SYNC).start(Tags.FOLDER_SYNC_KEY).text(syncKey) |
| .end().end().done(); |
| final EasResponse resp = sendHttpClientPost("FolderSync", s.toByteArray()); |
| final int resultCode; |
| try { |
| final int code = resp.getStatus(); |
| LogUtils.d(TAG, "FolderSync response: %d", code); |
| if (code == HttpStatus.SC_OK) { |
| // We need to parse the result to see if we've got a provisioning issue |
| // (EAS 14.0 only) |
| if (!resp.isEmpty()) { |
| new FolderSyncParser(mContext, mContext.getContentResolver(), |
| resp.getInputStream(), mAccount, isStatusOnly).parse(); |
| } |
| resultCode = MessagingException.NO_ERROR; |
| } else if (code == HttpStatus.SC_FORBIDDEN) { |
| // For validation only, we take 403 as ACCESS_DENIED (the account isn't |
| // authorized, possibly due to device type) |
| resultCode = MessagingException.ACCESS_DENIED; |
| } else if (EasResponse.isProvisionError(code)) { |
| // The device needs to have security policies enforced |
| throw new CommandStatusException(CommandStatus.NEEDS_PROVISIONING); |
| } else if (code == HttpStatus.SC_NOT_FOUND) { |
| // We get a 404 from OWA addresses (which are NOT EAS addresses) |
| resultCode = MessagingException.PROTOCOL_VERSION_UNSUPPORTED; |
| } else if (code == HttpStatus.SC_UNAUTHORIZED) { |
| resultCode = resp.isMissingCertificate() |
| ? MessagingException.CLIENT_CERTIFICATE_REQUIRED |
| : MessagingException.AUTHENTICATION_FAILED; |
| } else if (EasResponse.isRedirectError(code)) { |
| throw new RedirectException(resp); |
| } else { |
| resultCode = MessagingException.UNSPECIFIED_EXCEPTION; |
| } |
| } finally { |
| resp.close(); |
| } |
| return resultCode; |
| } |
| |
| /** |
| * Send a Settings request to the server and process the response. |
| * @return Whether the request succeeded. |
| * @throws IOException |
| */ |
| private boolean sendSettings() throws IOException { |
| final Serializer s = new Serializer(); |
| s.start(Tags.SETTINGS_SETTINGS); |
| s.start(Tags.SETTINGS_DEVICE_INFORMATION).start(Tags.SETTINGS_SET); |
| s.data(Tags.SETTINGS_MODEL, Build.MODEL); |
| s.data(Tags.SETTINGS_OS, "Android " + Build.VERSION.RELEASE); |
| s.data(Tags.SETTINGS_USER_AGENT, USER_AGENT); |
| s.end().end().end().done(); // SETTINGS_SET, SETTINGS_DEVICE_INFORMATION, SETTINGS_SETTINGS |
| final EasResponse resp = sendHttpClientPost("Settings", s.toByteArray()); |
| try { |
| if (resp.getStatus() == HttpStatus.SC_OK) { |
| return new SettingsParser(resp.getInputStream()).parse(); |
| } |
| } finally { |
| resp.close(); |
| } |
| // On failures, simply return false |
| return false; |
| } |
| |
| private String getPolicyType() { |
| return (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? |
| EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE; |
| } |
| |
| /** |
| * Acknowledge a remote wipe command from the server. |
| * @param tempKey The security key of our current (temporary) policy. |
| * @throws IOException |
| */ |
| private void acknowledgeRemoteWipe(final String tempKey) |
| throws IOException { |
| acknowledgeProvisionImpl(tempKey, PROVISION_STATUS_OK, true); |
| } |
| |
| /** |
| * Acknowledge that we've set the required policy. |
| * @param tempKey The security key of our current (temporary) policy. |
| * @param result One of {@link #PROVISION_STATUS_OK} or {@link #PROVISION_STATUS_PARTIAL} |
| * indicating how well we enforce the policy. |
| * @return A new security sync key, or null on failure. |
| * @throws IOException |
| */ |
| private String acknowledgeProvision(final String tempKey, final String result) |
| throws IOException { |
| return acknowledgeProvisionImpl(tempKey, result, false); |
| } |
| |
| /** |
| * Common function doing the work for acknowledging remote wipes or provisioning. |
| * @param tempKey The security key of our current (temporary) policy. |
| * @param status One of {@link #PROVISION_STATUS_OK} or {@link #PROVISION_STATUS_PARTIAL} |
| * indicating how well we enforce the policy. |
| * @param remoteWipe Whether this is a remote wipe. |
| * @return A new security sync key, or null on failure. |
| * @throws IOException |
| */ |
| private String acknowledgeProvisionImpl(final String tempKey, final String status, |
| final boolean remoteWipe) throws IOException { |
| final Serializer s = new Serializer(); |
| s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES); |
| s.start(Tags.PROVISION_POLICY); |
| |
| // Use the proper policy type, depending on EAS version |
| s.data(Tags.PROVISION_POLICY_TYPE, getPolicyType()); |
| |
| s.data(Tags.PROVISION_POLICY_KEY, tempKey); |
| s.data(Tags.PROVISION_STATUS, status); |
| s.end().end(); // PROVISION_POLICY, PROVISION_POLICIES |
| if (remoteWipe) { |
| s.start(Tags.PROVISION_REMOTE_WIPE); |
| s.data(Tags.PROVISION_STATUS, PROVISION_STATUS_OK); |
| s.end(); |
| } |
| s.end().done(); // PROVISION_PROVISION |
| EasResponse resp = sendHttpClientPost("Provision", s.toByteArray()); |
| try { |
| if (resp.getStatus() == HttpStatus.SC_OK) { |
| final ProvisionParser pp = new ProvisionParser(mContext, resp.getInputStream()); |
| if (pp.parse()) { |
| // Return the final policy key from the ProvisionParser |
| final String result = |
| (pp.getSecuritySyncKey() == null) ? "failed" : "confirmed"; |
| LogUtils.i(TAG, "Provision %s for %s set", result, |
| PROVISION_STATUS_PARTIAL.equals(status) ? "PART" : "FULL"); |
| return pp.getSecuritySyncKey(); |
| } |
| } |
| } finally { |
| resp.close(); |
| } |
| // On failures, log issue and return null |
| LogUtils.i(TAG, "Provisioning failed for %s set", |
| PROVISION_STATUS_PARTIAL.equals(status) ? "PART" : "FULL"); |
| return null; |
| } |
| |
| /** |
| * Send an Exchange Provision request, and process the response to see if we can handle the |
| * provisioning requirements returned by the server. |
| * @return A {@link ProvisionParser} for the response, or null if we can't handle it. |
| * @throws IOException |
| */ |
| private ProvisionParser canProvision() throws IOException { |
| final Serializer s = new Serializer(); |
| s.start(Tags.PROVISION_PROVISION); |
| if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_SP1_DOUBLE) { |
| // Send settings information in 14.1 and greater |
| s.start(Tags.SETTINGS_DEVICE_INFORMATION).start(Tags.SETTINGS_SET); |
| s.data(Tags.SETTINGS_MODEL, Build.MODEL); |
| //s.data(Tags.SETTINGS_IMEI, ""); |
| //s.data(Tags.SETTINGS_FRIENDLY_NAME, "Friendly Name"); |
| s.data(Tags.SETTINGS_OS, "Android " + Build.VERSION.RELEASE); |
| //s.data(Tags.SETTINGS_OS_LANGUAGE, ""); |
| //s.data(Tags.SETTINGS_PHONE_NUMBER, ""); |
| //s.data(Tags.SETTINGS_MOBILE_OPERATOR, ""); |
| s.data(Tags.SETTINGS_USER_AGENT, USER_AGENT); |
| s.end().end(); // SETTINGS_SET, SETTINGS_DEVICE_INFORMATION |
| } |
| s.start(Tags.PROVISION_POLICIES); |
| s.start(Tags.PROVISION_POLICY); |
| s.data(Tags.PROVISION_POLICY_TYPE, getPolicyType()); |
| s.end().end().end().done(); // PROVISION_POLICY, PROVISION_POLICIES, PROVISION_PROVISION |
| final EasResponse resp = sendHttpClientPost("Provision", s.toByteArray()); |
| try { |
| int code = resp.getStatus(); |
| if (code == HttpStatus.SC_OK) { |
| final ProvisionParser pp = new ProvisionParser(mContext, resp.getInputStream()); |
| if (pp.parse()) { |
| // The PolicySet in the ProvisionParser will have the requirements for all KNOWN |
| // policies. If others are required, hasSupportablePolicySet will be false |
| if (pp.hasSupportablePolicySet() && |
| getProtocolVersion() == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) { |
| // In EAS 14.0, we need the final security key in order to use the settings |
| // command |
| final String policyKey = acknowledgeProvision(pp.getSecuritySyncKey(), |
| PROVISION_STATUS_OK); |
| if (policyKey != null) { |
| pp.setSecuritySyncKey(policyKey); |
| } |
| } else if (!pp.hasSupportablePolicySet()) { |
| // Try to acknowledge using the "partial" status (i.e. we can partially |
| // accommodate the required policies). The server will agree to this if the |
| // "allow non-provisionable devices" setting is enabled on the server |
| LogUtils.i(TAG, "PolicySet is NOT fully supportable"); |
| if (acknowledgeProvision(pp.getSecuritySyncKey(), |
| PROVISION_STATUS_PARTIAL) != null) { |
| // The server's ok with our inability to support policies, so we'll |
| // clear them |
| pp.clearUnsupportablePolicies(); |
| } |
| } |
| return pp; |
| } |
| } |
| } finally { |
| resp.close(); |
| } |
| |
| // On failures, simply return null |
| return null; |
| } |
| |
| /** |
| * Process the provisioning requirements that's returned by the server in response to a |
| * Provision request. |
| * @param pp A {@link ProvisionParser} for the server response to the Provision request. |
| * @return Whether we successfully handled the provisioning requirements. |
| * @throws IOException |
| */ |
| private boolean tryProvision(final ProvisionParser pp) throws IOException { |
| if (pp == null) return false; |
| // Get the policies from ProvisionParser |
| final Policy policy = pp.getPolicy(); |
| final Policy oldPolicy; |
| // Grab the old policy (if any) |
| if (mAccount.mPolicyKey > 0) { |
| oldPolicy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey); |
| } else { |
| oldPolicy = null; |
| } |
| // Update the account with a null policyKey (the key we've gotten is |
| // temporary and cannot be used for syncing) |
| PolicyServiceProxy.setAccountPolicy(mContext, mAccount.mId, policy, null); |
| // Make sure mAccount is current (with latest policy key) |
| mAccount.refresh(mContext); |
| if (pp.getRemoteWipe()) { |
| // We've gotten a remote wipe command |
| LogUtils.i(TAG, "!!! Remote wipe request received"); |
| // Start by setting the account to security hold |
| PolicyServiceProxy.setAccountHoldFlag(mContext, mAccount, true); |
| |
| // First, we've got to acknowledge it, but wrap the wipe in try/catch so that |
| // we wipe the device regardless of any errors in acknowledgment |
| try { |
| LogUtils.i(TAG, "!!! Acknowledging remote wipe to server"); |
| acknowledgeRemoteWipe(pp.getSecuritySyncKey()); |
| } catch (Exception e) { |
| // Because remote wipe is such a high priority task, we don't want to |
| // circumvent it if there's an exception in acknowledgment |
| } |
| // Then, tell SecurityPolicy to wipe the device |
| LogUtils.i(TAG, "!!! Executing remote wipe"); |
| PolicyServiceProxy.remoteWipe(mContext); |
| return false; |
| } else if (pp.hasSupportablePolicySet() && PolicyServiceProxy.isActive(mContext, policy)) { |
| // See if the required policies are in force; if they are, acknowledge the policies |
| // to the server and get the final policy key |
| // NOTE: For EAS 14.0, we already have the acknowledgment in the ProvisionParser |
| String securitySyncKey; |
| if (getProtocolVersion() == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) { |
| securitySyncKey = pp.getSecuritySyncKey(); |
| } else { |
| securitySyncKey = acknowledgeProvision(pp.getSecuritySyncKey(), |
| PROVISION_STATUS_OK); |
| } |
| if (securitySyncKey != null) { |
| // If attachment policies have changed, fix up any affected attachment records |
| if (oldPolicy != null) { |
| if ((oldPolicy.mDontAllowAttachments != policy.mDontAllowAttachments) || |
| (oldPolicy.mMaxAttachmentSize != policy.mMaxAttachmentSize)) { |
| Policy.setAttachmentFlagsForNewPolicy(mContext, mAccount, policy); |
| } |
| } |
| // Write the final policy key to the Account and say we've been successful |
| PolicyServiceProxy.setAccountPolicy(mContext, mAccount.mId, policy, |
| securitySyncKey); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Do the heavy lifting for validation and sync: |
| * - HTTP OPTIONS request to get protocol version from the server, if we don't already have it. |
| * - Exchange FolderSync request to get the folder info. |
| * (And exception handling for those operations.) |
| * Validation differs from sync in four ways: |
| * - Validation registers the client cert. |
| * - Validation doesn't save the FolderSync results. |
| * - Validation doesn't attempt to set device policies. |
| * - Validation must populate a bundle with the results of the validation. |
| * @param bundle If this is a validation call, this will be non-null, and this function will |
| * write the results to it (specifically it writes |
| * {@link EmailServiceProxy#VALIDATE_BUNDLE_RESULT_CODE}, |
| * {@link EmailServiceProxy#VALIDATE_BUNDLE_PROTOCOL_VERSION}, and |
| * {@link EmailServiceProxy#VALIDATE_BUNDLE_POLICY_SET} (when there's a policy to |
| * be had). |
| * If this is a sync call, bundle will be null. |
| * This function also uses the null/not null status to differentiate behavior in |
| * the few places where validation and sync don't do the same thing. |
| */ |
| protected void doValidationOrSync(final Bundle bundle) { |
| LogUtils.i(TAG, "Performing %s: %s, %s, ssl = %s", bundle != null ? "validation" : "sync", |
| mHostAuth.mAddress, mHostAuth.mLogin, mHostAuth.shouldUseSsl() ? "1" : "0"); |
| |
| if (bundle != null) { |
| if (mHostAuth.mClientCertAlias != null) { |
| try { |
| getClientConnectionManager().registerClientCert(mContext, mHostAuth); |
| } catch (final CertificateException e) { |
| // The client certificate the user specified is invalid/inaccessible. |
| bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, |
| MessagingException.CLIENT_CERTIFICATE_ERROR); |
| return; |
| } |
| } |
| } |
| |
| int resultCode; |
| |
| // Need a nested try here because the provisioning exception handler can throw IOException. |
| try { |
| try { |
| // TODO: also want to check protocol version at least once in a while after setup. |
| if (mAccount.mProtocolVersion == null) { |
| final int optionsResult = getServerProtocolVersion(); |
| if (optionsResult != MessagingException.NO_ERROR) { |
| if (bundle != null) { |
| bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, |
| optionsResult); |
| } |
| return; |
| } |
| if (bundle != null) { |
| bundle.putString(EmailServiceProxy.VALIDATE_BUNDLE_PROTOCOL_VERSION, |
| mAccount.mProtocolVersion); |
| } |
| } |
| resultCode = doFolderSync(bundle != null); |
| } catch (final CommandStatusException e) { |
| final int status = e.mStatus; |
| if (CommandStatus.isNeedsProvisioning(status)) { |
| // Get the policies and see if we are able to support them |
| final ProvisionParser pp = canProvision(); |
| if (pp != null && pp.hasSupportablePolicySet()) { |
| // Set the proper result code and save the PolicySet in our Bundle |
| if (bundle != null) { |
| resultCode = MessagingException.SECURITY_POLICIES_REQUIRED; |
| bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET, |
| pp.getPolicy()); |
| if (getProtocolVersion() == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) { |
| mAccount.mSecuritySyncKey = pp.getSecuritySyncKey(); |
| if (!sendSettings()) { |
| LogUtils.i(TAG, "Denied access: %s", |
| CommandStatus.toString(status)); |
| resultCode = MessagingException.ACCESS_DENIED; |
| } |
| } |
| } else if (tryProvision(pp)) { |
| resultCode = MessagingException.NO_ERROR; |
| } else { |
| resultCode = MessagingException.GENERAL_SECURITY; |
| } |
| } else { |
| // If not, set the proper code (the account will not be created) |
| resultCode = MessagingException.SECURITY_POLICIES_UNSUPPORTED; |
| if (bundle != null) { |
| bundle.putParcelable(EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET, |
| pp.getPolicy()); |
| } |
| } |
| } else if (CommandStatus.isDeniedAccess(status)) { |
| LogUtils.i(TAG, "Denied access: %s", CommandStatus.toString(status)); |
| resultCode = MessagingException.ACCESS_DENIED; |
| } else if (CommandStatus.isTransientError(status)) { |
| LogUtils.i(TAG, "Transient error: %s", CommandStatus.toString(status)); |
| resultCode = MessagingException.IOERROR; |
| } else { |
| LogUtils.i(TAG, "Unexpected response: %s", CommandStatus.toString(status)); |
| resultCode = MessagingException.UNSPECIFIED_EXCEPTION; |
| } |
| } catch (final RedirectException e) { |
| // We handle a limited number of redirects by recursion. |
| if (mRedirectCount < MAX_REDIRECTS && e.mRedirectAddress != null) { |
| ++mRedirectCount; |
| redirectHostAuth(e.mRedirectAddress); |
| if (bundle != null) { |
| bundle.putString(EmailServiceProxy.VALIDATE_BUNDLE_REDIRECT_ADDRESS, |
| e.mRedirectAddress); |
| } |
| doValidationOrSync(bundle); |
| return; |
| } else { |
| resultCode = MessagingException.UNSPECIFIED_EXCEPTION; |
| } |
| } |
| } catch (final IOException e) { |
| final Throwable cause = e.getCause(); |
| if (cause != null && cause instanceof CertificateException) { |
| // This could be because the server's certificate failed to validate. |
| resultCode = MessagingException.GENERAL_SECURITY; |
| } else { |
| resultCode = MessagingException.IOERROR; |
| } |
| } |
| |
| if (bundle != null) { |
| bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, resultCode); |
| } |
| } |
| |
| |
| /** |
| * Perform the actual validation. |
| * @return The validation response. |
| */ |
| public Bundle validate() { |
| final Bundle bundle = new Bundle(); |
| doValidationOrSync(bundle); |
| return bundle; |
| } |
| } |