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