. * * Consult LICENSE file for details ************************************************/ // #190, KD 2015-06-08 - We are missing the flags to truncate the buffer in PHP >= 5.4 if (version_compare(phpversion(), '5.4.0') < 0) { ob_start(null, 1048576); } else { ob_start(null, 1048576, PHP_OUTPUT_HANDLER_STDFLAGS); } // ignore user abortions because this can lead to weird errors - see ZP-239 ignore_user_abort(true); require_once 'vendor/autoload.php'; require_once 'config.php'; if (defined('LOG_MEMORY_PROFILER') && LOG_MEMORY_PROFILER) { if (function_exists('memprof_enable')) { memprof_enable(); } else { ZLog::Write(LOGLEVEL_WARN, "Memory profiler is enabled but the php-pecl-memprof extension was not found. Install and enable it"); } } // Attempt to set maximum execution time ini_set('max_execution_time', SCRIPT_TIMEOUT); set_time_limit(SCRIPT_TIMEOUT); try { // check config & initialize the basics ZPush::CheckConfig(); Request::Initialize(); ZLog::Initialize(); $autenticationInfo = Request::AuthenticationInfo(); $GETUser = Request::GetGETUser(); ZLog::Write(LOGLEVEL_DEBUG,"-------- Start"); ZLog::Write(LOGLEVEL_INFO, sprintf("Version='%s' method='%s' from='%s' cmd='%s' getUser='%s' devId='%s' devType='%s'", @constant('ZPUSH_VERSION'), Request::GetMethod(), Request::GetRemoteAddr(), Request::GetCommand(), $GETUser, Request::GetDeviceID(), Request::GetDeviceType())); // Stop here if this is an OPTIONS request if (Request::IsMethodOPTIONS()) { if (!$autenticationInfo || !$GETUser) { throw new AuthenticationRequiredException("Access denied. Please send authorisation information"); } else { throw new NoPostRequestException("Options request", NoPostRequestException::OPTIONS_REQUEST); } } ZPush::CheckAdvancedConfig(); // Process request headers and look for AS headers Request::ProcessHeaders(); // Check required GET parameters if(Request::IsMethodPOST() && (Request::GetCommandCode() === false || !Request::GetDeviceID() || !Request::GetDeviceType())) throw new FatalException("Requested the Z-Push URL without the required GET parameters"); // This won't be useful with Zarafa, but it will be with standalone Z-Push if (defined('PRE_AUTHORIZE_USERS') && PRE_AUTHORIZE_USERS === true) { if (!Request::IsMethodGET()) { // Check if User/Device are authorized if (ZPush::GetDeviceManager()->GetUserDevicePermission($GETUser, Request::GetDeviceID()) != SYNC_COMMONSTATUS_SUCCESS) { throw new AuthenticationRequiredException("Access denied. Username and Device not authorized"); } } } // Load the backend $backend = ZPush::GetBackend(); // always request the authorization header if (!$autenticationInfo || !$GETUser) throw new AuthenticationRequiredException("Access denied. Please send authorisation information"); // check the provisioning information if (PROVISIONING === true && Request::IsMethodPOST() && ZPush::CommandNeedsProvisioning(Request::GetCommandCode()) && ((Request::WasPolicyKeySent() && Request::GetPolicyKey() == 0) || ZPush::GetDeviceManager()->ProvisioningRequired(Request::GetPolicyKey())) && (LOOSE_PROVISIONING === false || (LOOSE_PROVISIONING === true && Request::WasPolicyKeySent()))) //TODO for AS 14 send a wbxml response throw new ProvisioningRequiredException(); // most commands require an authenticated user if (ZPush::CommandNeedsAuthentication(Request::GetCommandCode())) RequestProcessor::Authenticate(); // Do the actual processing of the request if (Request::IsMethodGET()) throw new NoPostRequestException("This is the Z-Push location and can only be accessed by Microsoft ActiveSync-capable devices", NoPostRequestException::GET_REQUEST); // Do the actual request header(ZPush::GetServerHeader()); // announce the supported AS versions (if not already sent to device) if (ZPush::GetDeviceManager()->AnnounceASVersion()) { $versions = ZPush::GetSupportedProtocolVersions(true); ZLog::Write(LOGLEVEL_INFO, sprintf("Announcing latest AS version to device: %s", $versions)); header("X-MS-RP: ". $versions); } RequestProcessor::Initialize(); RequestProcessor::HandleRequest(); // eventually the RequestProcessor wants to send other headers to the mobile foreach (RequestProcessor::GetSpecialHeaders() as $header) header($header); // log amount of data transferred // TODO check $len when streaming more data (e.g. Attachments), as the data will be send chunked ZPush::GetDeviceManager()->SentData(ob_get_length()); } catch (NoPostRequestException $nopostex) { $len = ob_get_length(); if ($len) { ZLog::Write(LOGLEVEL_WARN, sprintf("Cleaning %d octets of data", $len)); ob_clean(); } if ($nopostex->getCode() == NoPostRequestException::OPTIONS_REQUEST) { header(ZPush::GetServerHeader()); header(ZPush::GetSupportedProtocolVersions()); header(ZPush::GetSupportedCommands()); ZLog::Write(LOGLEVEL_INFO, $nopostex->getMessage()); } else if ($nopostex->getCode() == NoPostRequestException::GET_REQUEST) { if (Request::GetUserAgent()) ZLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent())); if (!headers_sent() && $nopostex->showLegalNotice()) ZPush::PrintZPushLegal('GET not supported', $nopostex->getMessage()); } } catch (Exception $ex) { $len = ob_get_length(); if ($len) { ZLog::Write(LOGLEVEL_WARN, sprintf("Cleaning %d octets of data", $len)); ob_clean(); } if (Request::GetUserAgent()) ZLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent())); $exclass = get_class($ex); if(!headers_sent()) { if ($ex instanceof ZPushException) { header('HTTP/1.1 '. $ex->getHTTPCodeString()); foreach ($ex->getHTTPHeaders() as $h) header($h); } // something really unexpected happened! else header('HTTP/1.1 500 Internal Server Error'); } else ZLog::Write(LOGLEVEL_FATAL, "Exception: ($exclass) - headers were already sent. Message: ". $ex->getMessage()); if ($ex instanceof AuthenticationRequiredException) { ZPush::PrintZPushLegal($exclass, sprintf('
%s
',$ex->getMessage())); // log the failed login attemt e.g. for fail2ban if (defined('LOGAUTHFAIL') && LOGAUTHFAIL != false) ZLog::Write(LOGLEVEL_WARN, sprintf("IP: %s failed to authenticate user '%s'", Request::GetRemoteAddr(), Request::GetAuthUser()? Request::GetAuthUser(): Request::GetGETUser() )); } // This could be a WBXML problem.. try to get the complete request else if ($ex instanceof WBXMLException) { ZLog::Write(LOGLEVEL_FATAL, "Request could not be processed correctly due to a WBXMLException. Please report this including WBXML debug data logged. Be aware that the debug data could contain confidential information."); } // Try to output some kind of error information. This is only possible if // the output had not started yet. If it has started already, we can't show the user the error, and // the device will give its own (useless) error message. else if (!($ex instanceof ZPushException) || $ex->showLegalNotice()) { $cmdinfo = (Request::GetCommand())? sprintf(" processing command %s", Request::GetCommand()): ""; $extrace = $ex->getTrace(); $trace = (!empty($extrace))? "\n\nTrace:\n". print_r($extrace,1):""; ZPush::PrintZPushLegal($exclass . $cmdinfo, sprintf('
%s
',$ex->getMessage() . $trace)); } // Announce exception to process loop detection if (ZPush::GetDeviceManager(false)) ZPush::GetDeviceManager()->AnnounceProcessException($ex); // Announce exception if the TopCollector if available ZPush::GetTopCollector()->AnnounceInformation(get_class($ex), true); } // FinishResponse ZPush::FinishResponse(); // destruct backend after all data is on the stream ZPush::GetBackend()->Logoff(); // save device data if the DeviceManager is available if (ZPush::GetDeviceManager(false)) ZPush::GetDeviceManager()->Save(); // end gracefully if (version_compare(phpversion(), '5.4.0') < 0) { $time_used = number_format(time() - $_SERVER["REQUEST_TIME"], 4); } else { $time_used = number_format(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 4); } ZLog::Write(LOGLEVEL_DEBUG, sprintf("-------- End - max mem: %s/%s - time: %s - code: %s", memory_get_peak_usage(false), memory_get_peak_usage(true), $time_used, http_response_code())); if (defined('LOG_MEMORY_PROFILER') && LOG_MEMORY_PROFILER) { if (function_exists('memprof_enable')) { // Be aware that the pid is not unique, so we will overwrite the output in some cases. But using the pid will be easier to relate the dump with the log lines memprof_dump_callgrind(fopen(LOG_MEMORY_PROFILER_FILE . "_" . getmypid(), "w")); } }