1<?php
2/**
3 * Horde_ActiveSync_Policies::
4 *
5 * @license   http://www.horde.org/licenses/gpl GPLv2
6 *
7 * @copyright 2010-2020 Horde LLC (http://www.horde.org)
8 * @author    Michael J Rubinsky <mrubinsk@horde.org>
9 * @package   ActiveSync
10 */
11/**
12 * Horde_ActiveSync_Policies:: Wraps all functionality related to generating
13 * the XML or WBXML for EAS Policies.
14 *
15 * @license   http://www.horde.org/licenses/gpl GPLv2
16 *
17 * @copyright 2010-2020 Horde LLC (http://www.horde.org)
18 * @author    Michael J Rubinsky <mrubinsk@horde.org>
19 * @package   ActiveSync
20 */
21class Horde_ActiveSync_Policies
22{
23    /* Policy configuration keys */
24    const POLICY_PIN                            = 'DevicePasswordEnabled';
25    const POLICY_AEFVALUE                       = 'MaxInactivityTimeDeviceLock';
26    const POLICY_CODEFREQ                       = 'codewordfrequency';
27    const POLICY_MINLENGTH                      = 'MinDevicePasswordLength';
28    const POLICY_COMPLEXITY                     = 'AlphanumericDevicePasswordRequired';
29    // 12.0
30    //const POLICY_PWDRECOVERY                    = 'passwordrecovery';
31    //const POLICY_PWDEXPIRATION                  = 'passwordexpiration';
32    //const POLICY_PWDHISTORY                     = 'passwordhistory';
33    const POLICY_ENCRYPTION                     = 'DeviceEncryptionEnabled';
34    const POLICY_ATC                            = 'AttachmentsEnabled';
35    const POLICY_MAXATCSIZE                     = 'MaxAttachmentSize';
36    const POLICY_MAXFAILEDATTEMPTS              = 'MaxDevicePasswordFailedAttempts';
37    // 12.1
38    const POLICY_ALLOW_SDCARD                   = 'AllowStorageCard';
39    const POLICY_ALLOW_CAMERA                   = 'AllowCamera';
40    const POLICY_ALLOW_SMS                      = 'AllowTextMessaging';
41    const POLICY_ALLOW_WIFI                     = 'AllowWiFi';
42    const POLICY_ALLOW_BLUETOOTH                = 'AllowBluetooth';
43    const POLICY_ALLOW_POPIMAP                  = 'AllowPOPIMAPEmail';
44    const POLICY_ALLOW_BROWSER                  = 'AllowBrowser';
45    const POLICY_REQUIRE_SMIME_SIGNED           = 'RequireSignedSMIMEMessages';
46    const POLICY_REQUIRE_SMIME_ENCRYPTED        = 'RequireEncryptedSMIMEMessages';
47    const POLICY_DEVICE_ENCRYPTION              = 'RequireDeviceEncryption';
48    const POLICY_ALLOW_HTML                     = 'AllowHTMLEmail';
49    const POLICY_MAX_EMAIL_AGE                  = 'MaxEmailAgeFilter';
50    //const POLICY_MAX_EMAIL_TRUNCATION           = 'maxemailtruncation';
51    //const POLICY_MAX_HTMLEMAIL_TRUNCATION       = 'maxhtmlemailtruncation';
52    const POLICY_ROAMING_NOPUSH                 = 'RequireManualSyncWhenRoaming';
53
54    /**
55     * Default policy values used in both 12.0 and 12.1
56     *
57     * @var array
58     */
59    protected $_defaults = array(
60        self::POLICY_PIN               => false,
61        self::POLICY_AEFVALUE          => '0',
62        self::POLICY_MAXFAILEDATTEMPTS => '5',
63        self::POLICY_CODEFREQ          => '0',
64        self::POLICY_MINLENGTH         => '5',
65    );
66
67    /**
68     * Deafaults for 12.0 policies.
69     *
70     * @var array
71     */
72    protected $_defaults_twelve = array(
73        self::POLICY_ATC               => '1',
74        self::POLICY_ENCRYPTION        => '0',
75        self::POLICY_MAXATCSIZE        => '5000000',
76        self::POLICY_COMPLEXITY        => '0',
77        //self::POLICY_PWDRECOVERY       => '0',
78        //self::POLICY_PWDEXPIRATION     => '0',
79        //self::POLICY_PWDHISTORY        => '0',
80    );
81
82    /**
83     * Defaults used only in 12.1
84     *
85     * @var array
86     */
87    protected $_defaults_twelveone = array(
88        // 1 == Allow/Yes, 0 == Disallow/No.
89        self::POLICY_ALLOW_SDCARD            => '1',
90        self::POLICY_ALLOW_CAMERA            => '1',
91        self::POLICY_ALLOW_SMS               => '1',
92        self::POLICY_ALLOW_WIFI              => '1',
93        self::POLICY_ALLOW_BLUETOOTH         => '1',
94        self::POLICY_ALLOW_POPIMAP           => '1',
95        self::POLICY_ALLOW_BROWSER           => '1',
96        self::POLICY_REQUIRE_SMIME_ENCRYPTED => '0',
97        self::POLICY_REQUIRE_SMIME_SIGNED    => '0',
98        self::POLICY_DEVICE_ENCRYPTION       => '0',
99        self::POLICY_ALLOW_HTML              => '1',
100        self::POLICY_MAX_EMAIL_AGE           => '0',
101        self::POLICY_ROAMING_NOPUSH          => '0',
102    );
103
104    /**
105     * Explicitly set policies.
106     *
107     * @var array
108     */
109    protected $_overrides;
110
111    /**
112     * Output stream
113     *
114     * @var Horde_ActiveSync_Wbxml_Encoder
115     */
116    protected $_encoder;
117
118    /**
119     * EAS version to support.
120     *
121     * @var long
122     */
123    protected $_version;
124
125    /**
126     * Local cache of all policies to send.
127     *
128     * @var array
129     */
130    protected $_policies = array();
131
132    /**
133     * Const'r
134     *
135     * @param Horde_ActiveSync_Wbxml_Encoder $encoder  The output stream encoder
136     * @param float $version                           The EAS Version.
137     * @param array $policies                          The policy array.
138     */
139    public function __construct(
140        Horde_ActiveSync_Wbxml_Encoder $encoder = null,
141        $version = Horde_ActiveSync::VERSION_TWELVEONE,
142        array $policies = array())
143    {
144        $this->_encoder = $encoder;
145        if ($version >= Horde_ActiveSync::VERSION_TWELVE) {
146            $this->_defaults = array_merge($this->_defaults, $this->_defaults_twelve);
147        }
148        if ($version >= Horde_ActiveSync::VERSION_TWELVEONE) {
149            $this->_defaults = array_merge($this->_defaults, $this->_defaults_twelveone);
150        }
151
152        $this->_version = $version;
153        $this->_overrides = $policies;
154    }
155
156    /**
157     * Return a list of all configurable policy names.
158     *
159     * @return array
160     */
161    public function getAvailablePolicies()
162    {
163        return array_keys($this->_defaults);
164    }
165
166    /**
167     * Determine if the requested policy settings are available for the current
168     * version being used.
169     *
170     * @return boolean  True if policies are available in current version, false
171     *                  otherwise.
172     */
173    public function validatePolicyVersion()
174    {
175        $policies = $this->_getPolicies();
176
177        // Validate the version against the required policies.
178        if ($this->_version < Horde_ActiveSync::VERSION_TWELVEONE) {
179            foreach ($policies as $key => $value) {
180                if (!empty($this->_defaults_twelveone[$key]) &&
181                    $this->_defaults_twelveone[$key] != $value) {
182
183                    return false;
184                }
185            }
186        }
187
188        return true;
189    }
190
191    /**
192     * Output the policies as XML. Only used in EAS Version 2.5. This method
193     * only outputs the 2.5 compatible policies.
194     *
195     * @throws Horde_ActiveSync_Exception
196     */
197    public function toXml()
198    {
199        if (empty($this->_encoder)) {
200            throw new Horde_ActiveSync_Exception('No output stream');
201        }
202
203        $policies = array_merge($this->_defaults, $this->_overrides);
204
205        $xml = '<wap-provisioningdoc><characteristic type="SecurityPolicy">'
206            . '<parm name="4131" value="' . ($policies[self::POLICY_PIN] ? 0 : 1) . '"/>'
207            . '</characteristic>';
208        if ($policies[self::POLICY_PIN]) {
209            $xml .= '<characteristic type="Registry">'
210            .   '<characteristic type="HKLM\Comm\Security\Policy\LASSD\AE\{50C13377-C66D-400C-889E-C316FC4AB374}">'
211            .   '<parm name="AEFrequencyType" value="' . (!empty($policies[self::POLICY_AEFVALUE]) ? 1 : 0) . '"/>'
212            .   (!empty($policies[self::POLICY_AEFVALUE]) ? '<parm name="AEFrequencyValue" value="' . $policies[self::POLICY_AEFVALUE] . '"/>' : '')
213            .   '</characteristic>';
214
215            if (!empty($policies[self::POLICY_MAXFAILEDATTEMPTS])) {
216                $xml .= '<characteristic type="HKLM\Comm\Security\Policy\LASSD"><parm name="DeviceWipeThreshold" value="' . $policies[self::POLICY_MAXFAILEDATTEMPTS] . '"/></characteristic>';
217            }
218            if (!empty($policies[self::POLICY_CODEFREQ])) {
219                $xml .= '<characteristic type="HKLM\Comm\Security\Policy\LASSD"><parm name="CodewordFrequency" value="' . $policies[self::POLICY_CODEFREQ] . '"/></characteristic>';
220            }
221            if (!empty($policies[self::POLICY_MINLENGTH])) {
222                $xml .= '<characteristic type="HKLM\Comm\Security\Policy\LASSD\LAP\lap_pw"><parm name="MinimumPasswordLength" value="' . $policies[self::POLICY_MINLENGTH] . '"/></characteristic>';
223            }
224            if ($policies[self::POLICY_COMPLEXITY] !== false) {
225                $xml .= '<characteristic type="HKLM\Comm\Security\Policy\LASSD\LAP\lap_pw"><parm name="PasswordComplexity" value="' . $policies[self::POLICY_COMPLEXITY] . '"/></characteristic>';
226            }
227            $xml .= '</characteristic>';
228        }
229        $xml .= '</wap-provisioningdoc>';
230
231        $this->_encoder->content($xml);
232    }
233
234
235    /**
236     * Output the policies as WBXML. Used in EAS Versions >= 12.0
237     */
238    public function toWbxml()
239    {
240        if (empty($this->_encoder)) {
241            throw new Horde_ActiveSync_Exception('No output stream');
242        }
243
244        $policies = $this->_getPolicies();
245
246        $this->_encoder->startTag('Provision:EASProvisionDoc');
247
248        $this->_sendPolicy(self::POLICY_PIN, $policies[self::POLICY_PIN] ? '1' : '0');
249        if ($policies[self::POLICY_PIN]) {
250            $this->_sendPolicy(self::POLICY_COMPLEXITY, $policies[self::POLICY_COMPLEXITY], true);
251            $this->_sendPolicy(self::POLICY_MINLENGTH, $policies[self::POLICY_MINLENGTH], true);
252            $this->_sendPolicy(self::POLICY_MAXFAILEDATTEMPTS, $policies[self::POLICY_MAXFAILEDATTEMPTS], true);
253            $this->_sendPolicy(self::POLICY_COMPLEXITY, $policies[self::POLICY_COMPLEXITY] >= 1 ? '1' : '0', true);
254        }
255        $this->_sendPolicy(self::POLICY_ENCRYPTION, $policies[self::POLICY_ENCRYPTION], true);
256        $this->_sendPolicy(self::POLICY_ATC, $policies[self::POLICY_ATC], false);
257        $this->_sendPolicy(self::POLICY_AEFVALUE, $policies[self::POLICY_AEFVALUE], true);
258        $this->_sendPolicy(self::POLICY_MAXATCSIZE, $policies[self::POLICY_MAXATCSIZE], true);
259        if ($this->_version > Horde_ActiveSync::VERSION_TWELVE) {
260            $this->_sendPolicy(self::POLICY_ALLOW_SDCARD, $policies[self::POLICY_ALLOW_SDCARD], true);
261            $this->_sendPolicy(self::POLICY_ALLOW_CAMERA, $policies[self::POLICY_ALLOW_CAMERA], true);
262            $this->_sendPolicy(self::POLICY_DEVICE_ENCRYPTION, $policies[self::POLICY_DEVICE_ENCRYPTION], true);
263            $this->_sendPolicy(self::POLICY_ALLOW_WIFI, $policies[self::POLICY_ALLOW_WIFI], true);
264            $this->_sendPolicy(self::POLICY_ALLOW_SMS, $policies[self::POLICY_ALLOW_SMS], true);
265            $this->_sendPolicy(self::POLICY_ALLOW_POPIMAP, $policies[self::POLICY_ALLOW_POPIMAP], true);
266            $this->_sendPolicy(self::POLICY_ALLOW_BLUETOOTH, $policies[self::POLICY_ALLOW_BLUETOOTH], true);
267            $this->_sendPolicy(self::POLICY_ROAMING_NOPUSH, $policies[self::POLICY_ROAMING_NOPUSH], true);
268            $this->_sendPolicy(self::POLICY_ALLOW_HTML, $policies[self::POLICY_ALLOW_HTML], true);
269            $this->_sendPolicy(self::POLICY_MAX_EMAIL_AGE, $policies[self::POLICY_MAX_EMAIL_AGE], true);
270            $this->_sendPolicy(self::POLICY_REQUIRE_SMIME_SIGNED, $policies[self::POLICY_REQUIRE_SMIME_SIGNED], true);
271            $this->_sendPolicy(self::POLICY_REQUIRE_SMIME_ENCRYPTED, $policies[self::POLICY_REQUIRE_SMIME_ENCRYPTED], true);
272            $this->_sendPolicy(self::POLICY_ALLOW_BROWSER, $policies[self::POLICY_ALLOW_BROWSER], true);
273        }
274
275        $this->_encoder->endTag();
276    }
277
278    /**
279     * Output a single policy value
280     *
281     * @param string $policy      The policy name
282     * @param mixed $value        The policy value
283     * @param boolean $nodefault  Don't send the policy if the value is default.
284     */
285    protected function _sendPolicy($policy, $value, $nodefault = false)
286    {
287        if ($nodefault && $value == $this->_defaults[$policy]) {
288            return;
289        }
290	if ($value === false) {
291            $value = 0;
292        } elseif ($value === true) {
293           $value = 1;
294        }
295        $this->_encoder->startTag('Provision:' . $policy);
296        $this->_encoder->content($value);
297        $this->_encoder->endTag();
298    }
299
300    protected function _getPolicies()
301    {
302        if (empty($this->_policies)) {
303            $this->_policies = array_merge($this->_defaults, $this->_overrides);
304        }
305
306        return $this->_policies;
307    }
308
309}
310