grommunio /
grommunio-sync
| 1 | <?php |
||||
| 2 | /* |
||||
| 3 | * SPDX-License-Identifier: AGPL-3.0-only |
||||
| 4 | * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH |
||||
| 5 | * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH |
||||
| 6 | * |
||||
| 7 | * This is the entry point through which all requests are processed. |
||||
| 8 | */ |
||||
| 9 | |||||
| 10 | ob_start(null, 1048576); |
||||
| 11 | |||||
| 12 | // ignore user abortions because this can lead to weird errors - see ZP-239 |
||||
| 13 | ignore_user_abort(true); |
||||
| 14 | |||||
| 15 | require_once 'vendor/autoload.php'; |
||||
| 16 | |||||
| 17 | if (!defined('ZPUSH_CONFIG')) define('ZPUSH_CONFIG', 'config.php'); |
||||
| 18 | include_once(ZPUSH_CONFIG); |
||||
| 19 | |||||
| 20 | // Attempt to set maximum execution time |
||||
| 21 | ini_set('max_execution_time', SCRIPT_TIMEOUT); |
||||
| 22 | set_time_limit(SCRIPT_TIMEOUT); |
||||
| 23 | |||||
| 24 | try { |
||||
| 25 | // check config & initialize the basics |
||||
| 26 | ZPush::CheckConfig(); |
||||
| 27 | Request::Initialize(); |
||||
| 28 | ZLog::Initialize(); |
||||
| 29 | |||||
| 30 | ZLog::Write(LOGLEVEL_DEBUG,"-------- Start"); |
||||
| 31 | ZLog::Write(LOGLEVEL_DEBUG, |
||||
| 32 | sprintf("cmd='%s' devType='%s' devId='%s' getUser='%s' from='%s' version='%s' method='%s'", |
||||
| 33 | Request::GetCommand(), Request::GetDeviceType(), Request::GetDeviceID(), Request::GetGETUser(), Request::GetRemoteAddr(), @constant('GROMMUNIOSYNC_VERSION'), Request::GetMethod() )); |
||||
| 34 | |||||
| 35 | // always request the authorization header |
||||
| 36 | if (! Request::HasAuthenticationInfo() || !Request::GetGETUser()) |
||||
| 37 | throw new AuthenticationRequiredException("Access denied. Please send authorisation information"); |
||||
| 38 | |||||
| 39 | ZPush::CheckAdvancedConfig(); |
||||
| 40 | |||||
| 41 | // Process request headers and look for AS headers |
||||
| 42 | Request::ProcessHeaders(); |
||||
| 43 | |||||
| 44 | // Stop here if this is an OPTIONS request |
||||
| 45 | if (Request::IsMethodOPTIONS()) { |
||||
| 46 | RequestProcessor::Authenticate(); |
||||
| 47 | throw new NoPostRequestException("Options request", NoPostRequestException::OPTIONS_REQUEST); |
||||
| 48 | } |
||||
| 49 | |||||
| 50 | // Check required GET parameters |
||||
| 51 | if(Request::IsMethodPOST() && (Request::GetCommandCode() === false || !Request::GetDeviceID() || !Request::GetDeviceType())) |
||||
| 52 | throw new FatalException("Requested the grommunio-sync URL without the required GET parameters"); |
||||
| 53 | |||||
| 54 | // Load the backend |
||||
| 55 | $backend = ZPush::GetBackend(); |
||||
| 56 | |||||
| 57 | // check the provisioning information |
||||
| 58 | if (PROVISIONING === true && Request::IsMethodPOST() && ZPush::CommandNeedsProvisioning(Request::GetCommandCode()) && |
||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 59 | ((Request::WasPolicyKeySent() && Request::GetPolicyKey() == 0) || ZPush::GetProvisioningManager()->ProvisioningRequired(Request::GetPolicyKey())) && |
||||
| 60 | (LOOSE_PROVISIONING === false || |
||||
| 61 | (LOOSE_PROVISIONING === true && Request::WasPolicyKeySent()))) |
||||
| 62 | //TODO for AS 14 send a wbxml response |
||||
| 63 | throw new ProvisioningRequiredException(); |
||||
| 64 | |||||
| 65 | // most commands require an authenticated user |
||||
| 66 | if (ZPush::CommandNeedsAuthentication(Request::GetCommandCode())) |
||||
|
0 ignored issues
–
show
It seems like
Request::GetCommandCode() can also be of type false; however, parameter $commandCode of ZPush::CommandNeedsAuthentication() does only seem to accept integer, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 67 | RequestProcessor::Authenticate(); |
||||
| 68 | |||||
| 69 | // Do the actual processing of the request |
||||
| 70 | if (Request::IsMethodGET()) |
||||
| 71 | throw new NoPostRequestException("This is the grommunio-sync location and can only be accessed by Microsoft ActiveSync-capable devices", NoPostRequestException::GET_REQUEST); |
||||
| 72 | |||||
| 73 | // Do the actual request |
||||
| 74 | header(ZPush::GetServerHeader()); |
||||
| 75 | |||||
| 76 | if (RequestProcessor::isUserAuthenticated()) { |
||||
| 77 | header("X-Grommunio-Sync-Version: ". @constant('GROMMUNIOSYNC_VERSION')); |
||||
| 78 | |||||
| 79 | // announce the supported AS versions (if not already sent to device) |
||||
| 80 | if (ZPush::GetDeviceManager()->AnnounceASVersion()) { |
||||
| 81 | $versions = ZPush::GetSupportedProtocolVersions(true); |
||||
| 82 | ZLog::Write(LOGLEVEL_INFO, sprintf("Announcing latest AS version to device: %s", $versions)); |
||||
| 83 | header("X-MS-RP: ". $versions); |
||||
| 84 | } |
||||
| 85 | } |
||||
| 86 | |||||
| 87 | RequestProcessor::Initialize(); |
||||
| 88 | RequestProcessor::HandleRequest(); |
||||
| 89 | |||||
| 90 | // eventually the RequestProcessor wants to send other headers to the mobile |
||||
| 91 | foreach (RequestProcessor::GetSpecialHeaders() as $header) { |
||||
| 92 | ZLog::Write(LOGLEVEL_DEBUG, sprintf("Special header: %s", $header)); |
||||
| 93 | header($header); |
||||
| 94 | } |
||||
| 95 | |||||
| 96 | // stream the data |
||||
| 97 | $len = ob_get_length(); |
||||
| 98 | $data = ob_get_contents(); |
||||
| 99 | ob_end_clean(); |
||||
| 100 | |||||
| 101 | // log amount of data transferred |
||||
| 102 | // TODO check $len when streaming more data (e.g. Attachments), as the data will be send chunked |
||||
| 103 | if (ZPush::GetDeviceManager(false)) { |
||||
| 104 | ZPush::GetDeviceManager()->SentData($len); |
||||
| 105 | } |
||||
| 106 | |||||
| 107 | // Unfortunately, even though grommunio-sync can stream the data to the client |
||||
| 108 | // with a chunked encoding, using chunked encoding breaks the progress bar |
||||
| 109 | // on the PDA. So the data is de-chunk here, written a content-length header and |
||||
| 110 | // data send as a 'normal' packet. If the output packet exceeds 1MB (see ob_start) |
||||
| 111 | // then it will be sent as a chunked packet anyway because PHP will have to flush |
||||
| 112 | // the buffer. |
||||
| 113 | if(!headers_sent()) |
||||
| 114 | header("Content-Length: $len"); |
||||
| 115 | |||||
| 116 | // send vnd.ms-sync.wbxml content type header if there is no content |
||||
| 117 | // otherwise text/html content type is added which might break some devices |
||||
| 118 | if (!headers_sent() && $len == 0) |
||||
| 119 | header("Content-Type: application/vnd.ms-sync.wbxml"); |
||||
| 120 | |||||
| 121 | print $data; |
||||
| 122 | |||||
| 123 | // destruct backend after all data is on the stream |
||||
| 124 | $backend->Logoff(); |
||||
| 125 | } |
||||
| 126 | |||||
| 127 | catch (NoPostRequestException $nopostex) { |
||||
| 128 | if ($nopostex->getCode() == NoPostRequestException::OPTIONS_REQUEST) { |
||||
| 129 | header(ZPush::GetServerHeader()); |
||||
| 130 | header(ZPush::GetSupportedProtocolVersions()); |
||||
| 131 | header(ZPush::GetSupportedCommands()); |
||||
| 132 | ZLog::Write(LOGLEVEL_INFO, $nopostex->getMessage()); |
||||
| 133 | } |
||||
| 134 | else if ($nopostex->getCode() == NoPostRequestException::GET_REQUEST) { |
||||
| 135 | if (Request::GetUserAgent()) |
||||
| 136 | ZLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent())); |
||||
| 137 | if (!headers_sent() && $nopostex->showLegalNotice()) |
||||
| 138 | ZPush::PrintGrommunioSyncLegal('GET not supported', $nopostex->getMessage()); |
||||
| 139 | } |
||||
| 140 | } |
||||
| 141 | |||||
| 142 | catch (Exception $ex) { |
||||
| 143 | // Extract any previous exception message for logging purpose. |
||||
| 144 | $exclass = get_class($ex); |
||||
| 145 | $exception_message = $ex->getMessage(); |
||||
| 146 | if($ex->getPrevious()){ |
||||
| 147 | do { |
||||
| 148 | $current_exception = $ex->getPrevious(); |
||||
| 149 | $exception_message .= ' -> ' . $current_exception->getMessage(); |
||||
| 150 | } while($current_exception->getPrevious()); |
||||
| 151 | } |
||||
| 152 | |||||
| 153 | if (Request::GetUserAgent()) |
||||
| 154 | ZLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent())); |
||||
| 155 | |||||
| 156 | ZLog::Write(LOGLEVEL_FATAL, sprintf('Exception: (%s) - %s', $exclass, $exception_message)); |
||||
| 157 | |||||
| 158 | if(!headers_sent()) { |
||||
| 159 | if ($ex instanceof ZPushException) { |
||||
| 160 | header('HTTP/1.1 '. $ex->getHTTPCodeString()); |
||||
| 161 | foreach ($ex->getHTTPHeaders() as $h) |
||||
| 162 | header($h); |
||||
| 163 | } |
||||
| 164 | // something really unexpected happened! |
||||
| 165 | else |
||||
| 166 | header('HTTP/1.1 500 Internal Server Error'); |
||||
| 167 | } |
||||
| 168 | |||||
| 169 | if ($ex instanceof AuthenticationRequiredException) { |
||||
| 170 | // Only print ZPush legal message for GET requests because |
||||
| 171 | // some devices send unauthorized OPTIONS requests |
||||
| 172 | // and don't expect anything in the response body |
||||
| 173 | if (Request::IsMethodGET()) { |
||||
| 174 | ZPush::PrintGrommunioSyncLegal($exclass, sprintf('<pre>%s</pre>',$ex->getMessage())); |
||||
| 175 | } |
||||
| 176 | |||||
| 177 | // log the failed login attempt e.g. for fail2ban |
||||
| 178 | if (defined('LOGAUTHFAIL') && LOGAUTHFAIL != false) |
||||
| 179 | ZLog::Write(LOGLEVEL_WARN, sprintf("IP: %s failed to authenticate user '%s'", Request::GetRemoteAddr(), Request::GetAuthUser()? Request::GetAuthUser(): Request::GetGETUser() )); |
||||
| 180 | } |
||||
| 181 | |||||
| 182 | // This could be a WBXML problem.. try to get the complete request |
||||
| 183 | else if ($ex instanceof WBXMLException) { |
||||
| 184 | ZLog::Write(LOGLEVEL_FATAL, "Request could not be processed correctly due to a WBXMLException. Please report this including the 'WBXML debug data' logged. Be aware that the debug data could contain confidential information."); |
||||
| 185 | } |
||||
| 186 | |||||
| 187 | // Try to output some kind of error information. This is only possible if |
||||
| 188 | // the output had not started yet. If it has started already, we can't show the user the error, and |
||||
| 189 | // the device will give its own (useless) error message. |
||||
| 190 | else if (!($ex instanceof ZPushException) || $ex->showLegalNotice()) { |
||||
| 191 | $cmdinfo = (Request::GetCommand())? sprintf(" processing command <i>%s</i>", Request::GetCommand()): ""; |
||||
| 192 | $extrace = $ex->getTrace(); |
||||
| 193 | $trace = (!empty($extrace))? "\n\nTrace:\n". print_r($extrace,1):""; |
||||
| 194 | ZPush::PrintGrommunioSyncLegal($exclass . $cmdinfo, sprintf('<pre>%s</pre>',$ex->getMessage() . $trace)); |
||||
| 195 | } |
||||
| 196 | |||||
| 197 | // Announce exception to process loop detection |
||||
| 198 | if (ZPush::GetDeviceManager(false)) |
||||
| 199 | ZPush::GetDeviceManager()->AnnounceProcessException($ex); |
||||
| 200 | |||||
| 201 | // Announce exception if the TopCollector if available |
||||
| 202 | ZPush::GetTopCollector()->AnnounceInformation(get_class($ex), true); |
||||
| 203 | } |
||||
| 204 | |||||
| 205 | // save device data if the DeviceManager is available |
||||
| 206 | if (ZPush::GetDeviceManager(false)) |
||||
| 207 | ZPush::GetDeviceManager()->Save(); |
||||
| 208 | |||||
| 209 | // end gracefully |
||||
| 210 | ZLog::Write(LOGLEVEL_INFO, |
||||
| 211 | sprintf("cmd='%s' memory='%s/%s' time='%ss' devType='%s' devId='%s' getUser='%s' from='%s' idle='%ss' version='%s' method='%s' httpcode='%s'", |
||||
| 212 | Request::GetCommand(), Utils::FormatBytes(memory_get_peak_usage(false)), Utils::FormatBytes(memory_get_peak_usage(true)), |
||||
| 213 | number_format(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2), |
||||
| 214 | Request::GetDeviceType(), Request::GetDeviceID(), Request::GetGETUser(), Request::GetRemoteAddr(), |
||||
| 215 | RequestProcessor::GetWaitTime(), @constant('GROMMUNIOSYNC_VERSION'), Request::GetMethod(), http_response_code() )); |
||||
| 216 | |||||
| 217 | ZLog::Write(LOGLEVEL_DEBUG, "-------- End"); |
||||
| 218 | |||||
| 219 |