Completed
Push — master ( 8969e1...3ade34 )
by Joas
21:37
created

CheckSetupController::isPhpMailerUsed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Derek <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Ko- <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin McCorkell <[email protected]>
13
 * @author Roeland Jago Douma <[email protected]>
14
 *
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OC\Settings\Controller;
32
33
use bantu\IniGetWrapper\IniGetWrapper;
34
use Doctrine\DBAL\DBALException;
35
use Doctrine\DBAL\Platforms\SqlitePlatform;
36
use GuzzleHttp\Exception\ClientException;
37
use OC\AppFramework\Http;
38
use OC\DB\Connection;
39
use OC\DB\MissingIndexInformation;
40
use OC\IntegrityCheck\Checker;
41
use OC\Lock\NoopLockingProvider;
42
use OCP\AppFramework\Controller;
43
use OCP\AppFramework\Http\DataDisplayResponse;
44
use OCP\AppFramework\Http\DataResponse;
45
use OCP\AppFramework\Http\RedirectResponse;
46
use OCP\Http\Client\IClientService;
47
use OCP\IConfig;
48
use OCP\IDateTimeFormatter;
49
use OCP\IDBConnection;
50
use OCP\IL10N;
51
use OCP\ILogger;
52
use OCP\IRequest;
53
use OCP\IURLGenerator;
54
use OCP\Lock\ILockingProvider;
55
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
56
use Symfony\Component\EventDispatcher\GenericEvent;
57
58
/**
59
 * @package OC\Settings\Controller
60
 */
61
class CheckSetupController extends Controller {
62
	/** @var IConfig */
63
	private $config;
64
	/** @var IClientService */
65
	private $clientService;
66
	/** @var \OC_Util */
67
	private $util;
68
	/** @var IURLGenerator */
69
	private $urlGenerator;
70
	/** @var IL10N */
71
	private $l10n;
72
	/** @var Checker */
73
	private $checker;
74
	/** @var ILogger */
75
	private $logger;
76
	/** @var EventDispatcherInterface */
77
	private $dispatcher;
78
	/** @var IDBConnection|Connection */
79
	private $db;
80
	/** @var ILockingProvider */
81
	private $lockingProvider;
82
	/** @var IDateTimeFormatter */
83
	private $dateTimeFormatter;
84
85 View Code Duplication
	public function __construct($AppName,
86
								IRequest $request,
87
								IConfig $config,
88
								IClientService $clientService,
89
								IURLGenerator $urlGenerator,
90
								\OC_Util $util,
91
								IL10N $l10n,
92
								Checker $checker,
93
								ILogger $logger,
94
								EventDispatcherInterface $dispatcher,
95
								IDBConnection $db,
96
								ILockingProvider $lockingProvider,
97
								IDateTimeFormatter $dateTimeFormatter) {
98
		parent::__construct($AppName, $request);
99
		$this->config = $config;
100
		$this->clientService = $clientService;
101
		$this->util = $util;
102
		$this->urlGenerator = $urlGenerator;
103
		$this->l10n = $l10n;
104
		$this->checker = $checker;
105
		$this->logger = $logger;
106
		$this->dispatcher = $dispatcher;
107
		$this->db = $db;
108
		$this->lockingProvider = $lockingProvider;
109
		$this->dateTimeFormatter = $dateTimeFormatter;
110
	}
111
112
	/**
113
	 * Checks if the server can connect to the internet using HTTPS and HTTP
114
	 * @return bool
115
	 */
116
	private function isInternetConnectionWorking() {
117
		if ($this->config->getSystemValue('has_internet_connection', true) === false) {
118
			return false;
119
		}
120
121
		$siteArray = ['www.nextcloud.com',
122
						'www.startpage.com',
123
						'www.eff.org',
124
						'www.edri.org',
125
			];
126
127
		foreach($siteArray as $site) {
128
			if ($this->isSiteReachable($site)) {
129
				return true;
130
			}
131
		}
132
		return false;
133
	}
134
135
	/**
136
	* Checks if the Nextcloud server can connect to a specific URL using both HTTPS and HTTP
137
	* @return bool
138
	*/
139
	private function isSiteReachable($sitename) {
140
		$httpSiteName = 'http://' . $sitename . '/';
141
		$httpsSiteName = 'https://' . $sitename . '/';
142
143
		try {
144
			$client = $this->clientService->newClient();
145
			$client->get($httpSiteName);
146
			$client->get($httpsSiteName);
147
		} catch (\Exception $e) {
148
			$this->logger->logException($e, ['app' => 'internet_connection_check']);
0 ignored issues
show
Documentation introduced by
$e is of type object<Exception>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
149
			return false;
150
		}
151
		return true;
152
	}
153
154
	/**
155
	 * Checks whether a local memcache is installed or not
156
	 * @return bool
157
	 */
158
	private function isMemcacheConfigured() {
159
		return $this->config->getSystemValue('memcache.local', null) !== null;
160
	}
161
162
	/**
163
	 * Whether /dev/urandom is available to the PHP controller
164
	 *
165
	 * @return bool
166
	 */
167
	private function isUrandomAvailable() {
168
		if(@file_exists('/dev/urandom')) {
169
			$file = fopen('/dev/urandom', 'rb');
170
			if($file) {
171
				fclose($file);
172
				return true;
173
			}
174
		}
175
176
		return false;
177
	}
178
179
	/**
180
	 * Public for the sake of unit-testing
181
	 *
182
	 * @return array
183
	 */
184
	protected function getCurlVersion() {
185
		return curl_version();
186
	}
187
188
	/**
189
	 * Check if the used  SSL lib is outdated. Older OpenSSL and NSS versions do
190
	 * have multiple bugs which likely lead to problems in combination with
191
	 * functionality required by ownCloud such as SNI.
192
	 *
193
	 * @link https://github.com/owncloud/core/issues/17446#issuecomment-122877546
194
	 * @link https://bugzilla.redhat.com/show_bug.cgi?id=1241172
195
	 * @return string
196
	 */
197
	private function isUsedTlsLibOutdated() {
198
		// Don't run check when:
199
		// 1. Server has `has_internet_connection` set to false
200
		// 2. AppStore AND S2S is disabled
201
		if(!$this->config->getSystemValue('has_internet_connection', true)) {
202
			return '';
203
		}
204
		if(!$this->config->getSystemValue('appstoreenabled', true)
205
			&& $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'no'
206
			&& $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes') === 'no') {
207
			return '';
208
		}
209
210
		$versionString = $this->getCurlVersion();
211
		if(isset($versionString['ssl_version'])) {
212
			$versionString = $versionString['ssl_version'];
213
		} else {
214
			return '';
215
		}
216
217
		$features = (string)$this->l10n->t('installing and updating apps via the app store or Federated Cloud Sharing');
218
		if(!$this->config->getSystemValue('appstoreenabled', true)) {
219
			$features = (string)$this->l10n->t('Federated Cloud Sharing');
220
		}
221
222
		// Check if at least OpenSSL after 1.01d or 1.0.2b
223
		if(strpos($versionString, 'OpenSSL/') === 0) {
224
			$majorVersion = substr($versionString, 8, 5);
225
			$patchRelease = substr($versionString, 13, 6);
226
227
			if(($majorVersion === '1.0.1' && ord($patchRelease) < ord('d')) ||
228
				($majorVersion === '1.0.2' && ord($patchRelease) < ord('b'))) {
229
				return (string) $this->l10n->t('cURL is using an outdated %s version (%s). Please update your operating system or features such as %s will not work reliably.', ['OpenSSL', $versionString, $features]);
230
			}
231
		}
232
233
		// Check if NSS and perform heuristic check
234
		if(strpos($versionString, 'NSS/') === 0) {
235
			try {
236
				$firstClient = $this->clientService->newClient();
237
				$firstClient->get('https://nextcloud.com/');
238
239
				$secondClient = $this->clientService->newClient();
240
				$secondClient->get('https://nextcloud.com/');
241
			} catch (ClientException $e) {
0 ignored issues
show
Bug introduced by
The class GuzzleHttp\Exception\ClientException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
242
				if($e->getResponse()->getStatusCode() === 400) {
243
					return (string) $this->l10n->t('cURL is using an outdated %s version (%s). Please update your operating system or features such as %s will not work reliably.', ['NSS', $versionString, $features]);
244
				}
245
			}
246
		}
247
248
		return '';
249
	}
250
251
	/**
252
	 * Whether the version is outdated
253
	 *
254
	 * @return bool
255
	 */
256
	protected function isPhpOutdated() {
257
		if (version_compare(PHP_VERSION, '7.0.0', '<')) {
258
			return true;
259
		}
260
261
		return false;
262
	}
263
264
	/**
265
	 * Whether the php version is still supported (at time of release)
266
	 * according to: https://secure.php.net/supported-versions.php
267
	 *
268
	 * @return array
269
	 */
270
	private function isPhpSupported() {
271
		return ['eol' => $this->isPhpOutdated(), 'version' => PHP_VERSION];
272
	}
273
274
	/**
275
	 * Check if the reverse proxy configuration is working as expected
276
	 *
277
	 * @return bool
278
	 */
279
	private function forwardedForHeadersWorking() {
280
		$trustedProxies = $this->config->getSystemValue('trusted_proxies', []);
281
		$remoteAddress = $this->request->getRemoteAddress();
282
283
		if (is_array($trustedProxies) && in_array($remoteAddress, $trustedProxies)) {
284
			return false;
285
		}
286
287
		// either not enabled or working correctly
288
		return true;
289
	}
290
291
	/**
292
	 * Checks if the correct memcache module for PHP is installed. Only
293
	 * fails if memcached is configured and the working module is not installed.
294
	 *
295
	 * @return bool
296
	 */
297
	private function isCorrectMemcachedPHPModuleInstalled() {
298
		if ($this->config->getSystemValue('memcache.distributed', null) !== '\OC\Memcache\Memcached') {
299
			return true;
300
		}
301
302
		// there are two different memcached modules for PHP
303
		// we only support memcached and not memcache
304
		// https://code.google.com/p/memcached/wiki/PHPClientComparison
305
		return !(!extension_loaded('memcached') && extension_loaded('memcache'));
306
	}
307
308
	/**
309
	 * Checks if set_time_limit is not disabled.
310
	 *
311
	 * @return bool
312
	 */
313
	private function isSettimelimitAvailable() {
314
		if (function_exists('set_time_limit')
315
			&& strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
316
			return true;
317
		}
318
319
		return false;
320
	}
321
322
	/**
323
	 * @return RedirectResponse
324
	 */
325
	public function rescanFailedIntegrityCheck() {
326
		$this->checker->runInstanceVerification();
327
		return new RedirectResponse(
328
			$this->urlGenerator->linkToRoute('settings.AdminSettings.index')
329
		);
330
	}
331
332
	/**
333
	 * @NoCSRFRequired
334
	 * @return DataResponse
335
	 */
336
	public function getFailedIntegrityCheckFiles() {
337
		if(!$this->checker->isCodeCheckEnforced()) {
338
			return new DataDisplayResponse('Integrity checker has been disabled. Integrity cannot be verified.');
339
		}
340
341
		$completeResults = $this->checker->getResults();
342
343
		if(!empty($completeResults)) {
344
			$formattedTextResponse = 'Technical information
345
=====================
346
The following list covers which files have failed the integrity check. Please read
347
the previous linked documentation to learn more about the errors and how to fix
348
them.
349
350
Results
351
=======
352
';
353
			foreach($completeResults as $context => $contextResult) {
354
				$formattedTextResponse .= "- $context\n";
355
356
				foreach($contextResult as $category => $result) {
357
					$formattedTextResponse .= "\t- $category\n";
358
					if($category !== 'EXCEPTION') {
359
						foreach ($result as $key => $results) {
360
							$formattedTextResponse .= "\t\t- $key\n";
361
						}
362
					} else {
363
						foreach ($result as $key => $results) {
364
							$formattedTextResponse .= "\t\t- $results\n";
365
						}
366
					}
367
368
				}
369
			}
370
371
			$formattedTextResponse .= '
372
Raw output
373
==========
374
';
375
			$formattedTextResponse .= print_r($completeResults, true);
376
		} else {
377
			$formattedTextResponse = 'No errors have been found.';
378
		}
379
380
381
		$response = new DataDisplayResponse(
382
			$formattedTextResponse,
383
			Http::STATUS_OK,
384
			[
385
				'Content-Type' => 'text/plain',
386
			]
387
		);
388
389
		return $response;
390
	}
391
392
	/**
393
	 * Checks whether a PHP opcache is properly set up
394
	 * @return bool
395
	 */
396
	protected function isOpcacheProperlySetup() {
397
		$iniWrapper = new IniGetWrapper();
398
399
		$isOpcacheProperlySetUp = true;
400
401
		if(!$iniWrapper->getBool('opcache.enable')) {
402
			$isOpcacheProperlySetUp = false;
403
		}
404
405
		if(!$iniWrapper->getBool('opcache.save_comments')) {
406
			$isOpcacheProperlySetUp = false;
407
		}
408
409
		if(!$iniWrapper->getBool('opcache.enable_cli')) {
410
			$isOpcacheProperlySetUp = false;
411
		}
412
413
		if($iniWrapper->getNumeric('opcache.max_accelerated_files') < 10000) {
414
			$isOpcacheProperlySetUp = false;
415
		}
416
417
		if($iniWrapper->getNumeric('opcache.memory_consumption') < 128) {
418
			$isOpcacheProperlySetUp = false;
419
		}
420
421
		if($iniWrapper->getNumeric('opcache.interned_strings_buffer') < 8) {
422
			$isOpcacheProperlySetUp = false;
423
		}
424
425
		return $isOpcacheProperlySetUp;
426
	}
427
428
	/**
429
	 * Check if the required FreeType functions are present
430
	 * @return bool
431
	 */
432
	protected function hasFreeTypeSupport() {
433
		return function_exists('imagettfbbox') && function_exists('imagettftext');
434
	}
435
436
	protected function hasMissingIndexes(): array {
437
		$indexInfo = new MissingIndexInformation();
438
		// Dispatch event so apps can also hint for pending index updates if needed
439
		$event = new GenericEvent($indexInfo);
440
		$this->dispatcher->dispatch(IDBConnection::CHECK_MISSING_INDEXES_EVENT, $event);
441
442
		return $indexInfo->getListOfMissingIndexes();
443
	}
444
445
	/**
446
	 * warn if outdated version of a memcache module is used
447
	 */
448
	protected function getOutdatedCaches(): array {
449
		$caches = [
450
			'apcu'	=> ['name' => 'APCu', 'version' => '4.0.6'],
451
			'redis'	=> ['name' => 'Redis', 'version' => '2.2.5'],
452
		];
453
		$outdatedCaches = [];
454
		foreach ($caches as $php_module => $data) {
455
			$isOutdated = extension_loaded($php_module) && version_compare(phpversion($php_module), $data['version'], '<');
456
			if ($isOutdated) {
457
				$outdatedCaches[] = $data;
458
			}
459
		}
460
461
		return $outdatedCaches;
462
	}
463
464
	protected function isSqliteUsed() {
465
		return strpos($this->config->getSystemValue('dbtype'), 'sqlite') !== false;
466
	}
467
468
	protected function isReadOnlyConfig(): bool {
469
		return \OC_Helper::isReadOnlyConfigEnabled();
470
	}
471
472
	protected function hasValidTransactionIsolationLevel(): bool {
473
		try {
474
			if ($this->db->getDatabasePlatform() instanceof SqlitePlatform) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Platforms\SqlitePlatform does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
475
				return true;
476
			}
477
478
			return $this->db->getTransactionIsolation() === Connection::TRANSACTION_READ_COMMITTED;
479
		} catch (DBALException $e) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\DBALException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
480
			// ignore
481
		}
482
483
		return true;
484
	}
485
486
	protected function hasFileinfoInstalled(): bool {
487
		return \OC_Util::fileInfoLoaded();
488
	}
489
490
	protected function hasWorkingFileLocking(): bool {
491
		return !($this->lockingProvider instanceof NoopLockingProvider);
492
	}
493
494
	protected function getSuggestedOverwriteCliURL(): string {
495
		$suggestedOverwriteCliUrl = '';
496
		if ($this->config->getSystemValue('overwrite.cli.url', '') === '') {
497
			$suggestedOverwriteCliUrl = $this->request->getServerProtocol() . '://' . $this->request->getInsecureServerHost() . \OC::$WEBROOT;
498
			if (!$this->config->getSystemValue('config_is_read_only', false)) {
499
				// Set the overwrite URL when it was not set yet.
500
				$this->config->setSystemValue('overwrite.cli.url', $suggestedOverwriteCliUrl);
501
				$suggestedOverwriteCliUrl = '';
502
			}
503
		}
504
		return $suggestedOverwriteCliUrl;
505
	}
506
507
	protected function getLastCronInfo(): array {
508
		$lastCronRun = $this->config->getAppValue('core', 'lastcron', 0);
509
		return [
510
			'diffInSeconds' => time() - $lastCronRun,
511
			'relativeTime' => $this->dateTimeFormatter->formatTimeSpan($lastCronRun),
512
			'backgroundJobsUrl' => $this->urlGenerator->linkToRoute('settings.AdminSettings.index', ['section' => 'server']) . '#backgroundjobs',
513
		];
514
	}
515
516
	protected function getCronErrors() {
517
		$errors = json_decode($this->config->getAppValue('core', 'cronErrors', ''), true);
518
519
		if (is_array($errors)) {
520
			return $errors;
521
		}
522
523
		return [];
524
	}
525
526
	protected function isPhpMailerUsed(): bool {
527
		return $this->config->getSystemValue('mail_smtpmode', 'php') === 'php';
528
	}
529
530
	/**
531
	 * @return DataResponse
532
	 */
533
	public function check() {
534
		return new DataResponse(
535
			[
536
				'isGetenvServerWorking' => !empty(getenv('PATH')),
537
				'isReadOnlyConfig' => $this->isReadOnlyConfig(),
538
				'hasValidTransactionIsolationLevel' => $this->hasValidTransactionIsolationLevel(),
539
				'outdatedCaches' => $this->getOutdatedCaches(),
540
				'hasFileinfoInstalled' => $this->hasFileinfoInstalled(),
541
				'hasWorkingFileLocking' => $this->hasWorkingFileLocking(),
542
				'suggestedOverwriteCliURL' => $this->getSuggestedOverwriteCliURL(),
543
				'cronInfo' => $this->getLastCronInfo(),
544
				'cronErrors' => $this->getCronErrors(),
545
				'serverHasInternetConnection' => $this->isInternetConnectionWorking(),
546
				'isMemcacheConfigured' => $this->isMemcacheConfigured(),
547
				'memcacheDocs' => $this->urlGenerator->linkToDocs('admin-performance'),
548
				'isUrandomAvailable' => $this->isUrandomAvailable(),
549
				'securityDocs' => $this->urlGenerator->linkToDocs('admin-security'),
550
				'isUsedTlsLibOutdated' => $this->isUsedTlsLibOutdated(),
551
				'phpSupported' => $this->isPhpSupported(),
552
				'forwardedForHeadersWorking' => $this->forwardedForHeadersWorking(),
553
				'reverseProxyDocs' => $this->urlGenerator->linkToDocs('admin-reverse-proxy'),
554
				'isCorrectMemcachedPHPModuleInstalled' => $this->isCorrectMemcachedPHPModuleInstalled(),
555
				'hasPassedCodeIntegrityCheck' => $this->checker->hasPassedCheck(),
556
				'codeIntegrityCheckerDocumentation' => $this->urlGenerator->linkToDocs('admin-code-integrity'),
557
				'isOpcacheProperlySetup' => $this->isOpcacheProperlySetup(),
558
				'phpOpcacheDocumentation' => $this->urlGenerator->linkToDocs('admin-php-opcache'),
559
				'isSettimelimitAvailable' => $this->isSettimelimitAvailable(),
560
				'hasFreeTypeSupport' => $this->hasFreeTypeSupport(),
561
				'missingIndexes' => $this->hasMissingIndexes(),
562
				'isSqliteUsed' => $this->isSqliteUsed(),
563
				'databaseConversionDocumentation' => $this->urlGenerator->linkToDocs('admin-db-conversion'),
564
				'isPhpMailerUsed' => $this->isPhpMailerUsed(),
565
				'mailSettingsDocumentation' => $this->urlGenerator->getAbsoluteURL('index.php/settings/admin')
566
			]
567
		);
568
	}
569
}
570