Merge "Initial implementation of EAS security"
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index 8766c61..5aa9fd9 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -40,6 +40,7 @@
 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 @@
     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 @@
         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 @@
                     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 @@
                     .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 @@
         }
     }
 
+    // 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 0000000..ffa1e45
--- /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 2c5c971..c2385d5 100644
--- a/src/com/android/exchange/adapter/Tags.java
+++ b/src/com/android/exchange/adapter/Tags.java
@@ -43,6 +43,7 @@
     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 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 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 @@
             "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",