Passed
Branch master (f497d2)
by Mike
03:18
created

GSync::CheckConfig()   F

Complexity

Conditions 34
Paths 3680

Size

Total Lines 113
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 34
eloc 58
nc 3680
nop 0
dl 0
loc 113
rs 0
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
 * Core functionalities
8
 */
9
10
class GSync {
11
	public const UNAUTHENTICATED = 1;
12
	public const UNPROVISIONED = 2;
13
	public const NOACTIVESYNCCOMMAND = 3;
14
	public const WEBSERVICECOMMAND = 4;    // DEPRECATED
15
	public const HIERARCHYCOMMAND = 5;
16
	public const PLAININPUT = 6;
17
	public const REQUESTHANDLER = 7;
18
	public const CLASS_NAME = 1;
19
	public const CLASS_REQUIRESPROTOCOLVERSION = 2;
20
	public const CLASS_DEFAULTTYPE = 3;
21
	public const CLASS_OTHERTYPES = 4;
22
23
	// AS versions
24
	public const ASV_1 = "1.0";
25
	public const ASV_2 = "2.0";
26
	public const ASV_21 = "2.1";
27
	public const ASV_25 = "2.5";
28
	public const ASV_12 = "12.0";
29
	public const ASV_121 = "12.1";
30
	public const ASV_14 = "14.0";
31
	public const ASV_141 = "14.1";
32
33
	/**
34
	 * Command codes for base64 encoded requests (AS >= 12.1).
35
	 */
36
	public const COMMAND_SYNC = 0;
37
	public const COMMAND_SENDMAIL = 1;
38
	public const COMMAND_SMARTFORWARD = 2;
39
	public const COMMAND_SMARTREPLY = 3;
40
	public const COMMAND_GETATTACHMENT = 4;
41
	public const COMMAND_FOLDERSYNC = 9;
42
	public const COMMAND_FOLDERCREATE = 10;
43
	public const COMMAND_FOLDERDELETE = 11;
44
	public const COMMAND_FOLDERUPDATE = 12;
45
	public const COMMAND_MOVEITEMS = 13;
46
	public const COMMAND_GETITEMESTIMATE = 14;
47
	public const COMMAND_MEETINGRESPONSE = 15;
48
	public const COMMAND_SEARCH = 16;
49
	public const COMMAND_SETTINGS = 17;
50
	public const COMMAND_PING = 18;
51
	public const COMMAND_ITEMOPERATIONS = 19;
52
	public const COMMAND_PROVISION = 20;
53
	public const COMMAND_RESOLVERECIPIENTS = 21;
54
	public const COMMAND_VALIDATECERT = 22;
55
56
	// Deprecated commands
57
	public const COMMAND_GETHIERARCHY = -1;
58
	public const COMMAND_CREATECOLLECTION = -2;
59
	public const COMMAND_DELETECOLLECTION = -3;
60
	public const COMMAND_MOVECOLLECTION = -4;
61
	public const COMMAND_NOTIFY = -5;
62
63
	// Latest supported State version
64
	public const STATE_VERSION = IStateMachine::STATEVERSION_02;
65
66
	// Versions 1.0, 2.0, 2.1 and 2.5 are deprecated (ZP-604)
67
	private static $supportedASVersions = [
68
		self::ASV_12,
69
		self::ASV_121,
70
		self::ASV_14,
71
		self::ASV_141,
72
	];
73
74
	private static $supportedCommands = [
75
		// COMMAND             AS VERSION   REQUESTHANDLER                                  OTHER SETTINGS
76
		self::COMMAND_SYNC => [self::ASV_1, self::REQUESTHANDLER => "Sync"],
77
		self::COMMAND_SENDMAIL => [self::ASV_1, self::REQUESTHANDLER => "SendMail"],
78
		self::COMMAND_SMARTFORWARD => [self::ASV_1, self::REQUESTHANDLER => "SendMail"],
79
		self::COMMAND_SMARTREPLY => [self::ASV_1, self::REQUESTHANDLER => "SendMail"],
80
		self::COMMAND_GETATTACHMENT => [self::ASV_1, self::REQUESTHANDLER => "GetAttachment"],
81
		self::COMMAND_GETHIERARCHY => [self::ASV_1, self::REQUESTHANDLER => "GetHierarchy", self::HIERARCHYCOMMAND], // deprecated but implemented
82
		self::COMMAND_CREATECOLLECTION => [self::ASV_1], // deprecated & not implemented
83
		self::COMMAND_DELETECOLLECTION => [self::ASV_1], // deprecated & not implemented
84
		self::COMMAND_MOVECOLLECTION => [self::ASV_1], // deprecated & not implemented
85
		self::COMMAND_FOLDERSYNC => [self::ASV_2, self::REQUESTHANDLER => "FolderSync", self::HIERARCHYCOMMAND],
86
		self::COMMAND_FOLDERCREATE => [self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND],
87
		self::COMMAND_FOLDERDELETE => [self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND],
88
		self::COMMAND_FOLDERUPDATE => [self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND],
89
		self::COMMAND_MOVEITEMS => [self::ASV_1, self::REQUESTHANDLER => "MoveItems"],
90
		self::COMMAND_GETITEMESTIMATE => [self::ASV_1, self::REQUESTHANDLER => "GetItemEstimate"],
91
		self::COMMAND_MEETINGRESPONSE => [self::ASV_1, self::REQUESTHANDLER => "MeetingResponse"],
92
		self::COMMAND_RESOLVERECIPIENTS => [self::ASV_1, self::REQUESTHANDLER => "ResolveRecipients"],
93
		self::COMMAND_VALIDATECERT => [self::ASV_1, self::REQUESTHANDLER => "ValidateCert"],
94
		self::COMMAND_PROVISION => [self::ASV_25, self::REQUESTHANDLER => "Provisioning", self::UNAUTHENTICATED, self::UNPROVISIONED],
95
		self::COMMAND_SEARCH => [self::ASV_1, self::REQUESTHANDLER => "Search"],
96
		self::COMMAND_PING => [self::ASV_2, self::REQUESTHANDLER => "Ping", self::UNPROVISIONED],
97
		self::COMMAND_NOTIFY => [self::ASV_1, self::REQUESTHANDLER => "Notify"], // deprecated & not implemented
98
		self::COMMAND_ITEMOPERATIONS => [self::ASV_12, self::REQUESTHANDLER => "ItemOperations"],
99
		self::COMMAND_SETTINGS => [self::ASV_12, self::REQUESTHANDLER => "Settings"],
100
	];
101
102
	private static $classes = [
103
		"Email" => [
104
			self::CLASS_NAME => "SyncMail",
105
			self::CLASS_REQUIRESPROTOCOLVERSION => false,
106
			self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_INBOX,
107
			self::CLASS_OTHERTYPES => [
108
				SYNC_FOLDER_TYPE_OTHER,
109
				SYNC_FOLDER_TYPE_DRAFTS,
110
				SYNC_FOLDER_TYPE_WASTEBASKET,
111
				SYNC_FOLDER_TYPE_SENTMAIL,
112
				SYNC_FOLDER_TYPE_OUTBOX,
113
				SYNC_FOLDER_TYPE_USER_MAIL,
114
				SYNC_FOLDER_TYPE_JOURNAL,
115
				SYNC_FOLDER_TYPE_USER_JOURNAL,
116
			],
117
		],
118
		"Contacts" => [
119
			self::CLASS_NAME => "SyncContact",
120
			self::CLASS_REQUIRESPROTOCOLVERSION => true,
121
			self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_CONTACT,
122
			self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_UNKNOWN],
123
		],
124
		"Calendar" => [
125
			self::CLASS_NAME => "SyncAppointment",
126
			self::CLASS_REQUIRESPROTOCOLVERSION => false,
127
			self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_APPOINTMENT,
128
			self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_APPOINTMENT],
129
		],
130
		"Tasks" => [
131
			self::CLASS_NAME => "SyncTask",
132
			self::CLASS_REQUIRESPROTOCOLVERSION => false,
133
			self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_TASK,
134
			self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_TASK],
135
		],
136
		"Notes" => [
137
			self::CLASS_NAME => "SyncNote",
138
			self::CLASS_REQUIRESPROTOCOLVERSION => false,
139
			self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_NOTE,
140
			self::CLASS_OTHERTYPES => [SYNC_FOLDER_TYPE_USER_NOTE],
141
		],
142
	];
143
144
	private static $stateMachine;
145
	private static $deviceManager;
146
	private static $provisioningManager;
147
	private static $topCollector;
148
	private static $backend;
149
	private static $addSyncFolders;
150
	private static $policies;
0 ignored issues
show
introduced by
The private property $policies is not used, and could be removed.
Loading history...
151
	private static $redis;
152
153
	/**
154
	 * Verifies configuration.
155
	 *
156
	 * @throws FatalMisconfigurationException
157
	 *
158
	 * @return bool
159
	 */
160
	public static function CheckConfig() {
161
		// check the php version
162
		if (version_compare(phpversion(), '5.4.0') < 0) {
163
			throw new FatalException("The configured PHP version is too old. Please make sure at least PHP 5.4 is used.");
164
		}
165
166
		// some basic checks
167
		if (!defined('BASE_PATH')) {
168
			throw new FatalMisconfigurationException("The BASE_PATH is not configured. Check if the config.php file is in place.");
169
		}
170
171
		if (substr(BASE_PATH, -1, 1) != "/") {
172
			throw new FatalMisconfigurationException("The BASE_PATH should terminate with a '/'");
173
		}
174
175
		if (!file_exists(BASE_PATH)) {
176
			throw new FatalMisconfigurationException("The configured BASE_PATH does not exist or can not be accessed.");
177
		}
178
179
		if (defined('BASE_PATH_CLI') && file_exists(BASE_PATH_CLI)) {
180
			define('REAL_BASE_PATH', BASE_PATH_CLI);
181
		}
182
		else {
183
			define('REAL_BASE_PATH', BASE_PATH);
184
		}
185
186
		if (!defined('LOGBACKEND')) {
187
			define('LOGBACKEND', 'filelog');
188
		}
189
190
		if (strtolower(LOGBACKEND) == 'syslog') {
0 ignored issues
show
introduced by
The condition strtolower(LOGBACKEND) == 'syslog' is always false.
Loading history...
191
			define('LOGBACKEND_CLASS', 'Syslog');
192
			if (!defined('LOG_SYSLOG_FACILITY')) {
193
				define('LOG_SYSLOG_FACILITY', LOG_LOCAL0);
194
			}
195
196
			if (!defined('LOG_SYSLOG_HOST')) {
197
				define('LOG_SYSLOG_HOST', false);
198
			}
199
200
			if (!defined('LOG_SYSLOG_PORT')) {
201
				define('LOG_SYSLOG_PORT', 514);
202
			}
203
204
			if (!defined('LOG_SYSLOG_PROGRAM')) {
205
				define('LOG_SYSLOG_PROGRAM', 'grommunio-sync');
206
			}
207
208
			if (!is_numeric(LOG_SYSLOG_PORT)) {
209
				throw new FatalMisconfigurationException("The LOG_SYSLOG_PORT must a be a number.");
210
			}
211
212
			if (LOG_SYSLOG_HOST && LOG_SYSLOG_PORT <= 0) {
213
				throw new FatalMisconfigurationException("LOG_SYSLOG_HOST is defined but the LOG_SYSLOG_PORT does not seem to be valid.");
214
			}
215
		}
216
		elseif (strtolower(LOGBACKEND) == 'filelog') {
0 ignored issues
show
introduced by
The condition strtolower(LOGBACKEND) == 'filelog' is always true.
Loading history...
217
			define('LOGBACKEND_CLASS', 'FileLog');
218
			if (!defined('LOGFILEDIR')) {
219
				throw new FatalMisconfigurationException("The LOGFILEDIR is not configured. Check if the config.php file is in place.");
220
			}
221
222
			if (substr(LOGFILEDIR, -1, 1) != "/") {
223
				throw new FatalMisconfigurationException("The LOGFILEDIR should terminate with a '/'");
224
			}
225
226
			if (!file_exists(LOGFILEDIR)) {
227
				throw new FatalMisconfigurationException("The configured LOGFILEDIR does not exist or can not be accessed.");
228
			}
229
230
			if ((!file_exists(LOGFILE) && !touch(LOGFILE)) || !is_writable(LOGFILE)) {
231
				throw new FatalMisconfigurationException("The configured LOGFILE can not be modified.");
232
			}
233
234
			if ((!file_exists(LOGERRORFILE) && !touch(LOGERRORFILE)) || !is_writable(LOGERRORFILE)) {
235
				throw new FatalMisconfigurationException("The configured LOGERRORFILE can not be modified.");
236
			}
237
238
			// check ownership on the (eventually) just created files
239
			Utils::FixFileOwner(LOGFILE);
240
			Utils::FixFileOwner(LOGERRORFILE);
241
		}
242
		else {
243
			define('LOGBACKEND_CLASS', LOGBACKEND);
244
		}
245
246
		// set time zone
247
		// code contributed by Robert Scheck (rsc)
248
		if (defined('TIMEZONE') ? constant('TIMEZONE') : false) {
249
			if (!@date_default_timezone_set(TIMEZONE)) {
250
				throw new FatalMisconfigurationException(sprintf("The configured TIMEZONE '%s' is not valid. Please check supported timezones at http://www.php.net/manual/en/timezones.php", constant('TIMEZONE')));
251
			}
252
		}
253
		elseif (!ini_get('date.timezone')) {
254
			date_default_timezone_set('Europe/Vienna');
255
		}
256
257
		if (defined('USE_X_FORWARDED_FOR_HEADER')) {
258
			SLog::Write(LOGLEVEL_INFO, "The configuration parameter 'USE_X_FORWARDED_FOR_HEADER' was deprecated in favor of 'USE_CUSTOM_REMOTE_IP_HEADER'. Please update your configuration.");
259
		}
260
261
		// check redis configuration - set defaults
262
		if (!defined('REDIS_HOST')) {
263
			define('REDIS_HOST', 'localhost');
264
		}
265
		if (!defined('REDIS_PORT')) {
266
			define('REDIS_PORT', 6379);
267
		}
268
		if (!defined('REDIS_AUTH')) {
269
			define('REDIS_AUTH', '');
270
		}
271
272
		return true;
273
	}
274
275
	/**
276
	 * Verifies Timezone, StateMachine and Backend configuration.
277
	 *
278
	 * @return bool
279
	 * @trows FatalMisconfigurationException
280
	 */
281
	public static function CheckAdvancedConfig() {
282
		global $specialLogUsers, $additionalFolders;
283
284
		if (!is_array($specialLogUsers)) {
285
			throw new FatalMisconfigurationException("The WBXML log users is not an array.");
286
		}
287
288
		if (!defined('SYNC_CONTACTS_MAXPICTURESIZE')) {
289
			define('SYNC_CONTACTS_MAXPICTURESIZE', 49152);
290
		}
291
		elseif ((!is_int(SYNC_CONTACTS_MAXPICTURESIZE) || SYNC_CONTACTS_MAXPICTURESIZE < 1)) {
0 ignored issues
show
introduced by
The condition is_int(SYNC_CONTACTS_MAXPICTURESIZE) is always true.
Loading history...
292
			throw new FatalMisconfigurationException("The SYNC_CONTACTS_MAXPICTURESIZE value must be a number higher than 0.");
293
		}
294
295
		if (!defined('USE_PARTIAL_FOLDERSYNC')) {
296
			define('USE_PARTIAL_FOLDERSYNC', false);
297
		}
298
299
		if (!defined('PING_LOWER_BOUND_LIFETIME')) {
300
			define('PING_LOWER_BOUND_LIFETIME', false);
301
		}
302
		elseif (PING_LOWER_BOUND_LIFETIME !== false && (!is_int(PING_LOWER_BOUND_LIFETIME) || PING_LOWER_BOUND_LIFETIME < 1 || PING_LOWER_BOUND_LIFETIME > 3540)) {
0 ignored issues
show
introduced by
The condition PING_LOWER_BOUND_LIFETIME !== false is always false.
Loading history...
303
			throw new FatalMisconfigurationException("The PING_LOWER_BOUND_LIFETIME value must be 'false' or a number between 1 and 3540 inclusively.");
304
		}
305
		if (!defined('PING_HIGHER_BOUND_LIFETIME')) {
306
			define('PING_HIGHER_BOUND_LIFETIME', false);
307
		}
308
		elseif (PING_HIGHER_BOUND_LIFETIME !== false && (!is_int(PING_HIGHER_BOUND_LIFETIME) || PING_HIGHER_BOUND_LIFETIME < 1 || PING_HIGHER_BOUND_LIFETIME > 3540)) {
0 ignored issues
show
introduced by
The condition PING_HIGHER_BOUND_LIFETIME !== false is always false.
Loading history...
309
			throw new FatalMisconfigurationException("The PING_HIGHER_BOUND_LIFETIME value must be 'false' or a number between 1 and 3540 inclusively.");
310
		}
311
		if (PING_HIGHER_BOUND_LIFETIME !== false && PING_LOWER_BOUND_LIFETIME !== false && PING_HIGHER_BOUND_LIFETIME < PING_LOWER_BOUND_LIFETIME) {
0 ignored issues
show
introduced by
The condition PING_HIGHER_BOUND_LIFETIME !== false is always false.
Loading history...
312
			throw new FatalMisconfigurationException("The PING_HIGHER_BOUND_LIFETIME value must be greater or equal to PING_LOWER_BOUND_LIFETIME.");
313
		}
314
315
		if (!defined('RETRY_AFTER_DELAY')) {
316
			define('RETRY_AFTER_DELAY', 300);
317
		}
318
		elseif (RETRY_AFTER_DELAY !== false && (!is_int(RETRY_AFTER_DELAY) || RETRY_AFTER_DELAY < 1)) {
0 ignored issues
show
introduced by
The condition is_int(RETRY_AFTER_DELAY) is always true.
Loading history...
319
			throw new FatalMisconfigurationException("The RETRY_AFTER_DELAY value must be 'false' or a number greater than 0.");
320
		}
321
322
		// set Grommunio backend defaults if not set
323
		if (!defined('MAPI_SERVER')) {
324
			define('MAPI_SERVER', 'default:');
325
		}
326
		if (!defined('STORE_STATE_FOLDER')) {
327
			define('STORE_STATE_FOLDER', 'GS-SyncState');
328
		}
329
330
		// the check on additional folders will not throw hard errors, as this is probably changed on live systems
331
		if (isset($additionalFolders) && !is_array($additionalFolders)) {
332
			SLog::Write(LOGLEVEL_ERROR, "GSync::CheckConfig(): The additional folders synchronization not available as array.");
333
		}
334
		else {
335
			// check configured data
336
			foreach ($additionalFolders as $af) {
337
				if (!is_array($af) || !isset($af['store']) || !isset($af['folderid']) || !isset($af['name']) || !isset($af['type'])) {
338
					SLog::Write(LOGLEVEL_ERROR, "GSync::CheckConfig(): the additional folder synchronization is not configured correctly. Missing parameters. Entry will be ignored.");
339
340
					continue;
341
				}
342
343
				if ($af['store'] == "" || $af['folderid'] == "" || $af['name'] == "" || $af['type'] == "") {
344
					SLog::Write(LOGLEVEL_WARN, "GSync::CheckConfig(): the additional folder synchronization is not configured correctly. Empty parameters. Entry will be ignored.");
345
346
					continue;
347
				}
348
349
				if (!in_array($af['type'], [SYNC_FOLDER_TYPE_USER_NOTE, SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_USER_APPOINTMENT, SYNC_FOLDER_TYPE_USER_TASK, SYNC_FOLDER_TYPE_USER_MAIL])) {
350
					SLog::Write(LOGLEVEL_ERROR, sprintf("GSync::CheckConfig(): the type of the additional synchronization folder '%s is not permitted.", $af['name']));
351
352
					continue;
353
				}
354
				// the data will be initialized when used via self::getAddFolders()
355
			}
356
		}
357
358
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Used timezone '%s'", date_default_timezone_get()));
359
360
		// get the statemachine, which will also try to load the backend.. This could throw errors
361
		self::GetStateMachine();
362
363
		return true;
364
	}
365
366
	/**
367
	 * Returns the StateMachine object
368
	 * which has to be an IStateMachine implementation.
369
	 *
370
	 * @throws FatalNotImplementedException
371
	 * @throws HTTPReturnCodeException
372
	 *
373
	 * @return object implementation of IStateMachine
374
	 */
375
	public static function GetStateMachine() {
376
		if (!isset(GSync::$stateMachine)) {
377
			// the backend could also return an own IStateMachine implementation
378
			GSync::$stateMachine = self::GetBackend()->GetStateMachine();
379
380
			if (GSync::$stateMachine->GetStateVersion() !== GSync::GetLatestStateVersion()) {
381
				if (class_exists("TopCollector")) {
382
					self::GetTopCollector()->AnnounceInformation("Run migration script!", true);
383
				}
384
385
				throw new ServiceUnavailableException(sprintf("The state version available to the %s is not the latest version - please run the state upgrade script. See release notes for more information.", get_class(GSync::$stateMachine)));
386
			}
387
		}
388
389
		return GSync::$stateMachine;
390
	}
391
392
	/**
393
	 * Returns the Redis object.
394
	 *
395
	 * @return object Redis
396
	 */
397
	public static function GetRedis() {
398
		if (!isset(GSync::$redis)) {
399
			GSync::$redis = new RedisConnection();
400
		}
401
402
		return GSync::$redis;
403
	}
404
405
	/**
406
	 * Returns the latest version of supported states.
407
	 *
408
	 * @return int
409
	 */
410
	public static function GetLatestStateVersion() {
411
		return self::STATE_VERSION;
412
	}
413
414
	/**
415
	 * Returns the ProvisioningManager object.
416
	 *
417
	 * @return object ProvisioningManager
418
	 */
419
	public static function GetProvisioningManager() {
420
		if (!isset(self::$provisioningManager)) {
421
			self::$provisioningManager = new ProvisioningManager();
422
		}
423
424
		return self::$provisioningManager;
425
	}
426
427
	/**
428
	 * Returns the DeviceManager object.
429
	 *
430
	 * @param bool $initialize (opt) default true: initializes the DeviceManager if not already done
431
	 *
432
	 * @return object DeviceManager
433
	 */
434
	public static function GetDeviceManager($initialize = true) {
435
		if (!isset(GSync::$deviceManager) && $initialize) {
436
			GSync::$deviceManager = new DeviceManager();
437
		}
438
439
		return GSync::$deviceManager;
440
	}
441
442
	/**
443
	 * Returns the Top data collector object.
444
	 *
445
	 * @return object TopCollector
446
	 */
447
	public static function GetTopCollector() {
448
		if (!isset(GSync::$topCollector)) {
449
			GSync::$topCollector = new TopCollector();
450
		}
451
452
		return GSync::$topCollector;
453
	}
454
455
	/**
456
	 * Loads a backend file.
457
	 *
458
	 * @param string $backendname
459
	 *
460
	 * @throws FatalNotImplementedException
461
	 *
462
	 * @return bool
463
	 */
464
	public static function IncludeBackend($backendname) {
465
		if ($backendname == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $backendname of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
466
			return false;
467
		}
468
469
		$backendname = strtolower($backendname);
470
		if (substr($backendname, 0, 7) !== 'backend') {
471
			throw new FatalNotImplementedException(sprintf("Backend '%s' is not allowed", $backendname));
472
		}
473
474
		$rbn = substr($backendname, 7);
475
476
		$subdirbackend = REAL_BASE_PATH . "backend/" . $rbn . "/" . $rbn . ".php";
477
		$stdbackend = REAL_BASE_PATH . "backend/" . $rbn . ".php";
478
479
		if (is_file($subdirbackend)) {
480
			$toLoad = $subdirbackend;
481
		}
482
		elseif (is_file($stdbackend)) {
483
			$toLoad = $stdbackend;
484
		}
485
		else {
486
			return false;
487
		}
488
489
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Including backend file: '%s'", $toLoad));
490
491
		return include_once $toLoad;
492
	}
493
494
	/**
495
	 * Returns the Backend for this request
496
	 * the backend has to be an IBackend implementation.
497
	 *
498
	 * @return object IBackend implementation
499
	 */
500
	public static function GetBackend() {
501
		// if the backend is not yet loaded, load backend drivers and instantiate it
502
		if (!isset(GSync::$backend)) {
503
			// Initialize Grommunio
504
			GSync::$backend = new Grommunio();
505
		}
506
507
		return GSync::$backend;
508
	}
509
510
	/**
511
	 * Returns additional folder objects which should be synchronized to the device.
512
	 *
513
	 * @param bool $backendIdsAsKeys if true the keys are backendids else folderids, default: true
514
	 *
515
	 * @return array
516
	 */
517
	public static function GetAdditionalSyncFolders($backendIdsAsKeys = true) {
518
		// get user based folders which should be synchronized
519
		$userFolder = self::GetDeviceManager()->GetAdditionalUserSyncFolders();
520
		$addfolders = self::getAddSyncFolders() + $userFolder;
521
		// if requested, we rewrite the backendids to folderids here
522
		if ($backendIdsAsKeys === false && !empty($addfolders)) {
523
			SLog::Write(LOGLEVEL_DEBUG, "GSync::GetAdditionalSyncFolders(): Requested AS folderids as keys for additional folders array, converting");
524
			$faddfolders = [];
525
			foreach ($addfolders as $backendId => $addFolder) {
526
				$fid = self::GetDeviceManager()->GetFolderIdForBackendId($backendId);
527
				$faddfolders[$fid] = $addFolder;
528
			}
529
			$addfolders = $faddfolders;
530
		}
531
532
		return $addfolders;
533
	}
534
535
	/**
536
	 * Returns additional folder objects which should be synchronized to the device.
537
	 *
538
	 * @param string $backendid
539
	 * @param bool   $noDebug   (opt) by default, debug message is shown
540
	 *
541
	 * @return string
542
	 */
543
	public static function GetAdditionalSyncFolderStore($backendid, $noDebug = false) {
544
		if (isset(self::getAddSyncFolders()[$backendid]->Store)) {
545
			$val = self::getAddSyncFolders()[$backendid]->Store;
546
		}
547
		else {
548
			$val = self::GetDeviceManager()->GetAdditionalUserSyncFolder($backendid);
549
			if (isset($val['store'])) {
550
				$val = $val['store'];
551
			}
552
		}
553
554
		if (!$noDebug) {
555
			SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::GetAdditionalSyncFolderStore('%s'): '%s'", $backendid, Utils::PrintAsString($val)));
556
		}
557
558
		return $val;
559
	}
560
561
	/**
562
	 * Returns a SyncObject class name for a folder class.
563
	 *
564
	 * @param string $folderclass
565
	 *
566
	 * @throws FatalNotImplementedException
567
	 *
568
	 * @return string
569
	 */
570
	public static function getSyncObjectFromFolderClass($folderclass) {
571
		if (!isset(self::$classes[$folderclass])) {
572
			throw new FatalNotImplementedException("Class '{$folderclass}' is not supported");
573
		}
574
575
		$class = self::$classes[$folderclass][self::CLASS_NAME];
576
		if (self::$classes[$folderclass][self::CLASS_REQUIRESPROTOCOLVERSION]) {
577
			return new $class(Request::GetProtocolVersion());
0 ignored issues
show
Bug Best Practice introduced by
The expression return new $class(Request::GetProtocolVersion()) returns the type object which is incompatible with the documented return type string.
Loading history...
578
		}
579
580
		return new $class();
0 ignored issues
show
Bug Best Practice introduced by
The expression return new $class() returns the type object which is incompatible with the documented return type string.
Loading history...
581
	}
582
583
	/**
584
	 * Initializes the SyncObjects for additional folders on demand.
585
	 * Uses DeviceManager->BuildSyncFolderObject() to do patching required for ZP-907.
586
	 *
587
	 * @return array
588
	 */
589
	private static function getAddSyncFolders() {
590
		global $additionalFolders;
591
		if (!isset(self::$addSyncFolders)) {
592
			self::$addSyncFolders = [];
593
594
			if (isset($additionalFolders) && !is_array($additionalFolders)) {
595
				SLog::Write(LOGLEVEL_ERROR, "GSync::getAddSyncFolders() : The additional folders synchronization not available as array.");
596
			}
597
			else {
598
				foreach ($additionalFolders as $af) {
599
					if (!is_array($af) || !isset($af['store']) || !isset($af['folderid']) || !isset($af['name']) || !isset($af['type'])) {
600
						SLog::Write(LOGLEVEL_ERROR, "GSync::getAddSyncFolders() : the additional folder synchronization is not configured correctly. Missing parameters. Entry will be ignored.");
601
602
						continue;
603
					}
604
605
					if ($af['store'] == "" || $af['folderid'] == "" || $af['name'] == "" || $af['type'] == "") {
606
						SLog::Write(LOGLEVEL_WARN, "GSync::getAddSyncFolders() : the additional folder synchronization is not configured correctly. Empty parameters. Entry will be ignored.");
607
608
						continue;
609
					}
610
611
					if (!in_array($af['type'], [SYNC_FOLDER_TYPE_USER_NOTE, SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_USER_APPOINTMENT, SYNC_FOLDER_TYPE_USER_TASK, SYNC_FOLDER_TYPE_USER_MAIL])) {
612
						SLog::Write(LOGLEVEL_ERROR, sprintf("GSync::getAddSyncFolders() : the type of the additional synchronization folder '%s is not permitted.", $af['name']));
613
614
						continue;
615
					}
616
617
					// don't fail hard if no flags are set, but we at least warn about it
618
					if (!isset($af['flags'])) {
619
						SLog::Write(LOGLEVEL_WARN, sprintf("GSync::getAddSyncFolders() : the additional folder '%s' is not configured completely. Missing 'flags' parameter, defaulting to DeviceManager::FLD_FLAGS_NONE.", $af['name']));
620
						$af['flags'] = DeviceManager::FLD_FLAGS_NONE;
621
					}
622
623
					$folder = self::GetDeviceManager()->BuildSyncFolderObject($af['store'], $af['folderid'], '0', $af['name'], $af['type'], $af['flags'], DeviceManager::FLD_ORIGIN_CONFIG);
624
					self::$addSyncFolders[$folder->BackendId] = $folder;
625
				}
626
			}
627
		}
628
629
		return self::$addSyncFolders;
630
	}
631
632
	/**
633
	 * Returns the default foldertype for a folder class.
634
	 *
635
	 * @param string $folderclass folderclass sent by the mobile
636
	 *
637
	 * @return string
638
	 */
639
	public static function getDefaultFolderTypeFromFolderClass($folderclass) {
640
		SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::getDefaultFolderTypeFromFolderClass('%s'): '%d'", $folderclass, self::$classes[$folderclass][self::CLASS_DEFAULTTYPE]));
641
642
		return self::$classes[$folderclass][self::CLASS_DEFAULTTYPE];
643
	}
644
645
	/**
646
	 * Returns the folder class for a foldertype.
647
	 *
648
	 * @param string $foldertype
649
	 *
650
	 * @return string/false     false if no class for this type is available
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/false at position 0 could not be parsed: Unknown type name 'string/false' at position 0 in string/false.
Loading history...
651
	 */
652
	public static function GetFolderClassFromFolderType($foldertype) {
653
		$class = false;
654
		foreach (self::$classes as $aClass => $cprops) {
655
			if ($cprops[self::CLASS_DEFAULTTYPE] == $foldertype || in_array($foldertype, $cprops[self::CLASS_OTHERTYPES])) {
656
				$class = $aClass;
657
658
				break;
659
			}
660
		}
661
		SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::GetFolderClassFromFolderType('%s'): %s", $foldertype, Utils::PrintAsString($class)));
662
663
		return $class;
664
	}
665
666
	/**
667
	 * Prints the grommunio-sync legal header to STDOUT
668
	 * Using this breaks ActiveSync synchronization if wbxml is expected.
669
	 *
670
	 * @param string $message           (opt) message to be displayed
671
	 * @param string $additionalMessage (opt) additional message to be displayed
672
	 *
673
	 * @return
674
	 */
675
	public static function PrintGrommunioSyncLegal($message = "", $additionalMessage = "") {
676
		SLog::Write(LOGLEVEL_DEBUG, "GSync::PrintGrommunioSyncLegal()");
677
678
		if ($message) {
679
			$message = "<h3>" . $message . "</h3>";
680
		}
681
		if ($additionalMessage) {
682
			$additionalMessage .= "<br>";
683
		}
684
685
		header("Content-type: text/html");
686
		echo <<<END
687
        <html>
688
        <header>
689
        <title>grommunio-sync ActiveSync</title>
690
        </header>
691
        <body>
692
        <font face="verdana">
693
        <h2>grommunio-sync - Open Source ActiveSync</h2>
694
        {$message} {$additionalMessage}
695
        <br><br>
696
        More information about grommunio can be found
697
        <a href="https://grommunio.com/">at the grommunio homepage</a><br>
698
        </font>
699
        </body>
700
        </html>
701
END;
702
	}
703
704
	/**
705
	 * Indicates the latest AS version supported by grommunio-sync.
706
	 *
707
	 * @return string
708
	 */
709
	public static function GetLatestSupportedASVersion() {
710
		return end(self::$supportedASVersions);
711
	}
712
713
	/**
714
	 * Indicates which is the highest AS version supported by the backend.
715
	 *
716
	 * @throws FatalNotImplementedException if the backend returns an invalid version
717
	 *
718
	 * @return string
719
	 */
720
	public static function GetSupportedASVersion() {
721
		$version = self::GetBackend()->GetSupportedASVersion();
722
		if (!in_array($version, self::$supportedASVersions)) {
723
			throw new FatalNotImplementedException(sprintf("AS version '%s' reported by the backend is not supported", $version));
724
		}
725
726
		return $version;
727
	}
728
729
	/**
730
	 * Returns AS server header.
731
	 *
732
	 * @return string
733
	 */
734
	public static function GetServerHeader() {
735
		if (self::GetSupportedASVersion() == self::ASV_25) {
736
			return "MS-Server-ActiveSync: 6.5.7638.1";
737
		}
738
739
		return "MS-Server-ActiveSync: " . self::GetSupportedASVersion();
740
	}
741
742
	/**
743
	 * Returns AS protocol versions which are supported.
744
	 *
745
	 * @param bool $valueOnly (opt) default: false (also returns the header name)
746
	 *
747
	 * @return string
748
	 */
749
	public static function GetSupportedProtocolVersions($valueOnly = false) {
750
		$versions = implode(',', array_slice(self::$supportedASVersions, 0, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions) + 1)));
751
		SLog::Write(LOGLEVEL_DEBUG, "GSync::GetSupportedProtocolVersions(): " . $versions);
752
753
		if ($valueOnly === true) {
754
			return $versions;
755
		}
756
757
		return "MS-ASProtocolVersions: " . $versions;
758
	}
759
760
	/**
761
	 * Returns AS commands which are supported.
762
	 *
763
	 * @return string
764
	 */
765
	public static function GetSupportedCommands() {
766
		$asCommands = [];
767
		// filter all non-activesync commands
768
		foreach (self::$supportedCommands as $c => $v) {
769
			if (!self::checkCommandOptions($c, self::NOACTIVESYNCCOMMAND) &&
770
					self::checkCommandOptions($c, self::GetSupportedASVersion())) {
771
				$asCommands[] = Utils::GetCommandFromCode($c);
772
			}
773
		}
774
775
		$commands = implode(',', $asCommands);
776
		SLog::Write(LOGLEVEL_DEBUG, "GSync::GetSupportedCommands(): " . $commands);
777
778
		return "MS-ASProtocolCommands: " . $commands;
779
	}
780
781
	/**
782
	 * Loads and instantiates a request processor for a command.
783
	 *
784
	 * @param int $commandCode
785
	 *
786
	 * @return RequestProcessor sub-class
787
	 */
788
	public static function GetRequestHandlerForCommand($commandCode) {
789
		if (!array_key_exists($commandCode, self::$supportedCommands) ||
790
				!array_key_exists(self::REQUESTHANDLER, self::$supportedCommands[$commandCode])) {
791
			throw new FatalNotImplementedException(sprintf("Command '%s' has no request handler or class", Utils::GetCommandFromCode($commandCode)));
792
		}
793
794
		$class = self::$supportedCommands[$commandCode][self::REQUESTHANDLER];
795
		$handlerclass = REAL_BASE_PATH . "lib/request/" . strtolower($class) . ".php";
796
797
		if (is_file($handlerclass)) {
798
			include $handlerclass;
799
		}
800
801
		if (class_exists($class)) {
802
			return new $class();
803
		}
804
805
		throw new FatalNotImplementedException(sprintf("Request handler '%s' can not be loaded", $class));
806
	}
807
808
	/**
809
	 * Indicates if a commands requires authentication or not.
810
	 *
811
	 * @param int $commandCode
812
	 *
813
	 * @return bool
814
	 */
815
	public static function CommandNeedsAuthentication($commandCode) {
816
		$stat = !self::checkCommandOptions($commandCode, self::UNAUTHENTICATED);
817
		SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::CommandNeedsAuthentication(%d): %s", $commandCode, Utils::PrintAsString($stat)));
818
819
		return $stat;
820
	}
821
822
	/**
823
	 * Indicates if the Provisioning check has to be forced on these commands.
824
	 *
825
	 * @param string $commandCode
826
	 *
827
	 * @return bool
828
	 */
829
	public static function CommandNeedsProvisioning($commandCode) {
830
		$stat = !self::checkCommandOptions($commandCode, self::UNPROVISIONED);
831
		SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::CommandNeedsProvisioning(%s): %s", $commandCode, Utils::PrintAsString($stat)));
832
833
		return $stat;
834
	}
835
836
	/**
837
	 * Indicates if these commands expect plain text input instead of wbxml.
838
	 *
839
	 * @param string $commandCode
840
	 *
841
	 * @return bool
842
	 */
843
	public static function CommandNeedsPlainInput($commandCode) {
844
		$stat = self::checkCommandOptions($commandCode, self::PLAININPUT);
845
		SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::CommandNeedsPlainInput(%d): %s", $commandCode, Utils::PrintAsString($stat)));
0 ignored issues
show
Bug introduced by
$stat of type object is incompatible with the type string expected by parameter $var of Utils::PrintAsString(). ( Ignorable by Annotation )

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

845
		SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::CommandNeedsPlainInput(%d): %s", $commandCode, Utils::PrintAsString(/** @scrutinizer ignore-type */ $stat)));
Loading history...
846
847
		return $stat;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $stat returns the type object which is incompatible with the documented return type boolean.
Loading history...
848
	}
849
850
	/**
851
	 * Indicates if the command to be executed operates on the hierarchy.
852
	 *
853
	 * @param int $commandCode
854
	 *
855
	 * @return bool
856
	 */
857
	public static function HierarchyCommand($commandCode) {
858
		$stat = self::checkCommandOptions($commandCode, self::HIERARCHYCOMMAND);
859
		SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::HierarchyCommand(%d): %s", $commandCode, Utils::PrintAsString($stat)));
0 ignored issues
show
Bug introduced by
$stat of type object is incompatible with the type string expected by parameter $var of Utils::PrintAsString(). ( Ignorable by Annotation )

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

859
		SLog::Write(LOGLEVEL_DEBUG, sprintf("GSync::HierarchyCommand(%d): %s", $commandCode, Utils::PrintAsString(/** @scrutinizer ignore-type */ $stat)));
Loading history...
860
861
		return $stat;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $stat returns the type object which is incompatible with the documented return type boolean.
Loading history...
862
	}
863
864
	/**
865
	 * Checks access types of a command.
866
	 *
867
	 * @param string $commandCode a commandCode
868
	 * @param string $option      e.g. self::UNAUTHENTICATED
869
	 *
870
	 * @throws FatalNotImplementedException
871
	 *
872
	 * @return object StateMachine
873
	 */
874
	private static function checkCommandOptions($commandCode, $option) {
875
		if ($commandCode === false) {
0 ignored issues
show
introduced by
The condition $commandCode === false is always false.
Loading history...
876
			return false;
877
		}
878
879
		if (!array_key_exists($commandCode, self::$supportedCommands)) {
880
			throw new FatalNotImplementedException(sprintf("Command '%s' is not supported", Utils::GetCommandFromCode($commandCode)));
0 ignored issues
show
Bug introduced by
$commandCode of type string is incompatible with the type integer expected by parameter $code of Utils::GetCommandFromCode(). ( Ignorable by Annotation )

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

880
			throw new FatalNotImplementedException(sprintf("Command '%s' is not supported", Utils::GetCommandFromCode(/** @scrutinizer ignore-type */ $commandCode)));
Loading history...
881
		}
882
883
		$capa = self::$supportedCommands[$commandCode];
884
		$defcapa = in_array($option, $capa, true);
885
886
		// if not looking for a default capability, check if the command is supported since a previous AS version
887
		if (!$defcapa) {
888
			$verkey = array_search($option, self::$supportedASVersions, true);
889
			if ($verkey !== false && ($verkey >= array_search($capa[0], self::$supportedASVersions))) {
890
				$defcapa = true;
891
			}
892
		}
893
894
		return $defcapa;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $defcapa returns the type boolean which is incompatible with the documented return type object.
Loading history...
895
	}
896
}
897