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",