Passed
Push — master ( 42b683...7c1a7f )
by
unknown
16:41 queued 02:17
created

ConfigCheck::error_version()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 4
dl 0
loc 2
rs 10
1
<?php
2
3
/**
4
 * This class will check the server configuration and it stops the
5
 * webapp from working until this is fixed.
6
 */
7
class ConfigCheck {
8
	public $result;
9
	public $siteconfig;
10
	public $phpini;
11
12
	public function __construct(public $haltOnError = true) {
13
		$this->result = true;
14
15
		// here we check our settings, changes to the config and
16
		// additional checks must be added/changed here
17
		$this->checkPHP("5.4", "You must upgrade PHP");
18
		$this->checkExtension("mapi", "", "If you have upgraded Gromox, please restart nginx/php-fpm");
19
		$this->checkExtension("gettext", "", "Install the gettext extension for PHP");
20
		$this->checkPHPsetting("session.auto_start", "0", "Modify this setting in '%s'");
21
		$this->checkPHPsetting("output_handler", "", "With this option set, it is unsure if the grommunio Web will work correctly");
22
		$this->checkPHPsetting("zlib.output_handler", "", "With this option set, it is unsure if the grommunio Web will work correctly");
23
24
		# Replicate value logic from php-src/ext/zlib/zlib.c
25
		$sv = ini_get("zlib.output_compression");
26
		$sv = strcasecmp($sv, "on") == 0 ? 1 : (strcasecmp($sv, "off") == 0 ? 0 : intval($sv));
27
		if ($sv != 0) {
28
			$this->error_config("zlib.output_compression", "off", "With this option enabled, it could occur that XMLHTTP requests will fail");
29
		}
30
31
		if (CONFIG_CHECK_COOKIES_HTTP) {
32
			$this->checkPHPsecurity("session.cookie_httponly", "on", "Modify this setting in '%s'");
33
		}
34
		if (CONFIG_CHECK_COOKIES_SSL) {
35
			$this->checkPHPsecurity("session.cookie_secure", "on", "Modify this setting in '%s'");
36
		}
37
38
		# More custom logic needed :(
39
		# When save_path is left unset/empty, ini_get reports it as empty,
40
		# even though PHP uses some directory anyway (usually /tmp).
41
		$sp = ini_get("session.save_path");
42
		if (strlen($sp) > 0 && !$this->checkDirectory(
43
			$sp,
44
			"w",
45
			"session.save_path is not writable. This means PHP is practically running stateless, which is incompatible with the requirements of grommunio-web."
46
		)) {
47
			error_log("session.save_path ({$sp}) is not writable");
48
		}
49
50
		$this->checkDirectory(TMP_PATH, "rw", "Please make sure this directory exists and is writable for nginx/php-fpm");
51
		$this->checkFunction("iconv", "Install the 'iconv' module for PHP, or else you don't have euro-sign support.");
52
		$this->checkFunction("gzencode", "You don't have zlib support: <a href=\"https://php.net/manual/en/ref.zlib.php#zlib.installation\">https://php.net/manual/en/ref.zlib.php#zlib.installation</a>");
53
		$this->checkLoader(DEBUG_LOADER, "Your 'DEBUG_LOADER' configuration isn't valid for the current folder");
54
55
		// check if there were *any* errors and we need to stop grommunio Web
56
		if (!$this->result && $this->haltOnError) {
57
			?>
58
				<p style="font-weight: bold;">grommunio Web can't start because of incompatible configuration.</p>
59
				<p>Please correct above errors, a good start is by checking your '<tt><?php echo $this->get_php_ini(); ?></tt>' file.</p>
60
				<p>You can disable this configuration check by editing the file '<tt><?php echo dirname((string) $_SERVER["SCRIPT_FILENAME"]); ?>/config.php</tt>', but this is not recommended.</p>
61
			<?php
62
			exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
63
		}
64
	}
65
66
	/**
67
	 * This function throws all the errors, make sure that the check-function
68
	 * will call this in case of an error.
69
	 *
70
	 * @param mixed $string
71
	 * @param mixed $help
72
	 */
73
	public function error($string, $help) {
74
		if ($this->haltOnError) {
75
			printf("<div style=\"color: #f00;\">%s</div><div style=\"font-size: smaller; margin-left: 20px;\">%s</div>\n", $string, $help);
76
		}
77
		else {
78
			trigger_error(strip_tags((string) $string), E_USER_NOTICE);
79
		}
80
		$this->result = false;
81
	}
82
83
	/**
84
	 * See error().
85
	 *
86
	 * @param mixed $name
87
	 * @param mixed $needed
88
	 * @param mixed $found
89
	 * @param mixed $help
90
	 */
91
	public function error_version($name, $needed, $found, $help) {
92
		$this->error("<strong>Version error:</strong> {$name} {$found} found, but {$needed} needed.", $help);
93
	}
94
95
	/**
96
	 * See error().
97
	 *
98
	 * @param mixed $name
99
	 * @param mixed $help
100
	 */
101
	public function error_notfound($name, $help) {
102
		$this->error("<strong>Not Found:</strong> {$name} not found", $help);
103
	}
104
105
	/**
106
	 * See error().
107
	 *
108
	 * @param mixed $name
109
	 * @param mixed $needed
110
	 * @param mixed $help
111
	 */
112
	public function error_config($name, $needed, $help) {
113
		$help = sprintf($help, "<tt>" . $this->get_php_ini() . "</tt>");
114
		$this->error("<strong>PHP Config error:</strong> {$name} must be '{$needed}'", $help);
115
	}
116
117
	/**
118
	 * See error().
119
	 *
120
	 * @param mixed $name
121
	 * @param mixed $needed
122
	 * @param mixed $help
123
	 */
124
	public function error_security($name, $needed, $help) {
125
		$help = sprintf($help, "<tt>" . $this->get_site_config() . "</tt>");
126
		$this->error("<strong>PHP Security Config error:</strong> {$name} must be '{$needed}'", $help);
127
	}
128
129
	/**
130
	 * See error().
131
	 *
132
	 * @param mixed $dir
133
	 * @param mixed $msg
134
	 * @param mixed $help
135
	 */
136
	public function error_directory($dir, $msg, $help) {
137
		$this->error("<strong>Directory Error:</strong> {$dir} {$msg}", $help);
138
	}
139
140
	/**
141
	 * Retrieves the location of the apache site config.
142
	 */
143
	public function get_site_config() {
144
		if (isset($this->siteconfig)) {
145
			return $this->siteconfig;
146
		}
147
148
		// This is not 100% accurate, so it needs to be improved a bit.
149
		$result = "sites-enabled" . DIRECTORY_SEPARATOR . "grommunio-web";
150
151
		ob_start();
152
		phpinfo(INFO_MODULES);
153
		$phpinfo = ob_get_contents();
154
		ob_end_clean();
155
156
		preg_match("/<td class=\"e\">[\\s]*Server Root[\\s]*<\\/td>[\\s]*<td class=\"v\">[\\s]*(.*)[\\s]*<\\/td>/i", $phpinfo, $matches);
157
		if (isset($matches[1])) {
158
			$result = trim($matches[1]) . DIRECTORY_SEPARATOR . $result;
159
		}
160
		$this->siteconfig = $result;
161
162
		return $result;
163
	}
164
165
	/**
166
	 * Retrieves the location of php.ini.
167
	 */
168
	public function get_php_ini() {
169
		if (isset($this->phpini)) {
170
			return $this->phpini;
171
		}
172
173
		$result = "php.ini";
174
175
		ob_start();
176
		phpinfo(INFO_GENERAL);
177
		$phpinfo = ob_get_contents();
178
		ob_end_clean();
179
180
		preg_match("/<td class=\"v\">(.*php[45]?\\.ini)/i", $phpinfo, $matches);
181
		if (isset($matches[0])) {
182
			$result = $matches[0];
183
		}
184
		$this->phpini = $result;
185
186
		return $result;
187
	}
188
189
	/*\
190
	*  Check functions					   *
191
	\*/
192
193
	/**
194
	 * Checks for the PHP version.
195
	 *
196
	 * @param mixed $version
197
	 * @param mixed $help_msg
198
	 */
199
	public function checkPHP($version, $help_msg = "") {
200
		$result = true;
201
		if (version_compare(phpversion(), $version) == -1) {
202
			$this->error_version("PHP", $version, phpversion(), $help_msg);
203
			$result = false;
204
		}
205
206
		return $result;
207
	}
208
209
	/**
210
	 * Check if extension is loaded and if the version is what we need.
211
	 *
212
	 * @param mixed $name
213
	 * @param mixed $version
214
	 * @param mixed $help_msg
215
	 */
216
	public function checkExtension($name, $version = "", $help_msg = "") {
217
		$result = true;
218
		if (extension_loaded($name)) {
219
			if (version_compare(phpversion($name), $version) == -1) {
220
				$this->error_version("PHP " . $name . " extension", phpversion($name), $version, $help_msg);
221
				$result = false;
222
			}
223
		}
224
		else {
225
			$this->error_notfound("PHP " . $name . " extension", $help_msg);
226
			$result = false;
227
		}
228
229
		return $result;
230
	}
231
232
	/**
233
	 * Check if a function exists.
234
	 *
235
	 * @param mixed $name
236
	 * @param mixed $help_msg
237
	 */
238
	public function checkFunction($name, $help_msg = "") {
239
		$result = true;
240
		if (!function_exists($name)) {
241
			$this->error_notfound("PHP function '" . $name . "' ", $help_msg);
242
			$result = false;
243
		}
244
245
		return $result;
246
	}
247
248
	/**
249
	 * This function checks if a specific php setting (php.ini) is set to a
250
	 * value we need, for example register_globals.
251
	 *
252
	 * @param mixed $setting
253
	 * @param mixed $value_needed
254
	 * @param mixed $help_msg
255
	 */
256
	public function checkPHPsetting($setting, $value_needed, $help_msg = "") {
257
		$result = true;
258
		$inival = ini_get($setting);
259
		if (strcmp($inival, (string) $value_needed) != 0) {
260
			$this->error_config($setting, $value_needed, $help_msg . " (Current value: \"{$inival}\")");
261
			$result = false;
262
		}
263
264
		return $result;
265
	}
266
267
	/**
268
	 * This function checks if a specific php setting (php.ini) is set to a
269
	 * value we need, for example register_globals.
270
	 *
271
	 * @param mixed $setting
272
	 * @param mixed $value_needed
273
	 * @param mixed $help_msg
274
	 */
275
	public function checkPHPsecurity($setting, $value_needed, $help_msg = "") {
276
		$result = true;
277
278
		// convert $value_needed
279
		$value = match ($value_needed) {
280
			"on", "yes", "true" => 1,
281
			"off", "no", "false" => 0,
282
			default => $value_needed,
283
		};
284
285
		if (ini_get($setting) != $value) {
286
			$this->error_security($setting, $value_needed, $help_msg);
287
			$result = false;
288
		}
289
290
		return $result;
291
	}
292
293
	/**
294
	 * This functions checks if a directory exists and if requested also if
295
	 * this directory is readable/writable specified with the $states parameter.
296
	 *
297
	 * $states is a string which can contain these chars:
298
	 *	r  - check if directory is readable
299
	 *	w  - check if directory is writable
300
	 *
301
	 * @param mixed $dir
302
	 * @param mixed $states
303
	 * @param mixed $help_msg
304
	 */
305
	public function checkDirectory($dir, $states = "r", $help_msg = "") {
306
		$result = true;
307
308
		if (!is_dir($dir)) {
309
			if (@mkdir($dir) === false) {
310
				$this->error_directory($dir, "couldn't be created", $help_msg);
311
			}
312
		}
313
314
		if (is_dir($dir)) {
315
			$states = strtolower((string) $states);
316
			if (str_contains($states, "r")) {
317
				if (!is_readable($dir)) {
318
					$this->error_directory($dir, "isn't readable", $help_msg);
319
					$result = false;
320
				}
321
			}
322
			if (str_contains($states, "w")) {
323
				if (!is_writable($dir)) {
324
					$this->error_directory($dir, "isn't writable", $help_msg);
325
					$result = false;
326
				}
327
			}
328
		}
329
		else {
330
			$this->error_directory($dir, "doesn't exist", $help_msg);
331
			$result = false;
332
		}
333
334
		return $result;
335
	}
336
337
	/**
338
	 * Check if the correct files are present in the current folder based on the DEBUG_LOADER configuration
339
	 * option. This should prevent odd errors when the incorrect folders are present.
340
	 *
341
	 * @param mixed $loader
342
	 * @param mixed $help_msg
343
	 */
344
	public function checkLoader($loader, $help_msg = "") {
345
		$result = true;
346
347
		switch ($loader) {
348
			case LOAD_RELEASE:
349
				if (!is_file(BASE_PATH . '/client/grommunio.js')) {
350
					$this->error('<strong>LOAD_RELEASE configured, but no release files found</strong>', $help_msg);
351
					$result = false;
352
				}
353
				elseif (is_dir(BASE_PATH . '/client/zarafa')) {
354
					$this->error('<strong>LOAD_RELEASE configured, but source files were found</strong>', $help_msg);
355
					$result = false;
356
				}
357
				break;
358
359
			case LOAD_DEBUG:
360
				if (!is_file(BASE_PATH . '/client/zarafa-debug.js')) {
361
					$this->error('<strong>LOAD_DEBUG configured, but no debug files found</strong>', $help_msg);
362
					$result = false;
363
				}
364
				elseif (is_dir(BASE_PATH . '/client/zarafa')) {
365
					$this->error('<strong>LOAD_DEBUG configured, but source files were found</strong>', $help_msg);
366
					$result = false;
367
				}
368
				break;
369
370
			case LOAD_SOURCE:
371
				if (!is_dir(BASE_PATH . '/client/zarafa')) {
372
					$this->error('<strong>LOAD_SOURCE configured, but no source files found</strong>', $help_msg);
373
					$result = false;
374
				}
375
				elseif (is_file(BASE_PATH . '/client/grommunio.js') || is_file(BASE_PATH . '/client/zarafa-debug.js')) {
376
					$this->error('<strong>LOAD_SOURCE configured, but release & debug file were found</strong>', $help_msg);
377
					$result = false;
378
				}
379
				break;
380
381
			default:
382
				$this->error('<strong>Unknown \'DEBUG_LOADER\' value: ' . $loader . '</strong>', $help_msg);
383
				$result = false;
384
				break;
385
		}
386
387
		return $result;
388
	}
389
}
390
391
?>
392