1<?php 2/** 3 * @license http://www.horde.org/licenses/gpl GPLv2 4 * 5 * @copyright 2009-2020 Horde LLC (http://www.horde.org) 6 * @author Michael J Rubinsky <mrubinsk@horde.org> 7 * @package ActiveSync 8 */ 9 10/** 11 * The Horde ActiveSync server. Entry point for performing all ActiveSync 12 * operations. 13 * 14 * @license http://www.horde.org/licenses/gpl GPLv2 15 * 16 * @copyright 2009-2020 Horde LLC (http://www.horde.org) 17 * @author Michael J Rubinsky <mrubinsk@horde.org> 18 * @package ActiveSync 19 * 20 * @property-read Horde_ActiveSync_Wbxml_Encoder $encoder The Wbxml encoder. 21 * @property-read Horde_ActiveSync_Wbxml_Decoder $decoder The Wbxml decoder. 22 * @property-read Horde_ActiveSync_State_Base $state The state object. 23 * @property-read Horde_Controller_Reqeust_Http $request The HTTP request object. 24 * @property-read Horde_ActiveSync_Driver_Base $driver The backend driver object. 25 * @property-read boolean|string $provisioning Provisioning support: True, False, or 'loose' 26 * @property-read boolean $multipart Indicate this is a multipart request. 27 * @property-read string $certPath Local path to the certificate bundle. 28 * @property-read Horde_ActiveSync_Device $device The current device object. 29 * @property-read Horde_ActiveSync_Log_Logger $logger The logger object. 30 */ 31class Horde_ActiveSync 32{ 33 /* Conflict resolution */ 34 const CONFLICT_OVERWRITE_SERVER = 0; 35 const CONFLICT_OVERWRITE_PIM = 1; 36 37 /* TRUNCATION Constants */ 38 const TRUNCATION_ALL = 0; 39 const TRUNCATION_1 = 1; 40 const TRUNCATION_2 = 2; 41 const TRUNCATION_3 = 3; 42 const TRUNCATION_4 = 4; 43 const TRUNCATION_5 = 5; 44 const TRUNCATION_6 = 6; 45 const TRUNCATION_7 = 7; 46 const TRUNCATION_8 = 8; 47 const TRUNCATION_9 = 9; 48 const TRUNCATION_NONE = 9; // @deprecated 49 50 /* FOLDERHIERARCHY */ 51 const FOLDERHIERARCHY_FOLDERS = 'FolderHierarchy:Folders'; 52 const FOLDERHIERARCHY_FOLDER = 'FolderHierarchy:Folder'; 53 const FOLDERHIERARCHY_DISPLAYNAME = 'FolderHierarchy:DisplayName'; 54 const FOLDERHIERARCHY_SERVERENTRYID = 'FolderHierarchy:ServerEntryId'; 55 const FOLDERHIERARCHY_PARENTID = 'FolderHierarchy:ParentId'; 56 const FOLDERHIERARCHY_TYPE = 'FolderHierarchy:Type'; 57 const FOLDERHIERARCHY_RESPONSE = 'FolderHierarchy:Response'; 58 const FOLDERHIERARCHY_STATUS = 'FolderHierarchy:Status'; 59 const FOLDERHIERARCHY_CONTENTCLASS = 'FolderHierarchy:ContentClass'; 60 const FOLDERHIERARCHY_CHANGES = 'FolderHierarchy:Changes'; 61 const FOLDERHIERARCHY_SYNCKEY = 'FolderHierarchy:SyncKey'; 62 const FOLDERHIERARCHY_FOLDERSYNC = 'FolderHierarchy:FolderSync'; 63 const FOLDERHIERARCHY_COUNT = 'FolderHierarchy:Count'; 64 const FOLDERHIERARCHY_VERSION = 'FolderHierarchy:Version'; 65 66 /* SYNC */ 67 const SYNC_SYNCHRONIZE = 'Synchronize'; 68 const SYNC_REPLIES = 'Replies'; 69 const SYNC_ADD = 'Add'; 70 const SYNC_MODIFY = 'Modify'; 71 const SYNC_REMOVE = 'Remove'; 72 const SYNC_FETCH = 'Fetch'; 73 const SYNC_SYNCKEY = 'SyncKey'; 74 const SYNC_CLIENTENTRYID = 'ClientEntryId'; 75 const SYNC_SERVERENTRYID = 'ServerEntryId'; 76 const SYNC_STATUS = 'Status'; 77 const SYNC_FOLDER = 'Folder'; 78 const SYNC_FOLDERTYPE = 'FolderType'; 79 const SYNC_VERSION = 'Version'; 80 const SYNC_FOLDERID = 'FolderId'; 81 const SYNC_GETCHANGES = 'GetChanges'; 82 const SYNC_MOREAVAILABLE = 'MoreAvailable'; 83 const SYNC_WINDOWSIZE = 'WindowSize'; 84 const SYNC_COMMANDS = 'Commands'; 85 const SYNC_OPTIONS = 'Options'; 86 const SYNC_FILTERTYPE = 'FilterType'; 87 const SYNC_TRUNCATION = 'Truncation'; 88 const SYNC_RTFTRUNCATION = 'RtfTruncation'; 89 const SYNC_CONFLICT = 'Conflict'; 90 const SYNC_FOLDERS = 'Folders'; 91 const SYNC_DATA = 'Data'; 92 const SYNC_DELETESASMOVES = 'DeletesAsMoves'; 93 const SYNC_NOTIFYGUID = 'NotifyGUID'; 94 const SYNC_SUPPORTED = 'Supported'; 95 const SYNC_SOFTDELETE = 'SoftDelete'; 96 const SYNC_MIMESUPPORT = 'MIMESupport'; 97 const SYNC_MIMETRUNCATION = 'MIMETruncation'; 98 const SYNC_NEWMESSAGE = 'NewMessage'; 99 const SYNC_PARTIAL = 'Partial'; 100 const SYNC_WAIT = 'Wait'; 101 const SYNC_LIMIT = 'Limit'; 102 // 14 103 const SYNC_HEARTBEATINTERVAL = 'HeartbeatInterval'; 104 const SYNC_CONVERSATIONMODE = 'ConversationMode'; 105 const SYNC_MAXITEMS = 'MaxItems'; 106 107 /* Document library */ 108 const SYNC_DOCUMENTLIBRARY_LINKID = 'DocumentLibrary:LinkId'; 109 const SYNC_DOCUMENTLIBRARY_DISPLAYNAME = 'DocumentLibrary:DisplayName'; 110 const SYNC_DOCUMENTLIBRARY_ISFOLDER = 'DocumentLibrary:IsFolder'; 111 const SYNC_DOCUMENTLIBRARY_CREATIONDATE = 'DocumentLibrary:CreationDate'; 112 const SYNC_DOCUMENTLIBRARY_LASTMODIFIEDDATE = 'DocumentLibrary:LastModifiedDate'; 113 const SYNC_DOCUMENTLIBRARY_ISHIDDEN = 'DocumentLibrary:IsHidden'; 114 const SYNC_DOCUMENTLIBRARY_CONTENTLENGTH = 'DocumentLibrary:ContentLength'; 115 const SYNC_DOCUMENTLIBRARY_CONTENTTYPE = 'DocumentLibrary:ContentType'; 116 117 /* AIRSYNCBASE */ 118 const AIRSYNCBASE_BODYPREFERENCE = 'AirSyncBase:BodyPreference'; 119 const AIRSYNCBASE_TYPE = 'AirSyncBase:Type'; 120 const AIRSYNCBASE_TRUNCATIONSIZE = 'AirSyncBase:TruncationSize'; 121 const AIRSYNCBASE_ALLORNONE = 'AirSyncBase:AllOrNone'; 122 const AIRSYNCBASE_BODY = 'AirSyncBase:Body'; 123 const AIRSYNCBASE_DATA = 'AirSyncBase:Data'; 124 const AIRSYNCBASE_ESTIMATEDDATASIZE = 'AirSyncBase:EstimatedDataSize'; 125 const AIRSYNCBASE_TRUNCATED = 'AirSyncBase:Truncated'; 126 const AIRSYNCBASE_ATTACHMENTS = 'AirSyncBase:Attachments'; 127 const AIRSYNCBASE_ATTACHMENT = 'AirSyncBase:Attachment'; 128 const AIRSYNCBASE_DISPLAYNAME = 'AirSyncBase:DisplayName'; 129 const AIRSYNCBASE_FILEREFERENCE = 'AirSyncBase:FileReference'; 130 const AIRSYNCBASE_METHOD = 'AirSyncBase:Method'; 131 const AIRSYNCBASE_CONTENTID = 'AirSyncBase:ContentId'; 132 const AIRSYNCBASE_CONTENTLOCATION = 'AirSyncBase:ContentLocation'; 133 const AIRSYNCBASE_ISINLINE = 'AirSyncBase:IsInline'; 134 const AIRSYNCBASE_NATIVEBODYTYPE = 'AirSyncBase:NativeBodyType'; 135 const AIRSYNCBASE_CONTENTTYPE = 'AirSyncBase:ContentType'; 136 const AIRSYNCBASE_LOCATION = 'AirSyncBase:Location'; 137 138 // 14.0 139 const AIRSYNCBASE_PREVIEW = 'AirSyncBase:Preview'; 140 141 // 14.1 142 const AIRSYNCBASE_BODYPARTPREFERENCE = 'AirSyncBase:BodyPartPreference'; 143 const AIRSYNCBASE_BODYPART = 'AirSyncBase:BodyPart'; 144 const AIRSYNCBASE_STATUS = 'AirSyncBase:Status'; 145 146 // 16.0 147 const AIRSYNCBASE_ADD = 'AirSyncBase:Add'; 148 const AIRSYNCBASE_DELETE = 'AirSyncBase:Delete'; 149 const AIRSYNCBASE_CLIENTID = 'AirSyncBase:ClientId'; 150 const AIRSYNCBASE_CONTENT = 'AirSyncBase:Content'; 151 const AIRSYNCBASE_ANNOTATION = 'AirSyncBase:Annotation'; 152 const AIRSYNCBASE_STREET = 'AirSyncBase:Street'; 153 const AIRSYNCBASE_CITY = 'AirSyncBase:City'; 154 const AIRSYNCBASE_STATE = 'AirSyncBase:State'; 155 const AIRSYNCBASE_COUNTRY = 'AirSyncBase:Country'; 156 const AIRSYNCBASE_POSTALCODE = 'AirSyncBase:PostalCode'; 157 const AIRSYNCBASE_LATITUDE = 'AirSyncBase:Latitude'; 158 const AIRSYNCBASE_LONGITUDE = 'AirSyncBase:Longitude'; 159 const AIRSYNCBASE_ACCURACY = 'AirSyncBase:Accuracy'; 160 const AIRSYNCBASE_ALTITUDE = 'AirSyncBase:Altitude'; 161 const AIRSYNCBASE_ALTITUDEACCURACY = 'AirSyncBase:AltitudeAccuracy'; 162 const AIRSYNCBASE_LOCATIONURI = 'AirSyncBase:LocationUri'; 163 const AIRSYNCBASE_INSTANCEID = 'AirSyncBase:InstanceId'; 164 165 166 /* Body type prefs */ 167 const BODYPREF_TYPE_PLAIN = 1; 168 const BODYPREF_TYPE_HTML = 2; 169 const BODYPREF_TYPE_RTF = 3; 170 const BODYPREF_TYPE_MIME = 4; 171 172 /* PROVISION */ 173 const PROVISION_PROVISION = 'Provision:Provision'; 174 const PROVISION_POLICIES = 'Provision:Policies'; 175 const PROVISION_POLICY = 'Provision:Policy'; 176 const PROVISION_POLICYTYPE = 'Provision:PolicyType'; 177 const PROVISION_POLICYKEY = 'Provision:PolicyKey'; 178 const PROVISION_DATA = 'Provision:Data'; 179 const PROVISION_STATUS = 'Provision:Status'; 180 const PROVISION_REMOTEWIPE = 'Provision:RemoteWipe'; 181 const PROVISION_EASPROVISIONDOC = 'Provision:EASProvisionDoc'; 182 183 /* Policy types */ 184 const POLICYTYPE_XML = 'MS-WAP-Provisioning-XML'; 185 const POLICYTYPE_WBXML = 'MS-EAS-Provisioning-WBXML'; 186 187 /* Flags */ 188 // @TODO: H6 Change this to CHANGE_TYPE_NEW 189 const FLAG_NEWMESSAGE = 'NewMessage'; 190 191 /* Folder types */ 192 const FOLDER_TYPE_OTHER = 1; 193 const FOLDER_TYPE_INBOX = 2; 194 const FOLDER_TYPE_DRAFTS = 3; 195 const FOLDER_TYPE_WASTEBASKET = 4; 196 const FOLDER_TYPE_SENTMAIL = 5; 197 const FOLDER_TYPE_OUTBOX = 6; 198 const FOLDER_TYPE_TASK = 7; 199 const FOLDER_TYPE_APPOINTMENT = 8; 200 const FOLDER_TYPE_CONTACT = 9; 201 const FOLDER_TYPE_NOTE = 10; 202 const FOLDER_TYPE_JOURNAL = 11; 203 const FOLDER_TYPE_USER_MAIL = 12; 204 const FOLDER_TYPE_USER_APPOINTMENT = 13; 205 const FOLDER_TYPE_USER_CONTACT = 14; 206 const FOLDER_TYPE_USER_TASK = 15; 207 const FOLDER_TYPE_USER_JOURNAL = 16; 208 const FOLDER_TYPE_USER_NOTE = 17; 209 const FOLDER_TYPE_UNKNOWN = 18; 210 const FOLDER_TYPE_RECIPIENT_CACHE = 19; 211 // @TODO, remove const definition in H6, not used anymore. 212 const FOLDER_TYPE_DUMMY = 999999; 213 214 /* Origin of changes **/ 215 const CHANGE_ORIGIN_PIM = 0; 216 const CHANGE_ORIGIN_SERVER = 1; 217 const CHANGE_ORIGIN_NA = 3; 218 219 /* Remote wipe **/ 220 const RWSTATUS_NA = 0; 221 const RWSTATUS_OK = 1; 222 const RWSTATUS_PENDING = 2; 223 const RWSTATUS_WIPED = 3; 224 225 /* GAL **/ 226 const GAL_DISPLAYNAME = 'GAL:DisplayName'; 227 const GAL_PHONE = 'GAL:Phone'; 228 const GAL_OFFICE = 'GAL:Office'; 229 const GAL_TITLE = 'GAL:Title'; 230 const GAL_COMPANY = 'GAL:Company'; 231 const GAL_ALIAS = 'GAL:Alias'; 232 const GAL_FIRSTNAME = 'GAL:FirstName'; 233 const GAL_LASTNAME = 'GAL:LastName'; 234 const GAL_HOMEPHONE = 'GAL:HomePhone'; 235 const GAL_MOBILEPHONE = 'GAL:MobilePhone'; 236 const GAL_EMAILADDRESS = 'GAL:EmailAddress'; 237 // 14.1 238 const GAL_PICTURE = 'GAL:Picture'; 239 const GAL_STATUS = 'GAL:Status'; 240 const GAL_DATA = 'GAL:Data'; 241 242 /* Request Type */ 243 const REQUEST_TYPE_SYNC = 'sync'; 244 const REQUEST_TYPE_FOLDERSYNC = 'foldersync'; 245 246 /* Change Type */ 247 const CHANGE_TYPE_CHANGE = 'change'; 248 const CHANGE_TYPE_DELETE = 'delete'; 249 const CHANGE_TYPE_FLAGS = 'flags'; 250 const CHANGE_TYPE_MOVE = 'move'; 251 const CHANGE_TYPE_FOLDERSYNC = 'foldersync'; 252 const CHANGE_TYPE_SOFTDELETE = 'softdelete'; 253 254 // @since 2.36.0 255 const CHANGE_TYPE_DRAFT = 'draft'; 256 257 /* Internal flags to indicate change is a change in reply/forward state */ 258 const CHANGE_REPLY_STATE = '@--reply--@'; 259 const CHANGE_REPLYALL_STATE = '@--replyall--@'; 260 const CHANGE_FORWARD_STATE = '@--forward--@'; 261 262 /* RM */ 263 const RM_SUPPORT = 'RightsManagement:RightsManagementSupport'; 264 const RM_TEMPLATEID = 'RightsManagement:TemplateId'; 265 266 /* Collection Classes */ 267 const CLASS_EMAIL = 'Email'; 268 const CLASS_CONTACTS = 'Contacts'; 269 const CLASS_CALENDAR = 'Calendar'; 270 const CLASS_TASKS = 'Tasks'; 271 const CLASS_NOTES = 'Notes'; 272 const CLASS_SMS = 'SMS'; 273 274 /* Filtertype constants */ 275 const FILTERTYPE_ALL = 0; 276 const FILTERTYPE_1DAY = 1; 277 const FILTERTYPE_3DAYS = 2; 278 const FILTERTYPE_1WEEK = 3; 279 const FILTERTYPE_2WEEKS = 4; 280 const FILTERTYPE_1MONTH = 5; 281 const FILTERTYPE_3MONTHS = 6; 282 const FILTERTYPE_6MONTHS = 7; 283 const FILTERTYPE_INCOMPLETETASKS = 8; 284 285 // @todo normalize to string values. 286 const PROVISIONING_FORCE = true; 287 const PROVISIONING_LOOSE = 'loose'; 288 const PROVISIONING_NONE = false; 289 290 const FOLDER_ROOT = 0; 291 292 const VERSION_TWOFIVE = '2.5'; 293 const VERSION_TWELVE = '12.0'; 294 const VERSION_TWELVEONE = '12.1'; 295 const VERSION_FOURTEEN = '14.0'; 296 const VERSION_FOURTEENONE = '14.1'; 297 const VERSION_SIXTEEN = '16.0'; 298 299 const MIME_SUPPORT_NONE = 0; 300 const MIME_SUPPORT_SMIME = 1; 301 const MIME_SUPPORT_ALL = 2; 302 303 const IMAP_FLAG_REPLY = 'reply'; 304 const IMAP_FLAG_FORWARD = 'forward'; 305 306 /* Result Type */ 307 const RESOLVE_RESULT_GAL = 1; 308 const RESOLVE_RESULT_ADDRESSBOOK = 2; 309 310 /* Auth failure reasons */ 311 const AUTH_REASON_USER_DENIED = 'user'; 312 const AUTH_REASON_DEVICE_DENIED = 'device'; 313 314 /* Internal flag indicates all possible fields are ghosted */ 315 const ALL_GHOSTED = 'allghosted'; 316 317 const LIBRARY_VERSION = '2.41.4'; 318 319 /** 320 * Logger 321 * 322 * @var Horde_ActiveSync_Interface_LoggerFactory 323 */ 324 protected $_loggerFactory; 325 326 /** 327 * The logger for this class. 328 * 329 * @var Horde_Log_Logger 330 */ 331 protected static $_logger; 332 333 /** 334 * Provisioning support 335 * 336 * @var string 337 */ 338 protected $_provisioning; 339 340 /** 341 * Highest version to support. 342 * 343 * @var float 344 */ 345 protected $_maxVersion = self::VERSION_SIXTEEN; 346 347 /** 348 * The actual version we are supporting. 349 * 350 * @var float 351 */ 352 protected static $_version; 353 354 /** 355 * Multipart support? 356 * 357 * @var boolean 358 */ 359 protected $_multipart = false; 360 361 /** 362 * Support gzip compression of certain data parts? 363 * 364 * @var boolean 365 */ 366 protected $_compression = false; 367 368 /** 369 * Local cache of Get variables/decoded base64 uri 370 * 371 * @var array 372 */ 373 protected $_get = array(); 374 375 /** 376 * Path to root certificate bundle 377 * 378 * @var string 379 */ 380 protected $_certPath; 381 382 /** 383 * 384 * @var Horde_ActiveSync_Device 385 */ 386 protected static $_device; 387 388 /** 389 * Wbxml encoder 390 * 391 * @var Horde_ActiveSync_Wbxml_Encoder 392 */ 393 protected $_encoder; 394 395 /** 396 * Wbxml decoder 397 * 398 * @var Horde_ActiveSync_Wbxml_Decoder 399 */ 400 protected $_decoder; 401 402 /** 403 * The singleton collections handler. 404 * 405 * @var Horde_ActiveSync_Collections 406 */ 407 protected $_collectionsObj; 408 409 /** 410 * Global error flag. 411 * 412 * @var boolean 413 */ 414 protected $_globalError = false; 415 416 /** 417 * Process id (used in logging). 418 * 419 * @var integer 420 */ 421 protected $_procid; 422 423 /** 424 * Flag to indicate we need to update the device version. 425 * 426 * @var boolean 427 */ 428 protected $_needMsRp = false; 429 430 /** 431 * Supported EAS versions. 432 * 433 * @var array 434 */ 435 protected static $_supportedVersions = array( 436 self::VERSION_TWOFIVE, 437 self::VERSION_TWELVE, 438 self::VERSION_TWELVEONE, 439 self::VERSION_FOURTEEN, 440 self::VERSION_FOURTEENONE, 441 self::VERSION_SIXTEEN 442 ); 443 444 /** 445 * Factory method for creating Horde_ActiveSync_Message objects. 446 * 447 * @param string $message The message type. 448 * @since 2.4.0 449 * 450 * @return Horde_ActiveSync_Message_Base The concrete message object. 451 * @todo For H6, move to Horde_ActiveSync_Message_Base::factory() 452 */ 453 public static function messageFactory($message) 454 { 455 $class = 'Horde_ActiveSync_Message_' . $message; 456 if (!class_exists($class)) { 457 throw new InvalidArgumentException(sprintf('Class %s does not exist.', $class)); 458 } 459 460 return new $class(array( 461 'logger' => self::$_logger, 462 'protocolversion' => self::$_version, 463 'device' => self::$_device)); 464 } 465 466 /** 467 * Const'r 468 * 469 * @param Horde_ActiveSync_Driver_Base $driver The backend driver. 470 * @param Horde_ActiveSync_Wbxml_Decoder $decoder The Wbxml decoder. 471 * @param Horde_ActiveSync_Wbxml_Endcoder $encoder The Wbxml encoder. 472 * @param Horde_ActiveSync_State_Base $state The state driver. 473 * @param Horde_Controller_Request_Http $request The HTTP request object. 474 * 475 * @return Horde_ActiveSync The ActiveSync server object. 476 */ 477 public function __construct( 478 Horde_ActiveSync_Driver_Base $driver, 479 Horde_ActiveSync_Wbxml_Decoder $decoder, 480 Horde_ActiveSync_Wbxml_Encoder $encoder, 481 Horde_ActiveSync_State_Base $state, 482 Horde_Controller_Request_Http $request) 483 { 484 // The http request 485 $this->_request = $request; 486 487 // Backend driver 488 $this->_driver = $driver; 489 $this->_driver->setProtocolVersion($this->getProtocolVersion()); 490 491 // Device state manager 492 $this->_state = $state; 493 494 // Wbxml handlers 495 $this->_encoder = $encoder; 496 $this->_decoder = $decoder; 497 498 $this->_procid = getmypid(); 499 } 500 501 /** 502 * Return a collections singleton. 503 * 504 * @return Horde_ActiveSync_Collections 505 * @since 2.4.0 506 */ 507 public function getCollectionsObject() 508 { 509 if (empty($this->_collectionsObj)) { 510 $this->_collectionsObj = new Horde_ActiveSync_Collections($this->getSyncCache(), $this); 511 } 512 513 return $this->_collectionsObj; 514 } 515 516 /** 517 * Return a new, fully configured SyncCache. 518 * 519 * @return Horde_ActiveSync_SyncCache 520 * @since 2.4.0 521 */ 522 public function getSyncCache() 523 { 524 return new Horde_ActiveSync_SyncCache( 525 $this->_state, 526 self::$_device->id, 527 self::$_device->user, 528 self::$_logger 529 ); 530 } 531 532 /** 533 * Return an Importer object. 534 * 535 * @return Horde_ActiveSync_Connector_Importer 536 * @since 2.4.0 537 */ 538 public function getImporter() 539 { 540 $importer = new Horde_ActiveSync_Connector_Importer($this); 541 $importer->setLogger(self::$_logger); 542 543 return $importer; 544 } 545 546 /** 547 * Authenticate to the backend. 548 * 549 * @param Horde_ActiveSync_Credentials $credentials The credentials object. 550 * 551 * @return boolean True on successful authentication to the backend. 552 * @throws Horde_ActiveSync_Exception 553 */ 554 public function authenticate(Horde_ActiveSync_Credentials $credentials) 555 { 556 if (!$credentials->username) { 557 // No provided username or Authorization header. 558 self::$_logger->notice('Client did not provide authentication data.'); 559 return false; 560 } 561 562 $user = $this->_driver->getUsernameFromEmail($credentials->username); 563 $pos = strrpos($user, '\\'); 564 if ($pos !== false) { 565 $domain = substr($user, 0, $pos); 566 $user = substr($user, $pos + 1); 567 } else { 568 $domain = null; 569 } 570 571 // Authenticate 572 if ($result = $this->_driver->authenticate($user, $credentials->password, $domain)) { 573 if ($result === self::AUTH_REASON_USER_DENIED) { 574 $this->_globalError = Horde_ActiveSync_Status::SYNC_NOT_ALLOWED; 575 } elseif ($result === self::AUTH_REASON_DEVICE_DENIED) { 576 $this->_globalError = Horde_ActiveSync_Status::DEVICE_BLOCKED_FOR_USER; 577 } elseif ($result !== true) { 578 $this->_globalError = Horde_ActiveSync_Status::DENIED; 579 } 580 } else { 581 return false; 582 } 583 584 if (!$this->_driver->setup($user)) { 585 return false; 586 } 587 588 return true; 589 } 590 591 /** 592 * Allow to force the highest version to support. 593 * 594 * @param float $version The highest version 595 */ 596 public function setSupportedVersion($version) 597 { 598 $this->_maxVersion = $version; 599 } 600 601 /** 602 * Set the local path to the root certificate bundle. 603 * 604 * @param string $path The local path to the bundle. 605 */ 606 public function setRootCertificatePath($path) 607 { 608 $this->_certPath = $path; 609 } 610 611 /** 612 * Getter 613 * 614 * @param string $property The property to return. 615 * 616 * @return mixed The value of the requested property. 617 */ 618 public function __get($property) 619 { 620 switch ($property) { 621 case 'encoder': 622 case 'decoder': 623 case 'state': 624 case 'request': 625 case 'driver': 626 case 'provisioning': 627 case 'multipart': 628 case 'certPath': 629 $property = '_' . $property; 630 return $this->$property; 631 case 'logger': 632 return self::$_logger; 633 case 'device': 634 return self::$_device; 635 default: 636 throw new InvalidArgumentException(sprintf( 637 'The property %s does not exist', 638 $property) 639 ); 640 } 641 } 642 643 /** 644 * Setter for the logger factory. 645 * 646 * @param Horde_ActiveSync_Interface_LoggerFactory $logger The logger factory. 647 */ 648 public function setLogger(Horde_ActiveSync_Interface_LoggerFactory $logger) 649 { 650 $this->_loggerFactory = $logger; 651 } 652 653 /** 654 * Instantiate the logger from the factory and inject into all needed 655 * objects. 656 * 657 * @param array $options [description] 658 */ 659 protected function _setLogger(array $options) 660 { 661 if (!empty($this->_loggerFactory)) { 662 // @TODO. Remove wrapper. 663 self::$_logger = self::_wrapLogger($this->_loggerFactory->create($options)); 664 $this->_encoder->setLogger(self::$_logger); 665 $this->_decoder->setLogger(self::$_logger); 666 $this->_driver->setLogger(self::$_logger); 667 $this->_state->setLogger(self::$_logger); 668 } 669 } 670 671 public static function _wrapLogger(Horde_Log_Logger $logger) 672 { 673 if (!($logger instanceof Horde_ActiveSync_Log_Logger)) { 674 return new Horde_ActiveSync_Log_Logger_Deprecated(null, $logger); 675 } 676 677 return $logger; 678 } 679 680 /** 681 * Setter for provisioning support 682 * 683 */ 684 public function setProvisioning($provision) 685 { 686 $this->_provisioning = $provision; 687 } 688 689 /** 690 * Send the headers indicating that provisioning is required. 691 */ 692 public function provisioningRequired() 693 { 694 $this->provisionHeader(); 695 $this->activeSyncHeader(); 696 $this->versionHeader(); 697 $this->commandsHeader(); 698 header('Cache-Control: private'); 699 } 700 701 /** 702 * The heart of the server. Dispatch a request to the appropriate request 703 * handler. 704 * 705 * @param string $cmd The command we are requesting. 706 * @param string $devId The device id making the request. @deprecated 707 * 708 * @return string|boolean false if failed, true if succeeded and response 709 * content is wbxml, otherwise the 710 * content-type string to send in the response. 711 * @throws Horde_ActiveSync_Exception 712 * @throws Horde_ActiveSync_Exception_InvalidRequest 713 * @throws Horde_ActiveSync_PermissionDenied 714 */ 715 public function handleRequest($cmd, $devId) 716 { 717 $get = $this->getGetVars(); 718 if (empty($cmd)) { 719 $cmd = $get['Cmd']; 720 } 721 if (empty($devId)) { 722 $devId = !empty($get['DeviceId']) ? Horde_String::upper($get['DeviceId']) : null; 723 } else { 724 $devId = Horde_String::upper($devId); 725 } 726 $this->_setLogger($get); 727 728 // @TODO: Remove is_callable check for H6. 729 // Callback to give the backend the option to limit EAS version based 730 // on user/device/etc... 731 if (is_callable(array($this->_driver, 'versionCallback'))) { 732 $this->_driver->versionCallback($this); 733 } 734 735 // Autodiscovery handles authentication on it's own. 736 if ($cmd == 'Autodiscover') { 737 $request = new Horde_ActiveSync_Request_Autodiscover($this, new Horde_ActiveSync_Device($this->_state)); 738 739 if (!empty(self::$_logger)) { 740 $request->setLogger(self::$_logger); 741 } 742 743 $result = $request->handle($this->_request); 744 $this->_driver->clearAuthentication(); 745 return $result; 746 } 747 if (!$this->authenticate(new Horde_ActiveSync_Credentials($this))) { 748 $this->activeSyncHeader(); 749 $this->versionHeader(); 750 $this->commandsHeader(); 751 throw new Horde_Exception_AuthenticationFailure(); 752 } 753 754 self::$_logger->info(sprintf( 755 '%s%s request received for user %s', 756 str_repeat('-', 10), 757 Horde_String::upper($cmd), 758 $this->_driver->getUser()) 759 ); 760 761 // These are all handled in the same class. 762 if ($cmd == 'FolderDelete' || $cmd == 'FolderUpdate') { 763 $cmd = 'FolderCreate'; 764 } 765 766 // Device id is REQUIRED 767 if (empty($devId)) { 768 if ($cmd == 'Options') { 769 $this->_handleOptionsRequest(); 770 $this->_driver->clearAuthentication(); 771 return true; 772 } 773 $this->_driver->clearAuthentication(); 774 throw new Horde_ActiveSync_Exception_InvalidRequest('Device failed to send device id.'); 775 } 776 777 // EAS Version 778 $version = $this->getProtocolVersion(); 779 780 // Device. Even though versions of EAS > 12.1 are supposed to send 781 // EAS status codes back to indicate various errors in allowing a client 782 // to connect, we just throw an exception (thus causing a HTTP error 783 // code to be sent as in versions 12.1 and below). Until we refactor for 784 // Horde 6, we don't know the response type to wrap the status code in 785 // until we load the request handler, which requires we start to parse 786 // the WBXML stream and device information etc... This saves resources 787 // as well as keeps things cleaner until we refactor. 788 $device_result = $this->_handleDevice($devId); 789 790 // Don't bother with everything else if all we want are Options 791 if ($cmd == 'Options') { 792 $this->_handleOptionsRequest(); 793 $this->_driver->clearAuthentication(); 794 return true; 795 } 796 797 // Set provisioning support now that we are authenticated. 798 $this->setProvisioning($this->_driver->getProvisioning(self::$_device)); 799 800 // Read the initial Wbxml header 801 $this->_decoder->readWbxmlHeader(); 802 803 // Support Multipart response for ITEMOPERATIONS requests? 804 $headers = $this->_request->getHeaders(); 805 if ((!empty($headers['ms-asacceptmultipart']) && $headers['ms-asacceptmultipart'] == 'T') || 806 !empty($get['AcceptMultiPart'])) { 807 $this->_multipart = true; 808 self::$_logger->info('Requesting multipart data.'); 809 } 810 811 // Load the request handler to handle the request 812 // We must send the EAS header here, since some requests may start 813 // output and be large enough to flush the buffer (e.g., GetAttachment) 814 // See Bug: 12486 815 $this->activeSyncHeader(); 816 if ($cmd != 'GetAttachment') { 817 $this->contentTypeHeader(); 818 } 819 820 // Should we announce a new version is available to the client? 821 if (!empty($this->_needMsRp)) { 822 self::$_logger->info('Announcing X-MS-RP to client.'); 823 header("X-MS-RP: ". $this->getSupportedVersions()); 824 } 825 826 // @TODO: Look at getting rid of having to set the version in the driver 827 // and get it from the device object for H6. 828 $this->_driver->setDevice(self::$_device); 829 $class = 'Horde_ActiveSync_Request_' . basename($cmd); 830 if (class_exists($class)) { 831 $request = new $class($this); 832 $request->setLogger(self::$_logger); 833 $result = $request->handle(); 834 self::$_logger->info(sprintf( 835 'Maximum memory usage for ActiveSync request: %d bytes.', 836 memory_get_peak_usage(true)) 837 ); 838 839 return $result; 840 } 841 842 $this->_driver->clearAuthentication(); 843 throw new Horde_ActiveSync_Exception_InvalidRequest(basename($cmd) . ' not supported.'); 844 } 845 846 /** 847 * Handle device checks. Takes into account permissions and restrictions 848 * via various callback methods. 849 * 850 * @param string $devId The client provided device id. 851 * 852 * @return boolean If EAS version is > 12.1 returns false on any type of 853 * failure in allowing the device to connect. Sets 854 * appropriate internal variables to indicate the type of 855 * error to return to the client. Failure on EAS version 856 * < 12.1 results in throwing exceptions. Otherwise, return 857 * true. 858 * @throws Horde_ActiveSync_Exception, Horde_Exception_AuthenticationFailure 859 */ 860 protected function _handleDevice($devId) 861 { 862 $get = $this->getGetVars(); 863 $version = $this->getProtocolVersion(); 864 865 // Does device exist AND does the user have an account on the device? 866 if (!$this->_state->deviceExists($devId, $this->_driver->getUser())) { 867 // Device might exist, but with a new (additional) user account 868 if ($this->_state->deviceExists($devId)) { 869 self::$_device = $this->_state->loadDeviceInfo($devId); 870 } else { 871 self::$_device = new Horde_ActiveSync_Device($this->_state); 872 } 873 self::$_device->policykey = 0; 874 self::$_device->userAgent = $this->_request->getHeader('User-Agent'); 875 self::$_device->deviceType = !empty($get['DeviceType']) ? $get['DeviceType'] : ''; 876 self::$_device->rwstatus = self::RWSTATUS_NA; 877 self::$_device->user = $this->_driver->getUser(); 878 self::$_device->id = $devId; 879 self::$_device->needsVersionUpdate($this->getSupportedVersions()); 880 self::$_device->version = $version; 881 // @TODO: Remove is_callable check for H6. 882 // Combine this with the modifyDevice callback? Allow $device 883 // to be modified here? 884 if (is_callable(array($this->_driver, 'createDeviceCallback'))) { 885 $callback_ret = $this->_driver->createDeviceCallback(self::$_device); 886 if ($callback_ret !== true) { 887 $msg = sprintf( 888 'The device %s was disallowed for user %s per policy settings.', 889 self::$_device->id, 890 self::$_device->user); 891 self::$_logger->err($msg); 892 // Always throw exception in place of status code since we 893 // won't have a version number before the device is created. 894 throw new Horde_Exception_AuthenticationFailure($msg, $callback_ret); 895 } else { 896 // Give the driver a chance to modify device properties. 897 if (is_callable(array($this->_driver, 'modifyDeviceCallback'))) { 898 self::$_device = $this->_driver->modifyDeviceCallback(self::$_device); 899 } 900 } 901 } 902 } else { 903 self::$_device = $this->_state->loadDeviceInfo($devId, $this->_driver->getUser()); 904 905 // If the device state was removed from storage, we may lose the 906 // device properties, so try to repopulate what we can. userAgent 907 // is ALWAYS available, so if it's missing, the state is gone. 908 if (empty(self::$_device->userAgent)) { 909 self::$_device->userAgent = $this->_request->getHeader('User-Agent'); 910 self::$_device->deviceType = !empty($get['DeviceType']) ? $get['DeviceType'] : ''; 911 self::$_device->user = $this->_driver->getUser(); 912 } 913 914 if (empty(self::$_device->version)) { 915 self::$_device->version = $version; 916 } 917 if (self::$_device->version < $this->_maxVersion && 918 self::$_device->needsVersionUpdate($this->getSupportedVersions())) { 919 $this->_needMsRp = true; 920 } 921 922 // Give the driver a chance to modify device properties. 923 if (is_callable(array($this->_driver, 'modifyDeviceCallback'))) { 924 self::$_device = $this->_driver->modifyDeviceCallback(self::$_device); 925 } 926 } 927 928 // Save the device now that we know it is at least allowed to connect, 929 // or it has connected successfully at least once in the past. 930 self::$_device->save(); 931 if (is_callable(array($this->_driver, 'deviceCallback'))) { 932 $callback_ret = $this->_driver->deviceCallback(self::$_device); 933 if ($callback_ret !== true) { 934 $msg = sprintf( 935 'The device %s was disallowed for user %s per policy settings.', 936 self::$_device->id, 937 self::$_device->user); 938 self::$_logger->err($msg); 939 if ($version > self::VERSION_TWELVEONE) { 940 // Use a status code here, since the device has already 941 // connected. 942 $this->_globalError = $callback_ret; 943 return false; 944 } else { 945 throw new Horde_Exception_AuthenticationFailure($msg, $callback_ret); 946 } 947 } 948 } 949 950 // Lastly, check if the device has been set to blocked. 951 if (self::$_device->blocked) { 952 $msg = sprintf( 953 'The device %s was blocked.', 954 self::$_device->id); 955 self::$_logger->err($msg); 956 if ($version > self::VERSION_TWELVEONE) { 957 $this->_globalError = Horde_ActiveSync_Status::DEVICE_BLOCKED_FOR_USER; 958 return false; 959 } else { 960 throw new Horde_ActiveSync_Exception($msg); 961 } 962 } 963 964 return true; 965 } 966 967 /** 968 * Send the MS_Server-ActiveSync header. 969 * 970 * @return array Returns an array of the headers that were sent. 971 * @since 2.39.0 972 */ 973 public function activeSyncHeader() 974 { 975 $headers = array( 976 'Allow: OPTIONS,POST', 977 sprintf('Server: Horde_ActiveSync Library v%s', self::LIBRARY_VERSION), 978 'Public: OPTIONS,POST' 979 ); 980 981 switch ($this->_maxVersion) { 982 case self::VERSION_TWOFIVE: 983 $headers[] = 'MS-Server-ActiveSync: 6.5.7638.1'; 984 break; 985 case self::VERSION_TWELVE: 986 $headers[] = 'MS-Server-ActiveSync: 12.0'; 987 break; 988 case self::VERSION_TWELVEONE: 989 $headers[] = 'MS-Server-ActiveSync: 12.1'; 990 break; 991 case self::VERSION_FOURTEEN: 992 $headers[] = 'MS-Server-ActiveSync: 14.0'; 993 break; 994 case self::VERSION_FOURTEENONE: 995 $headers[] = 'MS-Server-ActiveSync: 14.1'; 996 break; 997 case self::VERSION_SIXTEEN: 998 $headers[] = 'MS-Server-ActiveSync: 16.0'; 999 } 1000 1001 foreach ($headers as $hdr) { 1002 header($hdr); 1003 } 1004 1005 return $headers; 1006 } 1007 1008 /** 1009 * Send the protocol versions header. 1010 * 1011 * @return string The header that was sent. @since 2.39.0 1012 */ 1013 public function versionHeader() 1014 { 1015 $hdr = sprintf('MS-ASProtocolVersions: %s', $this->getSupportedVersions()); 1016 header($hdr); 1017 1018 return $hdr; 1019 } 1020 1021 /** 1022 * Return supported versions in a comma delimited string suitable for 1023 * sending as the MS-ASProtocolVersions header. 1024 * 1025 * @return string 1026 */ 1027 public function getSupportedVersions() 1028 { 1029 return implode(',', array_slice(self::$_supportedVersions, 0, (array_search($this->_maxVersion, self::$_supportedVersions) + 1))); 1030 } 1031 1032 /** 1033 * Send protocol commands header. 1034 * 1035 * @return string The header that was sent. @since 2.39.0 1036 */ 1037 public function commandsHeader() 1038 { 1039 $hdr = sprintf('MS-ASProtocolCommands: %s', $this->getSupportedCommands()); 1040 header($hdr); 1041 1042 return $hdr; 1043 } 1044 1045 /** 1046 * Return the supported commands in a comma delimited string suitable for 1047 * sending as the MS-ASProtocolCommands header. 1048 * 1049 * @return string 1050 */ 1051 public function getSupportedCommands() 1052 { 1053 switch ($this->_maxVersion) { 1054 case self::VERSION_TWOFIVE: 1055 return 'Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,ResolveRecipients,ValidateCert,Provision,Search,Ping'; 1056 1057 case self::VERSION_TWELVE: 1058 case self::VERSION_TWELVEONE: 1059 case self::VERSION_FOURTEEN: 1060 case self::VERSION_FOURTEENONE: 1061 case self::VERSION_SIXTEEN: 1062 return 'Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Search,Settings,Ping,ItemOperations,Provision,ResolveRecipients,ValidateCert'; 1063 } 1064 } 1065 1066 /** 1067 * Send provision header 1068 */ 1069 public function provisionHeader() 1070 { 1071 header('HTTP/1.1 449 Retry after sending a PROVISION command'); 1072 } 1073 1074 /** 1075 * Obtain the policy key header from the request. 1076 * 1077 * @return integer The policy key or '0' if not set. 1078 */ 1079 public function getPolicyKey() 1080 { 1081 // Policy key can come from header or encoded request parameters. 1082 $this->_policykey = $this->_request->getHeader('X-MS-PolicyKey'); 1083 if (empty($this->_policykey)) { 1084 $get = $this->getGetVars(); 1085 if (!empty($get['PolicyKey'])) { 1086 $this->_policykey = $get['PolicyKey']; 1087 } else { 1088 $this->_policykey = 0; 1089 } 1090 } 1091 1092 return $this->_policykey; 1093 } 1094 1095 /** 1096 * Obtain the ActiveSync protocol version requested by the client headers. 1097 * 1098 * @return string The EAS version requested by the client. 1099 */ 1100 public function getProtocolVersion() 1101 { 1102 if (!isset(self::$_version)) { 1103 self::$_version = $this->_request->getHeader('MS-ASProtocolVersion'); 1104 if (empty(self::$_version)) { 1105 $get = $this->getGetVars(); 1106 self::$_version = empty($get['ProtVer']) ? '1.0' : $get['ProtVer']; 1107 } 1108 } 1109 return self::$_version; 1110 } 1111 1112 /** 1113 * Return the GET variables passed from the device, decoding from 1114 * base64 if needed. 1115 * 1116 * @return array A hash of get variables => values. 1117 */ 1118 public function getGetVars() 1119 { 1120 if (!empty($this->_get)) { 1121 return $this->_get; 1122 } 1123 1124 $results = array(); 1125 $get = $this->_request->getGetVars(); 1126 1127 // Do we need to decode the request parameters? 1128 if (!isset($get['Cmd']) && !isset($get['DeviceId']) && !isset($get['DeviceType'])) { 1129 $serverVars = $this->_request->getServerVars(); 1130 if (isset($serverVars['QUERY_STRING']) && strlen($serverVars['QUERY_STRING']) >= 10) { 1131 $results = Horde_ActiveSync_Utils::decodeBase64($serverVars['QUERY_STRING']); 1132 // Normalize values. 1133 switch ($results['DeviceType']) { 1134 case 'PPC': 1135 $results['DeviceType'] = 'PocketPC'; 1136 break; 1137 case 'SP': 1138 $results['DeviceType'] = 'SmartPhone'; 1139 break; 1140 case 'WP': 1141 case 'WP8': 1142 $results['DeviceType'] = 'WindowsPhone'; 1143 break; 1144 case 'android': 1145 case 'android40': 1146 $results['DeviceType'] = 'android'; 1147 } 1148 $this->_get = $results; 1149 } 1150 } else { 1151 $this->_get = $get; 1152 } 1153 1154 return $this->_get; 1155 } 1156 1157 /** 1158 * Return any global errors that occured during initial connection. 1159 * 1160 * @since 2.4.0 1161 * @return mixed A Horde_ActiveSync_Status:: constant of boolean false if 1162 * no errors. 1163 */ 1164 public function checkGlobalError() 1165 { 1166 return $this->_globalError; 1167 } 1168 1169 /** 1170 * Send the content type header. 1171 * 1172 */ 1173 public function contentTypeHeader($content_type = null) 1174 { 1175 if (!empty($content_type)) { 1176 header('Content-Type: ' . $content_type); 1177 return; 1178 } 1179 if ($this->_multipart) { 1180 header('Content-Type: application/vnd.ms-sync.multipart'); 1181 } else { 1182 header('Content-Type: application/vnd.ms-sync.wbxml'); 1183 } 1184 } 1185 1186 /** 1187 * Send the OPTIONS request response headers. 1188 */ 1189 protected function _handleOptionsRequest() 1190 { 1191 $as_headers = implode("\r\n", $this->activeSyncHeader()); 1192 $version_header = $this->versionHeader(); 1193 $cmd_header = $this->commandsHeader(); 1194 self::$_logger->meta(sprintf( 1195 "Returning OPTIONS response:\r\n%s\r\n%s\r\n%s", 1196 $as_headers, $version_header, $cmd_header) 1197 ); 1198 } 1199 1200 /** 1201 * Return the number of bytes corresponding to the requested trunction 1202 * constant. This applies to MIMETRUNCATION only. 1203 * 1204 * @param integer $truncation The constant. 1205 * 1206 * @return integer|boolean Either the size, in bytes, to truncate or 1207 * falso if no truncation. 1208 * @since 2.20.0 1209 */ 1210 public static function getMIMETruncSize($truncation) 1211 { 1212 switch($truncation) { 1213 case Horde_ActiveSync::TRUNCATION_ALL: 1214 return 0; 1215 case Horde_ActiveSync::TRUNCATION_1: 1216 return 4096; 1217 case Horde_ActiveSync::TRUNCATION_2: 1218 return 5120; 1219 case Horde_ActiveSync::TRUNCATION_3: 1220 return 7168; 1221 case Horde_ActiveSync::TRUNCATION_4: 1222 return 10240; 1223 case Horde_ActiveSync::TRUNCATION_5: 1224 return 20480; 1225 case Horde_ActiveSync::TRUNCATION_6: 1226 return 51200; 1227 case Horde_ActiveSync::TRUNCATION_7: 1228 return 102400; 1229 case Horde_ActiveSync::TRUNCATION_8: 1230 return false; 1231 default: 1232 return 1024; // Default to 1Kb 1233 } 1234 } 1235 1236 /** 1237 * Return the number of bytes corresponding to the requested trunction 1238 * constant. 1239 * 1240 * @param integer $truncation The constant. 1241 * 1242 * @return integer|boolean Either the size, in bytes, to truncate or 1243 * falso if no truncation. 1244 * 1245 */ 1246 public static function getTruncSize($truncation) 1247 { 1248 switch($truncation) { 1249 case Horde_ActiveSync::TRUNCATION_ALL: 1250 return 0; 1251 case Horde_ActiveSync::TRUNCATION_1: 1252 return 512; 1253 case Horde_ActiveSync::TRUNCATION_2: 1254 return 1024; 1255 case Horde_ActiveSync::TRUNCATION_3: 1256 return 2048; 1257 case Horde_ActiveSync::TRUNCATION_4: 1258 return 5120; 1259 case Horde_ActiveSync::TRUNCATION_5: 1260 return 10240; 1261 case Horde_ActiveSync::TRUNCATION_6: 1262 return 20480; 1263 case Horde_ActiveSync::TRUNCATION_7: 1264 return 51200; 1265 case Horde_ActiveSync::TRUNCATION_8: 1266 return 102400; 1267 case Horde_ActiveSync::TRUNCATION_9: 1268 case Horde_ActiveSync::TRUNCATION_NONE: // @deprecated 1269 return false; 1270 default: 1271 return 1024; // Default to 1Kb 1272 } 1273 } 1274 1275} 1276