Completed
Push — stable13 ( 30dea9...761dae )
by Morris
36:15 queued 15:31
created
apps/user_ldap/lib/Connection.php 1 patch
Indentation   +612 added lines, -612 removed lines patch added patch discarded remove patch
@@ -62,617 +62,617 @@
 block discarded – undo
62 62
  * @property string ldapEmailAttribute
63 63
  */
64 64
 class Connection extends LDAPUtility {
65
-	private $ldapConnectionRes = null;
66
-	private $configPrefix;
67
-	private $configID;
68
-	private $configured = false;
69
-	private $hasPagedResultSupport = true;
70
-	//whether connection should be kept on __destruct
71
-	private $dontDestruct = false;
72
-
73
-	/**
74
-	 * @var bool runtime flag that indicates whether supported primary groups are available
75
-	 */
76
-	public $hasPrimaryGroups = true;
77
-
78
-	/**
79
-	 * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
80
-	 */
81
-	public $hasGidNumber = true;
82
-
83
-	//cache handler
84
-	protected $cache;
85
-
86
-	/** @var Configuration settings handler **/
87
-	protected $configuration;
88
-
89
-	protected $doNotValidate = false;
90
-
91
-	protected $ignoreValidation = false;
92
-
93
-	protected $bindResult = [];
94
-
95
-	/**
96
-	 * Constructor
97
-	 * @param ILDAPWrapper $ldap
98
-	 * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
99
-	 * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
100
-	 */
101
-	public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
102
-		parent::__construct($ldap);
103
-		$this->configPrefix = $configPrefix;
104
-		$this->configID = $configID;
105
-		$this->configuration = new Configuration($configPrefix,
106
-												 !is_null($configID));
107
-		$memcache = \OC::$server->getMemCacheFactory();
108
-		if($memcache->isAvailable()) {
109
-			$this->cache = $memcache->createDistributed();
110
-		}
111
-		$helper = new Helper(\OC::$server->getConfig());
112
-		$this->doNotValidate = !in_array($this->configPrefix,
113
-			$helper->getServerConfigurationPrefixes());
114
-		$this->hasPagedResultSupport =
115
-			intval($this->configuration->ldapPagingSize) !== 0
116
-			|| $this->ldap->hasPagedResultSupport();
117
-	}
118
-
119
-	public function __destruct() {
120
-		if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
121
-			@$this->ldap->unbind($this->ldapConnectionRes);
122
-		}
123
-		$this->bindResult = [];
124
-	}
125
-
126
-	/**
127
-	 * defines behaviour when the instance is cloned
128
-	 */
129
-	public function __clone() {
130
-		$this->configuration = new Configuration($this->configPrefix,
131
-												 !is_null($this->configID));
132
-		if(count($this->bindResult) !== 0 && $this->bindResult['result'] === true) {
133
-			$this->bindResult = [];
134
-		}
135
-		$this->ldapConnectionRes = null;
136
-		$this->dontDestruct = true;
137
-	}
138
-
139
-	/**
140
-	 * @param string $name
141
-	 * @return bool|mixed
142
-	 */
143
-	public function __get($name) {
144
-		if(!$this->configured) {
145
-			$this->readConfiguration();
146
-		}
147
-
148
-		if($name === 'hasPagedResultSupport') {
149
-			return $this->hasPagedResultSupport;
150
-		}
151
-
152
-		return $this->configuration->$name;
153
-	}
154
-
155
-	/**
156
-	 * @param string $name
157
-	 * @param mixed $value
158
-	 */
159
-	public function __set($name, $value) {
160
-		$this->doNotValidate = false;
161
-		$before = $this->configuration->$name;
162
-		$this->configuration->$name = $value;
163
-		$after = $this->configuration->$name;
164
-		if($before !== $after) {
165
-			if ($this->configID !== '' && $this->configID !== null) {
166
-				$this->configuration->saveConfiguration();
167
-			}
168
-			$this->validateConfiguration();
169
-		}
170
-	}
171
-
172
-	/**
173
-	 * @param string $rule
174
-	 * @return array
175
-	 * @throws \RuntimeException
176
-	 */
177
-	public function resolveRule($rule) {
178
-		return $this->configuration->resolveRule($rule);
179
-	}
180
-
181
-	/**
182
-	 * sets whether the result of the configuration validation shall
183
-	 * be ignored when establishing the connection. Used by the Wizard
184
-	 * in early configuration state.
185
-	 * @param bool $state
186
-	 */
187
-	public function setIgnoreValidation($state) {
188
-		$this->ignoreValidation = (bool)$state;
189
-	}
190
-
191
-	/**
192
-	 * initializes the LDAP backend
193
-	 * @param bool $force read the config settings no matter what
194
-	 */
195
-	public function init($force = false) {
196
-		$this->readConfiguration($force);
197
-		$this->establishConnection();
198
-	}
199
-
200
-	/**
201
-	 * Returns the LDAP handler
202
-	 */
203
-	public function getConnectionResource() {
204
-		if(!$this->ldapConnectionRes) {
205
-			$this->init();
206
-		} else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
207
-			$this->ldapConnectionRes = null;
208
-			$this->establishConnection();
209
-		}
210
-		if(is_null($this->ldapConnectionRes)) {
211
-			\OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, \OCP\Util::ERROR);
212
-			throw new ServerNotAvailableException('Connection to LDAP server could not be established');
213
-		}
214
-		return $this->ldapConnectionRes;
215
-	}
216
-
217
-	/**
218
-	 * resets the connection resource
219
-	 */
220
-	public function resetConnectionResource() {
221
-		if(!is_null($this->ldapConnectionRes)) {
222
-			@$this->ldap->unbind($this->ldapConnectionRes);
223
-			$this->ldapConnectionRes = null;
224
-			$this->bindResult = [];
225
-		}
226
-	}
227
-
228
-	/**
229
-	 * @param string|null $key
230
-	 * @return string
231
-	 */
232
-	private function getCacheKey($key) {
233
-		$prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
234
-		if(is_null($key)) {
235
-			return $prefix;
236
-		}
237
-		return $prefix.hash('sha256', $key);
238
-	}
239
-
240
-	/**
241
-	 * @param string $key
242
-	 * @return mixed|null
243
-	 */
244
-	public function getFromCache($key) {
245
-		if(!$this->configured) {
246
-			$this->readConfiguration();
247
-		}
248
-		if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
249
-			return null;
250
-		}
251
-		$key = $this->getCacheKey($key);
252
-
253
-		return json_decode(base64_decode($this->cache->get($key)), true);
254
-	}
255
-
256
-	/**
257
-	 * @param string $key
258
-	 * @param mixed $value
259
-	 *
260
-	 * @return string
261
-	 */
262
-	public function writeToCache($key, $value) {
263
-		if(!$this->configured) {
264
-			$this->readConfiguration();
265
-		}
266
-		if(is_null($this->cache)
267
-			|| !$this->configuration->ldapCacheTTL
268
-			|| !$this->configuration->ldapConfigurationActive) {
269
-			return null;
270
-		}
271
-		$key   = $this->getCacheKey($key);
272
-		$value = base64_encode(json_encode($value));
273
-		$this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
274
-	}
275
-
276
-	public function clearCache() {
277
-		if(!is_null($this->cache)) {
278
-			$this->cache->clear($this->getCacheKey(null));
279
-		}
280
-	}
281
-
282
-	/**
283
-	 * Caches the general LDAP configuration.
284
-	 * @param bool $force optional. true, if the re-read should be forced. defaults
285
-	 * to false.
286
-	 * @return null
287
-	 */
288
-	private function readConfiguration($force = false) {
289
-		if((!$this->configured || $force) && !is_null($this->configID)) {
290
-			$this->configuration->readConfiguration();
291
-			$this->configured = $this->validateConfiguration();
292
-		}
293
-	}
294
-
295
-	/**
296
-	 * set LDAP configuration with values delivered by an array, not read from configuration
297
-	 * @param array $config array that holds the config parameters in an associated array
298
-	 * @param array &$setParameters optional; array where the set fields will be given to
299
-	 * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
300
-	 */
301
-	public function setConfiguration($config, &$setParameters = null) {
302
-		if(is_null($setParameters)) {
303
-			$setParameters = array();
304
-		}
305
-		$this->doNotValidate = false;
306
-		$this->configuration->setConfiguration($config, $setParameters);
307
-		if(count($setParameters) > 0) {
308
-			$this->configured = $this->validateConfiguration();
309
-		}
310
-
311
-
312
-		return $this->configured;
313
-	}
314
-
315
-	/**
316
-	 * saves the current Configuration in the database and empties the
317
-	 * cache
318
-	 * @return null
319
-	 */
320
-	public function saveConfiguration() {
321
-		$this->configuration->saveConfiguration();
322
-		$this->clearCache();
323
-	}
324
-
325
-	/**
326
-	 * get the current LDAP configuration
327
-	 * @return array
328
-	 */
329
-	public function getConfiguration() {
330
-		$this->readConfiguration();
331
-		$config = $this->configuration->getConfiguration();
332
-		$cta = $this->configuration->getConfigTranslationArray();
333
-		$result = array();
334
-		foreach($cta as $dbkey => $configkey) {
335
-			switch($configkey) {
336
-				case 'homeFolderNamingRule':
337
-					if(strpos($config[$configkey], 'attr:') === 0) {
338
-						$result[$dbkey] = substr($config[$configkey], 5);
339
-					} else {
340
-						$result[$dbkey] = '';
341
-					}
342
-					break;
343
-				case 'ldapBase':
344
-				case 'ldapBaseUsers':
345
-				case 'ldapBaseGroups':
346
-				case 'ldapAttributesForUserSearch':
347
-				case 'ldapAttributesForGroupSearch':
348
-					if(is_array($config[$configkey])) {
349
-						$result[$dbkey] = implode("\n", $config[$configkey]);
350
-						break;
351
-					} //else follows default
352
-				default:
353
-					$result[$dbkey] = $config[$configkey];
354
-			}
355
-		}
356
-		return $result;
357
-	}
358
-
359
-	private function doSoftValidation() {
360
-		//if User or Group Base are not set, take over Base DN setting
361
-		foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
362
-			$val = $this->configuration->$keyBase;
363
-			if(empty($val)) {
364
-				$this->configuration->$keyBase = $this->configuration->ldapBase;
365
-			}
366
-		}
367
-
368
-		foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
369
-					  'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
370
-				as $expertSetting => $effectiveSetting) {
371
-			$uuidOverride = $this->configuration->$expertSetting;
372
-			if(!empty($uuidOverride)) {
373
-				$this->configuration->$effectiveSetting = $uuidOverride;
374
-			} else {
375
-				$uuidAttributes = Access::UUID_ATTRIBUTES;
376
-				array_unshift($uuidAttributes, 'auto');
377
-				if(!in_array($this->configuration->$effectiveSetting,
378
-							$uuidAttributes)
379
-					&& (!is_null($this->configID))) {
380
-					$this->configuration->$effectiveSetting = 'auto';
381
-					$this->configuration->saveConfiguration();
382
-					\OCP\Util::writeLog('user_ldap',
383
-										'Illegal value for the '.
384
-										$effectiveSetting.', '.'reset to '.
385
-										'autodetect.', \OCP\Util::INFO);
386
-				}
387
-
388
-			}
389
-		}
390
-
391
-		$backupPort = intval($this->configuration->ldapBackupPort);
392
-		if ($backupPort <= 0) {
393
-			$this->configuration->backupPort = $this->configuration->ldapPort;
394
-		}
395
-
396
-		//make sure empty search attributes are saved as simple, empty array
397
-		$saKeys = array('ldapAttributesForUserSearch',
398
-						'ldapAttributesForGroupSearch');
399
-		foreach($saKeys as $key) {
400
-			$val = $this->configuration->$key;
401
-			if(is_array($val) && count($val) === 1 && empty($val[0])) {
402
-				$this->configuration->$key = array();
403
-			}
404
-		}
405
-
406
-		if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
407
-			&& $this->configuration->ldapTLS) {
408
-			$this->configuration->ldapTLS = false;
409
-			\OCP\Util::writeLog('user_ldap',
410
-								'LDAPS (already using secure connection) and '.
411
-								'TLS do not work together. Switched off TLS.',
412
-								\OCP\Util::INFO);
413
-		}
414
-	}
415
-
416
-	/**
417
-	 * @return bool
418
-	 */
419
-	private function doCriticalValidation() {
420
-		$configurationOK = true;
421
-		$errorStr = 'Configuration Error (prefix '.
422
-					strval($this->configPrefix).'): ';
423
-
424
-		//options that shall not be empty
425
-		$options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
426
-						 'ldapGroupDisplayName', 'ldapLoginFilter');
427
-		foreach($options as $key) {
428
-			$val = $this->configuration->$key;
429
-			if(empty($val)) {
430
-				switch($key) {
431
-					case 'ldapHost':
432
-						$subj = 'LDAP Host';
433
-						break;
434
-					case 'ldapPort':
435
-						$subj = 'LDAP Port';
436
-						break;
437
-					case 'ldapUserDisplayName':
438
-						$subj = 'LDAP User Display Name';
439
-						break;
440
-					case 'ldapGroupDisplayName':
441
-						$subj = 'LDAP Group Display Name';
442
-						break;
443
-					case 'ldapLoginFilter':
444
-						$subj = 'LDAP Login Filter';
445
-						break;
446
-					default:
447
-						$subj = $key;
448
-						break;
449
-				}
450
-				$configurationOK = false;
451
-				\OCP\Util::writeLog('user_ldap',
452
-									$errorStr.'No '.$subj.' given!',
453
-									\OCP\Util::WARN);
454
-			}
455
-		}
456
-
457
-		//combinations
458
-		$agent = $this->configuration->ldapAgentName;
459
-		$pwd = $this->configuration->ldapAgentPassword;
460
-		if (
461
-			($agent === ''  && $pwd !== '')
462
-			|| ($agent !== '' && $pwd === '')
463
-		) {
464
-			\OCP\Util::writeLog('user_ldap',
465
-								$errorStr.'either no password is given for the '.
466
-								'user agent or a password is given, but not an '.
467
-								'LDAP agent.',
468
-				\OCP\Util::WARN);
469
-			$configurationOK = false;
470
-		}
471
-
472
-		$base = $this->configuration->ldapBase;
473
-		$baseUsers = $this->configuration->ldapBaseUsers;
474
-		$baseGroups = $this->configuration->ldapBaseGroups;
475
-
476
-		if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
477
-			\OCP\Util::writeLog('user_ldap',
478
-								$errorStr.'Not a single Base DN given.',
479
-								\OCP\Util::WARN);
480
-			$configurationOK = false;
481
-		}
482
-
483
-		if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
484
-		   === false) {
485
-			\OCP\Util::writeLog('user_ldap',
486
-								$errorStr.'login filter does not contain %uid '.
487
-								'place holder.',
488
-								\OCP\Util::WARN);
489
-			$configurationOK = false;
490
-		}
491
-
492
-		return $configurationOK;
493
-	}
494
-
495
-	/**
496
-	 * Validates the user specified configuration
497
-	 * @return bool true if configuration seems OK, false otherwise
498
-	 */
499
-	private function validateConfiguration() {
500
-
501
-		if($this->doNotValidate) {
502
-			//don't do a validation if it is a new configuration with pure
503
-			//default values. Will be allowed on changes via __set or
504
-			//setConfiguration
505
-			return false;
506
-		}
507
-
508
-		// first step: "soft" checks: settings that are not really
509
-		// necessary, but advisable. If left empty, give an info message
510
-		$this->doSoftValidation();
511
-
512
-		//second step: critical checks. If left empty or filled wrong, mark as
513
-		//not configured and give a warning.
514
-		return $this->doCriticalValidation();
515
-	}
516
-
517
-
518
-	/**
519
-	 * Connects and Binds to LDAP
520
-	 *
521
-	 * @throws ServerNotAvailableException
522
-	 */
523
-	private function establishConnection() {
524
-		if(!$this->configuration->ldapConfigurationActive) {
525
-			return null;
526
-		}
527
-		static $phpLDAPinstalled = true;
528
-		if(!$phpLDAPinstalled) {
529
-			return false;
530
-		}
531
-		if(!$this->ignoreValidation && !$this->configured) {
532
-			\OCP\Util::writeLog('user_ldap',
533
-								'Configuration is invalid, cannot connect',
534
-								\OCP\Util::WARN);
535
-			return false;
536
-		}
537
-		if(!$this->ldapConnectionRes) {
538
-			if(!$this->ldap->areLDAPFunctionsAvailable()) {
539
-				$phpLDAPinstalled = false;
540
-				\OCP\Util::writeLog('user_ldap',
541
-									'function ldap_connect is not available. Make '.
542
-									'sure that the PHP ldap module is installed.',
543
-									\OCP\Util::ERROR);
544
-
545
-				return false;
546
-			}
547
-			if($this->configuration->turnOffCertCheck) {
548
-				if(putenv('LDAPTLS_REQCERT=never')) {
549
-					\OCP\Util::writeLog('user_ldap',
550
-						'Turned off SSL certificate validation successfully.',
551
-						\OCP\Util::DEBUG);
552
-				} else {
553
-					\OCP\Util::writeLog('user_ldap',
554
-										'Could not turn off SSL certificate validation.',
555
-										\OCP\Util::WARN);
556
-				}
557
-			}
558
-
559
-			$isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
560
-				|| $this->getFromCache('overrideMainServer'));
561
-			$isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
562
-			$bindStatus = false;
563
-			try {
564
-				if (!$isOverrideMainServer) {
565
-					$this->doConnect($this->configuration->ldapHost,
566
-						$this->configuration->ldapPort);
567
-					return $this->bind();
568
-				}
569
-			} catch (ServerNotAvailableException $e) {
570
-				if(!$isBackupHost) {
571
-					throw $e;
572
-				}
573
-			}
574
-
575
-			//if LDAP server is not reachable, try the Backup (Replica!) Server
576
-			if($isBackupHost || $isOverrideMainServer) {
577
-				$this->doConnect($this->configuration->ldapBackupHost,
578
-								 $this->configuration->ldapBackupPort);
579
-				$this->bindResult = [];
580
-				$bindStatus = $this->bind();
581
-				$error = $this->ldap->isResource($this->ldapConnectionRes) ?
582
-					$this->ldap->errno($this->ldapConnectionRes) : -1;
583
-				if($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
584
-					//when bind to backup server succeeded and failed to main server,
585
-					//skip contacting him until next cache refresh
586
-					$this->writeToCache('overrideMainServer', true);
587
-				}
588
-			}
589
-
590
-			return $bindStatus;
591
-		}
592
-		return null;
593
-	}
594
-
595
-	/**
596
-	 * @param string $host
597
-	 * @param string $port
598
-	 * @return bool
599
-	 * @throws \OC\ServerNotAvailableException
600
-	 */
601
-	private function doConnect($host, $port) {
602
-		if ($host === '') {
603
-			return false;
604
-		}
605
-
606
-		$this->ldapConnectionRes = $this->ldap->connect($host, $port);
607
-
608
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
609
-			throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
610
-		}
611
-
612
-		if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
613
-			throw new ServerNotAvailableException('Could not disable LDAP referrals.');
614
-		}
615
-
616
-		if($this->configuration->ldapTLS) {
617
-			if(!$this->ldap->startTls($this->ldapConnectionRes)) {
618
-				throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
619
-			}
620
-		}
621
-
622
-		return true;
623
-	}
624
-
625
-	/**
626
-	 * Binds to LDAP
627
-	 */
628
-	public function bind() {
629
-		if(!$this->configuration->ldapConfigurationActive) {
630
-			return false;
631
-		}
632
-		$cr = $this->ldapConnectionRes;
633
-		if(!$this->ldap->isResource($cr)) {
634
-			$cr = $this->getConnectionResource();
635
-		}
636
-
637
-		if(
638
-			count($this->bindResult) !== 0
639
-			&& $this->bindResult['dn'] === $this->configuration->ldapAgentName
640
-			&& \OC::$server->getHasher()->verify(
641
-				$this->configPrefix . $this->configuration->ldapAgentPassword,
642
-				$this->bindResult['hash']
643
-			)
644
-		) {
645
-			// don't attempt to bind again with the same data as before
646
-			// bind might have been invoked via getConnectionResource(),
647
-			// but we need results specifically for e.g. user login
648
-			return $this->bindResult['result'];
649
-		}
650
-
651
-		$ldapLogin = @$this->ldap->bind($cr,
652
-										$this->configuration->ldapAgentName,
653
-										$this->configuration->ldapAgentPassword);
654
-
655
-		$this->bindResult = [
656
-			'dn' => $this->configuration->ldapAgentName,
657
-			'hash' => \OC::$server->getHasher()->hash($this->configPrefix . $this->configuration->ldapAgentPassword),
658
-			'result' => $ldapLogin,
659
-		];
660
-
661
-		if(!$ldapLogin) {
662
-			$errno = $this->ldap->errno($cr);
663
-
664
-			\OCP\Util::writeLog('user_ldap',
665
-				'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
666
-				\OCP\Util::WARN);
667
-
668
-			// Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
669
-			if($errno !== 0x00 && $errno !== 0x31) {
670
-				$this->ldapConnectionRes = null;
671
-			}
672
-
673
-			return false;
674
-		}
675
-		return true;
676
-	}
65
+    private $ldapConnectionRes = null;
66
+    private $configPrefix;
67
+    private $configID;
68
+    private $configured = false;
69
+    private $hasPagedResultSupport = true;
70
+    //whether connection should be kept on __destruct
71
+    private $dontDestruct = false;
72
+
73
+    /**
74
+     * @var bool runtime flag that indicates whether supported primary groups are available
75
+     */
76
+    public $hasPrimaryGroups = true;
77
+
78
+    /**
79
+     * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
80
+     */
81
+    public $hasGidNumber = true;
82
+
83
+    //cache handler
84
+    protected $cache;
85
+
86
+    /** @var Configuration settings handler **/
87
+    protected $configuration;
88
+
89
+    protected $doNotValidate = false;
90
+
91
+    protected $ignoreValidation = false;
92
+
93
+    protected $bindResult = [];
94
+
95
+    /**
96
+     * Constructor
97
+     * @param ILDAPWrapper $ldap
98
+     * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
99
+     * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
100
+     */
101
+    public function __construct(ILDAPWrapper $ldap, $configPrefix = '', $configID = 'user_ldap') {
102
+        parent::__construct($ldap);
103
+        $this->configPrefix = $configPrefix;
104
+        $this->configID = $configID;
105
+        $this->configuration = new Configuration($configPrefix,
106
+                                                    !is_null($configID));
107
+        $memcache = \OC::$server->getMemCacheFactory();
108
+        if($memcache->isAvailable()) {
109
+            $this->cache = $memcache->createDistributed();
110
+        }
111
+        $helper = new Helper(\OC::$server->getConfig());
112
+        $this->doNotValidate = !in_array($this->configPrefix,
113
+            $helper->getServerConfigurationPrefixes());
114
+        $this->hasPagedResultSupport =
115
+            intval($this->configuration->ldapPagingSize) !== 0
116
+            || $this->ldap->hasPagedResultSupport();
117
+    }
118
+
119
+    public function __destruct() {
120
+        if(!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
121
+            @$this->ldap->unbind($this->ldapConnectionRes);
122
+        }
123
+        $this->bindResult = [];
124
+    }
125
+
126
+    /**
127
+     * defines behaviour when the instance is cloned
128
+     */
129
+    public function __clone() {
130
+        $this->configuration = new Configuration($this->configPrefix,
131
+                                                    !is_null($this->configID));
132
+        if(count($this->bindResult) !== 0 && $this->bindResult['result'] === true) {
133
+            $this->bindResult = [];
134
+        }
135
+        $this->ldapConnectionRes = null;
136
+        $this->dontDestruct = true;
137
+    }
138
+
139
+    /**
140
+     * @param string $name
141
+     * @return bool|mixed
142
+     */
143
+    public function __get($name) {
144
+        if(!$this->configured) {
145
+            $this->readConfiguration();
146
+        }
147
+
148
+        if($name === 'hasPagedResultSupport') {
149
+            return $this->hasPagedResultSupport;
150
+        }
151
+
152
+        return $this->configuration->$name;
153
+    }
154
+
155
+    /**
156
+     * @param string $name
157
+     * @param mixed $value
158
+     */
159
+    public function __set($name, $value) {
160
+        $this->doNotValidate = false;
161
+        $before = $this->configuration->$name;
162
+        $this->configuration->$name = $value;
163
+        $after = $this->configuration->$name;
164
+        if($before !== $after) {
165
+            if ($this->configID !== '' && $this->configID !== null) {
166
+                $this->configuration->saveConfiguration();
167
+            }
168
+            $this->validateConfiguration();
169
+        }
170
+    }
171
+
172
+    /**
173
+     * @param string $rule
174
+     * @return array
175
+     * @throws \RuntimeException
176
+     */
177
+    public function resolveRule($rule) {
178
+        return $this->configuration->resolveRule($rule);
179
+    }
180
+
181
+    /**
182
+     * sets whether the result of the configuration validation shall
183
+     * be ignored when establishing the connection. Used by the Wizard
184
+     * in early configuration state.
185
+     * @param bool $state
186
+     */
187
+    public function setIgnoreValidation($state) {
188
+        $this->ignoreValidation = (bool)$state;
189
+    }
190
+
191
+    /**
192
+     * initializes the LDAP backend
193
+     * @param bool $force read the config settings no matter what
194
+     */
195
+    public function init($force = false) {
196
+        $this->readConfiguration($force);
197
+        $this->establishConnection();
198
+    }
199
+
200
+    /**
201
+     * Returns the LDAP handler
202
+     */
203
+    public function getConnectionResource() {
204
+        if(!$this->ldapConnectionRes) {
205
+            $this->init();
206
+        } else if(!$this->ldap->isResource($this->ldapConnectionRes)) {
207
+            $this->ldapConnectionRes = null;
208
+            $this->establishConnection();
209
+        }
210
+        if(is_null($this->ldapConnectionRes)) {
211
+            \OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, \OCP\Util::ERROR);
212
+            throw new ServerNotAvailableException('Connection to LDAP server could not be established');
213
+        }
214
+        return $this->ldapConnectionRes;
215
+    }
216
+
217
+    /**
218
+     * resets the connection resource
219
+     */
220
+    public function resetConnectionResource() {
221
+        if(!is_null($this->ldapConnectionRes)) {
222
+            @$this->ldap->unbind($this->ldapConnectionRes);
223
+            $this->ldapConnectionRes = null;
224
+            $this->bindResult = [];
225
+        }
226
+    }
227
+
228
+    /**
229
+     * @param string|null $key
230
+     * @return string
231
+     */
232
+    private function getCacheKey($key) {
233
+        $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
234
+        if(is_null($key)) {
235
+            return $prefix;
236
+        }
237
+        return $prefix.hash('sha256', $key);
238
+    }
239
+
240
+    /**
241
+     * @param string $key
242
+     * @return mixed|null
243
+     */
244
+    public function getFromCache($key) {
245
+        if(!$this->configured) {
246
+            $this->readConfiguration();
247
+        }
248
+        if(is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
249
+            return null;
250
+        }
251
+        $key = $this->getCacheKey($key);
252
+
253
+        return json_decode(base64_decode($this->cache->get($key)), true);
254
+    }
255
+
256
+    /**
257
+     * @param string $key
258
+     * @param mixed $value
259
+     *
260
+     * @return string
261
+     */
262
+    public function writeToCache($key, $value) {
263
+        if(!$this->configured) {
264
+            $this->readConfiguration();
265
+        }
266
+        if(is_null($this->cache)
267
+            || !$this->configuration->ldapCacheTTL
268
+            || !$this->configuration->ldapConfigurationActive) {
269
+            return null;
270
+        }
271
+        $key   = $this->getCacheKey($key);
272
+        $value = base64_encode(json_encode($value));
273
+        $this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
274
+    }
275
+
276
+    public function clearCache() {
277
+        if(!is_null($this->cache)) {
278
+            $this->cache->clear($this->getCacheKey(null));
279
+        }
280
+    }
281
+
282
+    /**
283
+     * Caches the general LDAP configuration.
284
+     * @param bool $force optional. true, if the re-read should be forced. defaults
285
+     * to false.
286
+     * @return null
287
+     */
288
+    private function readConfiguration($force = false) {
289
+        if((!$this->configured || $force) && !is_null($this->configID)) {
290
+            $this->configuration->readConfiguration();
291
+            $this->configured = $this->validateConfiguration();
292
+        }
293
+    }
294
+
295
+    /**
296
+     * set LDAP configuration with values delivered by an array, not read from configuration
297
+     * @param array $config array that holds the config parameters in an associated array
298
+     * @param array &$setParameters optional; array where the set fields will be given to
299
+     * @return boolean true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
300
+     */
301
+    public function setConfiguration($config, &$setParameters = null) {
302
+        if(is_null($setParameters)) {
303
+            $setParameters = array();
304
+        }
305
+        $this->doNotValidate = false;
306
+        $this->configuration->setConfiguration($config, $setParameters);
307
+        if(count($setParameters) > 0) {
308
+            $this->configured = $this->validateConfiguration();
309
+        }
310
+
311
+
312
+        return $this->configured;
313
+    }
314
+
315
+    /**
316
+     * saves the current Configuration in the database and empties the
317
+     * cache
318
+     * @return null
319
+     */
320
+    public function saveConfiguration() {
321
+        $this->configuration->saveConfiguration();
322
+        $this->clearCache();
323
+    }
324
+
325
+    /**
326
+     * get the current LDAP configuration
327
+     * @return array
328
+     */
329
+    public function getConfiguration() {
330
+        $this->readConfiguration();
331
+        $config = $this->configuration->getConfiguration();
332
+        $cta = $this->configuration->getConfigTranslationArray();
333
+        $result = array();
334
+        foreach($cta as $dbkey => $configkey) {
335
+            switch($configkey) {
336
+                case 'homeFolderNamingRule':
337
+                    if(strpos($config[$configkey], 'attr:') === 0) {
338
+                        $result[$dbkey] = substr($config[$configkey], 5);
339
+                    } else {
340
+                        $result[$dbkey] = '';
341
+                    }
342
+                    break;
343
+                case 'ldapBase':
344
+                case 'ldapBaseUsers':
345
+                case 'ldapBaseGroups':
346
+                case 'ldapAttributesForUserSearch':
347
+                case 'ldapAttributesForGroupSearch':
348
+                    if(is_array($config[$configkey])) {
349
+                        $result[$dbkey] = implode("\n", $config[$configkey]);
350
+                        break;
351
+                    } //else follows default
352
+                default:
353
+                    $result[$dbkey] = $config[$configkey];
354
+            }
355
+        }
356
+        return $result;
357
+    }
358
+
359
+    private function doSoftValidation() {
360
+        //if User or Group Base are not set, take over Base DN setting
361
+        foreach(array('ldapBaseUsers', 'ldapBaseGroups') as $keyBase) {
362
+            $val = $this->configuration->$keyBase;
363
+            if(empty($val)) {
364
+                $this->configuration->$keyBase = $this->configuration->ldapBase;
365
+            }
366
+        }
367
+
368
+        foreach(array('ldapExpertUUIDUserAttr'  => 'ldapUuidUserAttribute',
369
+                        'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute')
370
+                as $expertSetting => $effectiveSetting) {
371
+            $uuidOverride = $this->configuration->$expertSetting;
372
+            if(!empty($uuidOverride)) {
373
+                $this->configuration->$effectiveSetting = $uuidOverride;
374
+            } else {
375
+                $uuidAttributes = Access::UUID_ATTRIBUTES;
376
+                array_unshift($uuidAttributes, 'auto');
377
+                if(!in_array($this->configuration->$effectiveSetting,
378
+                            $uuidAttributes)
379
+                    && (!is_null($this->configID))) {
380
+                    $this->configuration->$effectiveSetting = 'auto';
381
+                    $this->configuration->saveConfiguration();
382
+                    \OCP\Util::writeLog('user_ldap',
383
+                                        'Illegal value for the '.
384
+                                        $effectiveSetting.', '.'reset to '.
385
+                                        'autodetect.', \OCP\Util::INFO);
386
+                }
387
+
388
+            }
389
+        }
390
+
391
+        $backupPort = intval($this->configuration->ldapBackupPort);
392
+        if ($backupPort <= 0) {
393
+            $this->configuration->backupPort = $this->configuration->ldapPort;
394
+        }
395
+
396
+        //make sure empty search attributes are saved as simple, empty array
397
+        $saKeys = array('ldapAttributesForUserSearch',
398
+                        'ldapAttributesForGroupSearch');
399
+        foreach($saKeys as $key) {
400
+            $val = $this->configuration->$key;
401
+            if(is_array($val) && count($val) === 1 && empty($val[0])) {
402
+                $this->configuration->$key = array();
403
+            }
404
+        }
405
+
406
+        if((stripos($this->configuration->ldapHost, 'ldaps://') === 0)
407
+            && $this->configuration->ldapTLS) {
408
+            $this->configuration->ldapTLS = false;
409
+            \OCP\Util::writeLog('user_ldap',
410
+                                'LDAPS (already using secure connection) and '.
411
+                                'TLS do not work together. Switched off TLS.',
412
+                                \OCP\Util::INFO);
413
+        }
414
+    }
415
+
416
+    /**
417
+     * @return bool
418
+     */
419
+    private function doCriticalValidation() {
420
+        $configurationOK = true;
421
+        $errorStr = 'Configuration Error (prefix '.
422
+                    strval($this->configPrefix).'): ';
423
+
424
+        //options that shall not be empty
425
+        $options = array('ldapHost', 'ldapPort', 'ldapUserDisplayName',
426
+                            'ldapGroupDisplayName', 'ldapLoginFilter');
427
+        foreach($options as $key) {
428
+            $val = $this->configuration->$key;
429
+            if(empty($val)) {
430
+                switch($key) {
431
+                    case 'ldapHost':
432
+                        $subj = 'LDAP Host';
433
+                        break;
434
+                    case 'ldapPort':
435
+                        $subj = 'LDAP Port';
436
+                        break;
437
+                    case 'ldapUserDisplayName':
438
+                        $subj = 'LDAP User Display Name';
439
+                        break;
440
+                    case 'ldapGroupDisplayName':
441
+                        $subj = 'LDAP Group Display Name';
442
+                        break;
443
+                    case 'ldapLoginFilter':
444
+                        $subj = 'LDAP Login Filter';
445
+                        break;
446
+                    default:
447
+                        $subj = $key;
448
+                        break;
449
+                }
450
+                $configurationOK = false;
451
+                \OCP\Util::writeLog('user_ldap',
452
+                                    $errorStr.'No '.$subj.' given!',
453
+                                    \OCP\Util::WARN);
454
+            }
455
+        }
456
+
457
+        //combinations
458
+        $agent = $this->configuration->ldapAgentName;
459
+        $pwd = $this->configuration->ldapAgentPassword;
460
+        if (
461
+            ($agent === ''  && $pwd !== '')
462
+            || ($agent !== '' && $pwd === '')
463
+        ) {
464
+            \OCP\Util::writeLog('user_ldap',
465
+                                $errorStr.'either no password is given for the '.
466
+                                'user agent or a password is given, but not an '.
467
+                                'LDAP agent.',
468
+                \OCP\Util::WARN);
469
+            $configurationOK = false;
470
+        }
471
+
472
+        $base = $this->configuration->ldapBase;
473
+        $baseUsers = $this->configuration->ldapBaseUsers;
474
+        $baseGroups = $this->configuration->ldapBaseGroups;
475
+
476
+        if(empty($base) && empty($baseUsers) && empty($baseGroups)) {
477
+            \OCP\Util::writeLog('user_ldap',
478
+                                $errorStr.'Not a single Base DN given.',
479
+                                \OCP\Util::WARN);
480
+            $configurationOK = false;
481
+        }
482
+
483
+        if(mb_strpos($this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
484
+            === false) {
485
+            \OCP\Util::writeLog('user_ldap',
486
+                                $errorStr.'login filter does not contain %uid '.
487
+                                'place holder.',
488
+                                \OCP\Util::WARN);
489
+            $configurationOK = false;
490
+        }
491
+
492
+        return $configurationOK;
493
+    }
494
+
495
+    /**
496
+     * Validates the user specified configuration
497
+     * @return bool true if configuration seems OK, false otherwise
498
+     */
499
+    private function validateConfiguration() {
500
+
501
+        if($this->doNotValidate) {
502
+            //don't do a validation if it is a new configuration with pure
503
+            //default values. Will be allowed on changes via __set or
504
+            //setConfiguration
505
+            return false;
506
+        }
507
+
508
+        // first step: "soft" checks: settings that are not really
509
+        // necessary, but advisable. If left empty, give an info message
510
+        $this->doSoftValidation();
511
+
512
+        //second step: critical checks. If left empty or filled wrong, mark as
513
+        //not configured and give a warning.
514
+        return $this->doCriticalValidation();
515
+    }
516
+
517
+
518
+    /**
519
+     * Connects and Binds to LDAP
520
+     *
521
+     * @throws ServerNotAvailableException
522
+     */
523
+    private function establishConnection() {
524
+        if(!$this->configuration->ldapConfigurationActive) {
525
+            return null;
526
+        }
527
+        static $phpLDAPinstalled = true;
528
+        if(!$phpLDAPinstalled) {
529
+            return false;
530
+        }
531
+        if(!$this->ignoreValidation && !$this->configured) {
532
+            \OCP\Util::writeLog('user_ldap',
533
+                                'Configuration is invalid, cannot connect',
534
+                                \OCP\Util::WARN);
535
+            return false;
536
+        }
537
+        if(!$this->ldapConnectionRes) {
538
+            if(!$this->ldap->areLDAPFunctionsAvailable()) {
539
+                $phpLDAPinstalled = false;
540
+                \OCP\Util::writeLog('user_ldap',
541
+                                    'function ldap_connect is not available. Make '.
542
+                                    'sure that the PHP ldap module is installed.',
543
+                                    \OCP\Util::ERROR);
544
+
545
+                return false;
546
+            }
547
+            if($this->configuration->turnOffCertCheck) {
548
+                if(putenv('LDAPTLS_REQCERT=never')) {
549
+                    \OCP\Util::writeLog('user_ldap',
550
+                        'Turned off SSL certificate validation successfully.',
551
+                        \OCP\Util::DEBUG);
552
+                } else {
553
+                    \OCP\Util::writeLog('user_ldap',
554
+                                        'Could not turn off SSL certificate validation.',
555
+                                        \OCP\Util::WARN);
556
+                }
557
+            }
558
+
559
+            $isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
560
+                || $this->getFromCache('overrideMainServer'));
561
+            $isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
562
+            $bindStatus = false;
563
+            try {
564
+                if (!$isOverrideMainServer) {
565
+                    $this->doConnect($this->configuration->ldapHost,
566
+                        $this->configuration->ldapPort);
567
+                    return $this->bind();
568
+                }
569
+            } catch (ServerNotAvailableException $e) {
570
+                if(!$isBackupHost) {
571
+                    throw $e;
572
+                }
573
+            }
574
+
575
+            //if LDAP server is not reachable, try the Backup (Replica!) Server
576
+            if($isBackupHost || $isOverrideMainServer) {
577
+                $this->doConnect($this->configuration->ldapBackupHost,
578
+                                    $this->configuration->ldapBackupPort);
579
+                $this->bindResult = [];
580
+                $bindStatus = $this->bind();
581
+                $error = $this->ldap->isResource($this->ldapConnectionRes) ?
582
+                    $this->ldap->errno($this->ldapConnectionRes) : -1;
583
+                if($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
584
+                    //when bind to backup server succeeded and failed to main server,
585
+                    //skip contacting him until next cache refresh
586
+                    $this->writeToCache('overrideMainServer', true);
587
+                }
588
+            }
589
+
590
+            return $bindStatus;
591
+        }
592
+        return null;
593
+    }
594
+
595
+    /**
596
+     * @param string $host
597
+     * @param string $port
598
+     * @return bool
599
+     * @throws \OC\ServerNotAvailableException
600
+     */
601
+    private function doConnect($host, $port) {
602
+        if ($host === '') {
603
+            return false;
604
+        }
605
+
606
+        $this->ldapConnectionRes = $this->ldap->connect($host, $port);
607
+
608
+        if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
609
+            throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
610
+        }
611
+
612
+        if(!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
613
+            throw new ServerNotAvailableException('Could not disable LDAP referrals.');
614
+        }
615
+
616
+        if($this->configuration->ldapTLS) {
617
+            if(!$this->ldap->startTls($this->ldapConnectionRes)) {
618
+                throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
619
+            }
620
+        }
621
+
622
+        return true;
623
+    }
624
+
625
+    /**
626
+     * Binds to LDAP
627
+     */
628
+    public function bind() {
629
+        if(!$this->configuration->ldapConfigurationActive) {
630
+            return false;
631
+        }
632
+        $cr = $this->ldapConnectionRes;
633
+        if(!$this->ldap->isResource($cr)) {
634
+            $cr = $this->getConnectionResource();
635
+        }
636
+
637
+        if(
638
+            count($this->bindResult) !== 0
639
+            && $this->bindResult['dn'] === $this->configuration->ldapAgentName
640
+            && \OC::$server->getHasher()->verify(
641
+                $this->configPrefix . $this->configuration->ldapAgentPassword,
642
+                $this->bindResult['hash']
643
+            )
644
+        ) {
645
+            // don't attempt to bind again with the same data as before
646
+            // bind might have been invoked via getConnectionResource(),
647
+            // but we need results specifically for e.g. user login
648
+            return $this->bindResult['result'];
649
+        }
650
+
651
+        $ldapLogin = @$this->ldap->bind($cr,
652
+                                        $this->configuration->ldapAgentName,
653
+                                        $this->configuration->ldapAgentPassword);
654
+
655
+        $this->bindResult = [
656
+            'dn' => $this->configuration->ldapAgentName,
657
+            'hash' => \OC::$server->getHasher()->hash($this->configPrefix . $this->configuration->ldapAgentPassword),
658
+            'result' => $ldapLogin,
659
+        ];
660
+
661
+        if(!$ldapLogin) {
662
+            $errno = $this->ldap->errno($cr);
663
+
664
+            \OCP\Util::writeLog('user_ldap',
665
+                'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
666
+                \OCP\Util::WARN);
667
+
668
+            // Set to failure mode, if LDAP error code is not LDAP_SUCCESS or LDAP_INVALID_CREDENTIALS
669
+            if($errno !== 0x00 && $errno !== 0x31) {
670
+                $this->ldapConnectionRes = null;
671
+            }
672
+
673
+            return false;
674
+        }
675
+        return true;
676
+    }
677 677
 
678 678
 }
Please login to merge, or discard this patch.