diff --git a/lam-packaging/debian/README.Debian b/lam-packaging/debian/README.Debian index dbc34a966..34698b55a 100644 --- a/lam-packaging/debian/README.Debian +++ b/lam-packaging/debian/README.Debian @@ -14,7 +14,6 @@ Configuration: All settings can be edited via the webfrontend. Please point your browser to the LAM start page and then select "LAM configuration". - The default password for the configuration is "lam". Lamdaemon: diff --git a/lam-packaging/docker/start.sh b/lam-packaging/docker/start.sh index bf5db8d9d..d2adf7db7 100755 --- a/lam-packaging/docker/start.sh +++ b/lam-packaging/docker/start.sh @@ -4,7 +4,7 @@ # This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) # Copyright (C) 2019 Felix Bartels -# 2019 - 2024 Roland Gruber +# 2019 - 2025 Roland Gruber # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -59,7 +59,6 @@ if [ "$LAM_SKIP_PRECONFIGURE" != "true" ]; then LAM_CONFIGURATION_PASSWORD="${LAM_CONFIGURATION_PASSWORD:-}" sed -i -f- /etc/ldap-account-manager/config.cfg <<- EOF - s|"password": "[^"]*"|"password": "${LAM_PASSWORD_SSHA}"|; s|"license": "[^"]*"|"license": "${LAM_LICENSE}"|; s|"configDatabaseType": "[^"]*"|"configDatabaseType": "${LAM_CONFIGURATION_DATABASE}"|; s|"configDatabaseServer": "[^"]*"|"configDatabaseServer": "${LAM_CONFIGURATION_HOST}"|; @@ -68,6 +67,9 @@ if [ "$LAM_SKIP_PRECONFIGURE" != "true" ]; then s|"configDatabaseUser": "[^"]*"|"configDatabaseUser": "${LAM_CONFIGURATION_USER}"|; s|"configDatabasePassword": "[^"]*"|"configDatabasePassword": "${LAM_CONFIGURATION_PASSWORD}"|; EOF + if ! grep -e '"password":' /etc/ldap-account-manager/config.cfg > /dev/null; then + sed -i "2i\ \ \"password\": \"${LAM_PASSWORD_SSHA}\"," /etc/ldap-account-manager/config.cfg + fi unset LAM_PASSWORD set +e @@ -82,12 +84,14 @@ EOF sed -i -f- /var/lib/ldap-account-manager/config/lam.conf <<- EOF s|"ServerURL": "[^"]*"|"ServerURL": "${LDAP_SERVER}"|; s|"Admins": "[^"]*"|"Admins": "${LDAP_ADMIN_USER}"|; - s|"Passwd": "[^"]*"|"Passwd": "${LAM_PASSWORD_SSHA}"|; s|"treeViewSuffix": "[^"]*"|"treeViewSuffix": "${LDAP_BASE_DN}"|; s|"defaultLanguage": "[^"]*"|"defaultLanguage": "${LAM_LANG}.utf8"|; s|"suffix_user": "[^"]*"|"suffix_user": "${LDAP_USERS_DN}"|; s|"suffix_group": "[^"]*"|"suffix_group": "${LDAP_GROUPS_DN}"|; EOF + if ! grep -e '"Passwd":' /var/lib/ldap-account-manager/config/lam.conf > /dev/null; then + sed -i "2i\ \ \"Passwd\": \"${LAM_PASSWORD_SSHA}\"," /var/lib/ldap-account-manager/config/lam.conf + fi fi diff --git a/lam/HISTORY b/lam/HISTORY index b2d6d5790..6f61b00e2 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -1,5 +1,6 @@ March 2025 9.1 - Usability improvements (348, 360) + - Security: LAM no longer ships with any default passwords, main configuration password is requested on login if not yet set (#390) - Fixed bugs: -> Ambiguous tooltip on profile editor for Shadow users (#394) -> Self service photo file enhancements (#396) diff --git a/lam/README b/lam/README index b9aab3c01..a7bd58eb9 100644 --- a/lam/README +++ b/lam/README @@ -15,9 +15,6 @@ LAM - Readme Installation and documentation: Please see the LAM manual in docs/manual/index.html. - Default password: - The default password to edit the configuration options is "lam". - Download: You can get the newest version at https://www.ldap-account-manager.org/. diff --git a/lam/config/addressbook.sample.conf b/lam/config/addressbook.sample.conf index ba6fa60d4..3b46d4538 100644 --- a/lam/config/addressbook.sample.conf +++ b/lam/config/addressbook.sample.conf @@ -3,7 +3,6 @@ "useTLS": "yes", "followReferrals": "false", "pagedResults": "false", - "Passwd": "{CRYPT-SHA512}$6$ZJcXwaxHP0GQH0Rd$Ggkn8Wz\/8ntCM9v0TywomjkgSvV.3BoayFwnc9QP3MV.b7HWaqLOA8urP2e7HyEmU\/JmC8xR7jTqrXCHC4kFr. WkpjWHdheEhQMEdRSDBSZA==", "Admins": "cn=Manager,dc=my-domain,dc=com", "defaultLanguage": "en_GB.utf8", "scriptPath": "", diff --git a/lam/config/config.cfg.sample b/lam/config/config.cfg.sample index 046734c5e..f1ed0d05d 100644 --- a/lam/config/config.cfg.sample +++ b/lam/config/config.cfg.sample @@ -1,5 +1,4 @@ { - "password": "{CRYPT-SHA512}$6$WheNHdlVwDoL4s.x$DrZ10TpIGQa5wd0jbvtm8eaTleJCf1nec3ihOaNwMdPUKVFCphXwtnTSmFFXjhGa45RlrSEWhDVyjLCMiV\/.c. V2hlTkhkbFZ3RG9MNHMueA==", "default": "lam", "sessionTimeout": "30", "hideLoginErrorDetails": "false", diff --git a/lam/config/samba3.sample.conf b/lam/config/samba3.sample.conf index 66c7864f4..4bfddb42e 100644 --- a/lam/config/samba3.sample.conf +++ b/lam/config/samba3.sample.conf @@ -3,7 +3,6 @@ "useTLS": "yes", "followReferrals": "false", "pagedResults": "false", - "Passwd": "{CRYPT-SHA512}$6$MUWJEkvtUY7G5sFA$QS6voQCksH9gNbbbQpjDKt65iez9bgKQI2x60DAffCK5.LO\/\/QfYTetQ6V2PlUR32CTkuhlSXSGXnH9scD\/zb0 TVVXSkVrdnRVWTdHNXNGQQ==", "Admins": "cn=Manager,dc=my-domain,dc=com", "defaultLanguage": "en_GB.utf8", "scriptPath": "", diff --git a/lam/config/unix.sample.conf b/lam/config/unix.sample.conf index 53aedf331..865c24cd5 100644 --- a/lam/config/unix.sample.conf +++ b/lam/config/unix.sample.conf @@ -3,7 +3,6 @@ "useTLS": "no", "followReferrals": "false", "pagedResults": "false", - "Passwd": "{CRYPT-SHA512}$6$zvb8WVEHSAKEGtGO$573kA9Us8LtGLLm5Gu87P\/vIiF\/2Ol\/DauzPmUpvC4eCL\/t0WWiwBaY19Rx5G3wzbeZWWlE1kp2fikrpZTZ51\/ enZiOFdWRUhTQUtFR3RHTw==", "Admins": "cn=Manager,dc=my-domain,dc=com", "defaultLanguage": "en_GB.utf8", "scriptPath": "", diff --git a/lam/config/windows_samba4.sample.conf b/lam/config/windows_samba4.sample.conf index e521c78a7..f2ab63f47 100644 --- a/lam/config/windows_samba4.sample.conf +++ b/lam/config/windows_samba4.sample.conf @@ -3,7 +3,6 @@ "useTLS": "no", "followReferrals": "false", "pagedResults": "false", - "Passwd": "{CRYPT-SHA512}$6$9IWWua4lbp7uiLCC$AHPgST1YAm3yUAWKGeNZ5f9GCo1wBGyVo3MGvAt6.UOtQ9dYxs4WeQ4mlzjR30rD6cRayMNRBWqYFuBLvzn9T0 OUlXV3VhNGxicDd1aUxDQw==", "Admins": "cn=Administrator,cn=users,dc=my-domain,dc=com", "defaultLanguage": "en_GB.utf8", "scriptPath": "", diff --git a/lam/docs/manual-sources/appendix-troubleshooting.xml b/lam/docs/manual-sources/appendix-troubleshooting.xml index 97e6763b5..eaf6cfa62 100644 --- a/lam/docs/manual-sources/appendix-troubleshooting.xml +++ b/lam/docs/manual-sources/appendix-troubleshooting.xml @@ -28,27 +28,23 @@ Locate config.cfg: On DEB/RPM installations it is in - /usr/share/ldap-account-manager/config and for tar.bz2 in config + /usr/share/ldap-account-manager/config and + for tar.bz2 in config folder. - Locate the "password" entry in the file + Locate the "password" line in the file - Replace the password hash after "password: " with your new - clear-text password (e.g. "secret") + Remove the password line in the configuration file - After the change the line should look like this: - - password: secret - - You can now login using your new password. Set the password once - again via GUI in main configuration settings. This will then put again - a hash value in the config.cfg file. + When you open LAM's start page you will now be asked to set a + new password.
diff --git a/lam/docs/manual-sources/chapter-configuration.xml b/lam/docs/manual-sources/chapter-configuration.xml index 4cdf0a059..f82352a9f 100644 --- a/lam/docs/manual-sources/chapter-configuration.xml +++ b/lam/docs/manual-sources/chapter-configuration.xml @@ -44,11 +44,6 @@
General settings - After selecting "Edit general settings" you will need to enter the - master configuration password. - The default password for new installations is "lam". Now you can edit the - general settings. -
Configuration Database diff --git a/lam/docs/manual-sources/overview.xml b/lam/docs/manual-sources/overview.xml index c12fe9bf1..f9c57580f 100644 --- a/lam/docs/manual-sources/overview.xml +++ b/lam/docs/manual-sources/overview.xml @@ -87,26 +87,15 @@ Edge (max. 2 years old) - - - Opera (max. 2 years old) - - The default password to edit the configuration options is - "lam". - License: LAM is published under the GNU General Public License. The complete list of licenses can be found in the copyright file. - Default password: - - The default password for the LAM configuration is "lam". - Have fun! The LAM development team diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 90ac5bdf5..6d8e535e2 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -12,7 +12,7 @@ use function LAM\TYPES\getScopeFromTypeId; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2024 Roland Gruber + Copyright (C) 2003 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -50,6 +50,19 @@ include_once __DIR__ . "/types.inc"; /** 2-factor */ include_once __DIR__ . '/2factor.inc'; +/** + * Checks if the configuration password is secure. + * + * @param string $password password + * @return bool is secure + */ +function isValidConfigurationPassword(string $password): bool { + return preg_match('/[a-zA-Z]/', $password) + && preg_match('/\d/', $password) + && preg_match('/[^a-zA-Z0-9]/', $password) + && (strlen($password) >= 8); +} + /** * Sets the environment variables for custom SSL CA certificates. */ @@ -1453,6 +1466,15 @@ class LAMConfig { return "{CRYPT-SHA512}" . crypt($password, '$6$' . $salt) . " " . base64_encode($salt); } + /** + * Returns if the server profile has a password set. + * + * @return bool password is set + */ + public function hasPasswordSet(): bool { + return ($this->Passwd != null) && ($this->Passwd !== ''); + } + /** * Returns the LDAP suffix for the given account type * @@ -3242,8 +3264,9 @@ class LAMCfgMain { /** * Saves the configuration to the persistence layer. + * @throws LAMException error saving config */ - public function save() { + public function save(): void { if ($this->configDatabaseType === self::DATABASE_MYSQL) { $this->saveLocal(true); $this->saveDb(); @@ -3261,7 +3284,7 @@ class LAMCfgMain { @chmod($sslPath, 0600); } else { - StatusMessage("ERROR", _("Cannot write certificate file. Please check the permissions of config/serverCerts.pem.")); + throw new LAMException(_("Cannot write certificate file. Please check the permissions of config/serverCerts.pem.")); } } // delete SSL certificate @@ -3269,7 +3292,7 @@ class LAMCfgMain { $sslPath = $this->getInternalSSLCaCertFileName(); $result = @unlink($sslPath); if (!$result) { - StatusMessage("ERROR", _("Cannot write certificate file. Please check the permissions of config/serverCerts.pem.")); + throw new LAMException(_("Cannot write certificate file. Please check the permissions of config/serverCerts.pem.")); } } } @@ -3296,6 +3319,7 @@ class LAMCfgMain { * Saves preferences to config file config.cfg * * @param bool $persistenceOnly store only persistence related data + * @throws LAMException error saving config */ public function saveLocal(bool $persistenceOnly): void { $data = $persistenceOnly ? $this->exportPersistenceData() : $this->exportData(); @@ -3308,10 +3332,19 @@ class LAMCfgMain { chmod($this->conffile, 0600); } else { - StatusMessage("ERROR", "", _("Cannot open config file!") . " (" . $this->conffile . ")"); + throw new LAMException(_("Cannot open config file!") . " (" . $this->conffile . ")"); } } + /** + * Returns if the main config has a password set. + * + * @return bool password is set + */ + public function hasPasswordSet(): bool { + return ($this->password != null) && ($this->password !== ''); + } + /** * Sets a new config password. * diff --git a/lam/locale/es_ES/LC_MESSAGES/messages.mo b/lam/locale/es_ES/LC_MESSAGES/messages.mo index 4a2d51003..9c5853e96 100644 Binary files a/lam/locale/es_ES/LC_MESSAGES/messages.mo and b/lam/locale/es_ES/LC_MESSAGES/messages.mo differ diff --git a/lam/locale/es_ES/LC_MESSAGES/messages.po b/lam/locale/es_ES/LC_MESSAGES/messages.po index 68ac6c6ee..a0ef96e4d 100644 --- a/lam/locale/es_ES/LC_MESSAGES/messages.po +++ b/lam/locale/es_ES/LC_MESSAGES/messages.po @@ -5,12 +5,13 @@ # Julio C. Ortega, 2022-2024 # Julio C. Ortega, 2015-2021 # Leandro Lattanzio, 2023 +# Roland Gruber , 2025 msgid "" msgstr "" "Project-Id-Version: LDAP Account Manager\n" "Report-Msgid-Bugs-To: post@rolandgruber.de\n" "PO-Revision-Date: 2011-09-29 18:53+0000\n" -"Last-Translator: Julio C. Ortega, 2022-2024\n" +"Last-Translator: Roland Gruber , 2025\n" "Language-Team: Spanish (Spain) (http://app.transifex.com/gruberroland/lam/language/es_ES/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -8775,11 +8776,11 @@ msgstr "Móvil" #: ../lib/modules/inetOrgPerson.inc:3760 ../lib/modules/inetOrgPerson.inc:3802 #: ../lib/modules/inetOrgPerson.inc:4147 msgid "Mobile number" -msgstr "Numero de ḿmóbil" +msgstr "Número de móvil" #: ../lib/modules/inetOrgPerson.inc:2751 msgid "Mobile telephone number" -msgstr "Número de teléfono movil" +msgstr "Número de teléfono móvil" #: ../lib/modules/qmailGroup.inc:183 ../lib/modules/qmailGroup.inc:187 #: ../lib/modules/qmailGroup.inc:316 ../lib/modules/qmailGroup.inc:376 @@ -16357,7 +16358,7 @@ msgstr "Número de Fax del usuario." #: ../lib/modules/windowsLDSUser.inc:312 ../lib/modules/inetOrgPerson.inc:648 #: ../lib/modules/inetOrgPerson.inc:652 ../lib/modules/windowsUser.inc:423 msgid "The user's mobile number." -msgstr "Numero de móvil del usuario." +msgstr "Número de móvil del usuario." #: ../lib/modules/windowsLDSUser.inc:284 ../lib/modules/windowsLDSUser.inc:288 #: ../lib/modules/inetOrgPerson.inc:780 ../lib/modules/inetOrgPerson.inc:784 diff --git a/lam/templates/config/conflogin.php b/lam/templates/config/conflogin.php index a6737ade6..9a5eaefd0 100644 --- a/lam/templates/config/conflogin.php +++ b/lam/templates/config/conflogin.php @@ -1,22 +1,22 @@ getProfiles(); + $profileNames = $serverProfilePersistenceManager->getProfiles(); + foreach ($profileNames as $profileName) { + $profile = $serverProfilePersistenceManager->loadProfile($profileName); + if (!$profile->hasPasswordSet()) { + $profileNamesWithoutPassword[] = $profileName; + } + } } catch (LAMException $e) { logNewMessage(LOG_ERR, 'Unable to read server profiles: ' . $e->getTitle()); } printHeaderContents(_("Login"), '../..'); -if (count($files) < 1) { +if (count($profileNames) < 1) { $message = new htmlStatusMessage('INFO', _("No server profiles found. Please create one.")); } +if ($profileNamesWithoutPassword !== []) { + $message = new htmlStatusMessage('INFO', _("There is at least one server profile without password. Please click on the manage server profiles link to set a password."), + htmlspecialchars(implode(', ', $profileNamesWithoutPassword))); +} ?> @@ -128,36 +139,34 @@ printJsIncludes('../..'); // message if ($message !== null) { - $row->add($message, 12); + $row->add($message); $row->addVerticalSpacer('2rem'); } $box = new htmlResponsiveRow(); - if (count($files) > 0) { - $box->add(new htmlOutputText(_("Please enter your password to change the server preferences:")), 12); + if (count($profileNames) > 0) { + $box->add(new htmlOutputText(_("Please enter your password to change the server preferences:"))); $box->addVerticalSpacer('1.5rem'); $conf = new LAMCfgMain(); $selectedProfile = []; - $profilesExisting = false; - $profiles = $files; - if (!empty($_COOKIE["lam_default_profile"]) && in_array($_COOKIE["lam_default_profile"], $files)) { + if (!empty($_COOKIE["lam_default_profile"]) && in_array($_COOKIE["lam_default_profile"], $profileNames)) { $selectedProfile[] = $_COOKIE["lam_default_profile"]; } else { $selectedProfile[] = $conf->default; } - $box->add(new htmlResponsiveSelect('filename', $profiles, $selectedProfile, _('Profile name')), 12); + $box->add(new htmlResponsiveSelect('filename', $profileNames, $selectedProfile, _('Profile name'))); $passwordInput = new htmlResponsiveInputField(_('Password'), 'passwd', '', '200'); $passwordInput->setIsPassword(true); $passwordInput->setCSSClasses(['lam-initial-focus']); - $box->add($passwordInput, 12); + $box->add($passwordInput); $box->addVerticalSpacer('1rem'); $button = new htmlButton('submit', _("Ok")); $button->setCSSClasses(['lam-primary']); $box->addLabel($button); $box->add(new htmlOutputText(''), 0, 6); $box->addVerticalSpacer('1.5rem'); - $box->add(new htmlHorizontalLine(), 12); + $box->add(new htmlHorizontalLine()); $box->addVerticalSpacer('1.5rem'); } $manageLink = new htmlLink(_("Manage server profiles"), 'profmanage.php'); @@ -165,7 +174,7 @@ printJsIncludes('../..'); $boxDiv = new htmlDiv(null, $box); $boxDiv->setCSSClasses(['roundedShadowBox', 'limitWidth', 'text-center']); - $row->add($boxDiv, 12); + $row->add($boxDiv); // back link $row->addVerticalSpacer('2rem'); diff --git a/lam/templates/config/confmain.php b/lam/templates/config/confmain.php index d8a55979d..453e9a74a 100644 --- a/lam/templates/config/confmain.php +++ b/lam/templates/config/confmain.php @@ -27,7 +27,7 @@ use ServerProfilePersistenceManager; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2023 Roland Gruber + Copyright (C) 2003 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -842,10 +842,13 @@ function checkInput(): array { $conf->setTwoFactorRememberDevicePassword($_POST['twoFactorRememberDevicePassword']); } // check if password was changed - if (isset($_POST['passwd1']) && ($_POST['passwd1'] != '')) { - if ($_POST['passwd1'] != $_POST['passwd2']) { + if (!empty($_POST['passwd1'])) { + if ($_POST['passwd1'] !== $_POST['passwd2']) { $errors[] = ["ERROR", _("Passwords are different!")]; } + elseif (!isValidConfigurationPassword($_POST['passwd1'])) { + $errors[] = ["ERROR", _('Profile password'), _('Please enter at least 8 characters including letters, a number and a symbol.')]; + } else { // set new password $conf->set_Passwd($_POST['passwd1']); diff --git a/lam/templates/config/mainmanage.php b/lam/templates/config/mainmanage.php index 5c13990d6..15c84b554 100644 --- a/lam/templates/config/mainmanage.php +++ b/lam/templates/config/mainmanage.php @@ -4,28 +4,28 @@ namespace LAM\CONFIG; use htmlJavaScript; use htmlResponsiveTable; use LAM\LOGIN\WEBAUTHN\WebauthnManager; -use \LAMCfgMain; -use \htmlTable; -use \htmlTitle; -use \htmlStatusMessage; -use \htmlSubTitle; -use \htmlSpacer; -use \htmlOutputText; -use \htmlLink; -use \htmlGroup; -use \htmlButton; -use \htmlHelpLink; -use \htmlInputField; -use \htmlInputFileUpload; -use \DateTime; -use \DateTimeZone; -use \htmlResponsiveRow; -use \htmlResponsiveInputTextarea; -use \htmlResponsiveSelect; -use \htmlResponsiveInputCheckbox; -use \htmlResponsiveInputField; -use \htmlDiv; -use \htmlHiddenInput; +use LAMCfgMain; +use htmlTable; +use htmlTitle; +use htmlStatusMessage; +use htmlSubTitle; +use htmlSpacer; +use htmlOutputText; +use htmlLink; +use htmlGroup; +use htmlButton; +use htmlHelpLink; +use htmlInputField; +use htmlInputFileUpload; +use DateTime; +use DateTimeZone; +use htmlResponsiveRow; +use htmlResponsiveInputTextarea; +use htmlResponsiveSelect; +use htmlResponsiveInputCheckbox; +use htmlResponsiveInputField; +use htmlDiv; +use htmlHiddenInput; use LAMException; use LamTemporaryFilesManager; use PDO; @@ -33,7 +33,7 @@ use PDO; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2024 Roland Gruber + Copyright (C) 2003 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -114,7 +114,8 @@ printHeaderContents(_("Edit general settings"), '../..'); - + menu   @@ -134,37 +135,41 @@ $errors = []; $messages = []; // check if submit button was pressed if (isset($_POST['submitFormData'])) { - if (extension_loaded('PDO')) { - // set database - $cfg->configDatabaseType = $_POST['configDatabaseType']; - $cfg->configDatabaseServer = $_POST['configDatabaseServer']; - $cfg->configDatabasePort = $_POST['configDatabasePort']; - $cfg->configDatabaseName = $_POST['configDatabaseName']; - $cfg->configDatabaseUser = $_POST['configDatabaseUser']; - $cfg->configDatabasePassword = $_POST['configDatabasePassword']; - if ($cfg->configDatabaseType === LAMCfgMain::DATABASE_MYSQL) { - if (empty($cfg->configDatabaseServer) || !get_preg($cfg->configDatabaseServer, 'hostname')) { - $errors[] = _('Please enter a valid database host name.'); - } - if (empty($cfg->configDatabaseName)) { - $errors[] = _('Please enter a valid database name.'); - } - if (empty($cfg->configDatabaseUser)) { - $errors[] = _('Please enter a valid database user.'); - } - if (empty($cfg->configDatabasePassword)) { - $errors[] = _('Please enter a valid database password.'); - } - } - } + if (extension_loaded('PDO')) { + // set database + $cfg->configDatabaseType = $_POST['configDatabaseType']; + $cfg->configDatabaseServer = $_POST['configDatabaseServer']; + $cfg->configDatabasePort = $_POST['configDatabasePort']; + $cfg->configDatabaseName = $_POST['configDatabaseName']; + $cfg->configDatabaseUser = $_POST['configDatabaseUser']; + $cfg->configDatabasePassword = $_POST['configDatabasePassword']; + if ($cfg->configDatabaseType === LAMCfgMain::DATABASE_MYSQL) { + if (empty($cfg->configDatabaseServer) || !get_preg($cfg->configDatabaseServer, 'hostname')) { + $errors[] = _('Please enter a valid database host name.'); + } + if (empty($cfg->configDatabaseName)) { + $errors[] = _('Please enter a valid database name.'); + } + if (empty($cfg->configDatabaseUser)) { + $errors[] = _('Please enter a valid database user.'); + } + if (empty($cfg->configDatabasePassword)) { + $errors[] = _('Please enter a valid database password.'); + } + } + } // set master password - if (isset($_POST['masterpassword']) && ($_POST['masterpassword'] != "")) { - if ($_POST['masterpassword'] && $_POST['masterpassword2'] && ($_POST['masterpassword'] == $_POST['masterpassword2'])) { + if (!empty($_POST['masterpassword'])) { + if (($_POST['masterpassword'] !== $_POST['masterpassword2'])) { + $errors[] = _("Master passwords are different."); + } + elseif (!isValidConfigurationPassword($_POST['masterpassword'])) { + $errors[] = _('Please enter at least 8 characters including letters, a number and a symbol.'); + } + else { $cfg->setPassword($_POST['masterpassword']); $msg = _("New master password set successfully."); unset($_SESSION["mainconf_password"]); - } else { - $errors[] = _("Master passwords are different or empty!"); } } // set license @@ -176,19 +181,19 @@ if (isset($_POST['submitFormData'])) { $cfg->licenseEmailFrom = $_POST['licenseEmailFrom']; $cfg->licenseEmailTo = $_POST['licenseEmailTo']; if ((($cfg->licenseWarningType === LAMCfgMain::LICENSE_WARNING_EMAIL) || ($cfg->licenseWarningType === LAMCfgMain::LICENSE_WARNING_ALL)) - && !get_preg($cfg->licenseEmailFrom, 'email')) { - $errors[] = _('Licence') . ': ' . _('From address') . ' - ' . _('Please enter a valid email address!'); - } + && !get_preg($cfg->licenseEmailFrom, 'email')) { + $errors[] = _('Licence') . ': ' . _('From address') . ' - ' . _('Please enter a valid email address!'); + } if (($cfg->licenseWarningType === LAMCfgMain::LICENSE_WARNING_EMAIL) || ($cfg->licenseWarningType === LAMCfgMain::LICENSE_WARNING_ALL)) { - $toEmails = preg_split('/;[ ]*/', $cfg->licenseEmailTo); - if ($toEmails !== false) { + $toEmails = preg_split('/;[ ]*/', $cfg->licenseEmailTo); + if ($toEmails !== false) { foreach ($toEmails as $toEmail) { if (!get_preg($toEmail, 'email')) { $errors[] = _('Licence') . ': ' . _('To address') . ' - ' . _('Please enter a valid email address!'); break; } } - } + } } } // set session timeout @@ -213,11 +218,12 @@ if (isset($_POST['submitFormData'])) { } } $allowedHosts = implode(",", $allowedHostsList); - } else { + } + else { $allowedHosts = ""; } $cfg->allowedHosts = $allowedHosts; - // set allowed hosts for self service + // set allowed hosts for self-service if (isLAMProVersion()) { if (isset($_POST['allowedHostsSelfService'])) { $allowedHostsSelfService = $_POST['allowedHostsSelfService']; @@ -237,7 +243,8 @@ if (isset($_POST['submitFormData'])) { } $allowedHostsSelfServiceList = array_unique($allowedHostsSelfServiceList); $allowedHostsSelfService = implode(",", $allowedHostsSelfServiceList); - } else { + } + else { $allowedHostsSelfService = ""; } $cfg->allowedHostsSelfService = $allowedHostsSelfService; @@ -247,34 +254,37 @@ if (isset($_POST['submitFormData'])) { // set log destination if ($_POST['logDestination'] == "none") { $cfg->logDestination = "NONE"; - } elseif ($_POST['logDestination'] == "syslog") { + } + elseif ($_POST['logDestination'] == "syslog") { $cfg->logDestination = "SYSLOG"; - } elseif ($_POST['logDestination'] == "remote") { + } + elseif ($_POST['logDestination'] == "remote") { $cfg->logDestination = "REMOTE:" . $_POST['logRemote']; $remoteParts = explode(':', $_POST['logRemote']); if ((count($remoteParts) !== 2) || !get_preg($remoteParts[0], 'DNSname') || !get_preg($remoteParts[1], 'digit')) { $errors[] = _("Please enter a valid remote server in format \"server:port\"."); } - } else { - $isValidLogFile = isset($_POST['logFile']) && LAMCfgMain::isValidLogFilename($_POST['logFile']); + } + else { + $isValidLogFile = isset($_POST['logFile']) && LAMCfgMain::isValidLogFilename($_POST['logFile']); $blockedPrefixes = ['/usr', '/etc', '/dev', '/boot', '/lib', '/proc', '/root', '/run', '/sys', '/snap']; if (!empty($_SERVER['DOCUMENT_ROOT'])) { - $blockedPrefixes[] = $_SERVER['DOCUMENT_ROOT']; - } + $blockedPrefixes[] = $_SERVER['DOCUMENT_ROOT']; + } foreach ($blockedPrefixes as $blockedPrefix) { - if (!$isValidLogFile) { - break; - } - if (str_starts_with($_POST['logFile'], $blockedPrefix)) { - $isValidLogFile = false; - } - } + if (!$isValidLogFile) { + break; + } + if (str_starts_with($_POST['logFile'], $blockedPrefix)) { + $isValidLogFile = false; + } + } if ($isValidLogFile) { $cfg->logDestination = $_POST['logFile']; - } + } else { $errors[] = _("The log file is empty or contains invalid characters! Valid characters are: a-z, A-Z, 0-9, /, ., _ and -. The file must end with '.log' or '.txt'."); - } + } } // password policies $cfg->passwordMinLength = $_POST['passwordMinLength']; @@ -315,8 +325,8 @@ if (isset($_POST['submitFormData'])) { else { $messages[] = _('You might need to restart your webserver for changes to take effect.'); } - } - } + } + } } } if (isset($_POST['sslCaCertDelete'])) { @@ -327,7 +337,7 @@ if (isset($_POST['submitFormData'])) { $matches = []; if (preg_match('/^ldaps:\/\/([a-zA-Z0-9_.-]+)(:(\d+))?$/', $_POST['serverurl'], $matches)) { $port = '636'; - if (isset($matches[3]) && !empty($matches[3])) { + if (!empty($matches[3])) { $port = $matches[3]; } $pemResult = getLDAPSSLCertificate($matches[1], $port); @@ -335,10 +345,12 @@ if (isset($_POST['submitFormData'])) { $messages[] = _('Imported certificate from server.'); $messages[] = _('You might need to restart your webserver for changes to take effect.'); $cfg->uploadSSLCaCert($pemResult); - } else { + } + else { $errors[] = _('Unable to import server certificate. Please use the upload function.'); } - } else { + } + else { $errors[] = _('Invalid server name. Please enter "server" or "server:port".'); } } @@ -355,19 +367,19 @@ if (isset($_POST['submitFormData'])) { $cfg->mailEncryption = $_POST['mailEncryption']; $cfg->mailServer = $_POST['mailServer']; if (!empty($cfg->mailServer) && !get_preg($cfg->mailServer, 'hostAndPort')) { - $errors[] = _('Please enter the mail server with host name and port.'); - } + $errors[] = _('Please enter the mail server with host name and port.'); + } $mailAttribute = strtolower($_POST['mailAttribute']); $mailBackupAttribute = strtolower($_POST['mailBackupAttribute']); if (empty($mailAttribute)) { $cfg->mailAttribute = LAMCfgMain::MAIL_ATTRIBUTE_DEFAULT; - } - elseif (preg_match('/^[a-z0-9_-]+$/', $mailAttribute)) { - $cfg->mailAttribute = $mailAttribute; - } + } + elseif (preg_match('/^[a-z0-9_-]+$/', $mailAttribute)) { + $cfg->mailAttribute = $mailAttribute; + } else { - $errors[] = _('The mail attributes are invalid.'); - } + $errors[] = _('The mail attributes are invalid.'); + } if (empty($mailBackupAttribute)) { $cfg->mailBackupAttribute = LAMCfgMain::MAIL_BACKUP_ATTRIBUTE_DEFAULT; } @@ -379,22 +391,25 @@ if (isset($_POST['submitFormData'])) { } } $cfg->errorReporting = $_POST['errorReporting']; - // module settings - $allModules = getAllModules(); - $moduleSettings = $cfg->getModuleSettings(); - foreach ($allModules as $module) { - $module->checkGlobalConfigOptions($moduleSettings, $messages, $errors); - } - $cfg->setModuleSettings($moduleSettings); + // module settings + $allModules = getAllModules(); + $moduleSettings = $cfg->getModuleSettings(); + foreach ($allModules as $module) { + $module->checkGlobalConfigOptions($moduleSettings, $messages, $errors); + } + $cfg->setModuleSettings($moduleSettings); // save settings - if (isset($_POST['submit'])) { - $cfg->save(); - if (empty($errors)) { - $scriptTag = new htmlJavaScript('window.lam.dialog.showSuccessMessageAndRedirect("' . _("Your settings were successfully saved.") . '", "", "' . _('Ok') . '", "../login.php")'); - parseHtml(null, $scriptTag, [], false, null); - echo ''; - exit(); - } + if (isset($_POST['submit']) && empty($errors)) { + try { + $cfg->save(); + $scriptTag = new htmlJavaScript('window.lam.dialog.showSuccessMessageAndRedirect("' . _("Your settings were successfully saved.") . '", "", "' . _('Ok') . '", "../login.php")'); + parseHtml(null, $scriptTag, [], false, null); + echo ''; + exit(); + } + catch (LAMException $e) { + $errors[] = $e->getTitle(); + } } } @@ -415,82 +430,82 @@ if (isset($_POST['submitFormData'])) { // check if config file is writable if (!$cfg->isWritable()) { - $row->add(new htmlStatusMessage('WARN', _('The config file is not writable.'), _('Your changes cannot be saved until you make the file writable for the webserver user.')), 12); + $row->add(new htmlStatusMessage('WARN', _('The config file is not writable.'), _('Your changes cannot be saved until you make the file writable for the webserver user.'))); } // database if (extension_loaded('PDO')) { - $row->add(new htmlSubTitle(_('Configuration storage')), 12); + $row->add(new htmlSubTitle(_('Configuration storage'))); $storageProviders = [ - _('Local file system') => LAMCfgMain::DATABASE_FILE_SYSTEM - ]; + _('Local file system') => LAMCfgMain::DATABASE_FILE_SYSTEM + ]; if (in_array('mysql', PDO::getAvailableDrivers())) { $storageProviders['MySQL'] = LAMCfgMain::DATABASE_MYSQL; } $storageProviderSelect = new htmlResponsiveSelect('configDatabaseType', $storageProviders, [$cfg->configDatabaseType], _('Database type'), '293'); $storageProviderSelect->setHasDescriptiveElements(true); $dbRowsToShow = [ - LAMCfgMain::DATABASE_FILE_SYSTEM => [], - LAMCfgMain::DATABASE_MYSQL => ['configDatabaseServer', 'configDatabasePort', 'configDatabaseName', 'configDatabaseUser', 'configDatabasePassword'] - ]; + LAMCfgMain::DATABASE_FILE_SYSTEM => [], + LAMCfgMain::DATABASE_MYSQL => ['configDatabaseServer', 'configDatabasePort', 'configDatabaseName', 'configDatabaseUser', 'configDatabasePassword'] + ]; $storageProviderSelect->setTableRowsToShow($dbRowsToShow); $dbRowsToHide = [ - LAMCfgMain::DATABASE_FILE_SYSTEM => ['configDatabaseServer', 'configDatabasePort', 'configDatabaseName', 'configDatabaseUser', 'configDatabasePassword'], - LAMCfgMain::DATABASE_MYSQL => [] - ]; + LAMCfgMain::DATABASE_FILE_SYSTEM => ['configDatabaseServer', 'configDatabasePort', 'configDatabaseName', 'configDatabaseUser', 'configDatabasePassword'], + LAMCfgMain::DATABASE_MYSQL => [] + ]; $storageProviderSelect->setTableRowsToHide($dbRowsToHide); - $row->add($storageProviderSelect, 12); + $row->add($storageProviderSelect); $dbHost = new htmlResponsiveInputField(_('Database host'), 'configDatabaseServer', $cfg->configDatabaseServer, '273'); $dbHost->setRequired(true); - $row->add($dbHost, 12); + $row->add($dbHost); $dbPort = new htmlResponsiveInputField(_('Database port'), 'configDatabasePort', $cfg->configDatabasePort, '274'); - $row->add($dbPort, 12); + $row->add($dbPort); $dbName = new htmlResponsiveInputField(_('Database name'), 'configDatabaseName', $cfg->configDatabaseName, '276'); $dbName->setRequired(true); - $row->add($dbName, 12); + $row->add($dbName); $dbUser = new htmlResponsiveInputField(_('Database user'), 'configDatabaseUser', $cfg->configDatabaseUser, '275'); $dbUser->setRequired(true); - $row->add($dbUser, 12); + $row->add($dbUser); $dbPassword = new htmlResponsiveInputField(_('Database password'), 'configDatabasePassword', deobfuscateText($cfg->configDatabasePassword), '275'); $dbPassword->setRequired(true); $dbPassword->setIsPassword(true); - $row->add($dbPassword, 12); - } + $row->add($dbPassword); + } // license if (isLAMProVersion()) { - $row->add(new htmlSubTitle(_('Licence')), 12); - $row->add(new htmlResponsiveInputTextarea('license', implode("\n", $cfg->getLicenseLines()), '30', '10', _('Licence'), '287'), 12); + $row->add(new htmlSubTitle(_('Licence'))); + $row->add(new htmlResponsiveInputTextarea('license', implode("\n", $cfg->getLicenseLines()), '30', '10', _('Licence'), '287')); $warningOptions = [ - _('Screen') => LAMCfgMain::LICENSE_WARNING_SCREEN, - _('Email') => LAMCfgMain::LICENSE_WARNING_EMAIL, - _('Both') => LAMCfgMain::LICENSE_WARNING_ALL, - _('None') => LAMCfgMain::LICENSE_WARNING_NONE - ]; + _('Screen') => LAMCfgMain::LICENSE_WARNING_SCREEN, + _('Email') => LAMCfgMain::LICENSE_WARNING_EMAIL, + _('Both') => LAMCfgMain::LICENSE_WARNING_ALL, + _('None') => LAMCfgMain::LICENSE_WARNING_NONE + ]; $warningTypeSelect = new htmlResponsiveSelect('licenseWarningType', $warningOptions, [$cfg->getLicenseWarningType()], _('Expiration warning'), '288'); $warningTypeSelect->setHasDescriptiveElements(true); $warningTypeSelect->setSortElements(false); $warningTypeSelect->setTableRowsToHide([ - LAMCfgMain::LICENSE_WARNING_SCREEN => ['licenseEmailFrom', 'licenseEmailTo'], - LAMCfgMain::LICENSE_WARNING_NONE => ['licenseEmailFrom', 'licenseEmailTo'] - ]); + LAMCfgMain::LICENSE_WARNING_SCREEN => ['licenseEmailFrom', 'licenseEmailTo'], + LAMCfgMain::LICENSE_WARNING_NONE => ['licenseEmailFrom', 'licenseEmailTo'] + ]); $warningTypeSelect->setTableRowsToShow([ - LAMCfgMain::LICENSE_WARNING_EMAIL => ['licenseEmailFrom', 'licenseEmailTo'], - LAMCfgMain::LICENSE_WARNING_ALL => ['licenseEmailFrom', 'licenseEmailTo'] - ]); - $row->add($warningTypeSelect, 12); + LAMCfgMain::LICENSE_WARNING_EMAIL => ['licenseEmailFrom', 'licenseEmailTo'], + LAMCfgMain::LICENSE_WARNING_ALL => ['licenseEmailFrom', 'licenseEmailTo'] + ]); + $row->add($warningTypeSelect); $licenseFrom = new htmlResponsiveInputField(_('From address'), 'licenseEmailFrom', $cfg->licenseEmailFrom, '289'); $licenseFrom->setRequired(true); - $row->add($licenseFrom, 12); + $row->add($licenseFrom); $licenseTo = new htmlResponsiveInputField(_('To address'), 'licenseEmailTo', $cfg->licenseEmailTo, '290'); $licenseTo->setRequired(true); - $row->add($licenseTo, 12); + $row->add($licenseTo); $row->add(new htmlSpacer(null, '1rem'), true); } // security settings - $row->add(new htmlSubTitle(_("Security settings")), 12); + $row->add(new htmlSubTitle(_("Security settings"))); $options = [5, 10, 20, 30, 60, 90, 120, 240]; $row->add(new htmlResponsiveSelect('sessionTimeout', $options, [$cfg->sessionTimeout], _("Session timeout"), '238')); $hideLoginErrorDetails = ($cfg->hideLoginErrorDetails === 'true'); @@ -545,80 +560,82 @@ if (isset($_POST['submitFormData'])) { $serial = $sslCerts[$i]['serialNumber'] ?? ''; $validTo = $sslCerts[$i]['validTo_time_t'] ?? ''; if (get_preg($validTo, 'digit')) { - $date = DateTime::createFromFormat('U', $validTo, new DateTimeZone('UTC')); - if ($date !== false) { + $date = DateTime::createFromFormat('U', $validTo, new DateTimeZone('UTC')); + if ($date !== false) { $validTo = $date->format('Y-m-d'); - } - } + } + } $cn = $sslCerts[$i]['subject']['CN'] ?? ''; $delBtn = new htmlButton('deleteCert_' . $i, 'del.svg', true); $certsData[] = [ - new htmlOutputText($cn), - new htmlDiv(null, new htmlOutputText($validTo), ['nowrap']), - new htmlOutputText($serial), - $delBtn - ]; + new htmlOutputText($cn), + new htmlDiv(null, new htmlOutputText($validTo), ['nowrap']), + new htmlOutputText($serial), + $delBtn + ]; } $certsTable = new htmlResponsiveTable($certsTitles, $certsData); $certsTable->setCSSClasses(['text-left']); - $row->add($certsTable, 12); + $row->add($certsTable); } // password policy - $row->add(new htmlSubTitle(_("Password policy")), 12); + $row->add(new htmlSubTitle(_("Password policy"))); $optionsPwdLength = []; for ($i = 0; $i <= 50; $i++) { $optionsPwdLength[] = $i; } $options4 = [0, 1, 2, 3, 4]; - $row->add(new htmlResponsiveSelect('passwordMinLength', $optionsPwdLength, [$cfg->passwordMinLength], _('Minimum password length'), '242'), 12); + $row->add(new htmlResponsiveSelect('passwordMinLength', $optionsPwdLength, [$cfg->passwordMinLength], _('Minimum password length'), '242')); $row->addVerticalSpacer('1rem'); - $row->add(new htmlResponsiveSelect('passwordMinLower', $optionsPwdLength, [$cfg->passwordMinLower], _('Minimum lowercase characters'), '242'), 12); - $row->add(new htmlResponsiveSelect('passwordMinUpper', $optionsPwdLength, [$cfg->passwordMinUpper], _('Minimum uppercase characters'), '242'), 12); - $row->add(new htmlResponsiveSelect('passwordMinNumeric', $optionsPwdLength, [$cfg->passwordMinNumeric], _('Minimum numeric characters'), '242'), 12); - $row->add(new htmlResponsiveSelect('passwordMinSymbol', $optionsPwdLength, [$cfg->passwordMinSymbol], _('Minimum symbolic characters'), '242'), 12); - $row->add(new htmlResponsiveSelect('passwordMinClasses', $options4, [$cfg->passwordMinClasses], _('Minimum character classes'), '242'), 12); + $row->add(new htmlResponsiveSelect('passwordMinLower', $optionsPwdLength, [$cfg->passwordMinLower], _('Minimum lowercase characters'), '242')); + $row->add(new htmlResponsiveSelect('passwordMinUpper', $optionsPwdLength, [$cfg->passwordMinUpper], _('Minimum uppercase characters'), '242')); + $row->add(new htmlResponsiveSelect('passwordMinNumeric', $optionsPwdLength, [$cfg->passwordMinNumeric], _('Minimum numeric characters'), '242')); + $row->add(new htmlResponsiveSelect('passwordMinSymbol', $optionsPwdLength, [$cfg->passwordMinSymbol], _('Minimum symbolic characters'), '242')); + $row->add(new htmlResponsiveSelect('passwordMinClasses', $options4, [$cfg->passwordMinClasses], _('Minimum character classes'), '242')); $row->addVerticalSpacer('1rem'); $rulesCountOptions = [_('all') => '-1', '3' => '3', '4' => '4']; $rulesCountSelect = new htmlResponsiveSelect('passwordRulesCount', $rulesCountOptions, [$cfg->checkedRulesCount], _('Number of rules that must match'), '246'); $rulesCountSelect->setHasDescriptiveElements(true); - $row->add($rulesCountSelect, 12); + $row->add($rulesCountSelect); $passwordMustNotContainUser = ($cfg->passwordMustNotContainUser === 'true'); - $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContainUser', $passwordMustNotContainUser, _('Password must not contain user name'), '247'), 12); + $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContainUser', $passwordMustNotContainUser, _('Password must not contain user name'), '247')); $passwordMustNotContain3Chars = ($cfg->passwordMustNotContain3Chars === 'true'); - $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContain3Chars', $passwordMustNotContain3Chars, _('Password must not contain part of user/first/last name'), '248'), 12); + $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContain3Chars', $passwordMustNotContain3Chars, _('Password must not contain part of user/first/last name'), '248')); if (function_exists('curl_init')) { $row->addVerticalSpacer('1rem'); - $row->add(new htmlResponsiveInputField(_('External password check'), 'externalPwdCheckUrl', $cfg->externalPwdCheckUrl, '249'), 12); + $row->add(new htmlResponsiveInputField(_('External password check'), 'externalPwdCheckUrl', $cfg->externalPwdCheckUrl, '249')); } // logging - $row->add(new htmlSubTitle(_("Logging")), 12); + $row->add(new htmlSubTitle(_("Logging"))); $levelOptions = [ - _("Debug") => LOG_DEBUG, - _("Notice") => LOG_NOTICE, - _("Warning") => LOG_WARNING, - _("Error") => LOG_ERR - ]; + _("Debug") => LOG_DEBUG, + _("Notice") => LOG_NOTICE, + _("Warning") => LOG_WARNING, + _("Error") => LOG_ERR + ]; $levelSelect = new htmlResponsiveSelect('logLevel', $levelOptions, [$cfg->logLevel], _("Log level"), '239'); $levelSelect->setHasDescriptiveElements(true); - $row->add($levelSelect, 12); + $row->add($levelSelect); $destinationOptions = [ - _("No logging") => "none", - _("System logging") => "syslog", - _("File") => 'file', - _("Remote") => 'remote' - ]; + _("No logging") => "none", + _("System logging") => "syslog", + _("File") => 'file', + _("Remote") => 'remote' + ]; $destinationSelected = 'file'; $destinationPath = $cfg->logDestination; $destinationRemote = ''; if ($cfg->logDestination == 'NONE') { $destinationSelected = 'none'; $destinationPath = ''; - } elseif ($cfg->logDestination == 'SYSLOG') { + } + elseif ($cfg->logDestination == 'SYSLOG') { $destinationSelected = 'syslog'; $destinationPath = ''; - } elseif (str_starts_with($cfg->logDestination, 'REMOTE')) { + } + elseif (str_starts_with($cfg->logDestination, 'REMOTE')) { $destinationSelected = 'remote'; $remoteParts = explode(':', $cfg->logDestination, 2); $destinationRemote = empty($remoteParts[1]) ? '' : $remoteParts[1]; @@ -626,31 +643,31 @@ if (isset($_POST['submitFormData'])) { } $logDestinationSelect = new htmlResponsiveSelect('logDestination', $destinationOptions, [$destinationSelected], _("Log destination"), '240'); $logDestinationSelect->setTableRowsToHide([ - 'none' => ['logFile', 'logRemote'], - 'syslog' => ['logFile', 'logRemote'], - 'remote' => ['logFile'], - 'file' => ['logRemote'] - ]); + 'none' => ['logFile', 'logRemote'], + 'syslog' => ['logFile', 'logRemote'], + 'remote' => ['logFile'], + 'file' => ['logRemote'] + ]); $logDestinationSelect->setTableRowsToShow([ - 'file' => ['logFile'], - 'remote' => ['logRemote'] - ]); + 'file' => ['logFile'], + 'remote' => ['logRemote'] + ]); $logDestinationSelect->setHasDescriptiveElements(true); - $row->add($logDestinationSelect, 12); - $row->add(new htmlResponsiveInputField(_('File'), 'logFile', $destinationPath), 12); - $row->add(new htmlResponsiveInputField(_('Remote server'), 'logRemote', $destinationRemote, '251'), 12); + $row->add($logDestinationSelect); + $row->add(new htmlResponsiveInputField(_('File'), 'logFile', $destinationPath)); + $row->add(new htmlResponsiveInputField(_('Remote server'), 'logRemote', $destinationRemote, '251')); $errorLogOptions = [ - _('PHP system setting') => LAMCfgMain::ERROR_REPORTING_SYSTEM, - _('default') => LAMCfgMain::ERROR_REPORTING_DEFAULT, - _('all') => LAMCfgMain::ERROR_REPORTING_ALL - ]; + _('PHP system setting') => LAMCfgMain::ERROR_REPORTING_SYSTEM, + _('default') => LAMCfgMain::ERROR_REPORTING_DEFAULT, + _('all') => LAMCfgMain::ERROR_REPORTING_ALL + ]; $errorLogSelect = new htmlResponsiveSelect('errorReporting', $errorLogOptions, [$cfg->errorReporting], _('PHP error reporting'), '244'); $errorLogSelect->setHasDescriptiveElements(true); $row->add($errorLogSelect); // mail options if (isLAMProVersion()) { - $row->add(new htmlSubTitle(_('Mail options')), 12); + $row->add(new htmlSubTitle(_('Mail options'))); $mailServer = new htmlResponsiveInputField(_("Mail server"), 'mailServer', $cfg->mailServer, '253'); $row->add($mailServer); $mailUser = new htmlResponsiveInputField(_("User name"), 'mailUser', $cfg->mailUser, '254'); @@ -659,10 +676,10 @@ if (isset($_POST['submitFormData'])) { $mailPassword->setIsPassword(true); $row->add($mailPassword); $mailEncryptionOptions = [ - 'TLS' => LAMCfgMain::SMTP_TLS, - 'SSL' => LAMCfgMain::SMTP_SSL, - _('None') => LAMCfgMain::SMTP_NONE - ]; + 'TLS' => LAMCfgMain::SMTP_TLS, + 'SSL' => LAMCfgMain::SMTP_SSL, + _('None') => LAMCfgMain::SMTP_NONE + ]; $selectedMailEncryption = empty($cfg->mailEncryption) ? LAMCfgMain::SMTP_TLS : $cfg->mailEncryption; $mailEncryptionSelect = new htmlResponsiveSelect('mailEncryption', $mailEncryptionOptions, [$selectedMailEncryption], _('Encryption protocol'), '256'); $mailEncryptionSelect->setHasDescriptiveElements(true); @@ -675,20 +692,20 @@ if (isset($_POST['submitFormData'])) { . "', '" . getSecurityTokenValue() . "', '" . _('Ok') . "', '" . _('Cancel') . "', '" . _('Test settings') . "')"); $row->addLabel(new htmlOutputText(" ", false)); $row->addField($mailTestButton); - $testDialogDivContent = new htmlResponsiveRow(); - $fromAddressInput = new htmlResponsiveInputField(_('From address'), 'testSmtpFrom', null, null, true); - $fromAddressInput->setType('email'); - $testDialogDivContent->add($fromAddressInput); - $toAddressInput = new htmlResponsiveInputField(_('To address'), 'testSmtpTo', null, null, true); + $testDialogDivContent = new htmlResponsiveRow(); + $fromAddressInput = new htmlResponsiveInputField(_('From address'), 'testSmtpFrom', null, null, true); + $fromAddressInput->setType('email'); + $testDialogDivContent->add($fromAddressInput); + $toAddressInput = new htmlResponsiveInputField(_('To address'), 'testSmtpTo', null, null, true); $toAddressInput->setType('email'); $testDialogDivContent->add($toAddressInput); - $testDialogDiv = new htmlDiv('smtpTestDialogDiv', $testDialogDivContent, ['hidden']); - $row->add($testDialogDiv); + $testDialogDiv = new htmlDiv('smtpTestDialogDiv', $testDialogDivContent, ['hidden']); + $row->add($testDialogDiv); } // webauthn management if (extension_loaded('PDO') - && in_array('sqlite', \PDO::getAvailableDrivers())) { + && in_array('sqlite', PDO::getAvailableDrivers())) { include_once __DIR__ . '/../../lib/webauthn.inc'; $webAuthnManager = new WebauthnManager(); try { @@ -696,7 +713,7 @@ if (isset($_POST['submitFormData'])) { if ($database->hasRegisteredCredentials()) { $row->add(new htmlSubTitle(_('WebAuthn devices'))); $webauthnSearchField = new htmlResponsiveInputField(_('User DN'), 'webauthn_searchTerm', null, '252'); - $row->add($webauthnSearchField, 12); + $row->add($webauthnSearchField); $row->addVerticalSpacer('0.5rem'); $row->add(new htmlButton('webauthn_search', _('Search')), 12, 12, 12, 'text-center'); $resultDiv = new htmlDiv('webauthn_results', new htmlOutputText(''), ['lam-webauthn-results', 'text-left']); @@ -708,48 +725,48 @@ if (isset($_POST['submitFormData'])) { } } catch (LAMException $e) { - logNewMessage(LOG_ERR, 'Webauthn error: ' . $e->getTitle() . ' ' . $e->getMessage()); - $row->add(new htmlStatusMessage('ERROR', $e->getTitle())); - } + logNewMessage(LOG_ERR, 'Webauthn error: ' . $e->getTitle() . ' ' . $e->getMessage()); + $row->add(new htmlStatusMessage('ERROR', $e->getTitle())); + } } - // module settings + // module settings $modules = getAllModules(); - $supportsGlobalCronJob = false; - foreach ($modules as $module) { - $supportsGlobalCronJob = $supportsGlobalCronJob || $module->supportsGlobalCronJob(); - $moduleOptions = $module->getGlobalConfigOptions($cfg->getModuleSettings()); - if (empty($moduleOptions)) { - continue; - } - $row->add(new htmlSubTitle($module->get_alias())); - foreach ($moduleOptions as $moduleOption) { - $row->add($moduleOption); - } + $supportsGlobalCronJob = false; + foreach ($modules as $module) { + $supportsGlobalCronJob = $supportsGlobalCronJob || $module->supportsGlobalCronJob(); + $moduleOptions = $module->getGlobalConfigOptions($cfg->getModuleSettings()); + if (empty($moduleOptions)) { + continue; + } + $row->add(new htmlSubTitle($module->get_alias())); + foreach ($moduleOptions as $moduleOption) { + $row->add($moduleOption); + } $row->addVerticalSpacer('3rem'); - } + } - // global cron job - if ($supportsGlobalCronJob) { + // global cron job + if ($supportsGlobalCronJob) { $row->add(new htmlSubTitle(_('Global cron job'))); - $cronCommand = dirname(__FILE__, 3) . '/lib/runCronJobs.sh all'; - $row->addLabel(new htmlOutputText('Cron command')); + $cronCommand = dirname(__FILE__, 3) . '/lib/runCronJobs.sh all'; + $row->addLabel(new htmlOutputText('Cron command')); $cmdGroup = new htmlGroup(); $cmdGroup->addElement(new htmlOutputText('0 0 * * * ' . $cronCommand)); $cmdGroup->addElement(new htmlSpacer('2rem', null)); $cmdGroup->addElement(new htmlHelpLink('294')); $row->addField($cmdGroup); - } + } // change master password $row->add(new htmlSubTitle(_("Change master password"))); $pwd1 = new htmlResponsiveInputField(_("New master password"), 'masterpassword', '', '235'); $pwd1->setIsPassword(true, false, true); - $row->add($pwd1, 12); + $row->add($pwd1); $pwd2 = new htmlResponsiveInputField(_("Reenter password"), 'masterpassword2', ''); $pwd2->setIsPassword(true, false, true); $pwd2->setSameValueFieldID('masterpassword'); - $row->add($pwd2, 12); + $row->add($pwd2); $row->addVerticalSpacer('3rem'); // buttons @@ -760,8 +777,8 @@ if (isset($_POST['submitFormData'])) { $buttonTable->addElement($saveButton); $buttonTable->addElement(new htmlSpacer('0.5rem', null)); $buttonTable->addElement(new htmlButton('cancel', _("Cancel"))); - $row->add($buttonTable, 12); - $row->add(new htmlHiddenInput('submitFormData', '1'), 12); + $row->add($buttonTable); + $row->add(new htmlHiddenInput('submitFormData', '1')); } $box = new htmlDiv(null, $row); diff --git a/lam/templates/config/profmanage.php b/lam/templates/config/profmanage.php index 8f1699474..40bccaf4e 100644 --- a/lam/templates/config/profmanage.php +++ b/lam/templates/config/profmanage.php @@ -19,7 +19,7 @@ use ServerProfilePersistenceManager; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2024 Roland Gruber + Copyright (C) 2003 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -155,9 +155,14 @@ if (isset($_POST['action'])) { if (preg_match("/^[a-z0-9_-]+$/i", (string) $_POST['defaultfilename'])) { $configMain = new LAMCfgMain(); $configMain->default = $_POST['defaultfilename']; - $configMain->save(); - $configMain = null; - $msg = _("New default profile set successfully."); + try { + $configMain->save(); + $configMain = null; + $msg = _("New default profile set successfully."); + } + catch (LAMException $e) { + $error = $e->getTitle(); + } } else { $error = _("Profile name is invalid!"); diff --git a/lam/templates/login.php b/lam/templates/login.php index 61f47e3a5..9748bfcc5 100644 --- a/lam/templates/login.php +++ b/lam/templates/login.php @@ -26,7 +26,7 @@ use ServerProfilePersistenceManager; This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) Copyright (C) 2003 - 2006 Michael Duergner - 2005 - 2024 Roland Gruber + 2005 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -60,10 +60,19 @@ include __DIR__ . '/../lib/checkEnvironment.inc'; /** security functions */ include_once(__DIR__ . "/../lib/security.inc"); -/** self service functions */ +/** self-service functions */ include_once(__DIR__ . "/../lib/selfService.inc"); /** access to configuration options */ include_once(__DIR__ . "/../lib/config.inc"); + +$cfgMain = new LAMCfgMain(); + +// check if main config password is set +if (!$cfgMain->hasPasswordSet()) { + metaRefresh('setInitialPassword.php'); + die(); +} + $licenseValidator = null; if (isLAMProVersion()) { include_once(__DIR__ . "/../lib/env.inc"); @@ -110,12 +119,10 @@ if (isset($_POST['language'])) { setcookie('lam_last_language', htmlspecialchars((string) $_POST['language']), $cookieOptions); } -// init some session variables -$default_Config = new LAMCfgMain(); -$_SESSION["cfgMain"] = $default_Config; +$_SESSION["cfgMain"] = $cfgMain; setSSLCaCert(); -$default_Profile = $default_Config->default; +$default_Profile = $cfgMain->default; if (isset($_COOKIE["lam_default_profile"]) && in_array($_COOKIE["lam_default_profile"], $profiles)) { $default_Profile = $_COOKIE["lam_default_profile"]; } @@ -142,7 +149,7 @@ catch (LAMException $e) { $error_message = $e->getTitle(); } -if (!isset($default_Config->default) || !in_array($default_Config->default, $profiles)) { +if (!isset($cfgMain->default) || !in_array($cfgMain->default, $profiles)) { $error_message = _('No default profile set. Please set it in the server profile configuration.'); } @@ -549,7 +556,7 @@ if (isset($_POST['checklogin'])) { cleanLDAPResult($searchInfo); if (empty($searchInfo)) { $searchSuccess = false; - if ($default_Config->isHideLoginErrorDetails()) { + if ($cfgMain->isHideLoginErrorDetails()) { $searchError = _('Wrong password/user name combination. Please try again.'); } else { @@ -560,7 +567,7 @@ if (isset($_POST['checklogin'])) { } elseif (count($searchInfo) > 1) { $searchSuccess = false; - if ($default_Config->isHideLoginErrorDetails()) { + if ($cfgMain->isHideLoginErrorDetails()) { $searchError = _('Wrong password/user name combination. Please try again.'); } else { @@ -575,7 +582,7 @@ if (isset($_POST['checklogin'])) { } else { $searchSuccess = false; - if ($default_Config->isHideLoginErrorDetails()) { + if ($cfgMain->isHideLoginErrorDetails()) { $searchError = _('Wrong password/user name combination. Please try again.'); } else { @@ -590,7 +597,7 @@ if (isset($_POST['checklogin'])) { } else { $searchSuccess = false; - if ($default_Config->isHideLoginErrorDetails()) { + if ($cfgMain->isHideLoginErrorDetails()) { $searchError = _('Wrong password/user name combination. Please try again.'); } else { @@ -641,13 +648,13 @@ if (isset($_POST['checklogin'])) { header("HTTP/1.1 403 Forbidden"); $extraMessage = null; if (($searchLDAP !== null) && ($e->getLdapErrorCode() == 49)) { - if (!$default_Config->isHideLoginErrorDetails()) { + if (!$cfgMain->isHideLoginErrorDetails()) { $extraMessage = getExtraInvalidCredentialsMessage($searchLDAP->server(), $username); } $searchLDAP->close(); } $message = $e->getMessage(); - if ($default_Config->isHideLoginErrorDetails()) { + if ($cfgMain->isHideLoginErrorDetails()) { $message = null; } display_LoginPage($licenseValidator, $e->getTitle(), $message, $extraMessage); diff --git a/lam/templates/setInitialPassword.php b/lam/templates/setInitialPassword.php new file mode 100644 index 000000000..310473a86 --- /dev/null +++ b/lam/templates/setInitialPassword.php @@ -0,0 +1,162 @@ +hasPasswordSet()) { + logNewMessage(LOG_ERR, 'Invalid attempt to set initial config password'); + die(); +} + +setlanguage(); + +if (!empty($_POST)) { + validateSecurityToken(); +} + +$message = null; + +// check if user already pressed button +if (isset($_POST['changePassword'])) { + // check new password + $password1 = $_POST['password1']; + $password2 = $_POST['password2']; + if ($password1 == '') { + $message = new htmlStatusMessage('ERROR', _('No password was entered!')); + printContent($message); + exit(); + } + // check if passwords match + if ($password1 != $password2) { + $message = new htmlStatusMessage('ERROR', _('Passwords are different!')); + printContent($message); + exit(); + } + // check password strength + $pwdPolicyResult = isValidConfigurationPassword($password1); + if (!$pwdPolicyResult) { + $message = new htmlStatusMessage('ERROR', _('Please enter at least 8 characters including letters, a number and a symbol.')); + printContent($message); + exit(); + } + // set new password + $cfgMain->setPassword($password1); + try { + $cfgMain->save(); + metaRefresh('config/mainlogin.php'); + exit(); + } + catch (LAMException $e) { + $message = new htmlStatusMessage('ERROR', $e->getTitle(), $e->getMessage()); + } +} + +printContent($message); + +/** + * Displays the content area + * + * @param htmlStatusMessage|null $message status message + * @param bool $showPasswordInputs show password input fields + */ +function printContent(htmlStatusMessage $message = null, bool $showPasswordInputs = true): void { + echo ' + + + + + + + '; + printHeaderContents('LDAP Account Manager', '..'); + echo ' + '; + printJsIncludes('..'); + + echo '
'; + echo "
\n"; + addSecurityTokenToSession(); + $container = new htmlResponsiveRow(); + if ($message !== null) { + $container->addVerticalSpacer('1rem'); + $container->add($message); + } + $container->addVerticalSpacer('2rem'); + if ($showPasswordInputs) { + $container->add(new htmlTitle(_('Initial configuration'))); + $container->addVerticalSpacer('3rem'); + $container->add(new htmlOutputText(_("Please enter your new LAM main configuration password.")), 12, 12, 12, 'text-center'); + $container->addVerticalSpacer('2rem'); + $pwdInput1 = new htmlResponsiveInputField(_('New password'), 'password1', ''); + $pwdInput1->setIsPassword(true, true, true); + $container->add($pwdInput1); + $pwdInput2 = new htmlResponsiveInputField(_('Repeat password'), 'password2', ''); + $pwdInput2->setIsPassword(true); + $pwdInput2->setSameValueFieldID('password1'); + $container->add($pwdInput2); + $container->addVerticalSpacer('1rem'); + $container->add(new htmlButton('changePassword', _("Submit")), 12, 12, 12, 'text-center'); + addSecurityTokenToMetaHTML($container); + $container->add(new htmlJavaScript('checkFieldsHaveSameValues("password1", "password2");')); + } + + parseHtml(null, $container, [], false, 'user'); + + echo '

+
+

+ + '; +} diff --git a/lam/tests/lib/LAMCfgMainTest.php b/lam/tests/lib/LAMCfgMainTest.php index a25832a2a..dc2d4b0c3 100644 --- a/lam/tests/lib/LAMCfgMainTest.php +++ b/lam/tests/lib/LAMCfgMainTest.php @@ -3,7 +3,7 @@ use PHPUnit\Framework\TestCase; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2020 - 2023 Roland Gruber + Copyright (C) 2020 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -56,6 +56,7 @@ class LAMCfgMainTest extends TestCase { /** * Mail related settings + * @throws LAMException error saving config */ public function testMail() { $this->assertEquals(LAMCfgMain::MAIL_ATTRIBUTE_DEFAULT, $this->conf->getMailAttribute()); @@ -81,6 +82,7 @@ class LAMCfgMainTest extends TestCase { /** * License related settings. + * @throws LAMException error saving config */ public function testLicense() { $timestamp = '12345';