Passed
Push — master ( 6522b6...8c95d3 )
by
unknown
04:16 queued 01:06
created

index.php (4 issues)

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() && (Request::GetCommandCode() === false || !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 (
75
				PROVISIONING === true &&
76
				Request::IsMethodPOST() &&
77
				GSync::CommandNeedsProvisioning(Request::GetCommandCode()) &&
78
				(
79
					(Request::WasPolicyKeySent() && Request::GetPolicyKey() == 0) ||
80
					GSync::GetProvisioningManager()->ProvisioningRequired(Request::GetPolicyKey())
81
				) && (
82
					LOOSE_PROVISIONING === false ||
83
					(LOOSE_PROVISIONING === true && Request::WasPolicyKeySent())
84
				)) {
85
			// TODO for AS 14 send a wbxml response
86
			throw new ProvisioningRequiredException();
87
		}
88
89
		// most commands require an authenticated user
90
		if (GSync::CommandNeedsAuthentication(Request::GetCommandCode())) {
0 ignored issues
show
Request::GetCommandCode() of type boolean|string is incompatible with the type integer expected by parameter $commandCode of GSync::CommandNeedsAuthentication(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

90
		if (GSync::CommandNeedsAuthentication(/** @scrutinizer ignore-type */ Request::GetCommandCode())) {
Loading history...
91
			RequestProcessor::Authenticate();
92
		}
93
94
		// Do the actual processing of the request
95
		if (Request::IsMethodGET()) {
96
			throw new NoPostRequestException("This is the grommunio-sync location and can only be accessed by Microsoft ActiveSync-capable devices", NoPostRequestException::GET_REQUEST);
97
		}
98
99
		// Do the actual request
100
		header(GSync::GetServerHeader());
101
102
		if (RequestProcessor::isUserAuthenticated()) {
103
			header("X-Grommunio-Sync-Version: " . @constant('GROMMUNIOSYNC_VERSION'));
104
105
			// announce the supported AS versions (if not already sent to device)
106
			if (GSync::GetDeviceManager()->AnnounceASVersion()) {
107
				$versions = GSync::GetSupportedProtocolVersions(true);
108
				SLog::Write(LOGLEVEL_INFO, sprintf("Announcing latest AS version to device: %s", $versions));
109
				header("X-MS-RP: " . $versions);
110
			}
111
		}
112
113
		RequestProcessor::Initialize();
114
		RequestProcessor::HandleRequest();
115
116
		// eventually the RequestProcessor wants to send other headers to the mobile
117
		foreach (RequestProcessor::GetSpecialHeaders() as $header) {
118
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Special header: %s", $header));
119
			header($header);
120
		}
121
122
		// stream the data
123
		$len = ob_get_length();
124
		$data = ob_get_contents();
125
		ob_end_clean();
126
127
		// log amount of data transferred
128
		// TODO check $len when streaming more data (e.g. Attachments), as the data will be send chunked
129
		if (GSync::GetDeviceManager(false)) {
130
			GSync::GetDeviceManager()->SentData($len);
131
		}
132
133
		// Unfortunately, even though grommunio-sync can stream the data to the client
134
		// with a chunked encoding, using chunked encoding breaks the progress bar
135
		// on the PDA. So the data is de-chunk here, written a content-length header and
136
		// data send as a 'normal' packet. If the output packet exceeds 1MB (see ob_start)
137
		// then it will be sent as a chunked packet anyway because PHP will have to flush
138
		// the buffer.
139
		if (!headers_sent()) {
140
			header("Content-Length: {$len}");
141
		}
142
143
		// send vnd.ms-sync.wbxml content type header if there is no content
144
		// otherwise text/html content type is added which might break some devices
145
		if (!headers_sent() && $len == 0) {
146
			header("Content-Type: application/vnd.ms-sync.wbxml");
147
		}
148
149
		echo $data;
150
151
		// destruct backend after all data is on the stream
152
		$backend->Logoff();
153
	}
154
	catch (NoPostRequestException $nopostex) {
155
		if ($nopostex->getCode() == NoPostRequestException::OPTIONS_REQUEST) {
156
			header(GSync::GetServerHeader());
157
			header(GSync::GetSupportedProtocolVersions());
158
			header(GSync::GetSupportedCommands());
159
			SLog::Write(LOGLEVEL_INFO, $nopostex->getMessage());
160
		}
161
		elseif ($nopostex->getCode() == NoPostRequestException::GET_REQUEST) {
162
			if (Request::GetUserAgent()) {
163
				SLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent()));
164
			}
165
			if (!headers_sent() && $nopostex->showLegalNotice()) {
166
				GSync::PrintGrommunioSyncLegal('GET not supported', $nopostex->getMessage());
167
			}
168
		}
169
	}
170
	catch (Exception $ex) {
171
		// Extract any previous exception message for logging purpose.
172
		$exclass = get_class($ex);
173
		$exception_message = $ex->getMessage();
174
		if ($ex->getPrevious()) {
175
			do {
176
				$current_exception = $ex->getPrevious();
177
				$exception_message .= ' -> ' . $current_exception->getMessage();
178
			}
179
			while ($current_exception->getPrevious());
180
		}
181
182
		if (Request::GetUserAgent()) {
183
			SLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent()));
184
		}
185
186
		SLog::Write(LOGLEVEL_FATAL, sprintf('Exception: (%s) - %s', $exclass, $exception_message));
187
188
		if (!headers_sent()) {
189
			if ($ex instanceof GSyncException) {
190
				header('HTTP/1.1 ' . $ex->getHTTPCodeString());
191
				foreach ($ex->getHTTPHeaders() as $h) {
192
					header($h);
193
				}
194
			}
195
			// something really unexpected happened!
196
			else {
197
				header('HTTP/1.1 500 Internal Server Error');
198
			}
199
		}
200
201
		if ($ex instanceof AuthenticationRequiredException) {
202
			// Only print GSync legal message for GET requests because
203
			// some devices send unauthorized OPTIONS requests
204
			// and don't expect anything in the response body
205
			if (Request::IsMethodGET()) {
206
				GSync::PrintGrommunioSyncLegal($exclass, sprintf('<pre>%s</pre>', $ex->getMessage()));
207
			}
208
209
			// log the failed login attempt e.g. for fail2ban
210
			if (defined('LOGAUTHFAIL') && LOGAUTHFAIL !== false) {
0 ignored issues
show
The condition LOGAUTHFAIL !== false is always false.
Loading history...
211
				SLog::Write(LOGLEVEL_WARN, sprintf("IP: %s failed to authenticate user '%s'", Request::GetRemoteAddr(), Request::GetAuthUser() ? Request::GetAuthUser() : Request::GetGETUser()));
212
			}
213
		}
214
215
		// This could be a WBXML problem.. try to get the complete request
216
		elseif ($ex instanceof WBXMLException) {
217
			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.");
218
		}
219
220
		// Try to output some kind of error information. This is only possible if
221
		// the output had not started yet. If it has started already, we can't show the user the error, and
222
		// the device will give its own (useless) error message.
223
		elseif (!($ex instanceof GSyncException) || $ex->showLegalNotice()) {
224
			$cmdinfo = (Request::GetCommand()) ? sprintf(" processing command <i>%s</i>", Request::GetCommand()) : "";
225
			$extrace = $ex->getTrace();
226
			$trace = (!empty($extrace)) ? "\n\nTrace:\n" . print_r($extrace, 1) : "";
0 ignored issues
show
Are you sure print_r($extrace, 1) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

226
			$trace = (!empty($extrace)) ? "\n\nTrace:\n" . /** @scrutinizer ignore-type */ print_r($extrace, 1) : "";
Loading history...
227
			GSync::PrintGrommunioSyncLegal($exclass . $cmdinfo, sprintf('<pre>%s</pre>', $ex->getMessage() . $trace));
228
		}
229
230
		// Announce exception to process loop detection
231
		if (GSync::GetDeviceManager(false)) {
232
			GSync::GetDeviceManager()->AnnounceProcessException($ex);
233
		}
234
235
		// Announce exception if the TopCollector if available
236
		GSync::GetTopCollector()->AnnounceInformation(get_class($ex), true);
237
	}
238
239
	// save device data if the DeviceManager is available
240
	if (GSync::GetDeviceManager(false)) {
241
		GSync::GetDeviceManager()->Save();
242
	}
243
244
	// end gracefully
245
	SLog::Write(
246
		LOGLEVEL_INFO,
247
		sprintf(
248
			"cmd='%s' memory='%s/%s' time='%ss' devType='%s' devId='%s' getUser='%s' from='%s' idle='%ss' version='%s' method='%s' httpcode='%s'",
249
			Request::GetCommand(),
250
			Utils::FormatBytes(memory_get_peak_usage(false)),
251
			Utils::FormatBytes(memory_get_peak_usage(true)),
252
			number_format(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2),
253
			Request::GetDeviceType(),
254
			Request::GetDeviceID(),
255
			Request::GetGETUser(),
256
			Request::GetRemoteAddr(),
257
			RequestProcessor::GetWaitTime(),
258
			@constant('GROMMUNIOSYNC_VERSION'),
259
			Request::GetMethod(),
260
			http_response_code()
0 ignored issues
show
It seems like http_response_code() can also be of type true; however, parameter $values of sprintf() does only seem to accept double|integer|string, 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 ignore-type  annotation

260
			/** @scrutinizer ignore-type */ http_response_code()
Loading history...
261
		)
262
	);
263
264
	SLog::Write(LOGLEVEL_DEBUG, "-------- End");
265