You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Connection.php 20KB

8 years ago
8 years ago
8 years ago
LDAP User Cleanup: Port from stable7 without further adjustements LDAP User Cleanup background job for user clean up adjust user backend for clean up register background job remove dead code dependency injection make Helper non-static for proper testing check whether it is OK to run clean up job. Do not forget to pass arguments. use correct method to get the config from server methods can be private, proper indirect testing is given no automatic user deletion make limit readable for test purposes make method less complex add first tests let preferences accept limit and offset for getUsersForValue DI via constructor does not work for background jobs after detecting, now we have retrieving deleted users and their details we need this method to be public for now finalize export method, add missing getter clean up namespaces and get rid of unnecessary files helper is not static anymore cleanup according to scrutinizer add cli tool to show deleted users uses are necessary after recent namespace change also remove user from mappings table on deletion add occ command to delete users fix use statement improve output big fixes / improvements PHP doc return true in userExists early for cleaning up deleted users bump version control state and interval with one config.php setting, now ldapUserCleanupInterval. 0 will disable it. enabled by default. improve doc rename cli method to be consistent with others introduce ldapUserCleanupInterval in sample config don't show last login as unix epoche start when no login happend less log output consistent namespace for OfflineUser rename GarbageCollector to DeletedUsersIndex and move it to user subdir fix unit tests add tests for deleteUser more test adjustements Conflicts: apps/user_ldap/ajax/clearMappings.php apps/user_ldap/appinfo/app.php apps/user_ldap/lib/access.php apps/user_ldap/lib/helper.php apps/user_ldap/tests/helper.php core/register_command.php lib/private/preferences.php lib/private/user.php add ldap:check-user to check user existance on the fly Conflicts: apps/user_ldap/lib/helper.php forgotten file PHPdoc fixes, no code change and don't forget to adjust tests
10 years ago
7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author Bart Visscher <bartv@thisnet.nl>
  7. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  8. * @author Jarkko Lehtoranta <devel@jlranta.com>
  9. * @author Joas Schilling <coding@schilljs.com>
  10. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  11. * @author Julius Härtl <jus@bitgrid.net>
  12. * @author Lukas Reschke <lukas@statuscode.ch>
  13. * @author Morris Jobke <hey@morrisjobke.de>
  14. * @author Robin Appelman <robin@icewind.nl>
  15. * @author Robin McCorkell <robin@mccorkell.me.uk>
  16. * @author Roeland Jago Douma <roeland@famdouma.nl>
  17. * @author Roger Szabo <roger.szabo@web.de>
  18. * @author root <root@localhost.localdomain>
  19. * @author Victor Dubiniuk <dubiniuk@owncloud.com>
  20. * @author Xuanwo <xuanwo@yunify.com>
  21. *
  22. * @license AGPL-3.0
  23. *
  24. * This code is free software: you can redistribute it and/or modify
  25. * it under the terms of the GNU Affero General Public License, version 3,
  26. * as published by the Free Software Foundation.
  27. *
  28. * This program is distributed in the hope that it will be useful,
  29. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  30. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  31. * GNU Affero General Public License for more details.
  32. *
  33. * You should have received a copy of the GNU Affero General Public License, version 3,
  34. * along with this program. If not, see <http://www.gnu.org/licenses/>
  35. *
  36. */
  37. namespace OCA\User_LDAP;
  38. use OC\ServerNotAvailableException;
  39. use Psr\Log\LoggerInterface;
  40. /**
  41. * magic properties (incomplete)
  42. * responsible for LDAP connections in context with the provided configuration
  43. *
  44. * @property string ldapHost
  45. * @property string ldapPort holds the port number
  46. * @property string ldapUserFilter
  47. * @property string ldapUserDisplayName
  48. * @property string ldapUserDisplayName2
  49. * @property string ldapUserAvatarRule
  50. * @property boolean turnOnPasswordChange
  51. * @property string[] ldapBaseUsers
  52. * @property int|null ldapPagingSize holds an integer
  53. * @property bool|mixed|void ldapGroupMemberAssocAttr
  54. * @property string ldapUuidUserAttribute
  55. * @property string ldapUuidGroupAttribute
  56. * @property string ldapExpertUUIDUserAttr
  57. * @property string ldapExpertUUIDGroupAttr
  58. * @property string ldapQuotaAttribute
  59. * @property string ldapQuotaDefault
  60. * @property string ldapEmailAttribute
  61. * @property string ldapExtStorageHomeAttribute
  62. * @property string homeFolderNamingRule
  63. * @property bool|string ldapNestedGroups
  64. * @property string[] ldapBaseGroups
  65. * @property string ldapGroupFilter
  66. * @property string ldapGroupDisplayName
  67. * @property string ldapLoginFilter
  68. * @property string ldapDynamicGroupMemberURL
  69. * @property string ldapGidNumber
  70. * @property int hasMemberOfFilterSupport
  71. * @property int useMemberOfToDetectMembership
  72. * @property string ldapMatchingRuleInChainState
  73. */
  74. class Connection extends LDAPUtility {
  75. /**
  76. * @var resource|\LDAP\Connection|null
  77. */
  78. private $ldapConnectionRes = null;
  79. /**
  80. * @var string
  81. */
  82. private $configPrefix;
  83. /**
  84. * @var ?string
  85. */
  86. private $configID;
  87. /**
  88. * @var bool
  89. */
  90. private $configured = false;
  91. /**
  92. * @var bool whether connection should be kept on __destruct
  93. */
  94. private $dontDestruct = false;
  95. /**
  96. * @var bool runtime flag that indicates whether supported primary groups are available
  97. */
  98. public $hasPrimaryGroups = true;
  99. /**
  100. * @var bool runtime flag that indicates whether supported POSIX gidNumber are available
  101. */
  102. public $hasGidNumber = true;
  103. /**
  104. * @var \OCP\ICache|null
  105. */
  106. protected $cache = null;
  107. /** @var Configuration settings handler **/
  108. protected $configuration;
  109. /**
  110. * @var bool
  111. */
  112. protected $doNotValidate = false;
  113. /**
  114. * @var bool
  115. */
  116. protected $ignoreValidation = false;
  117. /**
  118. * @var array{sum?: string, result?: bool}
  119. */
  120. protected $bindResult = [];
  121. /** @var LoggerInterface */
  122. protected $logger;
  123. /**
  124. * Constructor
  125. * @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
  126. * @param string|null $configID a string with the value for the appid column (appconfig table) or null for on-the-fly connections
  127. */
  128. public function __construct(ILDAPWrapper $ldap, string $configPrefix = '', ?string $configID = 'user_ldap') {
  129. parent::__construct($ldap);
  130. $this->configPrefix = $configPrefix;
  131. $this->configID = $configID;
  132. $this->configuration = new Configuration($configPrefix, !is_null($configID));
  133. $memcache = \OC::$server->getMemCacheFactory();
  134. if ($memcache->isAvailable()) {
  135. $this->cache = $memcache->createDistributed();
  136. }
  137. $helper = new Helper(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection());
  138. $this->doNotValidate = !in_array($this->configPrefix,
  139. $helper->getServerConfigurationPrefixes());
  140. $this->logger = \OC::$server->get(LoggerInterface::class);
  141. }
  142. public function __destruct() {
  143. if (!$this->dontDestruct && $this->ldap->isResource($this->ldapConnectionRes)) {
  144. @$this->ldap->unbind($this->ldapConnectionRes);
  145. $this->bindResult = [];
  146. }
  147. }
  148. /**
  149. * defines behaviour when the instance is cloned
  150. */
  151. public function __clone() {
  152. $this->configuration = new Configuration($this->configPrefix,
  153. !is_null($this->configID));
  154. if (count($this->bindResult) !== 0 && $this->bindResult['result'] === true) {
  155. $this->bindResult = [];
  156. }
  157. $this->ldapConnectionRes = null;
  158. $this->dontDestruct = true;
  159. }
  160. public function __get(string $name) {
  161. if (!$this->configured) {
  162. $this->readConfiguration();
  163. }
  164. return $this->configuration->$name;
  165. }
  166. /**
  167. * @param string $name
  168. * @param mixed $value
  169. */
  170. public function __set($name, $value) {
  171. $this->doNotValidate = false;
  172. $before = $this->configuration->$name;
  173. $this->configuration->$name = $value;
  174. $after = $this->configuration->$name;
  175. if ($before !== $after) {
  176. if ($this->configID !== '' && $this->configID !== null) {
  177. $this->configuration->saveConfiguration();
  178. }
  179. $this->validateConfiguration();
  180. }
  181. }
  182. /**
  183. * @param string $rule
  184. * @return array
  185. * @throws \RuntimeException
  186. */
  187. public function resolveRule($rule) {
  188. return $this->configuration->resolveRule($rule);
  189. }
  190. /**
  191. * sets whether the result of the configuration validation shall
  192. * be ignored when establishing the connection. Used by the Wizard
  193. * in early configuration state.
  194. * @param bool $state
  195. */
  196. public function setIgnoreValidation($state) {
  197. $this->ignoreValidation = (bool)$state;
  198. }
  199. /**
  200. * initializes the LDAP backend
  201. * @param bool $force read the config settings no matter what
  202. */
  203. public function init($force = false) {
  204. $this->readConfiguration($force);
  205. $this->establishConnection();
  206. }
  207. /**
  208. * @return resource|\LDAP\Connection The LDAP resource
  209. */
  210. public function getConnectionResource() {
  211. if (!$this->ldapConnectionRes) {
  212. $this->init();
  213. } elseif (!$this->ldap->isResource($this->ldapConnectionRes)) {
  214. $this->ldapConnectionRes = null;
  215. $this->establishConnection();
  216. }
  217. if (is_null($this->ldapConnectionRes)) {
  218. $this->logger->error(
  219. 'No LDAP Connection to server ' . $this->configuration->ldapHost,
  220. ['app' => 'user_ldap']
  221. );
  222. throw new ServerNotAvailableException('Connection to LDAP server could not be established');
  223. }
  224. return $this->ldapConnectionRes;
  225. }
  226. /**
  227. * resets the connection resource
  228. */
  229. public function resetConnectionResource() {
  230. if (!is_null($this->ldapConnectionRes)) {
  231. @$this->ldap->unbind($this->ldapConnectionRes);
  232. $this->ldapConnectionRes = null;
  233. $this->bindResult = [];
  234. }
  235. }
  236. /**
  237. * @param string|null $key
  238. * @return string
  239. */
  240. private function getCacheKey($key) {
  241. $prefix = 'LDAP-'.$this->configID.'-'.$this->configPrefix.'-';
  242. if (is_null($key)) {
  243. return $prefix;
  244. }
  245. return $prefix.hash('sha256', $key);
  246. }
  247. /**
  248. * @param string $key
  249. * @return mixed|null
  250. */
  251. public function getFromCache($key) {
  252. if (!$this->configured) {
  253. $this->readConfiguration();
  254. }
  255. if (is_null($this->cache) || !$this->configuration->ldapCacheTTL) {
  256. return null;
  257. }
  258. $key = $this->getCacheKey($key);
  259. return json_decode(base64_decode($this->cache->get($key) ?? ''), true);
  260. }
  261. /**
  262. * @param string $key
  263. * @param mixed $value
  264. */
  265. public function writeToCache($key, $value): void {
  266. if (!$this->configured) {
  267. $this->readConfiguration();
  268. }
  269. if (is_null($this->cache)
  270. || !$this->configuration->ldapCacheTTL
  271. || !$this->configuration->ldapConfigurationActive) {
  272. return;
  273. }
  274. $key = $this->getCacheKey($key);
  275. $value = base64_encode(json_encode($value));
  276. $this->cache->set($key, $value, $this->configuration->ldapCacheTTL);
  277. }
  278. public function clearCache() {
  279. if (!is_null($this->cache)) {
  280. $this->cache->clear($this->getCacheKey(null));
  281. }
  282. }
  283. /**
  284. * Caches the general LDAP configuration.
  285. * @param bool $force optional. true, if the re-read should be forced. defaults
  286. * to false.
  287. * @return null
  288. */
  289. private function readConfiguration($force = false) {
  290. if ((!$this->configured || $force) && !is_null($this->configID)) {
  291. $this->configuration->readConfiguration();
  292. $this->configured = $this->validateConfiguration();
  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 bool true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
  300. */
  301. public function setConfiguration($config, &$setParameters = null): bool {
  302. if (is_null($setParameters)) {
  303. $setParameters = [];
  304. }
  305. $this->doNotValidate = false;
  306. $this->configuration->setConfiguration($config, $setParameters);
  307. if (count($setParameters) > 0) {
  308. $this->configured = $this->validateConfiguration();
  309. }
  310. return $this->configured;
  311. }
  312. /**
  313. * saves the current Configuration in the database and empties the
  314. * cache
  315. * @return null
  316. */
  317. public function saveConfiguration() {
  318. $this->configuration->saveConfiguration();
  319. $this->clearCache();
  320. }
  321. /**
  322. * get the current LDAP configuration
  323. * @return array
  324. */
  325. public function getConfiguration() {
  326. $this->readConfiguration();
  327. $config = $this->configuration->getConfiguration();
  328. $cta = $this->configuration->getConfigTranslationArray();
  329. $result = [];
  330. foreach ($cta as $dbkey => $configkey) {
  331. switch ($configkey) {
  332. case 'homeFolderNamingRule':
  333. if (strpos($config[$configkey], 'attr:') === 0) {
  334. $result[$dbkey] = substr($config[$configkey], 5);
  335. } else {
  336. $result[$dbkey] = '';
  337. }
  338. break;
  339. case 'ldapBase':
  340. case 'ldapBaseUsers':
  341. case 'ldapBaseGroups':
  342. case 'ldapAttributesForUserSearch':
  343. case 'ldapAttributesForGroupSearch':
  344. if (is_array($config[$configkey])) {
  345. $result[$dbkey] = implode("\n", $config[$configkey]);
  346. break;
  347. } //else follows default
  348. // no break
  349. default:
  350. $result[$dbkey] = $config[$configkey];
  351. }
  352. }
  353. return $result;
  354. }
  355. private function doSoftValidation() {
  356. //if User or Group Base are not set, take over Base DN setting
  357. foreach (['ldapBaseUsers', 'ldapBaseGroups'] as $keyBase) {
  358. $val = $this->configuration->$keyBase;
  359. if (empty($val)) {
  360. $this->configuration->$keyBase = $this->configuration->ldapBase;
  361. }
  362. }
  363. foreach (['ldapExpertUUIDUserAttr' => 'ldapUuidUserAttribute',
  364. 'ldapExpertUUIDGroupAttr' => 'ldapUuidGroupAttribute']
  365. as $expertSetting => $effectiveSetting) {
  366. $uuidOverride = $this->configuration->$expertSetting;
  367. if (!empty($uuidOverride)) {
  368. $this->configuration->$effectiveSetting = $uuidOverride;
  369. } else {
  370. $uuidAttributes = Access::UUID_ATTRIBUTES;
  371. array_unshift($uuidAttributes, 'auto');
  372. if (!in_array($this->configuration->$effectiveSetting,
  373. $uuidAttributes)
  374. && (!is_null($this->configID))) {
  375. $this->configuration->$effectiveSetting = 'auto';
  376. $this->configuration->saveConfiguration();
  377. $this->logger->info(
  378. 'Illegal value for the '.$effectiveSetting.', reset to autodetect.',
  379. ['app' => 'user_ldap']
  380. );
  381. }
  382. }
  383. }
  384. $backupPort = (int)$this->configuration->ldapBackupPort;
  385. if ($backupPort <= 0) {
  386. $this->configuration->backupPort = $this->configuration->ldapPort;
  387. }
  388. //make sure empty search attributes are saved as simple, empty array
  389. $saKeys = ['ldapAttributesForUserSearch',
  390. 'ldapAttributesForGroupSearch'];
  391. foreach ($saKeys as $key) {
  392. $val = $this->configuration->$key;
  393. if (is_array($val) && count($val) === 1 && empty($val[0])) {
  394. $this->configuration->$key = [];
  395. }
  396. }
  397. if ((stripos((string)$this->configuration->ldapHost, 'ldaps://') === 0)
  398. && $this->configuration->ldapTLS) {
  399. $this->configuration->ldapTLS = false;
  400. $this->logger->info(
  401. 'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.',
  402. ['app' => 'user_ldap']
  403. );
  404. }
  405. }
  406. /**
  407. * @return bool
  408. */
  409. private function doCriticalValidation() {
  410. $configurationOK = true;
  411. $errorStr = 'Configuration Error (prefix '.
  412. (string)$this->configPrefix .'): ';
  413. //options that shall not be empty
  414. $options = ['ldapHost', 'ldapPort', 'ldapUserDisplayName',
  415. 'ldapGroupDisplayName', 'ldapLoginFilter'];
  416. foreach ($options as $key) {
  417. $val = $this->configuration->$key;
  418. if (empty($val)) {
  419. switch ($key) {
  420. case 'ldapHost':
  421. $subj = 'LDAP Host';
  422. break;
  423. case 'ldapPort':
  424. $subj = 'LDAP Port';
  425. break;
  426. case 'ldapUserDisplayName':
  427. $subj = 'LDAP User Display Name';
  428. break;
  429. case 'ldapGroupDisplayName':
  430. $subj = 'LDAP Group Display Name';
  431. break;
  432. case 'ldapLoginFilter':
  433. $subj = 'LDAP Login Filter';
  434. break;
  435. default:
  436. $subj = $key;
  437. break;
  438. }
  439. $configurationOK = false;
  440. $this->logger->warning(
  441. $errorStr.'No '.$subj.' given!',
  442. ['app' => 'user_ldap']
  443. );
  444. }
  445. }
  446. //combinations
  447. $agent = $this->configuration->ldapAgentName;
  448. $pwd = $this->configuration->ldapAgentPassword;
  449. if (
  450. ($agent === '' && $pwd !== '')
  451. || ($agent !== '' && $pwd === '')
  452. ) {
  453. $this->logger->warning(
  454. $errorStr.'either no password is given for the user ' .
  455. 'agent or a password is given, but not an LDAP agent.',
  456. ['app' => 'user_ldap']
  457. );
  458. $configurationOK = false;
  459. }
  460. $base = $this->configuration->ldapBase;
  461. $baseUsers = $this->configuration->ldapBaseUsers;
  462. $baseGroups = $this->configuration->ldapBaseGroups;
  463. if (empty($base) && empty($baseUsers) && empty($baseGroups)) {
  464. $this->logger->warning(
  465. $errorStr.'Not a single Base DN given.',
  466. ['app' => 'user_ldap']
  467. );
  468. $configurationOK = false;
  469. }
  470. if (mb_strpos((string)$this->configuration->ldapLoginFilter, '%uid', 0, 'UTF-8')
  471. === false) {
  472. $this->logger->warning(
  473. $errorStr.'login filter does not contain %uid place holder.',
  474. ['app' => 'user_ldap']
  475. );
  476. $configurationOK = false;
  477. }
  478. return $configurationOK;
  479. }
  480. /**
  481. * Validates the user specified configuration
  482. * @return bool true if configuration seems OK, false otherwise
  483. */
  484. private function validateConfiguration() {
  485. if ($this->doNotValidate) {
  486. //don't do a validation if it is a new configuration with pure
  487. //default values. Will be allowed on changes via __set or
  488. //setConfiguration
  489. return false;
  490. }
  491. // first step: "soft" checks: settings that are not really
  492. // necessary, but advisable. If left empty, give an info message
  493. $this->doSoftValidation();
  494. //second step: critical checks. If left empty or filled wrong, mark as
  495. //not configured and give a warning.
  496. return $this->doCriticalValidation();
  497. }
  498. /**
  499. * Connects and Binds to LDAP
  500. *
  501. * @throws ServerNotAvailableException
  502. */
  503. private function establishConnection() {
  504. if (!$this->configuration->ldapConfigurationActive) {
  505. return null;
  506. }
  507. static $phpLDAPinstalled = true;
  508. if (!$phpLDAPinstalled) {
  509. return false;
  510. }
  511. if (!$this->ignoreValidation && !$this->configured) {
  512. $this->logger->warning(
  513. 'Configuration is invalid, cannot connect',
  514. ['app' => 'user_ldap']
  515. );
  516. return false;
  517. }
  518. if (!$this->ldapConnectionRes) {
  519. if (!$this->ldap->areLDAPFunctionsAvailable()) {
  520. $phpLDAPinstalled = false;
  521. $this->logger->error(
  522. 'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
  523. ['app' => 'user_ldap']
  524. );
  525. return false;
  526. }
  527. if ($this->configuration->turnOffCertCheck) {
  528. if (putenv('LDAPTLS_REQCERT=never')) {
  529. $this->logger->debug(
  530. 'Turned off SSL certificate validation successfully.',
  531. ['app' => 'user_ldap']
  532. );
  533. } else {
  534. $this->logger->warning(
  535. 'Could not turn off SSL certificate validation.',
  536. ['app' => 'user_ldap']
  537. );
  538. }
  539. }
  540. $isOverrideMainServer = ($this->configuration->ldapOverrideMainServer
  541. || $this->getFromCache('overrideMainServer'));
  542. $isBackupHost = (trim($this->configuration->ldapBackupHost) !== "");
  543. $bindStatus = false;
  544. try {
  545. if (!$isOverrideMainServer) {
  546. $this->doConnect($this->configuration->ldapHost,
  547. $this->configuration->ldapPort);
  548. return $this->bind();
  549. }
  550. } catch (ServerNotAvailableException $e) {
  551. if (!$isBackupHost) {
  552. throw $e;
  553. }
  554. }
  555. //if LDAP server is not reachable, try the Backup (Replica!) Server
  556. if ($isBackupHost || $isOverrideMainServer) {
  557. $this->doConnect($this->configuration->ldapBackupHost,
  558. $this->configuration->ldapBackupPort);
  559. $this->bindResult = [];
  560. $bindStatus = $this->bind();
  561. $error = $this->ldap->isResource($this->ldapConnectionRes) ?
  562. $this->ldap->errno($this->ldapConnectionRes) : -1;
  563. if ($bindStatus && $error === 0 && !$this->getFromCache('overrideMainServer')) {
  564. //when bind to backup server succeeded and failed to main server,
  565. //skip contacting him until next cache refresh
  566. $this->writeToCache('overrideMainServer', true);
  567. }
  568. }
  569. return $bindStatus;
  570. }
  571. return null;
  572. }
  573. /**
  574. * @param string $host
  575. * @param string $port
  576. * @return bool
  577. * @throws \OC\ServerNotAvailableException
  578. */
  579. private function doConnect($host, $port) {
  580. if ($host === '') {
  581. return false;
  582. }
  583. $this->ldapConnectionRes = $this->ldap->connect($host, $port);
  584. if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
  585. throw new ServerNotAvailableException('Could not set required LDAP Protocol version.');
  586. }
  587. if (!$this->ldap->setOption($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
  588. throw new ServerNotAvailableException('Could not disable LDAP referrals.');
  589. }
  590. if ($this->configuration->ldapTLS) {
  591. if (!$this->ldap->startTls($this->ldapConnectionRes)) {
  592. throw new ServerNotAvailableException('Start TLS failed, when connecting to LDAP host ' . $host . '.');
  593. }
  594. }
  595. return true;
  596. }
  597. /**
  598. * Binds to LDAP
  599. */
  600. public function bind() {
  601. if (!$this->configuration->ldapConfigurationActive) {
  602. return false;
  603. }
  604. $cr = $this->ldapConnectionRes;
  605. if (!$this->ldap->isResource($cr)) {
  606. $cr = $this->getConnectionResource();
  607. }
  608. if (
  609. count($this->bindResult) !== 0
  610. && $this->bindResult['sum'] === md5($this->configuration->ldapAgentName . $this->configPrefix . $this->configuration->ldapAgentPassword)
  611. ) {
  612. // don't attempt to bind again with the same data as before
  613. // bind might have been invoked via getConnectionResource(),
  614. // but we need results specifically for e.g. user login
  615. return $this->bindResult['result'];
  616. }
  617. $ldapLogin = @$this->ldap->bind($cr,
  618. $this->configuration->ldapAgentName,
  619. $this->configuration->ldapAgentPassword);
  620. $this->bindResult = [
  621. 'sum' => md5($this->configuration->ldapAgentName . $this->configPrefix . $this->configuration->ldapAgentPassword),
  622. 'result' => $ldapLogin,
  623. ];
  624. if (!$ldapLogin) {
  625. $errno = $this->ldap->errno($cr);
  626. $this->logger->warning(
  627. 'Bind failed: ' . $errno . ': ' . $this->ldap->error($cr),
  628. ['app' => 'user_ldap']
  629. );
  630. // Set to failure mode, if LDAP error code is not one of
  631. // - LDAP_SUCCESS (0)
  632. // - LDAP_INVALID_CREDENTIALS (49)
  633. // - LDAP_INSUFFICIENT_ACCESS (50, spotted Apple Open Directory)
  634. // - LDAP_UNWILLING_TO_PERFORM (53, spotted eDirectory)
  635. if (!in_array($errno, [0, 49, 50, 53], true)) {
  636. $this->ldapConnectionRes = null;
  637. }
  638. return false;
  639. }
  640. return true;
  641. }
  642. }