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