1 /* Copyright (C) 2010 The Android Open Source Project. 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package com.android.exchange.adapter; 17 18 import android.app.admin.DevicePolicyManager; 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.os.storage.StorageManager; 22 import android.os.storage.StorageVolume; 23 24 import com.android.emailcommon.provider.Policy; 25 import com.android.exchange.EasSyncService; 26 import com.android.exchange.R; 27 28 import org.xmlpull.v1.XmlPullParser; 29 import org.xmlpull.v1.XmlPullParserException; 30 import org.xmlpull.v1.XmlPullParserFactory; 31 32 import java.io.ByteArrayInputStream; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.util.ArrayList; 36 37 /** 38 * Parse the result of the Provision command 39 */ 40 public class ProvisionParser extends Parser { 41 private final EasSyncService mService; 42 private Policy mPolicy = null; 43 private String mSecuritySyncKey = null; 44 private boolean mRemoteWipe = false; 45 private boolean mIsSupportable = true; 46 private boolean smimeRequired = false; 47 private final Resources mResources; 48 49 public ProvisionParser(InputStream in, EasSyncService service) throws IOException { 50 super(in); 51 mService = service; 52 mResources = service.mContext.getResources(); 53 } 54 55 public Policy getPolicy() { 56 return mPolicy; 57 } 58 59 public String getSecuritySyncKey() { 60 return mSecuritySyncKey; 61 } 62 63 public void setSecuritySyncKey(String securitySyncKey) { 64 mSecuritySyncKey = securitySyncKey; 65 } 66 67 public boolean getRemoteWipe() { 68 return mRemoteWipe; 69 } 70 71 public boolean hasSupportablePolicySet() { 72 return (mPolicy != null) && mIsSupportable; 73 } 74 75 public void clearUnsupportablePolicies() { 76 mIsSupportable = true; 77 mPolicy.mProtocolPoliciesUnsupported = null; 78 } 79 80 private void addPolicyString(StringBuilder sb, int res) { 81 sb.append(mResources.getString(res)); 82 sb.append(Policy.POLICY_STRING_DELIMITER); 83 } 84 85 /** 86 * Complete setup of a Policy; we normalize it first (removing inconsistencies, etc.) and then 87 * generate the tokenized "protocol policies enforced" string. Note that unsupported policies 88 * must have been added prior to calling this method (this is only a possibility with wbxml 89 * policy documents, as all versions of the OS support the policies in xml documents). 90 */ 91 private void setPolicy(Policy policy) { 92 policy.normalize(); 93 StringBuilder sb = new StringBuilder(); 94 if (policy.mDontAllowAttachments) { 95 addPolicyString(sb, R.string.policy_dont_allow_attachments); 96 } 97 if (policy.mRequireManualSyncWhenRoaming) { 98 addPolicyString(sb, R.string.policy_require_manual_sync_roaming); 99 } 100 policy.mProtocolPoliciesEnforced = sb.toString(); 101 mPolicy = policy; 102 } 103 104 private boolean deviceSupportsEncryption() { 105 DevicePolicyManager dpm = (DevicePolicyManager) 106 mService.mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); 107 int status = dpm.getStorageEncryptionStatus(); 108 return status != DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; 109 } 110 111 private void parseProvisionDocWbxml() throws IOException { 112 Policy policy = new Policy(); 113 ArrayList<Integer> unsupportedList = new ArrayList<Integer>(); 114 boolean passwordEnabled = false; 115 116 while (nextTag(Tags.PROVISION_EAS_PROVISION_DOC) != END) { 117 boolean tagIsSupported = true; 118 int res = 0; 119 switch (tag) { 120 case Tags.PROVISION_DEVICE_PASSWORD_ENABLED: 121 if (getValueInt() == 1) { 122 passwordEnabled = true; 123 if (policy.mPasswordMode == Policy.PASSWORD_MODE_NONE) { 124 policy.mPasswordMode = Policy.PASSWORD_MODE_SIMPLE; 125 } 126 } 127 break; 128 case Tags.PROVISION_MIN_DEVICE_PASSWORD_LENGTH: 129 policy.mPasswordMinLength = getValueInt(); 130 break; 131 case Tags.PROVISION_ALPHA_DEVICE_PASSWORD_ENABLED: 132 if (getValueInt() == 1) { 133 policy.mPasswordMode = Policy.PASSWORD_MODE_STRONG; 134 } 135 break; 136 case Tags.PROVISION_MAX_INACTIVITY_TIME_DEVICE_LOCK: 137 // EAS gives us seconds, which is, happily, what the PolicySet requires 138 policy.mMaxScreenLockTime = getValueInt(); 139 break; 140 case Tags.PROVISION_MAX_DEVICE_PASSWORD_FAILED_ATTEMPTS: 141 policy.mPasswordMaxFails = getValueInt(); 142 break; 143 case Tags.PROVISION_DEVICE_PASSWORD_EXPIRATION: 144 policy.mPasswordExpirationDays = getValueInt(); 145 break; 146 case Tags.PROVISION_DEVICE_PASSWORD_HISTORY: 147 policy.mPasswordHistory = getValueInt(); 148 break; 149 case Tags.PROVISION_ALLOW_CAMERA: 150 policy.mDontAllowCamera = (getValueInt() == 0); 151 break; 152 case Tags.PROVISION_ALLOW_SIMPLE_DEVICE_PASSWORD: 153 // Ignore this unless there's any MSFT documentation for what this means 154 // Hint: I haven't seen any that's more specific than "simple" 155 getValue(); 156 break; 157 // The following policies, if false, can't be supported at the moment 158 case Tags.PROVISION_ALLOW_STORAGE_CARD: 159 case Tags.PROVISION_ALLOW_UNSIGNED_APPLICATIONS: 160 case Tags.PROVISION_ALLOW_UNSIGNED_INSTALLATION_PACKAGES: 161 case Tags.PROVISION_ALLOW_WIFI: 162 case Tags.PROVISION_ALLOW_TEXT_MESSAGING: 163 case Tags.PROVISION_ALLOW_POP_IMAP_EMAIL: 164 case Tags.PROVISION_ALLOW_IRDA: 165 case Tags.PROVISION_ALLOW_HTML_EMAIL: 166 case Tags.PROVISION_ALLOW_BROWSER: 167 case Tags.PROVISION_ALLOW_CONSUMER_EMAIL: 168 case Tags.PROVISION_ALLOW_INTERNET_SHARING: 169 if (getValueInt() == 0) { 170 tagIsSupported = false; 171 switch(tag) { 172 case Tags.PROVISION_ALLOW_STORAGE_CARD: 173 res = R.string.policy_dont_allow_storage_cards; 174 break; 175 case Tags.PROVISION_ALLOW_UNSIGNED_APPLICATIONS: 176 res = R.string.policy_dont_allow_unsigned_apps; 177 break; 178 case Tags.PROVISION_ALLOW_UNSIGNED_INSTALLATION_PACKAGES: 179 res = R.string.policy_dont_allow_unsigned_installers; 180 break; 181 case Tags.PROVISION_ALLOW_WIFI: 182 res = R.string.policy_dont_allow_wifi; 183 break; 184 case Tags.PROVISION_ALLOW_TEXT_MESSAGING: 185 res = R.string.policy_dont_allow_text_messaging; 186 break; 187 case Tags.PROVISION_ALLOW_POP_IMAP_EMAIL: 188 res = R.string.policy_dont_allow_pop_imap; 189 break; 190 case Tags.PROVISION_ALLOW_IRDA: 191 res = R.string.policy_dont_allow_irda; 192 break; 193 case Tags.PROVISION_ALLOW_HTML_EMAIL: 194 res = R.string.policy_dont_allow_html; 195 policy.mDontAllowHtml = true; 196 break; 197 case Tags.PROVISION_ALLOW_BROWSER: 198 res = R.string.policy_dont_allow_browser; 199 break; 200 case Tags.PROVISION_ALLOW_CONSUMER_EMAIL: 201 res = R.string.policy_dont_allow_consumer_email; 202 break; 203 case Tags.PROVISION_ALLOW_INTERNET_SHARING: 204 res = R.string.policy_dont_allow_internet_sharing; 205 break; 206 } 207 if (res > 0) { 208 unsupportedList.add(res); 209 } 210 } 211 break; 212 case Tags.PROVISION_ATTACHMENTS_ENABLED: 213 policy.mDontAllowAttachments = getValueInt() != 1; 214 break; 215 // Bluetooth: 0 = no bluetooth; 1 = only hands-free; 2 = allowed 216 case Tags.PROVISION_ALLOW_BLUETOOTH: 217 if (getValueInt() != 2) { 218 tagIsSupported = false; 219 unsupportedList.add(R.string.policy_bluetooth_restricted); 220 } 221 break; 222 // We may now support device (internal) encryption; we'll check this capability 223 // below with the call to SecurityPolicy.isSupported() 224 case Tags.PROVISION_REQUIRE_DEVICE_ENCRYPTION: 225 if (getValueInt() == 1) { 226 if (!deviceSupportsEncryption()) { 227 tagIsSupported = false; 228 unsupportedList.add(R.string.policy_require_encryption); 229 } else { 230 policy.mRequireEncryption = true; 231 } 232 } 233 break; 234 // Note that DEVICE_ENCRYPTION_ENABLED refers to SD card encryption, which the OS 235 // does not yet support. 236 case Tags.PROVISION_DEVICE_ENCRYPTION_ENABLED: 237 if (getValueInt() == 1) { 238 log("Policy requires SD card encryption"); 239 // Let's see if this can be supported on our device... 240 if (deviceSupportsEncryption()) { 241 StorageManager sm = (StorageManager)mService.mContext.getSystemService( 242 Context.STORAGE_SERVICE); 243 // NOTE: Private API! 244 // Go through volumes; if ANY are removable, we can't support this 245 // policy. 246 StorageVolume[] volumeList = sm.getVolumeList(); 247 for (StorageVolume volume: volumeList) { 248 if (volume.isRemovable()) { 249 tagIsSupported = false; 250 log("Removable: " + volume.getDescription(mService.mContext)); 251 break; // Break only from the storage volume loop 252 } else { 253 log("Not Removable: " + volume.getDescription(mService.mContext)); 254 } 255 } 256 if (tagIsSupported) { 257 // If this policy is requested, we MUST also require encryption 258 log("Device supports SD card encryption"); 259 policy.mRequireEncryption = true; 260 break; 261 } 262 } else { 263 log("Device doesn't support encryption; failing"); 264 tagIsSupported = false; 265 } 266 // If we fall through, we can't support the policy 267 unsupportedList.add(R.string.policy_require_sd_encryption); 268 } 269 break; 270 // Note this policy; we enforce it in ExchangeService 271 case Tags.PROVISION_REQUIRE_MANUAL_SYNC_WHEN_ROAMING: 272 policy.mRequireManualSyncWhenRoaming = getValueInt() == 1; 273 break; 274 // We are allowed to accept policies, regardless of value of this tag 275 // TODO: When we DO support a recovery password, we need to store the value in 276 // the account (so we know to utilize it) 277 case Tags.PROVISION_PASSWORD_RECOVERY_ENABLED: 278 // Read, but ignore, value 279 policy.mPasswordRecoveryEnabled = getValueInt() == 1; 280 break; 281 // The following policies, if true, can't be supported at the moment 282 case Tags.PROVISION_REQUIRE_SIGNED_SMIME_MESSAGES: 283 case Tags.PROVISION_REQUIRE_ENCRYPTED_SMIME_MESSAGES: 284 case Tags.PROVISION_REQUIRE_SIGNED_SMIME_ALGORITHM: 285 case Tags.PROVISION_REQUIRE_ENCRYPTION_SMIME_ALGORITHM: 286 if (getValueInt() == 1) { 287 tagIsSupported = false; 288 if (!smimeRequired) { 289 unsupportedList.add(R.string.policy_require_smime); 290 smimeRequired = true; 291 } 292 } 293 break; 294 case Tags.PROVISION_MAX_ATTACHMENT_SIZE: 295 int max = getValueInt(); 296 if (max > 0) { 297 policy.mMaxAttachmentSize = max; 298 } 299 break; 300 // Complex characters are supported 301 case Tags.PROVISION_MIN_DEVICE_PASSWORD_COMPLEX_CHARS: 302 policy.mPasswordComplexChars = getValueInt(); 303 break; 304 // The following policies are moot; they allow functionality that we don't support 305 case Tags.PROVISION_ALLOW_DESKTOP_SYNC: 306 case Tags.PROVISION_ALLOW_SMIME_ENCRYPTION_NEGOTIATION: 307 case Tags.PROVISION_ALLOW_SMIME_SOFT_CERTS: 308 case Tags.PROVISION_ALLOW_REMOTE_DESKTOP: 309 skipTag(); 310 break; 311 // We don't handle approved/unapproved application lists 312 case Tags.PROVISION_UNAPPROVED_IN_ROM_APPLICATION_LIST: 313 case Tags.PROVISION_APPROVED_APPLICATION_LIST: 314 // Parse and throw away the content 315 if (specifiesApplications(tag)) { 316 tagIsSupported = false; 317 if (tag == Tags.PROVISION_UNAPPROVED_IN_ROM_APPLICATION_LIST) { 318 unsupportedList.add(R.string.policy_app_blacklist); 319 } else { 320 unsupportedList.add(R.string.policy_app_whitelist); 321 } 322 } 323 break; 324 // We accept calendar age, since we never ask for more than two weeks, and that's 325 // the most restrictive policy 326 case Tags.PROVISION_MAX_CALENDAR_AGE_FILTER: 327 policy.mMaxCalendarLookback = getValueInt(); 328 break; 329 // We handle max email lookback 330 case Tags.PROVISION_MAX_EMAIL_AGE_FILTER: 331 policy.mMaxEmailLookback = getValueInt(); 332 break; 333 // We currently reject these next two policies 334 case Tags.PROVISION_MAX_EMAIL_BODY_TRUNCATION_SIZE: 335 case Tags.PROVISION_MAX_EMAIL_HTML_BODY_TRUNCATION_SIZE: 336 String value = getValue(); 337 // -1 indicates no required truncation 338 if (!value.equals("-1")) { 339 max = Integer.parseInt(value); 340 if (tag == Tags.PROVISION_MAX_EMAIL_BODY_TRUNCATION_SIZE) { 341 policy.mMaxTextTruncationSize = max; 342 unsupportedList.add(R.string.policy_text_truncation); 343 } else { 344 policy.mMaxHtmlTruncationSize = max; 345 unsupportedList.add(R.string.policy_html_truncation); 346 } 347 tagIsSupported = false; 348 } 349 break; 350 default: 351 skipTag(); 352 } 353 354 if (!tagIsSupported) { 355 log("Policy not supported: " + tag); 356 mIsSupportable = false; 357 } 358 } 359 360 // Make sure policy settings are valid; password not enabled trumps other password settings 361 if (!passwordEnabled) { 362 policy.mPasswordMode = Policy.PASSWORD_MODE_NONE; 363 } 364 365 if (!unsupportedList.isEmpty()) { 366 StringBuilder sb = new StringBuilder(); 367 for (int res: unsupportedList) { 368 addPolicyString(sb, res); 369 } 370 policy.mProtocolPoliciesUnsupported = sb.toString(); 371 } 372 373 setPolicy(policy); 374 } 375 376 /** 377 * Return whether or not either of the application list tags specifies any applications 378 * @param endTag the tag whose children we're walking through 379 * @return whether any applications were specified (by name or by hash) 380 * @throws IOException 381 */ 382 private boolean specifiesApplications(int endTag) throws IOException { 383 boolean specifiesApplications = false; 384 while (nextTag(endTag) != END) { 385 switch (tag) { 386 case Tags.PROVISION_APPLICATION_NAME: 387 case Tags.PROVISION_HASH: 388 specifiesApplications = true; 389 break; 390 default: 391 skipTag(); 392 } 393 } 394 return specifiesApplications; 395 } 396 397 /*package*/ void parseProvisionDocXml(String doc) throws IOException { 398 Policy policy = new Policy(); 399 400 try { 401 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 402 XmlPullParser parser = factory.newPullParser(); 403 parser.setInput(new ByteArrayInputStream(doc.getBytes()), "UTF-8"); 404 int type = parser.getEventType(); 405 if (type == XmlPullParser.START_DOCUMENT) { 406 type = parser.next(); 407 if (type == XmlPullParser.START_TAG) { 408 String tagName = parser.getName(); 409 if (tagName.equals("wap-provisioningdoc")) { 410 parseWapProvisioningDoc(parser, policy); 411 } 412 } 413 } 414 } catch (XmlPullParserException e) { 415 throw new IOException(); 416 } 417 418 setPolicy(policy); 419 } 420 421 /** 422 * Return true if password is required; otherwise false. 423 */ 424 private boolean parseSecurityPolicy(XmlPullParser parser, Policy policy) 425 throws XmlPullParserException, IOException { 426 boolean passwordRequired = true; 427 while (true) { 428 int type = parser.nextTag(); 429 if (type == XmlPullParser.END_TAG && parser.getName().equals("characteristic")) { 430 break; 431 } else if (type == XmlPullParser.START_TAG) { 432 String tagName = parser.getName(); 433 if (tagName.equals("parm")) { 434 String name = parser.getAttributeValue(null, "name"); 435 if (name.equals("4131")) { 436 String value = parser.getAttributeValue(null, "value"); 437 if (value.equals("1")) { 438 passwordRequired = false; 439 } 440 } 441 } 442 } 443 } 444 return passwordRequired; 445 } 446 447 private void parseCharacteristic(XmlPullParser parser, Policy policy) 448 throws XmlPullParserException, IOException { 449 boolean enforceInactivityTimer = true; 450 while (true) { 451 int type = parser.nextTag(); 452 if (type == XmlPullParser.END_TAG && parser.getName().equals("characteristic")) { 453 break; 454 } else if (type == XmlPullParser.START_TAG) { 455 if (parser.getName().equals("parm")) { 456 String name = parser.getAttributeValue(null, "name"); 457 String value = parser.getAttributeValue(null, "value"); 458 if (name.equals("AEFrequencyValue")) { 459 if (enforceInactivityTimer) { 460 if (value.equals("0")) { 461 policy.mMaxScreenLockTime = 1; 462 } else { 463 policy.mMaxScreenLockTime = 60*Integer.parseInt(value); 464 } 465 } 466 } else if (name.equals("AEFrequencyType")) { 467 // "0" here means we don't enforce an inactivity timeout 468 if (value.equals("0")) { 469 enforceInactivityTimer = false; 470 } 471 } else if (name.equals("DeviceWipeThreshold")) { 472 policy.mPasswordMaxFails = Integer.parseInt(value); 473 } else if (name.equals("CodewordFrequency")) { 474 // Ignore; has no meaning for us 475 } else if (name.equals("MinimumPasswordLength")) { 476 policy.mPasswordMinLength = Integer.parseInt(value); 477 } else if (name.equals("PasswordComplexity")) { 478 if (value.equals("0")) { 479 policy.mPasswordMode = Policy.PASSWORD_MODE_STRONG; 480 } else { 481 policy.mPasswordMode = Policy.PASSWORD_MODE_SIMPLE; 482 } 483 } 484 } 485 } 486 } 487 } 488 489 private void parseRegistry(XmlPullParser parser, Policy policy) 490 throws XmlPullParserException, IOException { 491 while (true) { 492 int type = parser.nextTag(); 493 if (type == XmlPullParser.END_TAG && parser.getName().equals("characteristic")) { 494 break; 495 } else if (type == XmlPullParser.START_TAG) { 496 String name = parser.getName(); 497 if (name.equals("characteristic")) { 498 parseCharacteristic(parser, policy); 499 } 500 } 501 } 502 } 503 504 private void parseWapProvisioningDoc(XmlPullParser parser, Policy policy) 505 throws XmlPullParserException, IOException { 506 while (true) { 507 int type = parser.nextTag(); 508 if (type == XmlPullParser.END_TAG && parser.getName().equals("wap-provisioningdoc")) { 509 break; 510 } else if (type == XmlPullParser.START_TAG) { 511 String name = parser.getName(); 512 if (name.equals("characteristic")) { 513 String atype = parser.getAttributeValue(null, "type"); 514 if (atype.equals("SecurityPolicy")) { 515 // If a password isn't required, stop here 516 if (!parseSecurityPolicy(parser, policy)) { 517 return; 518 } 519 } else if (atype.equals("Registry")) { 520 parseRegistry(parser, policy); 521 return; 522 } 523 } 524 } 525 } 526 } 527 528 private void parseProvisionData() throws IOException { 529 while (nextTag(Tags.PROVISION_DATA) != END) { 530 if (tag == Tags.PROVISION_EAS_PROVISION_DOC) { 531 parseProvisionDocWbxml(); 532 } else { 533 skipTag(); 534 } 535 } 536 } 537 538 private void parsePolicy() throws IOException { 539 String policyType = null; 540 while (nextTag(Tags.PROVISION_POLICY) != END) { 541 switch (tag) { 542 case Tags.PROVISION_POLICY_TYPE: 543 policyType = getValue(); 544 mService.userLog("Policy type: ", policyType); 545 break; 546 case Tags.PROVISION_POLICY_KEY: 547 mSecuritySyncKey = getValue(); 548 break; 549 case Tags.PROVISION_STATUS: 550 mService.userLog("Policy status: ", getValue()); 551 break; 552 case Tags.PROVISION_DATA: 553 if (policyType.equalsIgnoreCase(EasSyncService.EAS_2_POLICY_TYPE)) { 554 // Parse the old style XML document 555 parseProvisionDocXml(getValue()); 556 } else { 557 // Parse the newer WBXML data 558 parseProvisionData(); 559 } 560 break; 561 default: 562 skipTag(); 563 } 564 } 565 } 566 567 private void parsePolicies() throws IOException { 568 while (nextTag(Tags.PROVISION_POLICIES) != END) { 569 if (tag == Tags.PROVISION_POLICY) { 570 parsePolicy(); 571 } else { 572 skipTag(); 573 } 574 } 575 } 576 577 private void parseDeviceInformation() throws IOException { 578 while (nextTag(Tags.SETTINGS_DEVICE_INFORMATION) != END) { 579 if (tag == Tags.SETTINGS_STATUS) { 580 mService.userLog("DeviceInformation status: " + getValue()); 581 } else { 582 skipTag(); 583 } 584 } 585 } 586 587 @Override 588 public boolean parse() throws IOException { 589 boolean res = false; 590 if (nextTag(START_DOCUMENT) != Tags.PROVISION_PROVISION) { 591 throw new IOException(); 592 } 593 while (nextTag(START_DOCUMENT) != END_DOCUMENT) { 594 switch (tag) { 595 case Tags.PROVISION_STATUS: 596 int status = getValueInt(); 597 mService.userLog("Provision status: ", status); 598 res = (status == 1); 599 break; 600 case Tags.SETTINGS_DEVICE_INFORMATION: 601 parseDeviceInformation(); 602 break; 603 case Tags.PROVISION_POLICIES: 604 parsePolicies(); 605 break; 606 case Tags.PROVISION_REMOTE_WIPE: 607 // Indicate remote wipe command received 608 mRemoteWipe = true; 609 break; 610 default: 611 skipTag(); 612 } 613 } 614 return res; 615 } 616 } 617