1: <?php
2: /**
3: * ActiveSync Server - ported from ZPush
4: *
5: * Refactoring and other changes are
6: * Copyright 2009-2012 Horde LLC (http://www.horde.org/)
7: *
8: * @author Michael J. Rubinsky <mrubinsk@horde.org>
9: * @package ActiveSync
10: */
11: /**
12: * File : diffbackend.php
13: * Project : Z-Push
14: * Descr : We do a standard differential
15: * change detection by sorting both
16: * lists of items by their unique id,
17: * and then traversing both arrays
18: * of items at once. Changes can be
19: * detected by comparing items at
20: * the same position in both arrays.
21: *
22: * Created : 01.10.2007
23: *
24: * Zarafa Deutschland GmbH, www.zarafaserver.de
25: * This file is distributed under GPL-2.0.
26: * Consult COPYING file for details
27: */
28:
29: // POOMMAIL
30: //define("SYNC_POOMMAIL_ATTACHMENT","POOMMAIL:Attachment");
31: //define("SYNC_POOMMAIL_ATTACHMENTS","POOMMAIL:Attachments");
32: //define("SYNC_POOMMAIL_ATTNAME","POOMMAIL:AttName");
33: //define("SYNC_POOMMAIL_ATTSIZE","POOMMAIL:AttSize");
34: //define("SYNC_POOMMAIL_ATTOID","POOMMAIL:AttOid");
35: //define("SYNC_POOMMAIL_ATTMETHOD","POOMMAIL:AttMethod");
36: //define("SYNC_POOMMAIL_ATTREMOVED","POOMMAIL:AttRemoved");
37: //define("SYNC_POOMMAIL_BODY","POOMMAIL:Body");
38: //define("SYNC_POOMMAIL_BODYSIZE","POOMMAIL:BodySize");
39: //define("SYNC_POOMMAIL_BODYTRUNCATED","POOMMAIL:BodyTruncated");
40: //define("SYNC_POOMMAIL_DATERECEIVED","POOMMAIL:DateReceived");
41: //define("SYNC_POOMMAIL_DISPLAYNAME","POOMMAIL:DisplayName");
42: //define("SYNC_POOMMAIL_DISPLAYTO","POOMMAIL:DisplayTo");
43: //define("SYNC_POOMMAIL_IMPORTANCE","POOMMAIL:Importance");
44: //define("SYNC_POOMMAIL_MESSAGECLASS","POOMMAIL:MessageClass");
45: //define("SYNC_POOMMAIL_SUBJECT","POOMMAIL:Subject");
46: //define("SYNC_POOMMAIL_READ","POOMMAIL:Read");
47: //define("SYNC_POOMMAIL_TO","POOMMAIL:To");
48: //define("SYNC_POOMMAIL_CC","POOMMAIL:Cc");
49: //define("SYNC_POOMMAIL_FROM","POOMMAIL:From");
50: //define("SYNC_POOMMAIL_REPLY_TO","POOMMAIL:Reply-To");
51: //define("SYNC_POOMMAIL_ALLDAYEVENT","POOMMAIL:AllDayEvent");
52: //define("SYNC_POOMMAIL_CATEGORIES","POOMMAIL:Categories");
53: //define("SYNC_POOMMAIL_CATEGORY","POOMMAIL:Category");
54: //define("SYNC_POOMMAIL_DTSTAMP","POOMMAIL:DtStamp");
55: //define("SYNC_POOMMAIL_ENDTIME","POOMMAIL:EndTime");
56: //define("SYNC_POOMMAIL_INSTANCETYPE","POOMMAIL:InstanceType");
57: //define("SYNC_POOMMAIL_BUSYSTATUS","POOMMAIL:BusyStatus");
58: //define("SYNC_POOMMAIL_LOCATION","POOMMAIL:Location");
59: //define("SYNC_POOMMAIL_MEETINGREQUEST","POOMMAIL:MeetingRequest");
60: //define("SYNC_POOMMAIL_ORGANIZER","POOMMAIL:Organizer");
61: //define("SYNC_POOMMAIL_RECURRENCEID","POOMMAIL:RecurrenceId");
62: //define("SYNC_POOMMAIL_REMINDER","POOMMAIL:Reminder");
63: //define("SYNC_POOMMAIL_RESPONSEREQUESTED","POOMMAIL:ResponseRequested");
64: //define("SYNC_POOMMAIL_RECURRENCES","POOMMAIL:Recurrences");
65: //define("SYNC_POOMMAIL_RECURRENCE","POOMMAIL:Recurrence");
66: //define("SYNC_POOMMAIL_TYPE","POOMMAIL:Type");
67: //define("SYNC_POOMMAIL_UNTIL","POOMMAIL:Until");
68: //define("SYNC_POOMMAIL_OCCURRENCES","POOMMAIL:Occurrences");
69: //define("SYNC_POOMMAIL_INTERVAL","POOMMAIL:Interval");
70: //define("SYNC_POOMMAIL_DAYOFWEEK","POOMMAIL:DayOfWeek");
71: //define("SYNC_POOMMAIL_DAYOFMONTH","POOMMAIL:DayOfMonth");
72: //define("SYNC_POOMMAIL_WEEKOFMONTH","POOMMAIL:WeekOfMonth");
73: //define("SYNC_POOMMAIL_MONTHOFYEAR","POOMMAIL:MonthOfYear");
74: //define("SYNC_POOMMAIL_STARTTIME","POOMMAIL:StartTime");
75: //define("SYNC_POOMMAIL_SENSITIVITY","POOMMAIL:Sensitivity");
76: //define("SYNC_POOMMAIL_TIMEZONE","POOMMAIL:TimeZone");
77: //define("SYNC_POOMMAIL_GLOBALOBJID","POOMMAIL:GlobalObjId");
78: //define("SYNC_POOMMAIL_THREADTOPIC","POOMMAIL:ThreadTopic");
79: //define("SYNC_POOMMAIL_MIMEDATA","POOMMAIL:MIMEData");
80: //define("SYNC_POOMMAIL_MIMETRUNCATED","POOMMAIL:MIMETruncated");
81: //define("SYNC_POOMMAIL_MIMESIZE","POOMMAIL:MIMESize");
82: //define("SYNC_POOMMAIL_INTERNETCPID","POOMMAIL:InternetCPID");
83: //
84: //// ResolveRecipients
85: //define("SYNC_RESOLVERECIPIENTS_RESOLVERECIPIENTS","ResolveRecipients:ResolveRecipients");
86: //define("SYNC_RESOLVERECIPIENTS_RESPONSE","ResolveRecipients:Response");
87: //define("SYNC_RESOLVERECIPIENTS_STATUS","ResolveRecipients:Status");
88: //define("SYNC_RESOLVERECIPIENTS_TYPE","ResolveRecipients:Type");
89: //define("SYNC_RESOLVERECIPIENTS_RECIPIENT","ResolveRecipients:Recipient");
90: //define("SYNC_RESOLVERECIPIENTS_DISPLAYNAME","ResolveRecipients:DisplayName");
91: //define("SYNC_RESOLVERECIPIENTS_EMAILADDRESS","ResolveRecipients:EmailAddress");
92: //define("SYNC_RESOLVERECIPIENTS_CERTIFICATES","ResolveRecipients:Certificates");
93: //define("SYNC_RESOLVERECIPIENTS_CERTIFICATE","ResolveRecipients:Certificate");
94: //define("SYNC_RESOLVERECIPIENTS_MINICERTIFICATE","ResolveRecipients:MiniCertificate");
95: //define("SYNC_RESOLVERECIPIENTS_OPTIONS","ResolveRecipients:Options");
96: //define("SYNC_RESOLVERECIPIENTS_TO","ResolveRecipients:To");
97: //define("SYNC_RESOLVERECIPIENTS_CERTIFICATERETRIEVAL","ResolveRecipients:CertificateRetrieval");
98: //define("SYNC_RESOLVERECIPIENTS_RECIPIENTCOUNT","ResolveRecipients:RecipientCount");
99: //define("SYNC_RESOLVERECIPIENTS_MAXCERTIFICATES","ResolveRecipients:MaxCertificates");
100: //define("SYNC_RESOLVERECIPIENTS_MAXAMBIGUOUSRECIPIENTS","ResolveRecipients:MaxAmbiguousRecipients");
101: //define("SYNC_RESOLVERECIPIENTS_CERTIFICATECOUNT","ResolveRecipients:CertificateCount");
102: //
103: //// ValidateCert
104: //define("SYNC_VALIDATECERT_VALIDATECERT","ValidateCert:ValidateCert");
105: //define("SYNC_VALIDATECERT_CERTIFICATES","ValidateCert:Certificates");
106: //define("SYNC_VALIDATECERT_CERTIFICATE","ValidateCert:Certificate");
107: //define("SYNC_VALIDATECERT_CERTIFICATECHAIN","ValidateCert:CertificateChain");
108: //define("SYNC_VALIDATECERT_CHECKCRL","ValidateCert:CheckCRL");
109: //define("SYNC_VALIDATECERT_STATUS","ValidateCert:Status");
110:
111: /**
112: * Main ActiveSync class. Entry point for performing all ActiveSync operations
113: *
114: */
115: class Horde_ActiveSync
116: {
117: /* Conflict resolution */
118: const CONFLICT_OVERWRITE_SERVER = 0;
119: const CONFLICT_OVERWRITE_PIM = 1;
120:
121: const BACKEND_DISCARD_DATA = 1;
122:
123: /* TRUNCATION Constants */
124: const TRUNCATION_HEADERS = 0;
125: const TRUNCATION_512B = 1;
126: const TRUNCATION_1K = 2;
127: const TRUNCATION_5K = 4;
128: const TRUNCATION_SEVEN = 7;
129: const TRUNCATION_ALL = 9;
130:
131: /* Request related constants that are used in multiple places */
132: /* FOLDERHIERARCHY */
133: const FOLDERHIERARCHY_FOLDERS = 'FolderHierarchy:Folders';
134: const FOLDERHIERARCHY_FOLDER = 'FolderHierarchy:Folder';
135: const FOLDERHIERARCHY_DISPLAYNAME = 'FolderHierarchy:DisplayName';
136: const FOLDERHIERARCHY_SERVERENTRYID = 'FolderHierarchy:ServerEntryId';
137: const FOLDERHIERARCHY_PARENTID = 'FolderHierarchy:ParentId';
138: const FOLDERHIERARCHY_TYPE = 'FolderHierarchy:Type';
139: const FOLDERHIERARCHY_RESPONSE = 'FolderHierarchy:Response';
140: const FOLDERHIERARCHY_STATUS = 'FolderHierarchy:Status';
141: const FOLDERHIERARCHY_CONTENTCLASS = 'FolderHierarchy:ContentClass';
142: const FOLDERHIERARCHY_CHANGES = 'FolderHierarchy:Changes';
143: const FOLDERHIERARCHY_ADD = 'FolderHierarchy:Add';
144: const FOLDERHIERARCHY_REMOVE = 'FolderHierarchy:Remove';
145: const FOLDERHIERARCHY_UPDATE = 'FolderHierarchy:Update';
146: const FOLDERHIERARCHY_SYNCKEY = 'FolderHierarchy:SyncKey';
147: const FOLDERHIERARCHY_FOLDERCREATE = 'FolderHierarchy:FolderCreate';
148: const FOLDERHIERARCHY_FOLDERDELETE = 'FolderHierarchy:FolderDelete';
149: const FOLDERHIERARCHY_FOLDERUPDATE = 'FolderHierarchy:FolderUpdate';
150: const FOLDERHIERARCHY_FOLDERSYNC = 'FolderHierarchy:FolderSync';
151: const FOLDERHIERARCHY_COUNT = 'FolderHierarchy:Count';
152: const FOLDERHIERARCHY_VERSION = 'FolderHierarchy:Version';
153:
154: /* SYNC */
155: const SYNC_SYNCHRONIZE = 'Synchronize';
156: const SYNC_REPLIES = 'Replies';
157: const SYNC_ADD = 'Add';
158: const SYNC_MODIFY = 'Modify';
159: const SYNC_REMOVE = 'Remove';
160: const SYNC_FETCH = 'Fetch';
161: const SYNC_SYNCKEY = 'SyncKey';
162: const SYNC_CLIENTENTRYID = 'ClientEntryId';
163: const SYNC_SERVERENTRYID = 'ServerEntryId';
164: const SYNC_STATUS = 'Status';
165: const SYNC_FOLDER = 'Folder';
166: const SYNC_FOLDERTYPE = 'FolderType';
167: const SYNC_VERSION = 'Version';
168: const SYNC_FOLDERID = 'FolderId';
169: const SYNC_GETCHANGES = 'GetChanges';
170: const SYNC_MOREAVAILABLE = 'MoreAvailable';
171: const SYNC_WINDOWSIZE = 'WindowSize';
172: const SYNC_COMMANDS = 'Commands';
173: const SYNC_OPTIONS = 'Options';
174: const SYNC_FILTERTYPE = 'FilterType';
175: const SYNC_TRUNCATION = 'Truncation';
176: const SYNC_RTFTRUNCATION = 'RtfTruncation';
177: const SYNC_CONFLICT = 'Conflict';
178: const SYNC_FOLDERS = 'Folders';
179: const SYNC_DATA = 'Data';
180: const SYNC_DELETESASMOVES = 'DeletesAsMoves';
181: const SYNC_NOTIFYGUID = 'NotifyGUID';
182: const SYNC_SUPPORTED = 'Supported';
183: const SYNC_SOFTDELETE = 'SoftDelete';
184: const SYNC_MIMESUPPORT = 'MIMESupport';
185: const SYNC_MIMETRUNCATION = 'MIMETruncation';
186:
187: /* PROVISION */
188: const PROVISION_PROVISION = 'Provision:Provision';
189: const PROVISION_POLICIES = 'Provision:Policies';
190: const PROVISION_POLICY = 'Provision:Policy';
191: const PROVISION_POLICYTYPE = 'Provision:PolicyType';
192: const PROVISION_POLICYKEY = 'Provision:PolicyKey';
193: const PROVISION_DATA = 'Provision:Data';
194: const PROVISION_STATUS = 'Provision:Status';
195: const PROVISION_REMOTEWIPE = 'Provision:RemoteWipe';
196: const PROVISION_EASPROVISIONDOC = 'Provision:EASProvisionDoc';
197:
198: /* Flags */
199: const FLAG_NEWMESSAGE = 'NewMessage';
200:
201: /* Folder types */
202: const FOLDER_TYPE_OTHER = 1;
203: const FOLDER_TYPE_INBOX = 2;
204: const FOLDER_TYPE_DRAFTS = 3;
205: const FOLDER_TYPE_WASTEBASKET = 4;
206: const FOLDER_TYPE_SENTMAIL = 5;
207: const FOLDER_TYPE_OUTBOX = 6;
208: const FOLDER_TYPE_TASK = 7;
209: const FOLDER_TYPE_APPOINTMENT = 8;
210: const FOLDER_TYPE_CONTACT = 9;
211: const FOLDER_TYPE_NOTE = 10;
212: const FOLDER_TYPE_JOURNAL = 11;
213: const FOLDER_TYPE_USER_MAIL = 12;
214: const FOLDER_TYPE_USER_APPOINTMENT = 13;
215: const FOLDER_TYPE_USER_CONTACT = 14;
216: const FOLDER_TYPE_USER_TASK = 15;
217: const FOLDER_TYPE_USER_JOURNAL = 16;
218: const FOLDER_TYPE_USER_NOTE = 17;
219: const FOLDER_TYPE_UNKNOWN = 18;
220: const FOLDER_TYPE_RECIPIENT_CACHE = 19;
221: const FOLDER_TYPE_DUMMY = '__dummy.Folder.Id__';
222:
223: /** Origin of changes **/
224: const CHANGE_ORIGIN_PIM = 0;
225: const CHANGE_ORIGIN_SERVER = 1;
226: const CHANGE_ORIGIN_NA = 3;
227:
228: /** Remote wipe **/
229: const RWSTATUS_NA = 0;
230: const RWSTATUS_OK = 1;
231: const RWSTATUS_PENDING = 2;
232: const RWSTATUS_WIPED = 3;
233:
234: /** GAL **/
235: const GAL_DISPLAYNAME = 'GAL:DisplayName';
236: const GAL_PHONE = 'GAL:Phone';
237: const GAL_OFFICE = 'GAL:Office';
238: const GAL_TITLE = 'GAL:Title';
239: const GAL_COMPANY = 'GAL:Company';
240: const GAL_ALIAS = 'GAL:Alias';
241: const GAL_FIRSTNAME = 'GAL:FirstName';
242: const GAL_LASTNAME = 'GAL:LastName';
243: const GAL_HOMEPHONE = 'GAL:HomePhone';
244: const GAL_MOBILEPHONE = 'GAL:MobilePhone';
245: const GAL_EMAILADDRESS = 'GAL:EmailAddress';
246:
247: const PROVISIONING_LOOSE = 'loose';
248: /**
249: * Logger
250: *
251: * @var Horde_Log_Logger
252: */
253: protected $_logger;
254:
255: /**
256: * Provisioning support
257: *
258: * @var string (TODO _constant this)
259: */
260: protected $_provisioning;
261:
262: /**
263: * Const'r
264: *
265: * @param Horde_ActiveSync_Driver $driver The backend driver
266: * @param Horde_ActiveSync_StateMachine $state The state machine
267: * @param Horde_ActiveSync_Wbxml_Decoder $decoder The Wbxml decoder
268: * @param Horde_ActiveSync_Wbxml_Endcodder $encdoer The Wbxml encoder
269: *
270: * @return Horde_ActiveSync
271: */
272: public function __construct(Horde_ActiveSync_Driver_Base $driver,
273: Horde_ActiveSync_Wbxml_Decoder $decoder,
274: Horde_ActiveSync_Wbxml_Encoder $encoder,
275: Horde_Controller_Request_Http $request)
276: {
277: // Backend driver
278: $this->_driver = $driver;
279:
280: // Wbxml handlers
281: $this->_encoder = $encoder;
282: $this->_decoder = $decoder;
283:
284: // Read the initial Wbxml header
285: $this->_decoder->readWbxmlHeader();
286:
287: // The http request
288: $this->_request = $request;
289: }
290:
291: /**
292: * Setter for the logger
293: *
294: * @param Horde_Log_Logger $logger The logger object.
295: *
296: * @return void
297: */
298: public function setLogger(Horde_Log_Logger $logger)
299: {
300: $this->_logger = $logger;
301: $this->_encoder->setLogger($logger);
302: $this->_decoder->setLogger($logger);
303: $this->_driver->setLogger($logger);
304: }
305:
306: /**
307: * Setter for provisioning support
308: *
309: */
310: public function setProvisioning($provision)
311: {
312: $this->_provisioning = $provision;
313: }
314:
315: static public function provisioningRequired()
316: {
317: self::provisionHeader();
318: self::activeSyncHeader();
319: self::versionHeader();
320: self::commandsHeader();
321: header("Cache-Control: private");
322: }
323:
324: /**
325: * The heart of the server. Dispatch a request to the appropriate request
326: * handler.
327: *
328: * @param string $cmd The command we are requesting.
329: * @param string $devId The device id making the request.
330: *
331: * @return boolean
332: */
333: public function handleRequest($cmd, $devId)
334: {
335: $this->_logger->debug('['. $devId . '] ' . strtoupper($cmd) . ' request received for user ' . $this->_driver->getUser());
336:
337: // Don't bother with everything else if all we want are Options
338: if ($cmd == 'Options') {
339: self::activeSyncHeader();
340: self::versionHeader();
341: self::commandsHeader();
342: return true;
343: }
344:
345: $state = $this->_driver->getStateObject();
346:
347: if (is_null($devId)) {
348: throw new Horde_ActiveSync_Exception('Device failed to send device id.');
349: }
350: // Does device exist AND does the user have an account on the device?
351: if (!empty($devId) && !$state->deviceExists($devId, $this->_driver->getUser())) {
352: // Device might exist, but with a new (additional) user account
353: $device = new StdClass();
354: if ($state->deviceExists($devId)) {
355: $d = $state->loadDeviceInfo($devId, '');;
356: }
357: $device->policykey = 0;
358: $get = $this->_request->getGetVars();
359: $device->userAgent = $this->_request->getHeader('User-Agent');
360: $device->deviceType = !empty($get['DeviceType']) ? $get['DeviceType'] : '';
361: $device->rwstatus = self::RWSTATUS_NA;
362: $device->user = $this->_driver->getUser();
363: $device->id = $devId;
364: $state->setDeviceInfo($device);
365: } else {
366: $device = $state->loadDeviceInfo($devId, $this->_driver->getUser());
367: }
368:
369: // Load the request handler to handle the request
370: $class = 'Horde_ActiveSync_Request_' . basename($cmd);
371: $version = $this->getProtocolVersion();
372: if (class_exists($class)) {
373: $request = new $class(
374: $this->_driver,
375: $this->_decoder,
376: $this->_encoder,
377: $this->_request,
378: $this,
379: $device,
380: $this->_provisioning);
381: $request->setLogger($this->_logger);
382: // @TODO: The headers really should be output in the Rpc layer.
383: // Can't due that until Horde 5 because the InvalidRequest Exception
384: // was introduced after release i.e., this is a BC break.
385: try {
386: $result = $request->handle();
387: } catch (Horde_ActiveSync_Exception_InvalidRequest $e) {
388: $this->_logger->err('Returning HTTP 400:' . $e->getMessage());
389: header('HTTP/1.1 400 Invalid Request ' . $e->getMessage());
390: } catch (Horde_ActiveSync_Exception $e) {
391: $this->_logger->err('Returning HTTP 500:' . $e->getMessage());
392: header('HTTP/1.1 500');
393: }
394: $this->_driver->logOff();
395:
396: return $result;
397: }
398:
399: // No idea what the client is talking about
400: header('HTTP/1.1 400 Invalid Request ' . basename($cmd) . ' not supported.');
401: }
402:
403: /**
404: * Send the MS_Server-ActiveSync header
405: * (This is the version Exchange 2003 advertises)
406: *
407: * @return void
408: */
409: static public function activeSyncHeader()
410: {
411: header("MS-Server-ActiveSync: 6.5.7638.1");
412: }
413:
414: /**
415: * Send the protocol versions header
416: *
417: * @return void
418: */
419: static public function versionHeader()
420: {
421: header("MS-ASProtocolVersions: 1.0,2.0,2.1,2.5");
422: }
423:
424: /**
425: * Send protocol commands header. This contains appropriate command for
426: * ActiveSync version 2.5 support.
427: *
428: * @return void
429: */
430: static public function commandsHeader()
431: {
432: header("MS-ASProtocolCommands: Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,ResolveRecipients,ValidateCert,Provision,Search,Ping");
433: }
434:
435: /**
436: * Send provision header
437: *
438: * @return void
439: */
440: static public function provisionHeader()
441: {
442: header("HTTP/1.1 449 Retry after sending a PROVISION command");
443: }
444:
445: /**
446: * Obtain the policy key header from the request.
447: *
448: * @return integer The policy key or '0' if not set.
449: */
450: public function getPolicyKey()
451: {
452: $this->_policykey = $this->_request->getHeader('X-MS-PolicyKey');
453: if (empty($this->_policykey)) {
454: $this->_policykey = 0;
455: }
456:
457: return $this->_policykey;
458: }
459:
460: /**
461: * Obtain the ActiveSync protocol version
462: *
463: * @return string
464: */
465: public function getProtocolVersion()
466: {
467: if (isset($this->_version)) {
468: return $this->_version;
469: }
470: $this->_version = $this->_request->getHeader('MS-ASProtocolVersion');
471: if (empty($this->_version)) {
472: $this->_version = '1.0';
473: }
474:
475: return $this->_version;
476: }
477:
478: }