1<?php
2/* Copyright (C) 2007-2011 Laurent Destailleur  <eldy@users.sourceforge.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 */
18
19/**
20 *       \file       htdocs/core/login/functions_ldap.php
21 *       \ingroup    core
22 *       \brief      Authentication functions for LDAP
23 */
24
25
26/**
27 * Check validity of user/password/entity
28 * If test is ko, reason must be filled into $_SESSION["dol_loginmesg"]
29 *
30 * @param	string	$usertotest		Login
31 * @param	string	$passwordtotest	Password
32 * @param   int		$entitytotest   Numero of instance (always 1 if module multicompany not enabled)
33 * @return	string					Login if OK, '' if KO
34 */
35function check_user_password_ldap($usertotest, $passwordtotest, $entitytotest)
36{
37	global $db, $conf, $langs;
38	global $_POST;
39	global $dolibarr_main_auth_ldap_host, $dolibarr_main_auth_ldap_port;
40	global $dolibarr_main_auth_ldap_version, $dolibarr_main_auth_ldap_servertype;
41	global $dolibarr_main_auth_ldap_login_attribute, $dolibarr_main_auth_ldap_dn;
42	global $dolibarr_main_auth_ldap_admin_login, $dolibarr_main_auth_ldap_admin_pass;
43	global $dolibarr_main_auth_ldap_filter;
44	global $dolibarr_main_auth_ldap_debug;
45
46	// Force master entity in transversal mode
47	$entity = $entitytotest;
48	if (!empty($conf->multicompany->enabled) && !empty($conf->global->MULTICOMPANY_TRANSVERSE_MODE)) {
49		$entity = 1;
50	}
51
52	$login = '';
53	$resultFetchUser = '';
54
55	if (!function_exists("ldap_connect")) {
56		dol_syslog("functions_ldap::check_user_password_ldap Authentication KO failed to connect to LDAP. LDAP functions are disabled on this PHP", LOG_ERR);
57		sleep(1);
58
59		// Load translation files required by the page
60		$langs->loadLangs(array('main', 'other'));
61
62		$_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorLDAPFunctionsAreDisabledOnThisPHP").' '.$langs->transnoentitiesnoconv("TryAnotherConnectionMode");
63		return;
64	}
65
66	if ($usertotest) {
67		dol_syslog("functions_ldap::check_user_password_ldap usertotest=".$usertotest." passwordtotest=".preg_replace('/./', '*', $passwordtotest)." entitytotest=".$entitytotest);
68
69		// If test username/password asked, we define $test=false and $login var if ok, set $_SESSION["dol_loginmesg"] if ko
70		$ldaphost = $dolibarr_main_auth_ldap_host;
71		$ldapport = $dolibarr_main_auth_ldap_port;
72		$ldapversion = $dolibarr_main_auth_ldap_version;
73		$ldapservertype = (empty($dolibarr_main_auth_ldap_servertype) ? 'openldap' : $dolibarr_main_auth_ldap_servertype);
74
75		$ldapuserattr = $dolibarr_main_auth_ldap_login_attribute;
76		$ldapdn = $dolibarr_main_auth_ldap_dn;
77		$ldapadminlogin = $dolibarr_main_auth_ldap_admin_login;
78		$ldapadminpass = $dolibarr_main_auth_ldap_admin_pass;
79		$ldapdebug = (empty($dolibarr_main_auth_ldap_debug) || $dolibarr_main_auth_ldap_debug == "false" ? false : true);
80
81		if ($ldapdebug) {
82			print "DEBUG: Logging LDAP steps<br>\n";
83		}
84
85		require_once DOL_DOCUMENT_ROOT.'/core/class/ldap.class.php';
86		$ldap = new Ldap();
87		$ldap->server = explode(',', $ldaphost);
88		$ldap->serverPort = $ldapport;
89		$ldap->ldapProtocolVersion = $ldapversion;
90		$ldap->serverType = $ldapservertype;
91		$ldap->searchUser = $ldapadminlogin;
92		$ldap->searchPassword = $ldapadminpass;
93
94		if ($ldapdebug) {
95			dol_syslog("functions_ldap::check_user_password_ldap Server:".join(',', $ldap->server).", Port:".$ldap->serverPort.", Protocol:".$ldap->ldapProtocolVersion.", Type:".$ldap->serverType);
96			dol_syslog("functions_ldap::check_user_password_ldap uid/samacountname=".$ldapuserattr.", dn=".$ldapdn.", Admin:".$ldap->searchUser.", Pass:".$ldap->searchPassword);
97			print "DEBUG: Server:".join(',', $ldap->server).", Port:".$ldap->serverPort.", Protocol:".$ldap->ldapProtocolVersion.", Type:".$ldap->serverType."<br>\n";
98			print "DEBUG: uid/samacountname=".$ldapuserattr.", dn=".$ldapdn.", Admin:".$ldap->searchUser.", Pass:".$ldap->searchPassword."<br>\n";
99		}
100
101		$resultFetchLdapUser = 0;
102
103		// Define $userSearchFilter
104		$userSearchFilter = "";
105		if (empty($dolibarr_main_auth_ldap_filter)) {
106			$userSearchFilter = "(".$ldapuserattr."=".$usertotest.")";
107		} else {
108			$userSearchFilter = str_replace('%1%', $usertotest, $dolibarr_main_auth_ldap_filter);
109		}
110
111		// If admin login provided
112		// Code to get user in LDAP from an admin connection (may differ from user connection, done later)
113		if ($ldapadminlogin) {
114			$result = $ldap->connect_bind();
115			if ($result > 0) {
116				$resultFetchLdapUser = $ldap->fetch($usertotest, $userSearchFilter);
117				//dol_syslog('functions_ldap::check_user_password_ldap resultFetchLdapUser='.$resultFetchLdapUser);
118				if ($resultFetchLdapUser > 0 && $ldap->pwdlastset == 0) { // If ok but password need to be reset
119					dol_syslog('functions_ldap::check_user_password_ldap '.$usertotest.' must change password next logon');
120					if ($ldapdebug) {
121						print "DEBUG: User ".$usertotest." must change password<br>\n";
122					}
123					$ldap->close();
124					sleep(1);
125					$langs->load('ldap');
126					$_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("YouMustChangePassNextLogon", $usertotest, $ldap->domainFQDN);
127					return '';
128				}
129			} else {
130				if ($ldapdebug) {
131					print "DEBUG: ".$ldap->error."<br>\n";
132				}
133			}
134			$ldap->close();
135		}
136
137		// Forge LDAP user and password to test with them
138		// If LDAP need a dn with login like "uid=jbloggs,ou=People,dc=foo,dc=com", default dn may work even if previous code with
139		// admin login no exectued.
140		$ldap->searchUser = $ldapuserattr."=".$usertotest.",".$ldapdn; // Default dn (will work if LDAP accept a dn with login value inside)
141		// But if LDAP need a dn with name like "cn=Jhon Bloggs,ou=People,dc=foo,dc=com", previous part must have been executed to have
142		// dn detected into ldapUserDN.
143		if ($resultFetchLdapUser && !empty($ldap->ldapUserDN)) {
144			$ldap->searchUser = $ldap->ldapUserDN;
145		}
146		$ldap->searchPassword = $passwordtotest;
147
148		// Test with this->seachUser and this->searchPassword
149		//print $resultFetchLdapUser."-".$ldap->ldapUserDN."-".$ldap->searchUser.'-'.$ldap->searchPassword;exit;
150		$result = $ldap->connect_bind();
151		if ($result > 0) {
152			if ($result == 2) {	// Connection is ok for user/pass into LDAP
153				$login = $usertotest;
154				dol_syslog("functions_ldap::check_user_password_ldap $login authentication ok");
155				// For the case, we search the user id using a search key without the login (but using other fields like id),
156				// we need to get the real login to use in the ldap answer.
157				if (!empty($conf->global->LDAP_FIELD_LOGIN) && !empty($ldap->login)) {
158					$login = $ldap->login;
159					dol_syslog("functions_ldap::check_user_password_ldap login is now $login (LDAP_FIELD_LOGIN=".$conf->global->LDAP_FIELD_LOGIN.")");
160				}
161
162				require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
163
164				$tmpuser = new User($db);
165				$tmpuser->fetch('', $login, '', 1, ($entitytotest > 0 ? $entitytotest : -1));
166
167				$now = dol_now();
168				if ($tmpuser->datestartvalidity && $db->jdate($tmpuser->datestartvalidity) >= $now) {
169					$ldap->close();
170					// Load translation files required by the page
171					$langs->loadLangs(array('main', 'errors'));
172					$_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorLoginDateValidity");
173					return '--bad-login-validity--';
174				}
175				if ($tmpuser->dateendvalidity && $db->jdate($tmpuser->dateendvalidity) <= dol_get_first_hour($now)) {
176					$ldap->close();
177					// Load translation files required by the page
178					$langs->loadLangs(array('main', 'errors'));
179					$_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorLoginDateValidity");
180					return '--bad-login-validity--';
181				}
182
183				// ldap2dolibarr synchronisation
184				if ($login && !empty($conf->ldap->enabled) && $conf->global->LDAP_SYNCHRO_ACTIVE == 'ldap2dolibarr') {	// ldap2dolibarr synchronisation
185					dol_syslog("functions_ldap::check_user_password_ldap Sync ldap2dolibarr");
186
187					// On charge les attributs du user ldap
188					if ($ldapdebug) {
189						print "DEBUG: login ldap = ".$login."<br>\n";
190					}
191					$resultFetchLdapUser = $ldap->fetch($login, $userSearchFilter);
192
193					if ($ldapdebug) {
194						print "DEBUG: UACF = ".join(',', $ldap->uacf)."<br>\n";
195					}
196					if ($ldapdebug) {
197						print "DEBUG: pwdLastSet = ".dol_print_date($ldap->pwdlastset, 'day')."<br>\n";
198					}
199					if ($ldapdebug) {
200						print "DEBUG: badPasswordTime = ".dol_print_date($ldap->badpwdtime, 'day')."<br>\n";
201					}
202
203					// On recherche le user dolibarr en fonction de son SID ldap (only for Active Directory)
204					$sid = null;
205					if ($conf->global->LDAP_SERVER_TYPE == "activedirectory") {
206						$sid = $ldap->getObjectSid($login);
207						if ($ldapdebug) {
208							print "DEBUG: sid = ".$sid."<br>\n";
209						}
210					}
211
212					$usertmp = new User($db);
213					$resultFetchUser = $usertmp->fetch('', $login, $sid, 1, ($entitytotest > 0 ? $entitytotest : -1));
214					if ($resultFetchUser > 0) {
215						dol_syslog("functions_ldap::check_user_password_ldap Sync user found user id=".$usertmp->id);
216						// On verifie si le login a change et on met a jour les attributs dolibarr
217
218						if ($usertmp->login != $ldap->login && $ldap->login) {
219							$usertmp->login = $ldap->login;
220							$usertmp->update($usertmp);
221							// TODO Que faire si update echoue car on update avec un login deja existant pour un autre compte.
222						}
223
224						//$resultUpdate = $usertmp->update_ldap2dolibarr($ldap);
225					}
226
227					unset($usertmp);
228				}
229
230				if (!empty($conf->multicompany->enabled)) {	// We must check entity (even if sync is not active)
231					global $mc;
232
233					$usertmp = new User($db);
234					$usertmp->fetch('', $login);
235					$ret = $mc->checkRight($usertmp->id, $entitytotest);
236					if ($ret < 0) {
237						dol_syslog("functions_ldap::check_user_password_ldap Authentication KO entity '".$entitytotest."' not allowed for user id '".$usertmp->id."'", LOG_NOTICE);
238						$login = ''; // force authentication failure
239					}
240					unset($usertmp);
241				}
242			}
243			if ($result == 1) {
244				dol_syslog("functions_ldap::check_user_password_ldap Authentication KO bad user/password for '".$usertotest."'", LOG_NOTICE);
245				sleep(1);
246
247				// Load translation files required by the page
248				$langs->loadLangs(array('main', 'other'));
249
250				$_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorBadLoginPassword");
251			}
252		} else {
253			/* Login failed. Return false, together with the error code and text from
254			 ** the LDAP server. The common error codes and reasons are listed below :
255			 ** (for iPlanet, other servers may differ)
256			 ** 19 - Account locked out (too many invalid login attempts)
257			 ** 32 - User does not exist
258			 ** 49 - Wrong password
259			 ** 53 - Account inactive (manually locked out by administrator)
260			 */
261			dol_syslog("functions_ldap::check_user_password_ldap Authentication KO failed to connect to LDAP for '".$usertotest."'", LOG_NOTICE);
262			if (is_resource($ldap->connection)) {    // If connection ok but bind ko
263				$ldap->ldapErrorCode = ldap_errno($ldap->connection);
264				$ldap->ldapErrorText = ldap_error($ldap->connection);
265				dol_syslog("functions_ldap::check_user_password_ldap ".$ldap->ldapErrorCode." ".$ldap->ldapErrorText);
266			}
267			sleep(2); // Anti brut force protection
268
269			// Load translation files required by the page
270			$langs->loadLangs(array('main', 'other', 'errors'));
271			$_SESSION["dol_loginmesg"] = ($ldap->error ? $ldap->error : $langs->transnoentitiesnoconv("ErrorBadLoginPassword"));
272		}
273
274		$ldap->close();
275	}
276
277	return $login;
278}
279