A hint: This file contains one or more very long lines, so maybe it is better readable using the pure text view mode that shows the contents as wrapped lines within the browser window.
1 <?php 2 /*********************************************** 3 * File : index.php 4 * Project : Z-Push 5 * Descr : This is the entry point 6 * through which all requests 7 * are called. 8 * 9 * Created : 01.10.2007 10 * 11 * Zarafa Deutschland GmbH, www.zarafaserver.de 12 * This file is distributed under GPL v2. 13 * Consult LICENSE file for details 14 ************************************************/ 15 16 $sessionstarttime = microtime(true); 17 //we handle connection aborts ourself. necessary to keep sync state clean in heartbeat/wait 18 ignore_user_abort(true); 19 ob_start(false, 1048576); 20 21 include_once('zpushdefs.php'); 22 include_once("config.php"); 23 include_once("proto.php"); 24 include_once("request.php"); 25 include_once("debug.php"); 26 include_once("compat.php"); 27 include_once("version.php"); 28 29 // Define SCRIPT_TIMEOUT in case it is not defined in config.php or negative if you substract 600 30 if (!defined("SCRIPT_TIMEOUT")) { 31 define('REAL_SCRIPT_TIMEOUT', 3540+600); 32 } else { 33 if ((SCRIPT_TIMEOUT-600) < 660) 34 define('REAL_SCRIPT_TIMEOUT', SCRIPT_TIMEOUT+660); 35 else 36 define('REAL_SCRIPT_TIMEOUT', SCRIPT_TIMEOUT); 37 } 38 39 // Attempt to set maximum execution time 40 ini_set('max_execution_time', REAL_SCRIPT_TIMEOUT); 41 set_time_limit(REAL_SCRIPT_TIMEOUT); 42 43 44 debugLog("Start ------ THIS IS AN UNOFFICIAL DEVELOPER VERSION!"); 45 debugLog("Z-Push version: $zpush_version"); 46 debugLog("Client IP: ". $_SERVER['REMOTE_ADDR']); 47 debugLog("Set max_execution_time to ". ini_get('max_execution_time')); 48 //debugLog(print_r($_SERVER,true)); 49 //debugLog(print_r($_GET,true)); 50 //debugLog(print_r($_POST,true)); 51 //debugLog(print_r(apache_request_headers(),true)); 52 register_shutdown_function("shutdownCommunication"); 53 $cachestatus = SYNCCACHE_UNCHANGED; 54 $input = fopen("php://input", "r"); 55 $output = fopen("php://output", "w+"); 56 57 // The script must always be called with authorisation info 58 59 if (!isset($_SERVER['PHP_AUTH_PW'])) { 60 header("HTTP/1.1 401 Unauthorized"); 61 header("WWW-Authenticate: Basic realm=\"ZPush\""); 62 print("Access denied. Please send authorisation information"); 63 debugLog("Access denied: no password sent."); 64 debugLog("end"); 65 debugLog("--------"); 66 return; 67 } 68 69 // split username & domain if received as one 70 if (($pos = strrpos($_SERVER['PHP_AUTH_USER'], '\\')) === false) { 71 if (SEPARATE_UPN === true && 72 ($pos = strrpos($_SERVER['PHP_AUTH_USER'], '@')) !== false) { 73 $auth_user = substr($_SERVER['PHP_AUTH_USER'],0,$pos); 74 $auth_domain = substr($_SERVER['PHP_AUTH_USER'],$pos+1); 75 } else { 76 $auth_user = $_SERVER['PHP_AUTH_USER']; 77 $auth_domain = ''; 78 } 79 } else { 80 $auth_domain = substr($_SERVER['PHP_AUTH_USER'],0,$pos); 81 $auth_user = substr($_SERVER['PHP_AUTH_USER'],$pos+1); 82 } 83 84 85 $auth_pw = $_SERVER['PHP_AUTH_PW']; 86 87 $cmd = $user = $devid = $devtype = ""; 88 89 // Parse the standard GET parameters 90 if(isset($_GET["Cmd"])) 91 $cmd = $_GET["Cmd"]; 92 if(isset($_GET["User"])) 93 $user = $_GET["User"]; 94 if(isset($_GET["DeviceId"])) 95 $devid = $_GET["DeviceId"]; 96 if(isset($_GET["DeviceType"])) 97 $devtype = $_GET["DeviceType"]; 98 // Get the parameters from Query String in case they`re not in the get. 99 // AS >=12.1 100 if (!isset($_GET['Cmd']) && 101 !isset($_GET['DeviceId']) && 102 !isset($_GET['DeviceType']) && 103 isset($_SERVER['QUERY_STRING']) && 104 strlen($_SERVER['QUERY_STRING']) >= 10) { 105 $user = $auth_user; 106 $uri_decoded = base64uri_decode($_SERVER['QUERY_STRING']); 107 $devid = $uri_decoded['DevID']; 108 switch($uri_decoded['DevType']) { 109 case 'PPC' : $devtype = 'PocketPC'; break; 110 case 'SP' : $devtype = 'SmartPhone'; break; 111 }; 112 switch($uri_decoded['Command']) { 113 case '0' : $cmd = 'Sync'; break; 114 case '1' : $cmd = 'SendMail'; break; 115 case '2' : $cmd = 'SmartForward'; break; 116 case '3' : $cmd = 'SmartReply'; break; 117 case '4' : $cmd = 'GetAttachment'; break; 118 case '9' : $cmd = 'FolderSync'; break; 119 case '10' : $cmd = 'FolderCreate'; break; 120 case '11' : $cmd = 'FolderDelete'; break; 121 case '12' : $cmd = 'FolderUpdate'; break; 122 case '13' : $cmd = 'MoveItems'; break; 123 case '14' : $cmd = 'GetItemEstimate'; break; 124 case '15' : $cmd = 'MeetingResponse'; break; 125 case '16' : $cmd = 'Search'; break; 126 case '17' : $cmd = 'Settings'; break; 127 case '18' : $cmd = 'Ping'; break; 128 case '19' : $cmd = 'ItemOperations'; break; 129 case '20' : $cmd = 'Provision'; break; 130 case '21' : $cmd = 'ResolveRecipients'; break; 131 case '22' : $cmd = 'ValidateCert'; break; 132 } 133 if (isset($uri_decoded['AttachmentName'])) $_GET['AttachmentName'] = $uri_decoded['AttachmentName']; 134 if (isset($uri_decoded['ItemId'])) $_GET['ItemId'] = $uri_decoded['ItemId']; 135 if (isset($uri_decoded['CollectionId'])) $_GET['CollectionId'] = $uri_decoded['CollectionId']; 136 if (isset($uri_decoded['CollectionName'])) $_GET['CollectionName'] = $uri_decoded['CollectionName']; 137 if (isset($uri_decoded['ParentId'])) $_GET['ParentId'] = $uri_decoded['ParentId']; 138 if (isset($uri_decoded['LongId'])) $_GET['LongId'] = $uri_decoded['LongId']; 139 if (isset($uri_decoded['Occurrence'])) $_GET['Occurrence'] = $uri_decoded['Occurrence']; 140 if (isset($uri_decoded['Options'])) { 141 $uri_decoded['Options'] = bin2hex($uri_decoded['Options'])*1; 142 if($uri_decoded['Options'] & 0x01) debugLog("Save in sent Items"); 143 if($uri_decoded['Options'] & 0x02) debugLog("AcceptMultiPart"); 144 } 145 if (isset($uri_decoded['User'])) $_GET['User'] = $uri_decoded['User']; 146 debugLog('Base64 encoded URI contains: '.print_r($uri_decoded,true)); 147 }; 148 149 // The GET parameters are required 150 if($_SERVER["REQUEST_METHOD"] == "POST") { 151 if(!isset($user) || !isset($devid) || !isset($devtype)) { 152 print("Your device requested the Z-Push URL without the required GET parameters"); 153 return; 154 } 155 } 156 157 // Get the request headers so we can see the versions 158 $requestheaders = apache_request_headers(); 159 if (isset($requestheaders["Ms-Asprotocolversion"])) 160 $requestheaders["MS-ASProtocolVersion"] = $requestheaders["Ms-Asprotocolversion"]; 161 if (isset($requestheaders["MS-ASProtocolVersion"]) || 162 isset($uri_decoded['ProtVer'])) { 163 global $protocolversion; 164 if (isset($requestheaders["MS-ASProtocolVersion"])) 165 $protocolversion = $requestheaders["MS-ASProtocolVersion"]; 166 else 167 $protocolversion = $uri_decoded['ProtVer']/10; 168 debugLog("Client supports version " . $protocolversion); 169 } else { 170 global $protocolversion; 171 172 $protocolversion = "1.0"; 173 } 174 175 // START ADDED dw2412 Support Multipart response 176 // 177 if ((isset($requestheaders["MS-ASAcceptMultiPart"]) && 178 $requestheaders["MS-ASAcceptMultiPart"] == "T") || 179 (isset($uri_decoded['Options']) && 180 $uri_decoded['Options'] & 0x02)) { 181 $multipart = true; 182 } else { 183 $multipart = false; 184 } 185 // END ADDED dw2412 Support Multipart response 186 187 //mthaler@endo7.com disable multipart for roadsync 188 if (preg_match('/RoadSync/',$requestheaders['User-Agent'])) { 189 debugLog('Disable Multipart: '.$requestheaders['User-Agent']); 190 $multipart = false; 191 } 192 193 194 // START ADDED dw2412 Support gzip compression in result 195 if (isset($requestheaders["Accept-Encoding"])) { 196 $encodings = explode(", ",$requestheaders["Accept-Encoding"]); 197 debugLog("Current zlib output compression setting: ".ini_get("zlib.output_compression")); 198 199 if (array_search("gzip",$encodings) !== false && 200 function_exists('gzencode')) { 201 ini_set("zlib.output_compression",'0'); 202 debugLog("Enabled zlib output compression"); 203 define ("GZIP_OUTPUT",true); 204 } else { 205 ini_set("zlib.output_compression",'0'); 206 debugLog("Disabled zlib output compression"); 207 define ("GZIP_OUTPUT",false); 208 } 209 } else { 210 define ("GZIP_OUTPUT",false); 211 } 212 // END ADDED dw2412 Support gzip compression in result 213 214 if (isset($requestheaders["X-Ms-Policykey"])) 215 $requestheaders["X-MS-PolicyKey"] = $requestheaders["X-Ms-Policykey"]; 216 if (isset($requestheaders["X-MS-PolicyKey"]) || 217 isset($uri_decoded['PolKey'])) { 218 global $policykey; 219 if (isset($requestheaders["X-MS-PolicyKey"])) 220 $policykey = $requestheaders["X-MS-PolicyKey"]; 221 else 222 $policykey = $uri_decoded['PolKey']; 223 } else { 224 global $policykey; 225 $policykey = 0; 226 } 227 228 //get user agent 229 if (isset($requestheaders["User-Agent"])) { 230 global $useragent; 231 $useragent = $requestheaders["User-Agent"]; 232 } else { 233 global $useragent; 234 $useragent = "unknown"; 235 } 236 237 // if useragent starts with nokia limit message recipients - otherwise client will retrieve item unlimitted!!! 238 if (strncmp($useragent,"Nokia",5) == 0) { 239 debugLog("Nokia detected! Limit recipients to 100!"); 240 define("LIMIT_RECIPIENTS",100); 241 define("NOKIA_DETECTED",true); 242 } 243 244 // Load our backend driver 245 $backend_dir = opendir(BASE_PATH . "/backend"); 246 while($entry = readdir($backend_dir)) { 247 if(substr($entry,0,1) == "." || substr($entry,-3) != "php") 248 continue; 249 250 if (!function_exists("mapi_logon") && ($entry == "ics.php")) 251 continue; 252 253 include_once(BASE_PATH . "/backend/" . $entry); 254 } 255 256 // Initialize our backend 257 $backend = new $BACKEND_PROVIDER(); 258 259 if($backend->Logon($auth_user, $auth_domain, $auth_pw) == false) { 260 header("HTTP/1.1 401 Unauthorized"); 261 header("WWW-Authenticate: Basic realm=\"ZPush\""); 262 print("Access denied. Username or password incorrect."); 263 debugLog("Access denied: backend logon failed."); 264 debugLog("end"); 265 debugLog("--------"); 266 return; 267 } 268 //mthaler@endo7.com 269 define('VERSION_SUPPORT','2.0,2.1,2.5,12.0,12.1'); 270 define('VERSION_SUPPORT_LAST','12.1'); 271 272 // check policy header 273 274 if (PROVISIONING === true && $_SERVER["REQUEST_METHOD"] != 'OPTIONS' && $cmd != 'Ping' && $cmd != 'Provision' && 275 $backend->CheckPolicy($policykey, $devid) != SYNC_PROVISION_STATUS_SUCCESS && 276 (LOOSE_PROVISIONING === false || 277 (LOOSE_PROVISIONING === true && (isset($requestheaders["X-MS-PolicyKey"]) || isset($uri_decoded['PolKey']) )))) { 278 header("HTTP/1.1 449 Retry after sending a PROVISION command"); 279 // dw2412 changed to support AS14 Protocol 280 header("MS-Server-ActiveSync: ".VERSION_SUPPORT_LAST); 281 header("MS-ASProtocolVersions: ".VERSION_SUPPORT); 282 header("MS-ASProtocolRevisions: 12.1r1"); 283 //header("X-MS-MV: 14.0.255"); 284 // CHANGED dw2412 Support for Settings and ItemOperations command 285 header("MS-ASProtocolCommands: Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,Provision,ResolveRecipients,ValidateCert,Settings,Search,Ping,ItemOperations"); 286 header("Cache-Control: private"); 287 debugLog("POST cmd $cmd denied: Retry after sending a PROVISION command"); 288 debugLog("end"); 289 debugLog("--------"); 290 return; 291 } 292 293 // $user is usually the same as the PHP_AUTH_USER. This allows you to sync the 'john' account if you 294 // have sufficient privileges as user 'joe'. 295 if($backend->Setup($user, $devid, $protocolversion) == false) { 296 header("HTTP/1.1 401 Unauthorized"); 297 header("WWW-Authenticate: Basic realm=\"ZPush\""); 298 print("Access denied or user '$user' unknown."); 299 debugLog("Access denied: backend setup failed."); 300 debugLog("end"); 301 debugLog("--------"); 302 return; 303 } 304 305 // Do the actual request 306 switch($_SERVER["REQUEST_METHOD"]) { 307 case 'OPTIONS': 308 // dw2412 changed to support AS14 Protocol 309 // Beta E2K10 ID 310 // header("MS-Server-ActiveSync: 14.00.048.018"); 311 // header("MS-ASProtocolVersions: 1.0,2.0,2.1,2.5,12.0,12.1,14.0"); 312 // header("MS-ASProtocolRevisions: 12.1r1"); 313 // header("X-MS-MV: 14.0.255"); 314 header("MS-Server-ActiveSync: ".VERSION_SUPPORT_LAST); 315 header("MS-ASProtocolVersions: ".VERSION_SUPPORT); 316 317 /* header("MS-Server-ActiveSync: 14.1"); 318 header("MS-ASProtocolVersions: 1.0,2.0,2.1,2.5,12.0,12.1,14.0,14.1");*/ 319 // START ADDED dw2412 320 // Compare and send X-MS-RP depending on Protocol Version string 321 // write the new Protocol Version string if update send 322 include_once ('statemachine.php'); 323 $protstate = new StateMachine($devid,$user); 324 $protsupp = $protstate->getProtocolState(); 325 if ($protsupp !== false && $protsupp != VERSION_SUPPORT) { 326 header("X-MS-RP: ".VERSION_SUPPORT); 327 debugLog("Sending X-MS-RP to update Protocol Version on Device"); 328 $protstate->setProtocolState(VERSION_SUPPORT); 329 } 330 unset($protstate); 331 // END ADDED dw2412 332 // START CHANGED dw2412 Settings and ItemOperations Command Support 333 header("MS-ASProtocolCommands: Sync,SendMail,SmartForward,SmartReply,GetAttachment,GetHierarchy,CreateCollection,DeleteCollection,MoveCollection,FolderSync,FolderCreate,FolderDelete,FolderUpdate,MoveItems,GetItemEstimate,MeetingResponse,ResolveRecipients,ValidateCert,Provision,Settings,Search,Ping,ItemOperations"); 334 debugLog("Options request"); 335 break; 336 case 'POST': 337 // dw2412 changed to support AS14 Protocol 338 header("MS-Server-ActiveSync: ".VERSION_SUPPORT_LAST); 339 // header("MS-Server-ActiveSync: 14.1"); 340 debugLog("POST cmd: $cmd"); 341 // Update X-MS-RP In case version changed 342 include_once ('statemachine.php'); 343 $protstate = new StateMachine($devid,$user); 344 $protsupp = $protstate->getProtocolState(); 345 if ($protsupp !== false && $protsupp != VERSION_SUPPORT) { 346 header("X-MS-RP: ".VERSION_SUPPORT); 347 debugLog("Sending X-MS-RP to update Protocol Version on Device"); 348 $protstate->setProtocolState(VERSION_SUPPORT); 349 } 350 unset($protstate); 351 // Do the actual request 352 if(!HandleRequest($backend, $cmd, $devid, $protocolversion, $multipart)) { 353 // Request failed. Try to output some kind of error information. We can only do this if 354 // output had not started yet. If it has started already, we can't show the user the error, and 355 // the device will give its own (useless) error message. 356 if(!headers_sent()) { 357 header("Content-type: text/html"); 358 print("<BODY>\n"); 359 print("<h3>Error</h3><p>\n"); 360 print("There was a problem processing the <i>$cmd</i> command from your PDA.\n"); 361 print("<p>Here is the debug output:<p><pre>\n"); 362 print(getDebugInfo()); 363 print("</pre>\n"); 364 print("</BODY>\n"); 365 } 366 } 367 break; 368 case 'GET': 369 header("Content-type: text/html"); 370 print("<BODY>\n"); 371 print("<h3>GET not supported</h3><p>\n"); 372 print("This is the z-push location and can only be accessed by Microsoft ActiveSync-capable devices."); 373 print("</BODY>\n"); 374 break; 375 } 376 377 378 $len = ob_get_length(); 379 $data = ob_get_contents(); 380 ob_end_clean(); 381 382 // Unfortunately, even though zpush can stream the data to the client 383 // with a chunked encoding, using chunked encoding also breaks the progress bar 384 // on the PDA. So we de-chunk here and just output a content-length header and 385 // send it as a 'normal' packet. If the output packet exceeds 1MB (see ob_start) 386 // then it will be sent as a chunked packet anyway because PHP will have to flush 387 // the buffer. 388 if (!headers_sent()) { // dw2412 need to do this since i.E. getAttachmentData Request starts output in Backend... 389 // START CHANGED dw2412 Support gzip compression in result 390 // TODO: Find out what the hell is going on with compress DocumentLibrary body. If some needs the source 391 // pakets from windump, please mail me. 392 if (GZIP_OUTPUT == true && 393 !defined("OVERRIDE_GZIP") && 394 ($gz_data = gzencode($data,9,FORCE_GZIP)) !== false) { 395 $gzlen=strlen(bin2hex($gz_data))/2; 396 if ($len > $gzlen) { 397 debugLog("GZip Results: Original Size ".$len." / Compress Size ".$gzlen." byte(s) --> Send compressed data"); 398 header("Content-Encoding: gzip"); 399 header("Content-Length: ".$gzlen); 400 flush(); 401 sleep(2); 402 debugLog("Header Connection aborted :".(connection_aborted() ? "yes" : "no" )); 403 debugLog("Header Connection status :".connection_status()); 404 print $gz_data; 405 } else { 406 debugLog("GZip Results: Original Size ".$len." / Compress Size ".$gzlen." byte(s) --> Send uncompressed data"); 407 header("Content-Length: ".$len); 408 flush(); 409 sleep(2); 410 debugLog("Header Connection aborted :".(connection_aborted() ? "yes" : "no" )); 411 debugLog("Header Connection status :".connection_status()); 412 print $data; 413 } 414 } else { 415 debugLog("Output Results: GZip not used send Original Size ".$len." byte(s) --> Send uncompressed data"); 416 header("Content-Length: ".$len); 417 flush(); 418 sleep(2); 419 debugLog("Header Connection aborted :".(connection_aborted() ? "yes" : "no" )); 420 debugLog("Header Connection status :".connection_status()); 421 print $data; 422 } 423 // END CHANGED dw2412 Support gzip compression in result 424 // destruct backend after all data is on the stream 425 } else { // just output what we maybe got from the content buffer 426 flush(); 427 sleep(2); 428 debugLog("1st Part Connection aborted :".(connection_aborted() ? "yes" : "no" )); 429 debugLog("1st Part Connection status :".connection_status()); 430 debugLog("Output ".$len." Bytes of data found in content buffer since output already started earlier in backend"); 431 print $data; 432 } 433 flush(); 434 sleep(2); 435 debugLog("Session run time duration :".(microtime(true) - $sessionstarttime)); 436 debugLog("Body Connection aborted :".(connection_aborted() ? "yes" : "no" )); 437 debugLog("Body Connection status :".connection_status()); 438 $backend->Logoff(); 439 440 debugLog("end"); 441 debugLog("--------"); 442 443 ?>