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