PageRenderTime 1017ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/htdocs/core/lib/security2.lib.php

http://github.com/Dolibarr/dolibarr
PHP | 528 lines | 341 code | 62 blank | 125 comment | 75 complexity | 9c6f3800df43a3c64cf98a52fe39c9b9 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-3.0, LGPL-2.0, CC-BY-SA-4.0, BSD-3-Clause, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, MIT
  1. <?php
  2. /* Copyright (C) 2008-2011 Laurent Destailleur <eldy@users.sourceforge.net>
  3. * Copyright (C) 2008-2017 Regis Houssin <regis.houssin@inodbox.com>
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. * or see https://www.gnu.org/
  18. */
  19. /**
  20. * \file htdocs/core/lib/security2.lib.php
  21. * \ingroup core
  22. * \brief Set of function used for dolibarr security (not common functions).
  23. * Warning, this file must not depends on other library files, except function.lib.php
  24. * because it is used at low code level.
  25. */
  26. /**
  27. * Return user/group account of web server
  28. *
  29. * @param string $mode 'user' or 'group'
  30. * @return string Return user or group of web server
  31. */
  32. function dol_getwebuser($mode)
  33. {
  34. $t = '?';
  35. if ($mode == 'user') {
  36. $t = getenv('APACHE_RUN_USER'); // $_ENV['APACHE_RUN_USER'] is empty
  37. }
  38. if ($mode == 'group') {
  39. $t = getenv('APACHE_RUN_GROUP');
  40. }
  41. return $t;
  42. }
  43. /**
  44. * Return a login if login/pass was successfull
  45. *
  46. * @param string $usertotest Login value to test
  47. * @param string $passwordtotest Password value to test
  48. * @param string $entitytotest Instance of data we must check
  49. * @param array $authmode Array list of selected authentication mode array('http', 'dolibarr', 'xxx'...)
  50. * @param string $context Context checkLoginPassEntity was created for ('api', 'dav', 'ws', '')
  51. * @return string Login or ''
  52. */
  53. function checkLoginPassEntity($usertotest, $passwordtotest, $entitytotest, $authmode, $context = '')
  54. {
  55. global $conf, $langs;
  56. //global $dolauthmode; // To return authentication finally used
  57. // Check parameters
  58. if ($entitytotest == '') {
  59. $entitytotest = 1;
  60. }
  61. dol_syslog("checkLoginPassEntity usertotest=".$usertotest." entitytotest=".$entitytotest." authmode=".join(',', $authmode));
  62. $login = '';
  63. // Validation of login/pass/entity with standard modules
  64. if (empty($login)) {
  65. $test = true;
  66. foreach ($authmode as $mode) {
  67. if ($test && $mode && !$login) {
  68. // Validation of login/pass/entity for mode $mode
  69. $mode = trim($mode);
  70. $authfile = 'functions_'.$mode.'.php';
  71. $fullauthfile = '';
  72. $dirlogin = array_merge(array("/core/login"), (array) $conf->modules_parts['login']);
  73. foreach ($dirlogin as $reldir) {
  74. $dir = dol_buildpath($reldir, 0);
  75. $newdir = dol_osencode($dir);
  76. // Check if file found (do not use dol_is_file to avoid loading files.lib.php)
  77. $tmpnewauthfile = $newdir.(preg_match('/\/$/', $newdir) ? '' : '/').$authfile;
  78. if (is_file($tmpnewauthfile)) {
  79. $fullauthfile = $tmpnewauthfile;
  80. }
  81. }
  82. $result = false;
  83. if ($fullauthfile) {
  84. $result = include_once $fullauthfile;
  85. }
  86. if ($fullauthfile && $result) {
  87. // Call function to check user/password
  88. $function = 'check_user_password_'.$mode;
  89. $login = call_user_func($function, $usertotest, $passwordtotest, $entitytotest, $context);
  90. if ($login && $login != '--bad-login-validity--') { // Login is successfull
  91. $test = false; // To stop once at first login success
  92. $conf->authmode = $mode; // This properties is defined only when logged to say what mode was successfully used
  93. $dol_tz = GETPOST('tz');
  94. $dol_dst = GETPOST('dst');
  95. $dol_screenwidth = GETPOST('screenwidth');
  96. $dol_screenheight = GETPOST('screenheight');
  97. }
  98. } else {
  99. dol_syslog("Authentication KO - failed to load file '".$authfile."'", LOG_ERR);
  100. sleep(1);
  101. // Load translation files required by the page
  102. $langs->loadLangs(array('other', 'main', 'errors'));
  103. $_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorFailedToLoadLoginFileForMode", $mode);
  104. }
  105. }
  106. }
  107. }
  108. return $login;
  109. }
  110. if (!function_exists('dol_loginfunction')) {
  111. /**
  112. * Show Dolibarr default login page.
  113. * Part of this code is also duplicated into main.inc.php::top_htmlhead
  114. *
  115. * @param Translate $langs Lang object (must be initialized by a new).
  116. * @param Conf $conf Conf object
  117. * @param Societe $mysoc Company object
  118. * @return void
  119. */
  120. function dol_loginfunction($langs, $conf, $mysoc)
  121. {
  122. global $dolibarr_main_demo, $dolibarr_main_force_https;
  123. global $db, $hookmanager;
  124. $langs->loadLangs(array("main", "other", "help", "admin"));
  125. // Instantiate hooks of thirdparty module only if not already define
  126. $hookmanager->initHooks(array('mainloginpage'));
  127. $main_authentication = $conf->file->main_authentication;
  128. $session_name = session_name(); // Get current session name
  129. $dol_url_root = DOL_URL_ROOT;
  130. // Title
  131. $appli = constant('DOL_APPLICATION_TITLE');
  132. $title = $appli.' '.constant('DOL_VERSION');
  133. if (!empty($conf->global->MAIN_APPLICATION_TITLE)) {
  134. $title = $conf->global->MAIN_APPLICATION_TITLE;
  135. }
  136. $titletruedolibarrversion = constant('DOL_VERSION'); // $title used by login template after the @ to inform of true Dolibarr version
  137. // Note: $conf->css looks like '/theme/eldy/style.css.php'
  138. /*
  139. $conf->css = "/theme/".(GETPOST('theme','alpha')?GETPOST('theme','alpha'):$conf->theme)."/style.css.php";
  140. $themepath=dol_buildpath($conf->css,1);
  141. if (! empty($conf->modules_parts['theme'])) // Using this feature slow down application
  142. {
  143. foreach($conf->modules_parts['theme'] as $reldir)
  144. {
  145. if (file_exists(dol_buildpath($reldir.$conf->css, 0)))
  146. {
  147. $themepath=dol_buildpath($reldir.$conf->css, 1);
  148. break;
  149. }
  150. }
  151. }
  152. $conf_css = $themepath."?lang=".$langs->defaultlang;
  153. */
  154. // Select templates dir
  155. if (!empty($conf->modules_parts['tpl'])) { // Using this feature slow down application
  156. $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl/'));
  157. foreach ($dirtpls as $reldir) {
  158. $tmp = dol_buildpath($reldir.'login.tpl.php');
  159. if (file_exists($tmp)) {
  160. $template_dir = preg_replace('/login\.tpl\.php$/', '', $tmp);
  161. break;
  162. }
  163. }
  164. } else {
  165. $template_dir = DOL_DOCUMENT_ROOT."/core/tpl/";
  166. }
  167. // Set cookie for timeout management
  168. $prefix = dol_getprefix('');
  169. $sessiontimeout = 'DOLSESSTIMEOUT_'.$prefix;
  170. if (!empty($conf->global->MAIN_SESSION_TIMEOUT)) {
  171. setcookie($sessiontimeout, $conf->global->MAIN_SESSION_TIMEOUT, 0, "/", null, (empty($dolibarr_main_force_https) ? false : true), true);
  172. }
  173. if (GETPOST('urlfrom', 'alpha')) {
  174. $_SESSION["urlfrom"] = GETPOST('urlfrom', 'alpha');
  175. } else {
  176. unset($_SESSION["urlfrom"]);
  177. }
  178. if (!GETPOST("username", 'alpha')) {
  179. $focus_element = 'username';
  180. } else {
  181. $focus_element = 'password';
  182. }
  183. $demologin = '';
  184. $demopassword = '';
  185. if (!empty($dolibarr_main_demo)) {
  186. $tab = explode(',', $dolibarr_main_demo);
  187. $demologin = $tab[0];
  188. $demopassword = $tab[1];
  189. }
  190. // Execute hook getLoginPageOptions (for table)
  191. $parameters = array('entity' => GETPOST('entity', 'int'), 'switchentity' => GETPOST('switchentity', 'int'));
  192. $reshook = $hookmanager->executeHooks('getLoginPageOptions', $parameters); // Note that $action and $object may have been modified by some hooks.
  193. $morelogincontent = $hookmanager->resPrint;
  194. // Execute hook getLoginPageExtraOptions (eg for js)
  195. $parameters = array('entity' => GETPOST('entity', 'int'), 'switchentity' => GETPOST('switchentity', 'int'));
  196. $reshook = $hookmanager->executeHooks('getLoginPageExtraOptions', $parameters); // Note that $action and $object may have been modified by some hooks.
  197. $moreloginextracontent = $hookmanager->resPrint;
  198. //Redirect after connection
  199. $parameters = array('entity' => GETPOST('entity', 'int'), 'switchentity' => GETPOST('switchentity', 'int'));
  200. $reshook = $hookmanager->executeHooks('redirectAfterConnection', $parameters); // Note that $action and $object may have been modified by some hooks.
  201. $php_self = $hookmanager->resPrint;
  202. // Login
  203. $login = (!empty($hookmanager->resArray['username']) ? $hookmanager->resArray['username'] : (GETPOST("username", "alpha") ? GETPOST("username", "alpha") : $demologin));
  204. $password = $demopassword;
  205. // Show logo (search in order: small company logo, large company logo, theme logo, common logo)
  206. $width = 0;
  207. $urllogo = DOL_URL_ROOT.'/theme/common/login_logo.png';
  208. if (!empty($mysoc->logo_small) && is_readable($conf->mycompany->dir_output.'/logos/thumbs/'.$mysoc->logo_small)) {
  209. $urllogo = DOL_URL_ROOT.'/viewimage.php?cache=1&amp;modulepart=mycompany&amp;file='.urlencode('logos/thumbs/'.$mysoc->logo_small);
  210. } elseif (!empty($mysoc->logo) && is_readable($conf->mycompany->dir_output.'/logos/'.$mysoc->logo)) {
  211. $urllogo = DOL_URL_ROOT.'/viewimage.php?cache=1&amp;modulepart=mycompany&amp;file='.urlencode('logos/'.$mysoc->logo);
  212. $width = 128;
  213. } elseif (!empty($mysoc->logo_squarred_small) && is_readable($conf->mycompany->dir_output.'/logos/thumbs/'.$mysoc->logo_squarred_small)) {
  214. $urllogo = DOL_URL_ROOT.'/viewimage.php?cache=1&amp;modulepart=mycompany&amp;file='.urlencode('logos/thumbs/'.$mysoc->logo_squarred_small);
  215. } elseif (is_readable(DOL_DOCUMENT_ROOT.'/theme/dolibarr_logo.svg')) {
  216. $urllogo = DOL_URL_ROOT.'/theme/dolibarr_logo.svg';
  217. }
  218. // Security graphical code
  219. $captcha = 0;
  220. $captcha_refresh = '';
  221. if (function_exists("imagecreatefrompng") && !empty($conf->global->MAIN_SECURITY_ENABLECAPTCHA)) {
  222. $captcha = 1;
  223. $captcha_refresh = img_picto($langs->trans("Refresh"), 'refresh', 'id="captcha_refresh_img"');
  224. }
  225. // Extra link
  226. $forgetpasslink = 0;
  227. $helpcenterlink = 0;
  228. if (empty($conf->global->MAIN_SECURITY_DISABLEFORGETPASSLINK) || empty($conf->global->MAIN_HELPCENTER_DISABLELINK)) {
  229. if (empty($conf->global->MAIN_SECURITY_DISABLEFORGETPASSLINK)) {
  230. $forgetpasslink = 1;
  231. }
  232. if (empty($conf->global->MAIN_HELPCENTER_DISABLELINK)) {
  233. $helpcenterlink = 1;
  234. }
  235. }
  236. // Home message
  237. $main_home = '';
  238. if (!empty($conf->global->MAIN_HOME)) {
  239. $substitutionarray = getCommonSubstitutionArray($langs);
  240. complete_substitutions_array($substitutionarray, $langs);
  241. $texttoshow = make_substitutions($conf->global->MAIN_HOME, $substitutionarray, $langs);
  242. $main_home = dol_htmlcleanlastbr($texttoshow);
  243. }
  244. // Google AD
  245. $main_google_ad_client = ((!empty($conf->global->MAIN_GOOGLE_AD_CLIENT) && !empty($conf->global->MAIN_GOOGLE_AD_SLOT)) ? 1 : 0);
  246. // Set jquery theme
  247. $dol_loginmesg = (!empty($_SESSION["dol_loginmesg"]) ? $_SESSION["dol_loginmesg"] : '');
  248. $favicon = DOL_URL_ROOT.'/theme/dolibarr_256x256_color.png';
  249. if (!empty($mysoc->logo_squarred_mini)) {
  250. $favicon = DOL_URL_ROOT.'/viewimage.php?cache=1&modulepart=mycompany&file='.urlencode('logos/thumbs/'.$mysoc->logo_squarred_mini);
  251. }
  252. if (!empty($conf->global->MAIN_FAVICON_URL)) {
  253. $favicon = $conf->global->MAIN_FAVICON_URL;
  254. }
  255. $jquerytheme = 'base';
  256. if (!empty($conf->global->MAIN_USE_JQUERY_THEME)) {
  257. $jquerytheme = $conf->global->MAIN_USE_JQUERY_THEME;
  258. }
  259. // Set dol_hide_topmenu, dol_hide_leftmenu, dol_optimize_smallscreen, dol_no_mouse_hover
  260. $dol_hide_topmenu = GETPOST('dol_hide_topmenu', 'int');
  261. $dol_hide_leftmenu = GETPOST('dol_hide_leftmenu', 'int');
  262. $dol_optimize_smallscreen = GETPOST('dol_optimize_smallscreen', 'int');
  263. $dol_no_mouse_hover = GETPOST('dol_no_mouse_hover', 'int');
  264. $dol_use_jmobile = GETPOST('dol_use_jmobile', 'int');
  265. // Include login page template
  266. include $template_dir.'login.tpl.php';
  267. // Global html output events ($mesgs, $errors, $warnings)
  268. dol_htmloutput_events(0);
  269. $_SESSION["dol_loginmesg"] = '';
  270. }
  271. }
  272. /**
  273. * Fonction pour initialiser un salt pour la fonction crypt.
  274. *
  275. * @param int $type 2=>renvoi un salt pour cryptage DES
  276. * 12=>renvoi un salt pour cryptage MD5
  277. * non defini=>renvoi un salt pour cryptage par defaut
  278. * @return string Salt string
  279. */
  280. function makesalt($type = CRYPT_SALT_LENGTH)
  281. {
  282. dol_syslog("makesalt type=".$type);
  283. switch ($type) {
  284. case 12: // 8 + 4
  285. $saltlen = 8;
  286. $saltprefix = '$1$';
  287. $saltsuffix = '$';
  288. break;
  289. case 8: // 8 (Pour compatibilite, ne devrait pas etre utilise)
  290. $saltlen = 8;
  291. $saltprefix = '$1$';
  292. $saltsuffix = '$';
  293. break;
  294. case 2: // 2
  295. default: // by default, fall back on Standard DES (should work everywhere)
  296. $saltlen = 2;
  297. $saltprefix = '';
  298. $saltsuffix = '';
  299. break;
  300. }
  301. $salt = '';
  302. while (dol_strlen($salt) < $saltlen) {
  303. $salt .= chr(mt_rand(64, 126));
  304. }
  305. $result = $saltprefix.$salt.$saltsuffix;
  306. dol_syslog("makesalt return=".$result);
  307. return $result;
  308. }
  309. /**
  310. * Encode or decode database password in config file
  311. *
  312. * @param int $level Encode level: 0 no encoding, 1 encoding
  313. * @return int <0 if KO, >0 if OK
  314. */
  315. function encodedecode_dbpassconf($level = 0)
  316. {
  317. dol_syslog("encodedecode_dbpassconf level=".$level, LOG_DEBUG);
  318. $config = '';
  319. $passwd = '';
  320. $passwd_crypted = '';
  321. if ($fp = fopen(DOL_DOCUMENT_ROOT.'/conf/conf.php', 'r')) {
  322. while (!feof($fp)) {
  323. $buffer = fgets($fp, 4096);
  324. $lineofpass = 0;
  325. if (preg_match('/^[^#]*dolibarr_main_db_encrypted_pass[\s]*=[\s]*(.*)/i', $buffer, $reg)) { // Old way to save crypted value
  326. $val = trim($reg[1]); // This also remove CR/LF
  327. $val = preg_replace('/^["\']/', '', $val);
  328. $val = preg_replace('/["\'][\s;]*$/', '', $val);
  329. if (!empty($val)) {
  330. $passwd_crypted = $val;
  331. $val = dol_decode($val);
  332. $passwd = $val;
  333. $lineofpass = 1;
  334. }
  335. } elseif (preg_match('/^[^#]*dolibarr_main_db_pass[\s]*=[\s]*(.*)/i', $buffer, $reg)) {
  336. $val = trim($reg[1]); // This also remove CR/LF
  337. $val = preg_replace('/^["\']/', '', $val);
  338. $val = preg_replace('/["\'][\s;]*$/', '', $val);
  339. if (preg_match('/crypted:/i', $buffer)) {
  340. $val = preg_replace('/crypted:/i', '', $val);
  341. $passwd_crypted = $val;
  342. $val = dol_decode($val);
  343. $passwd = $val;
  344. } else {
  345. $passwd = $val;
  346. $val = dol_encode($val);
  347. $passwd_crypted = $val;
  348. }
  349. $lineofpass = 1;
  350. }
  351. // Output line
  352. if ($lineofpass) {
  353. // Add value at end of file
  354. if ($level == 0) {
  355. $config .= '$dolibarr_main_db_pass=\''.$passwd.'\';'."\n";
  356. }
  357. if ($level == 1) {
  358. $config .= '$dolibarr_main_db_pass=\'crypted:'.$passwd_crypted.'\';'."\n";
  359. }
  360. //print 'passwd = '.$passwd.' - passwd_crypted = '.$passwd_crypted;
  361. //exit;
  362. } else {
  363. $config .= $buffer;
  364. }
  365. }
  366. fclose($fp);
  367. // Write new conf file
  368. $file = DOL_DOCUMENT_ROOT.'/conf/conf.php';
  369. if ($fp = @fopen($file, 'w')) {
  370. fputs($fp, $config);
  371. fflush($fp);
  372. fclose($fp);
  373. clearstatcache();
  374. // It's config file, so we set read permission for creator only.
  375. // Should set permission to web user and groups for users used by batch
  376. //@chmod($file, octdec('0600'));
  377. return 1;
  378. } else {
  379. dol_syslog("encodedecode_dbpassconf Failed to open conf.php file for writing", LOG_WARNING);
  380. return -1;
  381. }
  382. } else {
  383. dol_syslog("encodedecode_dbpassconf Failed to read conf.php", LOG_ERR);
  384. return -2;
  385. }
  386. }
  387. /**
  388. * Return a generated password using default module
  389. *
  390. * @param boolean $generic true=Create generic password (32 chars/numbers), false=Use the configured password generation module
  391. * @param array $replaceambiguouschars Discard ambigous characters. For example array('I').
  392. * @param int $length Length of random string (Used only if $generic is true)
  393. * @return string New value for password
  394. * @see dol_hash()
  395. */
  396. function getRandomPassword($generic = false, $replaceambiguouschars = null, $length = 32)
  397. {
  398. global $db, $conf, $langs, $user;
  399. $generated_password = '';
  400. if ($generic) {
  401. $lowercase = "qwertyuiopasdfghjklzxcvbnm";
  402. $uppercase = "ASDFGHJKLZXCVBNMQWERTYUIOP";
  403. $numbers = "1234567890";
  404. $randomCode = "";
  405. $nbofchar = round($length / 3);
  406. $nbofcharlast = ($length - 2 * $nbofchar);
  407. //var_dump($nbofchar.'-'.$nbofcharlast);
  408. if (function_exists('random_int')) { // Cryptographic random
  409. $max = strlen($lowercase) - 1;
  410. for ($x = 0; $x < $nbofchar; $x++) {
  411. $tmp = random_int(0, $max);
  412. $randomCode .= $lowercase[$tmp];
  413. }
  414. $max = strlen($uppercase) - 1;
  415. for ($x = 0; $x < $nbofchar; $x++) {
  416. $tmp = random_int(0, $max);
  417. $randomCode .= $uppercase[$tmp];
  418. }
  419. $max = strlen($numbers) - 1;
  420. for ($x = 0; $x < $nbofcharlast; $x++) {
  421. $tmp = random_int(0, $max);
  422. $randomCode .= $numbers[$tmp];
  423. }
  424. $generated_password = str_shuffle($randomCode);
  425. } else {
  426. // Old platform, non cryptographic random
  427. $max = strlen($lowercase) - 1;
  428. for ($x = 0; $x < $nbofchar; $x++) {
  429. $tmp = mt_rand(0, $max);
  430. $randomCode .= $lowercase[$tmp];
  431. }
  432. $max = strlen($uppercase) - 1;
  433. for ($x = 0; $x < $nbofchar; $x++) {
  434. $tmp = mt_rand(0, $max);
  435. $randomCode .= $uppercase[$tmp];
  436. }
  437. $max = strlen($numbers) - 1;
  438. for ($x = 0; $x < $nbofcharlast; $x++) {
  439. $tmp = mt_rand(0, $max);
  440. $randomCode .= $numbers[$tmp];
  441. }
  442. $generated_password = str_shuffle($randomCode);
  443. }
  444. } elseif (!empty($conf->global->USER_PASSWORD_GENERATED)) {
  445. $nomclass = "modGeneratePass".ucfirst($conf->global->USER_PASSWORD_GENERATED);
  446. $nomfichier = $nomclass.".class.php";
  447. //print DOL_DOCUMENT_ROOT."/core/modules/security/generate/".$nomclass;
  448. require_once DOL_DOCUMENT_ROOT."/core/modules/security/generate/".$nomfichier;
  449. $genhandler = new $nomclass($db, $conf, $langs, $user);
  450. $generated_password = $genhandler->getNewGeneratedPassword();
  451. unset($genhandler);
  452. }
  453. // Do we have to discard some alphabetic characters ?
  454. if (is_array($replaceambiguouschars) && count($replaceambiguouschars) > 0) {
  455. $numbers = "ABCDEF";
  456. $max = strlen($numbers) - 1;
  457. if (function_exists('random_int')) { // Cryptographic random
  458. $tmp = random_int(0, $max);
  459. $generated_password = str_replace($replaceambiguouschars, $numbers[$tmp], $generated_password);
  460. } else {
  461. $tmp = mt_rand(0, $max);
  462. $generated_password = str_replace($replaceambiguouschars, $numbers[$tmp], $generated_password);
  463. }
  464. }
  465. return $generated_password;
  466. }