Home | History | Annotate | Download | only in adapter
      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