1<?php
2/* Copyright (C) 2004-2009 Laurent Destailleur  <eldy@users.sourceforge.net>
3 * Copyright (C) 2005-2007 Regis Houssin        <regis.houssin@inodbox.com>
4 * Copyright (C) 2013-2015 Juanjo Menent		<jmenent@2byte.es>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20/**
21 *		\file       htdocs/admin/security.php
22 *      \ingroup    setup
23 *      \brief      Page de configuration du module securite
24 */
25
26require '../main.inc.php';
27require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
28require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
29
30$action = GETPOST('action', 'aZ09');
31
32// Load translation files required by the page
33$langs->loadLangs(array("users", "admin", "other"));
34
35if (!$user->admin) accessforbidden();
36
37// Allow/Disallow change to clear passwords once passwords are crypted
38$allow_disable_encryption = true;
39
40/*
41 * Actions
42 */
43if ($action == 'setgeneraterule')
44{
45	if (!dolibarr_set_const($db, 'USER_PASSWORD_GENERATED', $_GET["value"], 'chaine', 0, '', $conf->entity))
46	{
47		dol_print_error($db);
48	} else {
49		header("Location: ".$_SERVER["PHP_SELF"]);
50		exit;
51	}
52}
53
54if ($action == 'activate_encrypt')
55{
56	$error = 0;
57
58	$db->begin();
59
60	dolibarr_set_const($db, "DATABASE_PWD_ENCRYPTED", "1", 'chaine', 0, '', $conf->entity);
61
62	$sql = "SELECT u.rowid, u.pass, u.pass_crypted";
63	$sql .= " FROM ".MAIN_DB_PREFIX."user as u";
64	$sql .= " WHERE u.pass IS NOT NULL AND LENGTH(u.pass) < 32"; // Not a MD5 value
65
66	$resql = $db->query($sql);
67	if ($resql)
68	{
69		$numrows = $db->num_rows($resql);
70		$i = 0;
71		while ($i < $numrows)
72		{
73			$obj = $db->fetch_object($resql);
74			if (dol_hash($obj->pass))
75			{
76				$sql = "UPDATE ".MAIN_DB_PREFIX."user";
77				$sql .= " SET pass_crypted = '".dol_hash($obj->pass)."', pass = NULL";
78				$sql .= " WHERE rowid=".$obj->rowid;
79				//print $sql;
80
81				$resql2 = $db->query($sql);
82				if (!$resql2)
83				{
84					dol_print_error($db);
85					$error++;
86					break;
87				}
88
89				$i++;
90			}
91		}
92	} else dol_print_error($db);
93
94	//print $error." ".$sql;
95	//exit;
96	if (!$error)
97	{
98		$db->commit();
99		header("Location: security.php");
100		exit;
101	} else {
102		$db->rollback();
103		dol_print_error($db, '');
104	}
105} elseif ($action == 'disable_encrypt')
106{
107	//On n'autorise pas l'annulation de l'encryption car les mots de passe ne peuvent pas etre decodes
108	//Do not allow "disable encryption" as passwords cannot be decrypted
109	if ($allow_disable_encryption)
110	{
111		dolibarr_del_const($db, "DATABASE_PWD_ENCRYPTED", $conf->entity);
112	}
113	header("Location: security.php");
114	exit;
115}
116
117if ($action == 'activate_encryptdbpassconf')
118{
119	$result = encodedecode_dbpassconf(1);
120	if ($result > 0)
121	{
122		sleep(3); // Don't know why but we need to wait file is completely saved before making the reload. Even with flush and clearstatcache, we need to wait.
123
124		// database value not required
125		//dolibarr_set_const($db, "MAIN_DATABASE_PWD_CONFIG_ENCRYPTED", "1");
126		header("Location: security.php");
127		exit;
128	} else {
129		setEventMessages($langs->trans('InstrucToEncodePass', dol_encode($dolibarr_main_db_pass)), null, 'warnings');
130	}
131} elseif ($action == 'disable_encryptdbpassconf')
132{
133	$result = encodedecode_dbpassconf(0);
134	if ($result > 0)
135	{
136		sleep(3); // Don't know why but we need to wait file is completely saved before making the reload. Even with flush and clearstatcache, we need to wait.
137
138		// database value not required
139		//dolibarr_del_const($db, "MAIN_DATABASE_PWD_CONFIG_ENCRYPTED",$conf->entity);
140		header("Location: security.php");
141		exit;
142	} else {
143		setEventMessages($langs->trans('InstrucToClearPass', $dolibarr_main_db_pass), null, 'warnings');
144	}
145}
146
147if ($action == 'activate_MAIN_SECURITY_DISABLEFORGETPASSLINK')
148{
149	dolibarr_set_const($db, "MAIN_SECURITY_DISABLEFORGETPASSLINK", '1', 'chaine', 0, '', $conf->entity);
150	header("Location: security.php");
151	exit;
152} elseif ($action == 'disable_MAIN_SECURITY_DISABLEFORGETPASSLINK')
153{
154	dolibarr_del_const($db, "MAIN_SECURITY_DISABLEFORGETPASSLINK", $conf->entity);
155	header("Location: security.php");
156	exit;
157}
158
159if ($action == 'updatepattern')
160{
161	$pattern = GETPOST("pattern", "alpha");
162	$explodePattern = explode(';', $pattern);
163
164	$patternInError = false;
165	if ($explodePattern[0] < 1 || $explodePattern[4] < 0) {
166		$patternInError = true;
167	}
168
169	if ($explodePattern[0] < $explodePattern[1] + $explodePattern[2] + $explodePattern[3]) {
170		$patternInError = true;
171	}
172
173	if (!$patternInError) {
174		dolibarr_set_const($db, "USER_PASSWORD_PATTERN", $pattern, 'chaine', 0, '', $conf->entity);
175		setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
176		header("Location: security.php");
177		exit;
178	}
179}
180
181
182
183/*
184 * View
185 */
186
187$form = new Form($db);
188
189$wikihelp = 'EN:Setup_Security|FR:Paramétrage_Sécurité|ES:Configuración_Seguridad';
190llxHeader('', $langs->trans("Passwords"), $wikihelp);
191
192print load_fiche_titre($langs->trans("SecuritySetup"), '', 'title_setup');
193
194print '<span class="opacitymedium">'.$langs->trans("GeneratedPasswordDesc")."</span><br>\n";
195print "<br>\n";
196
197
198$head = security_prepare_head();
199
200print dol_get_fiche_head($head, 'passwords', '', -1);
201
202
203// Choix du gestionnaire du generateur de mot de passe
204print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
205print '<input type="hidden" name="token" value="'.newToken().'">';
206print '<input type="hidden" name="action" value="update">';
207print '<input type="hidden" name="constname" value="USER_PASSWORD_GENERATED">';
208print '<input type="hidden" name="consttype" value="yesno">';
209
210// Charge tableau des modules generation
211$dir = "../core/modules/security/generate";
212clearstatcache();
213$handle = opendir($dir);
214$i = 1;
215if (is_resource($handle))
216{
217	while (($file = readdir($handle)) !== false)
218	{
219		if (preg_match('/(modGeneratePass[a-z]+)\.class\.php$/i', $file, $reg))
220		{
221			// Charging the numbering class
222			$classname = $reg[1];
223			require_once $dir.'/'.$file;
224
225			$obj = new $classname($db, $conf, $langs, $user);
226			$arrayhandler[$obj->id] = $obj;
227			$i++;
228		}
229	}
230	closedir($handle);
231}
232asort($arrayhandler);
233
234print '<table class="noborder centpercent">';
235print '<tr class="liste_titre">';
236print '<td colspan="2">'.$langs->trans("RuleForGeneratedPasswords").'</td>';
237print '<td>'.$langs->trans("Example").'</td>';
238print '<td class="center">'.$langs->trans("Activated").'</td>';
239print '</tr>';
240
241foreach ($arrayhandler as $key => $module)
242{
243	// Show modules according to features level
244	if ($module->version == 'development' && $conf->global->MAIN_FEATURES_LEVEL < 2) continue;
245	if ($module->version == 'experimental' && $conf->global->MAIN_FEATURES_LEVEL < 1) continue;
246
247	if ($module->isEnabled())
248	{
249		print '<tr class="oddeven"><td width="100">';
250		print ucfirst($key);
251		print "</td><td>\n";
252		print $module->getDescription().'<br>';
253		print $langs->trans("MinLength").': '.$module->length;
254		print '</td>';
255
256		// Show example of numbering module
257		print '<td class="nowrap">';
258		$tmp = $module->getExample();
259		if (preg_match('/^Error/', $tmp)) {
260			$langs->load("errors");
261			print '<div class="error">'.$langs->trans($tmp).'</div>';
262		} elseif ($tmp == 'NotConfigured') print $langs->trans($tmp);
263		else print $tmp;
264		print '</td>'."\n";
265
266		print '<td width="100" align="center">';
267		if ($conf->global->USER_PASSWORD_GENERATED == $key)
268		{
269			print img_picto('', 'tick');
270		} else {
271			print '<a href="'.$_SERVER['PHP_SELF'].'?action=setgeneraterule&amp;token='.newToken().'&amp;value='.$key.'">'.$langs->trans("Activate").'</a>';
272		}
273		print "</td></tr>\n";
274	}
275}
276print '</table>';
277print '</form>';
278
279//if($conf->global->MAIN_SECURITY_DISABLEFORGETPASSLINK == 1)
280// Patter for Password Perso
281if ($conf->global->USER_PASSWORD_GENERATED == "Perso") {
282	$tabConf = explode(";", $conf->global->USER_PASSWORD_PATTERN);
283	print '<br>';
284	print '<table class="noborder centpercent">';
285	print '<tr class="liste_titre">';
286	print '<td colspan="3"> '.$langs->trans("PasswordPatternDesc").'</td>';
287	print '</tr>';
288
289
290	print '<tr class="oddeven">';
291	print '<td>'.$langs->trans("MinLength")."</td>";
292	print '<td colspan="2"><input type="number" value="'.$tabConf[0].'" id="minlenght" min="1"></td>';
293	print '</tr>';
294
295
296	print '<tr class="oddeven">';
297	print '<td>'.$langs->trans("NbMajMin")."</td>";
298	print '<td colspan="2"><input type="number" value="'.$tabConf[1].'" id="NbMajMin" min="0"></td>';
299	print '</tr>';
300
301
302	print '<tr class="oddeven">';
303	print '<td>'.$langs->trans("NbNumMin")."</td>";
304	print '<td colspan="2"><input type="number" value="'.$tabConf[2].'" id="NbNumMin" min="0"></td>';
305	print '</tr>';
306
307
308	print '<tr class="oddeven">';
309	print '<td>'.$langs->trans("NbSpeMin")."</td>";
310	print '<td colspan="2"><input type="number" value="'.$tabConf[3].'" id="NbSpeMin" min="0"></td>';
311	print '</tr>';
312
313
314	print '<tr class="oddeven">';
315	print '<td>'.$langs->trans("NbIteConsecutive")."</td>";
316	print '<td colspan="2"><input type="number" value="'.$tabConf[4].'" id="NbIteConsecutive" min="0"></td>';
317	print '</tr>';
318
319
320	print '<tr class="oddeven">';
321	print '<td>'.$langs->trans("NoAmbiCaracAutoGeneration")."</td>";
322	print '<td colspan="2"><input type="checkbox" id="NoAmbiCaracAutoGeneration" '.($tabConf[5] ? "checked" : "").' min="0"> <span id="textcheckbox">'.($tabConf[5] ? $langs->trans("Activated") : $langs->trans("Disabled")).'</span></td>';
323	print '</tr>';
324
325	print '</table>';
326
327	print '<br>';
328	print '<div class="center">';
329	print '<a class="button button-save" id="linkChangePattern">'.$langs->trans("Save").'</a>';
330	print '</div>';
331	print '<br><br>';
332
333	print '<script type="text/javascript">';
334	print '	function getStringArg(){';
335	print '		var pattern = "";';
336	print '		pattern += $("#minlenght").val() + ";";';
337	print '		pattern += $("#NbMajMin").val() + ";";';
338	print '		pattern += $("#NbNumMin").val() + ";";';
339	print '		pattern += $("#NbSpeMin").val() + ";";';
340	print '		pattern += $("#NbIteConsecutive").val() + ";";';
341	print '		pattern += $("#NoAmbiCaracAutoGeneration")[0].checked ? "1" : "0";';
342	print '		return pattern;';
343	print '	}';
344
345	print '	function valuePossible(){';
346	print '		var fields = ["#minlenght", "#NbMajMin", "#NbNumMin", "#NbSpeMin", "#NbIteConsecutive"];';
347	print '		for(var i = 0 ; i < fields.length ; i++){';
348	print '		    if($(fields[i]).val() < $(fields[i]).attr("min")){';
349	print '		        return false;';
350	print '		    }';
351	print '		}';
352	print '		';
353	print '		var length = parseInt($("#minlenght").val());';
354	print '		var length_mini = parseInt($("#NbMajMin").val()) + parseInt($("#NbNumMin").val()) + parseInt($("#NbSpeMin").val());';
355	print '		return length >= length_mini;';
356	print '	}';
357
358	print '	function generatelink(){';
359	print '		return "security.php?action=updatepattern&pattern="+getStringArg();';
360	print '	}';
361
362	print '	function valuePatternChange(){';
363	print '     console.log("valuePatternChange");';
364	print '		var lang_save = "'.$langs->trans("Save").'";';
365	print '		var lang_error = "'.$langs->trans("Error").'";';
366	print '		var lang_Disabled = "'.$langs->trans("Disabled").'";';
367	print '		var lang_Activated = "'.$langs->trans("Activated").'";';
368	print '		$("#textcheckbox").html($("#NoAmbiCaracAutoGeneration")[0].checked ? unescape(lang_Activated) : unescape(lang_Disabled));';
369	print '		if(valuePossible()){';
370	print '			$("#linkChangePattern").attr("href",generatelink()).text(lang_save);';
371	print '		}';
372	print '		else{';
373	print '			$("#linkChangePattern").attr("href", null).text(lang_error);';
374	print '		}';
375	print '	}';
376
377	print '	$("#minlenght").change(function(){valuePatternChange();});';
378	print '	$("#NbMajMin").change(function(){valuePatternChange();});';
379	print '	$("#NbNumMin").change(function(){valuePatternChange();});';
380	print '	$("#NbSpeMin").change(function(){valuePatternChange();});';
381	print '	$("#NbIteConsecutive").change(function(){valuePatternChange();});';
382	print '	$("#NoAmbiCaracAutoGeneration").change(function(){valuePatternChange();});';
383
384	print '</script>';
385}
386
387
388// Cryptage mot de passe
389print '<br>';
390print "<form method=\"post\" action=\"".$_SERVER["PHP_SELF"]."\">";
391print '<input type="hidden" name="token" value="'.newToken().'">';
392print "<input type=\"hidden\" name=\"action\" value=\"encrypt\">";
393
394print '<table class="noborder centpercent">';
395print '<tr class="liste_titre">';
396print '<td colspan="3">'.$langs->trans("Parameters").'</td>';
397print '<td class="center">'.$langs->trans("Activated").'</td>';
398print '<td class="center">'.$langs->trans("Action").'</td>';
399print '</tr>';
400
401// Disable clear password in database
402print '<tr class="oddeven">';
403print '<td colspan="3">'.$langs->trans("DoNotStoreClearPassword").'</td>';
404print '<td align="center" width="60">';
405if (!empty($conf->global->DATABASE_PWD_ENCRYPTED))
406{
407	print img_picto($langs->trans("Active"), 'tick');
408}
409print '</td>';
410if (!$conf->global->DATABASE_PWD_ENCRYPTED)
411{
412	print '<td align="center" width="100">';
413	print '<a href="security.php?action=activate_encrypt">'.$langs->trans("Activate").'</a>';
414	print "</td>";
415}
416
417// Database conf file encryption
418if (!empty($conf->global->DATABASE_PWD_ENCRYPTED))
419{
420	print '<td align="center" width="100">';
421	if ($allow_disable_encryption)
422	{
423		//On n'autorise pas l'annulation de l'encryption car les mots de passe ne peuvent pas etre decodes
424	  	//Do not allow "disable encryption" as passwords cannot be decrypted
425	  	print '<a href="security.php?action=disable_encrypt">'.$langs->trans("Disable").'</a>';
426	} else {
427		print '-';
428	}
429	print "</td>";
430}
431print "</td>";
432print '</tr>';
433
434// Cryptage du mot de base de la base dans conf.php
435
436print '<tr class="oddeven">';
437print '<td colspan="3">'.$langs->trans("MainDbPasswordFileConfEncrypted").'</td>';
438print '<td align="center" width="60">';
439if (preg_match('/crypted:/i', $dolibarr_main_db_pass) || !empty($dolibarr_main_db_encrypted_pass))
440{
441	print img_picto($langs->trans("Active"), 'tick');
442}
443
444print '</td>';
445
446print '<td align="center" width="100">';
447if (empty($dolibarr_main_db_pass) && empty($dolibarr_main_db_encrypted_pass))
448{
449	$langs->load("errors");
450	print img_warning($langs->trans("WarningPassIsEmpty"));
451} else {
452	if (empty($dolibarr_main_db_encrypted_pass))
453	{
454		print '<a href="security.php?action=activate_encryptdbpassconf">'.$langs->trans("Activate").'</a>';
455	}
456	if (!empty($dolibarr_main_db_encrypted_pass))
457	{
458		print '<a href="security.php?action=disable_encryptdbpassconf">'.$langs->trans("Disable").'</a>';
459	}
460}
461print "</td>";
462
463print "</td>";
464print '</tr>';
465
466
467// Disable link "Forget password" on logon
468
469print '<tr class="oddeven">';
470print '<td colspan="3">'.$langs->trans("DisableForgetPasswordLinkOnLogonPage").'</td>';
471print '<td align="center" width="60">';
472if (!empty($conf->global->MAIN_SECURITY_DISABLEFORGETPASSLINK))
473{
474	print img_picto($langs->trans("Active"), 'tick');
475}
476print '</td>';
477if (empty($conf->global->MAIN_SECURITY_DISABLEFORGETPASSLINK))
478{
479	print '<td align="center" width="100">';
480	print '<a href="security.php?action=activate_MAIN_SECURITY_DISABLEFORGETPASSLINK">'.$langs->trans("Activate").'</a>';
481	print "</td>";
482}
483if (!empty($conf->global->MAIN_SECURITY_DISABLEFORGETPASSLINK))
484{
485	print '<td align="center" width="100">';
486	print '<a href="security.php?action=disable_MAIN_SECURITY_DISABLEFORGETPASSLINK">'.$langs->trans("Disable").'</a>';
487	print "</td>";
488}
489print "</td>";
490print '</tr>';
491
492
493print '</table>';
494print '</form>';
495print '<br>';
496
497if (GETPOST('info', 'int') > 0)
498{
499	if (function_exists('password_hash'))
500	{
501		print $langs->trans("Note: The function password_hash exists on your PHP")."<br>\n";
502	} else {
503		print $langs->trans("Note: The function password_hash does not exists on your PHP")."<br>\n";
504	}
505	print 'MAIN_SECURITY_HASH_ALGO = '.$conf->global->MAIN_SECURITY_HASH_ALGO."<br>\n";
506	print 'MAIN_SECURITY_SALT = '.$conf->global->MAIN_SECURITY_SALT."<br>\n";
507}
508
509print '</div>';
510
511// End of page
512llxFooter();
513$db->close();
514