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 com.android.email.SecurityPolicy; 19 import com.android.email.SecurityPolicy.PolicySet; 20 import com.android.exchange.EasSyncService; 21 22 import org.xmlpull.v1.XmlPullParser; 23 import org.xmlpull.v1.XmlPullParserException; 24 import org.xmlpull.v1.XmlPullParserFactory; 25 26 import java.io.ByteArrayInputStream; 27 import java.io.IOException; 28 import java.io.InputStream; 29 30 /** 31 * Parse the result of the Provision command 32 * 33 * Assuming a successful parse, we store the PolicySet and the policy key 34 */ 35 public class ProvisionParser extends Parser { 36 private EasSyncService mService; 37 PolicySet mPolicySet = null; 38 String mPolicyKey = null; 39 boolean mRemoteWipe = false; 40 boolean mIsSupportable = true; 41 42 public ProvisionParser(InputStream in, EasSyncService service) throws IOException { 43 super(in); 44 mService = service; 45 } 46 47 public PolicySet getPolicySet() { 48 return mPolicySet; 49 } 50 51 public String getPolicyKey() { 52 return mPolicyKey; 53 } 54 55 public boolean getRemoteWipe() { 56 return mRemoteWipe; 57 } 58 59 public boolean hasSupportablePolicySet() { 60 return (mPolicySet != null) && mIsSupportable; 61 } 62 63 private void parseProvisionDocWbxml() throws IOException { 64 int minPasswordLength = 0; 65 int passwordMode = PolicySet.PASSWORD_MODE_NONE; 66 int maxPasswordFails = 0; 67 int maxScreenLockTime = 0; 68 boolean supported = true; 69 70 while (nextTag(Tags.PROVISION_EAS_PROVISION_DOC) != END) { 71 switch (tag) { 72 case Tags.PROVISION_DEVICE_PASSWORD_ENABLED: 73 if (getValueInt() == 1) { 74 if (passwordMode == PolicySet.PASSWORD_MODE_NONE) { 75 passwordMode = PolicySet.PASSWORD_MODE_SIMPLE; 76 } 77 } 78 break; 79 case Tags.PROVISION_MIN_DEVICE_PASSWORD_LENGTH: 80 minPasswordLength = getValueInt(); 81 break; 82 case Tags.PROVISION_ALPHA_DEVICE_PASSWORD_ENABLED: 83 if (getValueInt() == 1) { 84 passwordMode = PolicySet.PASSWORD_MODE_STRONG; 85 } 86 break; 87 case Tags.PROVISION_MAX_INACTIVITY_TIME_DEVICE_LOCK: 88 // EAS gives us seconds, which is, happily, what the PolicySet requires 89 maxScreenLockTime = getValueInt(); 90 break; 91 case Tags.PROVISION_MAX_DEVICE_PASSWORD_FAILED_ATTEMPTS: 92 maxPasswordFails = getValueInt(); 93 break; 94 case Tags.PROVISION_ALLOW_SIMPLE_DEVICE_PASSWORD: 95 // Ignore this unless there's any MSFT documentation for what this means 96 // Hint: I haven't seen any that's more specific than "simple" 97 getValue(); 98 break; 99 // The following policy, if false, can't be supported at the moment 100 case Tags.PROVISION_ATTACHMENTS_ENABLED: 101 if (getValueInt() == 0) { 102 supported = false; 103 } 104 break; 105 // The following policies, if true, can't be supported at the moment 106 case Tags.PROVISION_DEVICE_ENCRYPTION_ENABLED: 107 case Tags.PROVISION_PASSWORD_RECOVERY_ENABLED: 108 case Tags.PROVISION_DEVICE_PASSWORD_EXPIRATION: 109 case Tags.PROVISION_DEVICE_PASSWORD_HISTORY: 110 case Tags.PROVISION_MAX_ATTACHMENT_SIZE: 111 if (getValueInt() == 1) { 112 supported = false; 113 } 114 break; 115 default: 116 skipTag(); 117 } 118 119 if (!supported) { 120 log("Policy not supported: " + tag); 121 mIsSupportable = false; 122 } 123 } 124 125 mPolicySet = new SecurityPolicy.PolicySet(minPasswordLength, passwordMode, 126 maxPasswordFails, maxScreenLockTime, true); 127 } 128 129 class ShadowPolicySet { 130 int mMinPasswordLength = 0; 131 int mPasswordMode = PolicySet.PASSWORD_MODE_NONE; 132 int mMaxPasswordFails = 0; 133 int mMaxScreenLockTime = 0; 134 } 135 136 public void parseProvisionDocXml(String doc) throws IOException { 137 ShadowPolicySet sps = new ShadowPolicySet(); 138 139 try { 140 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 141 XmlPullParser parser = factory.newPullParser(); 142 parser.setInput(new ByteArrayInputStream(doc.getBytes()), "UTF-8"); 143 int type = parser.getEventType(); 144 if (type == XmlPullParser.START_DOCUMENT) { 145 type = parser.next(); 146 if (type == XmlPullParser.START_TAG) { 147 String tagName = parser.getName(); 148 if (tagName.equals("wap-provisioningdoc")) { 149 parseWapProvisioningDoc(parser, sps); 150 } 151 } 152 } 153 } catch (XmlPullParserException e) { 154 throw new IOException(); 155 } 156 157 mPolicySet = new PolicySet(sps.mMinPasswordLength, sps.mPasswordMode, sps.mMaxPasswordFails, 158 sps.mMaxScreenLockTime, true); 159 } 160 161 /** 162 * Return true if password is required; otherwise false. 163 */ 164 boolean parseSecurityPolicy(XmlPullParser parser, ShadowPolicySet sps) 165 throws XmlPullParserException, IOException { 166 boolean passwordRequired = true; 167 while (true) { 168 int type = parser.nextTag(); 169 if (type == XmlPullParser.END_TAG && parser.getName().equals("characteristic")) { 170 break; 171 } else if (type == XmlPullParser.START_TAG) { 172 String tagName = parser.getName(); 173 if (tagName.equals("parm")) { 174 String name = parser.getAttributeValue(null, "name"); 175 if (name.equals("4131")) { 176 String value = parser.getAttributeValue(null, "value"); 177 if (value.equals("1")) { 178 passwordRequired = false; 179 } 180 } 181 } 182 } 183 } 184 return passwordRequired; 185 } 186 187 void parseCharacteristic(XmlPullParser parser, ShadowPolicySet sps) 188 throws XmlPullParserException, IOException { 189 boolean enforceInactivityTimer = true; 190 while (true) { 191 int type = parser.nextTag(); 192 if (type == XmlPullParser.END_TAG && parser.getName().equals("characteristic")) { 193 break; 194 } else if (type == XmlPullParser.START_TAG) { 195 if (parser.getName().equals("parm")) { 196 String name = parser.getAttributeValue(null, "name"); 197 String value = parser.getAttributeValue(null, "value"); 198 if (name.equals("AEFrequencyValue")) { 199 if (enforceInactivityTimer) { 200 if (value.equals("0")) { 201 sps.mMaxScreenLockTime = 1; 202 } else { 203 sps.mMaxScreenLockTime = 60*Integer.parseInt(value); 204 } 205 } 206 } else if (name.equals("AEFrequencyType")) { 207 // "0" here means we don't enforce an inactivity timeout 208 if (value.equals("0")) { 209 enforceInactivityTimer = false; 210 } 211 } else if (name.equals("DeviceWipeThreshold")) { 212 sps.mMaxPasswordFails = Integer.parseInt(value); 213 } else if (name.equals("CodewordFrequency")) { 214 // Ignore; has no meaning for us 215 } else if (name.equals("MinimumPasswordLength")) { 216 sps.mMinPasswordLength = Integer.parseInt(value); 217 } else if (name.equals("PasswordComplexity")) { 218 if (value.equals("0")) { 219 sps.mPasswordMode = PolicySet.PASSWORD_MODE_STRONG; 220 } else { 221 sps.mPasswordMode = PolicySet.PASSWORD_MODE_SIMPLE; 222 } 223 } 224 } 225 } 226 } 227 } 228 229 void parseRegistry(XmlPullParser parser, ShadowPolicySet sps) 230 throws XmlPullParserException, IOException { 231 while (true) { 232 int type = parser.nextTag(); 233 if (type == XmlPullParser.END_TAG && parser.getName().equals("characteristic")) { 234 break; 235 } else if (type == XmlPullParser.START_TAG) { 236 String name = parser.getName(); 237 if (name.equals("characteristic")) { 238 parseCharacteristic(parser, sps); 239 } 240 } 241 } 242 } 243 244 void parseWapProvisioningDoc(XmlPullParser parser, ShadowPolicySet sps) 245 throws XmlPullParserException, IOException { 246 while (true) { 247 int type = parser.nextTag(); 248 if (type == XmlPullParser.END_TAG && parser.getName().equals("wap-provisioningdoc")) { 249 break; 250 } else if (type == XmlPullParser.START_TAG) { 251 String name = parser.getName(); 252 if (name.equals("characteristic")) { 253 String atype = parser.getAttributeValue(null, "type"); 254 if (atype.equals("SecurityPolicy")) { 255 // If a password isn't required, stop here 256 if (!parseSecurityPolicy(parser, sps)) { 257 return; 258 } 259 } else if (atype.equals("Registry")) { 260 parseRegistry(parser, sps); 261 return; 262 } 263 } 264 } 265 } 266 } 267 268 public void parseProvisionData() throws IOException { 269 while (nextTag(Tags.PROVISION_DATA) != END) { 270 if (tag == Tags.PROVISION_EAS_PROVISION_DOC) { 271 parseProvisionDocWbxml(); 272 } else { 273 skipTag(); 274 } 275 } 276 } 277 278 public void parsePolicy() throws IOException { 279 String policyType = null; 280 while (nextTag(Tags.PROVISION_POLICY) != END) { 281 switch (tag) { 282 case Tags.PROVISION_POLICY_TYPE: 283 policyType = getValue(); 284 mService.userLog("Policy type: ", policyType); 285 break; 286 case Tags.PROVISION_POLICY_KEY: 287 mPolicyKey = getValue(); 288 break; 289 case Tags.PROVISION_STATUS: 290 mService.userLog("Policy status: ", getValue()); 291 break; 292 case Tags.PROVISION_DATA: 293 if (policyType.equalsIgnoreCase(EasSyncService.EAS_2_POLICY_TYPE)) { 294 // Parse the old style XML document 295 parseProvisionDocXml(getValue()); 296 } else { 297 // Parse the newer WBXML data 298 parseProvisionData(); 299 } 300 break; 301 default: 302 skipTag(); 303 } 304 } 305 } 306 307 public void parsePolicies() throws IOException { 308 while (nextTag(Tags.PROVISION_POLICIES) != END) { 309 if (tag == Tags.PROVISION_POLICY) { 310 parsePolicy(); 311 } else { 312 skipTag(); 313 } 314 } 315 } 316 317 @Override 318 public boolean parse() throws IOException { 319 boolean res = false; 320 if (nextTag(START_DOCUMENT) != Tags.PROVISION_PROVISION) { 321 throw new IOException(); 322 } 323 while (nextTag(START_DOCUMENT) != END_DOCUMENT) { 324 switch (tag) { 325 case Tags.PROVISION_STATUS: 326 int status = getValueInt(); 327 mService.userLog("Provision status: ", status); 328 res = (status == 1); 329 break; 330 case Tags.PROVISION_POLICIES: 331 parsePolicies(); 332 break; 333 case Tags.PROVISION_REMOTE_WIPE: 334 // Indicate remote wipe command received 335 mRemoteWipe = true; 336 break; 337 default: 338 skipTag(); 339 } 340 } 341 return res; 342 } 343 } 344 345