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