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