diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java index 8766c617a..5aa9fd97a 100644 --- a/src/com/android/exchange/EasSyncService.java +++ b/src/com/android/exchange/EasSyncService.java @@ -40,6 +40,7 @@ import com.android.exchange.adapter.EmailSyncAdapter; import com.android.exchange.adapter.FolderSyncParser; import com.android.exchange.adapter.MeetingResponseParser; import com.android.exchange.adapter.PingParser; +import com.android.exchange.adapter.ProvisionParser; import com.android.exchange.adapter.Serializer; import com.android.exchange.adapter.Tags; import com.android.exchange.adapter.Parser.EasParserException; @@ -142,6 +143,9 @@ public class EasSyncService extends AbstractSyncService { static private final int PING_FALLBACK_INBOX = 5; static private final int PING_FALLBACK_PIM = 25; + // MSFT's custom HTTP result code indicating the need to provision + static private final int HTTP_NEED_PROVISIONING = 449; + // Reasonable default public String mProtocolVersion = DEFAULT_PROTOCOL_VERSION; public Double mProtocolVersionDouble; @@ -224,6 +228,19 @@ public class EasSyncService extends AbstractSyncService { return (code == HttpStatus.SC_UNAUTHORIZED) || (code == HttpStatus.SC_FORBIDDEN); } + private void setupProtocolVersion(EasSyncService service, Header versionHeader) { + String versions = versionHeader.getValue(); + if (versions != null) { + if (versions.contains("12.0")) { + service.mProtocolVersion = "12.0"; + } + service.mProtocolVersionDouble = Double.parseDouble(service.mProtocolVersion); + if (service.mAccount != null) { + service.mAccount.mProtocolVersion = service.mProtocolVersion; + } + } + } + @Override public void validateAccount(String hostAddress, String userName, String password, int port, boolean ssl, boolean trustCertificates, Context context) throws MessagingException { @@ -251,6 +268,9 @@ public class EasSyncService extends AbstractSyncService { throw new MessagingException(MessagingException.IOERROR); } + // Make sure we've got the right protocol version set up + setupProtocolVersion(svc, versions); + // Run second test here for provisioning failures... Serializer s = new Serializer(); userLog("Try folder sync"); @@ -258,20 +278,17 @@ public class EasSyncService extends AbstractSyncService { .end().end().done(); resp = svc.sendHttpClientPost("FolderSync", s.toByteArray()); code = resp.getStatusLine().getStatusCode(); - if (code == HttpStatus.SC_FORBIDDEN) { - throw new MessagingException(MessagingException.SECURITY_POLICIES_UNSUPPORTED); + // We'll get one of the following responses if policies are required by the server + if (code == HttpStatus.SC_FORBIDDEN || code == HTTP_NEED_PROVISIONING) { + // Get the policies and see if we are able to support them + if (svc.canProvision()) { + // If so, send the advisory Exception (the account may be created later) + throw new MessagingException(MessagingException.SECURITY_POLICIES_REQUIRED); + } else + // If not, send the unsupported Exception (the account won't be created) + throw new MessagingException( + MessagingException.SECURITY_POLICIES_UNSUPPORTED); } - // PLACEHOLDER: Replace the above simple check with a more sophisticated - // check of server-mandated security policy support. There are three outcomes. - // 1. As below, if no policies required, simply return here as-is. - // 2. As above, if policies are required that we do not support, throw - // MessagingException.SECURITY_POLICIES_UNSUPPORTED. This is a validation - // failure. - // 3. New code: If policies are required that we *do* support, throw - // MessagingException.SECURITY_POLICIES_REQUIRED. This is an advisory to the - // UI that new policies will be required in order to use this account. - // See also: isSupported(PolicySet policies) - userLog("Validation successful"); return; } @@ -882,6 +899,29 @@ public class EasSyncService extends AbstractSyncService { } } + // TODO This is Exchange 2007 only at this point + private boolean canProvision() throws IOException { + Serializer s = new Serializer(); + s.start(Tags.PROVISION_PROVISION).start(Tags.PROVISION_POLICIES); + s.start(Tags.PROVISION_POLICY).data(Tags.PROVISION_POLICY_TYPE, "MS-EAS-Provisioning-WBXML") + .end().end().end().done(); + HttpResponse resp = sendHttpClientPost("Provision", s.toByteArray()); + int code = resp.getStatusLine().getStatusCode(); + if (code == HttpStatus.SC_OK) { + InputStream is = resp.getEntity().getContent(); + ProvisionParser pp = new ProvisionParser(is, this); + if (pp.parse()) { + // If true, we received policies from the server + // Retrieve them and write them to the framework + PolicySet ps = pp.getPolicySet(); + if (SecurityPolicy.getInstance(mContext).isSupported(ps)) { + return true; + } + } + } + return false; + } + /** * Performs FolderSync * diff --git a/src/com/android/exchange/adapter/ProvisionParser.java b/src/com/android/exchange/adapter/ProvisionParser.java new file mode 100644 index 000000000..ffa1e45a1 --- /dev/null +++ b/src/com/android/exchange/adapter/ProvisionParser.java @@ -0,0 +1,178 @@ +/* Copyright (C) 2010 The Android Open Source Project. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.exchange.adapter; + +import com.android.email.SecurityPolicy; +import com.android.email.SecurityPolicy.PolicySet; +import com.android.exchange.EasSyncService; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Parse the result of the Provision command + * + * Assuming a successful parse, we store the PolicySet and the policy key + */ +public class ProvisionParser extends Parser { + private EasSyncService mService; + PolicySet mPolicySet = null; + String mPolicyKey = null; + + public ProvisionParser(InputStream in, EasSyncService service) throws IOException { + super(in); + mService = service; + setDebug(true); + } + + public PolicySet getPolicySet() { + return mPolicySet; + } + + public String getPolicyKey() { + return mPolicyKey; + } + + public void parseProvisionDoc() throws IOException { + int minPasswordLength = 0; + int passwordMode = PolicySet.PASSWORD_MODE_NONE; + int maxPasswordFails = 0; + int maxScreenLockTime = 0; + boolean canSupport = false; + + while (nextTag(Tags.PROVISION_EAS_PROVISION_DOC) != END) { + switch (tag) { + case Tags.PROVISION_DEVICE_PASSWORD_ENABLED: + if (getValueInt() == 1) { + if (passwordMode == PolicySet.PASSWORD_MODE_NONE) { + passwordMode = PolicySet.PASSWORD_MODE_SIMPLE; + } + } + break; + case Tags.PROVISION_MIN_DEVICE_PASSWORD_LENGTH: + minPasswordLength = getValueInt(); + break; + case Tags.PROVISION_ALPHA_DEVICE_PASSWORD_ENABLED: + if (getValueInt() == 1) { + passwordMode = PolicySet.PASSWORD_MODE_STRONG; + } + break; + case Tags.PROVISION_MAX_INACTIVITY_TIME_DEVICE_LOCK: + // EAS gives us seconds, which is, happily, what the PolicySet requires + maxScreenLockTime = getValueInt(); + break; + case Tags.PROVISION_MAX_DEVICE_PASSWORD_FAILED_ATTEMPTS: + maxPasswordFails = getValueInt(); + break; + case Tags.PROVISION_ALLOW_SIMPLE_DEVICE_PASSWORD: + // Ignore this unless there's any MSFT documentation for what this means + // Hint: I haven't seen any that's more specific than "simple" + getValue(); + break; + // The following policy, if false, can't be supported at the moment + case Tags.PROVISION_ATTACHMENTS_ENABLED: + if (getValueInt() == 0) { + canSupport = false; + } + break; + // The following policies, if true, can't be supported at the moment + case Tags.PROVISION_DEVICE_ENCRYPTION_ENABLED: + case Tags.PROVISION_PASSWORD_RECOVERY_ENABLED: + case Tags.PROVISION_DEVICE_PASSWORD_EXPIRATION: + case Tags.PROVISION_DEVICE_PASSWORD_HISTORY: + case Tags.PROVISION_MAX_ATTACHMENT_SIZE: + if (getValueInt() == 1) { + canSupport = false; + } + break; + default: + skipTag(); + } + } + + if (canSupport) { + mPolicySet = new SecurityPolicy.PolicySet(minPasswordLength, passwordMode, + maxPasswordFails, maxScreenLockTime, true); + } + } + + public void parseProvisionData() throws IOException { + while (nextTag(Tags.PROVISION_DATA) != END) { + if (tag == Tags.PROVISION_EAS_PROVISION_DOC) { + parseProvisionDoc(); + } else { + skipTag(); + } + } + } + + public void parsePolicy() throws IOException { + while (nextTag(Tags.PROVISION_POLICY) != END) { + switch (tag) { + case Tags.PROVISION_POLICIES: + parsePolicies(); + break; + case Tags.PROVISION_POLICY_TYPE: + mService.userLog("Policy type: ", getValue()); + break; + case Tags.PROVISION_POLICY_KEY: + mPolicyKey = getValue(); + break; + case Tags.PROVISION_STATUS: + mService.userLog("Policy status: ", getValue()); + break; + case Tags.PROVISION_DATA: + parseProvisionData(); + break; + default: + skipTag(); + } + } + } + + public void parsePolicies() throws IOException { + while (nextTag(Tags.PROVISION_POLICIES) != END) { + if (tag == Tags.PROVISION_POLICY) { + parsePolicy(); + } else { + skipTag(); + } + } + } + + @Override + public boolean parse() throws IOException { + boolean res = false; + if (nextTag(START_DOCUMENT) != Tags.PROVISION_PROVISION) { + throw new IOException(); + } + while (nextTag(START_DOCUMENT) != END_DOCUMENT) { + switch (tag) { + case Tags.PROVISION_STATUS: + int status = getValueInt(); + mService.userLog("Provision status: ", status); + break; + case Tags.PROVISION_POLICIES: + parsePolicies(); + return (mPolicySet != null); + default: + skipTag(); + } + } + return res; + } +} + diff --git a/src/com/android/exchange/adapter/Tags.java b/src/com/android/exchange/adapter/Tags.java index 2c5c97154..c2385d53e 100644 --- a/src/com/android/exchange/adapter/Tags.java +++ b/src/com/android/exchange/adapter/Tags.java @@ -43,6 +43,7 @@ public class Tags { public static final int TASK = 0x09; public static final int CONTACTS2 = 0x0C; public static final int PING = 0x0D; + public static final int PROVISION = 0x0E; public static final int GAL = 0x10; public static final int BASE = 0x11; @@ -345,7 +346,6 @@ public class Tags { public static final int CONTACTS2_NICKNAME = CONTACTS2_PAGE + 0xD; public static final int CONTACTS2_MMS = CONTACTS2_PAGE + 0xE; - // The Ping constants are used by EasSyncService, and need to be public public static final int PING_PAGE = PING << PAGE_SHIFT; public static final int PING_PING = PING_PAGE + 5; public static final int PING_AUTD_STATE = PING_PAGE + 6; @@ -357,6 +357,66 @@ public class Tags { public static final int PING_CLASS = PING_PAGE + 0xC; public static final int PING_MAX_FOLDERS = PING_PAGE + 0xD; + public static final int PROVISION_PAGE = PROVISION << PAGE_SHIFT; + // EAS 2.5 + public static final int PROVISION_PROVISION = PROVISION_PAGE + 5; + public static final int PROVISION_POLICIES = PROVISION_PAGE + 6; + public static final int PROVISION_POLICY = PROVISION_PAGE + 7; + public static final int PROVISION_POLICY_TYPE = PROVISION_PAGE + 8; + public static final int PROVISION_POLICY_KEY = PROVISION_PAGE + 9; + public static final int PROVISION_DATA = PROVISION_PAGE + 0xA; + public static final int PROVISION_STATUS = PROVISION_PAGE + 0xB; + public static final int PROVISION_REMOTE_WIPE = PROVISION_PAGE + 0xC; + // EAS 12.0 + public static final int PROVISION_EAS_PROVISION_DOC = PROVISION_PAGE + 0xD; + public static final int PROVISION_DEVICE_PASSWORD_ENABLED = PROVISION_PAGE + 0xE; + public static final int PROVISION_ALPHA_DEVICE_PASSWORD_ENABLED = PROVISION_PAGE + 0xF; + public static final int PROVISION_DEVICE_ENCRYPTION_ENABLED = PROVISION_PAGE + 0x10; + public static final int PROVISION_PASSWORD_RECOVERY_ENABLED = PROVISION_PAGE + 0x11; + public static final int PROVISION_ATTACHMENTS_ENABLED = PROVISION_PAGE + 0x13; + public static final int PROVISION_MIN_DEVICE_PASSWORD_LENGTH = PROVISION_PAGE + 0x14; + public static final int PROVISION_MAX_INACTIVITY_TIME_DEVICE_LOCK = PROVISION_PAGE + 0x15; + public static final int PROVISION_MAX_DEVICE_PASSWORD_FAILED_ATTEMPTS = PROVISION_PAGE + 0x16; + public static final int PROVISION_MAX_ATTACHMENT_SIZE = PROVISION_PAGE + 0x17; + public static final int PROVISION_ALLOW_SIMPLE_DEVICE_PASSWORD = PROVISION_PAGE + 0x18; + public static final int PROVISION_DEVICE_PASSWORD_EXPIRATION = PROVISION_PAGE + 0x19; + public static final int PROVISION_DEVICE_PASSWORD_HISTORY = PROVISION_PAGE + 0x1A; + public static final int PROVISION_MAX_SUPPORTED_TAG = PROVISION_DEVICE_PASSWORD_HISTORY; + + // EAS 12.1 + public static final int PROVISION_ALLOW_STORAGE_CARD = PROVISION_PAGE + 0x1B; + public static final int PROVISION_ALLOW_CAMERA = PROVISION_PAGE + 0x1C; + public static final int PROVISION_REQUIRE_DEVICE_ENCRYPTION = PROVISION_PAGE + 0x1D; + public static final int PROVISION_ALLOW_UNSIGNED_APPLICATIONS = PROVISION_PAGE + 0x1E; + public static final int PROVISION_ALLOW_UNSIGNED_INSTALLATION_PACKAGES = PROVISION_PAGE + 0x1F; + public static final int PROVISION_MIN_DEVICE_PASSWORD_COMPLEX_CHARS = PROVISION_PAGE + 0x20; + public static final int PROVISION_ALLOW_WIFI = PROVISION_PAGE + 0x21; + public static final int PROVISION_ALLOW_TEXT_MESSAGING = PROVISION_PAGE + 0x22; + public static final int PROVISION_ALLOW_POP_IMAP_EMAIL = PROVISION_PAGE + 0x23; + public static final int PROVISION_ALLOW_BLUETOOTH = PROVISION_PAGE + 0x24; + public static final int PROVISION_ALLOW_IRDA = PROVISION_PAGE + 0x25; + public static final int PROVISION_REQUIRE_MANUAL_SYNC_WHEN_ROAMING = PROVISION_PAGE + 0x26; + public static final int PROVISION_ALLOW_DESKTOP_SYNC = PROVISION_PAGE + 0x27; + public static final int PROVISION_MAX_CALENDAR_AGE_FILTER = PROVISION_PAGE + 0x28; + public static final int PROVISION_ALLOW_HTML_EMAIL = PROVISION_PAGE + 0x29; + public static final int PROVISION_MAX_EMAIL_AGE_FILTER = PROVISION_PAGE + 0x2A; + public static final int PROVISION_MAX_EMAIL_BODY_TRUNCATION_SIZE = PROVISION_PAGE + 0x2B; + public static final int PROVISION_MAX_EMAIL_HTML_BODY_TRUNCATION_SIZE = PROVISION_PAGE + 0x2C; + public static final int PROVISION_REQUIRE_SIGNED_SMIME_MESSAGES = PROVISION_PAGE + 0x2D; + public static final int PROVISION_REQUIRE_ENCRYPTED_SMIME_MESSAGES = PROVISION_PAGE + 0x2E; + public static final int PROVISION_REQUIRE_SIGNED_SMIME_ALGORITHM = PROVISION_PAGE + 0x2F; + public static final int PROVISION_REQUIRE_ENCRYPTION_SMIME_ALGORITHM = PROVISION_PAGE + 0x30; + public static final int PROVISION_ALLOW_SMIME_ENCRYPTION_NEGOTIATION = PROVISION_PAGE + 0x31; + public static final int PROVISION_ALLOW_SMIME_SOFT_CERTS = PROVISION_PAGE + 0x32; + public static final int PROVISION_ALLOW_BROWSER = PROVISION_PAGE + 0x33; + public static final int PROVISION_ALLOW_CONSUMER_EMAIL = PROVISION_PAGE + 0x34; + public static final int PROVISION_ALLOW_REMOTE_DESKTOP = PROVISION_PAGE + 0x35; + public static final int PROVISION_ALLOW_INTERNET_SHARING = PROVISION_PAGE + 0x36; + public static final int PROVISION_UNAPPROVED_IN_ROM_APPLICATION_LIST = PROVISION_PAGE + 0x37; + public static final int PROVISION_APPLICATION_NAME = PROVISION_PAGE + 0x38; + public static final int PROVISION_APPROVED_APPLICATION_LIST = PROVISION_PAGE + 0x39; + public static final int PROVISION_HASH = PROVISION_PAGE + 0x3A; + public static final int BASE_PAGE = BASE << PAGE_SHIFT; public static final int BASE_BODY_PREFERENCE = BASE_PAGE + 5; public static final int BASE_TYPE = BASE_PAGE + 6; @@ -487,7 +547,8 @@ public class Tags { "Provision", "Policies", "Policy", "PolicyType", "PolicyKey", "Data", "ProvisionStatus", "RemoteWipe", "EASProvidionDoc", "DevicePasswordEnabled", "AlphanumericDevicePasswordRequired", - "DeviceEncryptionEnabled", "-unused-", "AttachmentsEnabled", "MinDevicePasswordLength", + "DeviceEncryptionEnabled", "PasswordRecoveryEnabled", "-unused-", "AttachmentsEnabled", + "MinDevicePasswordLength", "MaxInactivityTimeDeviceLock", "MaxDevicePasswordFailedAttempts", "MaxAttachmentSize", "AllowSimpleDevicePassword", "DevicePasswordExpiration", "DevicePasswordHistory", "AllowStorageCard", "AllowCamera", "RequireDeviceEncryption",