Files
cacti/auth_login.php
T

438 lines
15 KiB
PHP

<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| 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 the Free Software Foundation; either version 2 |
| of the License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
| This code is designed, written, and maintained by the Cacti Group. See |
| about.php and/or the AUTHORS file for specific developer information. |
+-------------------------------------------------------------------------+
| http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/
use Cacti\Auth\BasicAuthLoginProvider;
use Cacti\Auth\LocalAuthLoginProvider;
// include ldap support
require_once(__DIR__ . '/lib/ldap.php');
// set default action
set_default_action();
/**
* get the username from the post variable
* For all but basic, this means that two post variables must be
* set. Additionally, for basic authentication verify the user,
* and if not valid generate a fatal error.
*/
$username = auth_get_username(); // Get the username from either basic auth or the login form
// initialize some variables
$user = []; // An array that will include all user details
$user_enabled = true; // A variable to let plugins know that the user is enabled
$guest_user = false; // Indicates the Guest account is being used
$realm = 0; // The compensated realm used for template and user validation
$frv_realm = gnrv('realm', 0); // The dropdown value for realm
$auth_method = read_config_option('auth_method'); // The authentication method for Cacti
$error = false; // Global variable, will be true if any errors occur
$error_msg = ''; // The errors message in case there was a login error
// global variables for exception handling
global $error, $error_msg;
if (gnrv('action') == 'login' || $auth_method == AUTH_METHOD_BASIC) {
if ($auth_method > AUTH_METHOD_BASIC && $frv_realm <= 1) {
// User picked 'local' from dropdown;
$auth_method = AUTH_METHOD_CACTI;
} else {
$auth_method = read_config_option('auth_method');
}
// Compensate as the dropdown for LDAP is off by one
if ($frv_realm == 2) {
$realm = 3;
} elseif ($auth_method == AUTH_METHOD_BASIC) {
$realm = $auth_method;
} else {
$realm = $frv_realm;
}
cacti_log("DEBUG: User '" . $username . "' attempting to login with realm " . $frv_realm . ', using method ' . $auth_method, false, 'AUTH', POLLER_VERBOSITY_DEBUG);
switch ($auth_method) {
case AUTH_METHOD_NONE: // No authentication, should not be reachable
$error = true;
$error_msg = __esc('Cacti no longer supports No Authentication mode. Please contact your System Administrator.');
cacti_log('FATAL: No authentication attempted and not supported.', false, 'AUTH');
auth_display_custom_error_message($error_msg);
exit;
case AUTH_METHOD_CACTI: // Local authentication
cacti_log("DEBUG: Local User '" . $username . "' to attempt login.", false, 'AUTH', POLLER_VERBOSITY_DEBUG);
$user = (new LocalAuthLoginProvider())->authenticate($username, gnrv('login_password'))->user ?? [];
break;
case AUTH_METHOD_BASIC: // Basic authentication
cacti_log("DEBUG: Basic Auth User '" . $username . "' attempting to login.", false, 'AUTH', POLLER_VERBOSITY_DEBUG);
$user = (new BasicAuthLoginProvider())->resolve($username)->user ?? [];
break;
case AUTH_METHOD_LDAP: // LDAP Authentication
case AUTH_METHOD_PROVIDERS: // Login Providers
cacti_log("DEBUG: Provider User '" . $username . "' to attempt login.", false, 'AUTH', POLLER_VERBOSITY_DEBUG);
$user = login_providers_login_process($username);
break;
default: // Login Realm not determined
$error = true;
$error_msg = __esc('Unable to determine user Login Realm or Domain. Please contact your System Administrator.');
cacti_log("LOGIN FAILED: User '" . $username . "' Unable to determine Login Realm. Exiting.", false, 'AUTH');
auth_display_custom_error_message($error_msg);
exit;
}
// Create user from template if available
if (!$error && !cacti_sizeof($user) && get_template_account($username) > 0 && $username != '') {
$user = auth_login_create_user_from_template($username, $realm);
}
// Guest account checking - Not for builtin
if (!$error && !cacti_sizeof($user) && get_guest_account() > 0) {
// Locate guest user record
$guestRow = db_fetch_row_prepared('SELECT *
FROM user_auth
WHERE id = ?',
[get_guest_account()]
);
$user = is_array($guestRow) ? $guestRow : [];
if ($user) {
cacti_log("LOGIN: Authenticated user '" . $username . "' using guest account '" . $user['username'] . "'", false, 'AUTH');
if ($username != '' && get_template_account($username) == 0) {
raise_message('template_disabled', __('User was Authenticated, but the Template Account is disabled. Using Guest Account'), MESSAGE_LEVEL_WARN);
}
$guest_user = true;
} else {
// error
$error = true;
$error_msg = __('Access Denied! Guest user id %s does not exist. Please contact your Administrator.', read_config_option('guest_user'));
cacti_log("LOGIN FAILED: Unable to locate guest user '" . read_config_option('guest_user') . "'", false, 'AUTH');
if ($auth_method == AUTH_METHOD_BASIC) {
auth_display_custom_error_message($error_msg);
exit;
}
}
}
// We have a valid user, do final checks, log their login attempt, and redirect as required
if (!$error && cacti_sizeof($user)) {
$client_addr = get_client_addr();
if (!$guest_user) {
cacti_log("LOGIN: User '" . $user['username'] . "' authenticated from IP Address '" . $client_addr . "'", false, 'AUTH');
} else {
cacti_log("LOGIN: Guest User '" . $user['username'] . "' in use from IP Address '" . $client_addr . "'", false, 'AUTH');
}
db_execute_prepared('INSERT IGNORE INTO user_log
(username, user_id, result, ip, time)
VALUES (?, ?, 1, ?, NOW())',
[$username, $user['id'], $client_addr]
);
// check if the user account is enabled with the exception of guest users
$user_enabled = true;
if (!$guest_user && isset($user['enabled'])) {
$user_enabled = ($user['enabled'] == 'on' ? true : false);
}
// check if the user is enabled
if (!$user_enabled) {
$error = true;
$error_msg = __('Access Denied! User account disabled.');
if ($auth_method == AUTH_METHOD_BASIC) {
auth_display_custom_error_message($error_msg);
exit;
}
}
if (!$error && !auth_user_has_access($user)) {
// error
$error = true;
$error_msg = __('You do not have access to any area of Cacti. Contact your administrator.');
cacti_log(sprintf('LOGIN FAILED: User %s with id %s does not have access to any area of Cacti.', $user['username'], $user['id']), false, 'AUTH');
if ($auth_method == AUTH_METHOD_BASIC) {
auth_display_custom_error_message($error_msg);
exit;
}
}
if (!$error && !cacti_auth_transition((int) $user['id'], 'login')) {
$error = true;
$error_msg = __('Access Denied! User account locked.');
}
// remember me support. Not for guest of basic auth. The transition
// gate must pass first so a locked or missing account cannot mint a token.
if ($auth_method != AUTH_METHOD_BASIC && $user['id'] !== get_guest_account()) {
if (!$error && isrv('remember_me') && read_config_option('auth_cache_enabled') == 'on' && auth_realm_allows_cookies((int) $realm)) {
set_auth_cookie($user);
}
}
if (!$error) {
// set the php session
$_SESSION[SESS_USER_ID] = $user['id'];
$_SESSION[SESS_USER_AGENT] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION[SESS_CLIENT_ADDR] = get_client_addr();
// handle 'force change password'
if ($user['must_change_password'] == 'on' && $auth_method == AUTH_METHOD_CACTI && $user['password_change'] == 'on') {
$_SESSION[SESS_CHANGE_PASSWORD] = true;
}
$user = auth_apply_group_login_opts($user);
if (user_setting_exists('user_language', $_SESSION[SESS_USER_ID])) {
$_SESSION[SESS_USER_LANGUAGE] = read_user_setting('user_language');
}
cacti_log("DEBUG: User '" . $username . "' about to re-direct to preferred login page", false, 'AUTH', POLLER_VERBOSITY_DEBUG);
auth_login_redirect($user['login_opts']);
}
} else {
$id = db_fetch_cell_prepared('SELECT id
FROM user_auth
WHERE username = ?
AND realm = ?',
[$username, $frv_realm]
);
switch ($frv_realm) {
case '0':
case '1':
$realm_name = 'Local';
break;
case '2':
$realm_name = 'LDAP';
break;
default:
$realm_name = 'Domains LDAP';
break;
}
// BAD username/password builtin and LDAP
db_execute_prepared('INSERT IGNORE INTO user_log
(username, user_id, result, ip, time)
VALUES (?, ?, 0, ?, NOW())',
[$username, !empty($id) ? $id : 0, get_client_addr()]
);
cacti_log('LOGIN FAILED: ' . $realm_name . " login failed for user '" . $username . "' from IP address '" . get_client_addr() . "'.", false, 'AUTH');
// Single machine-parseable failure line for fail2ban. This is the universal
// choke point for a failed login across every realm, so it fires exactly once
// per attempt (auth_process_lockout() deliberately does not emit its own).
// $frv_realm is the dropdown value the $realm_name switch above keys on.
auth_log_failure($username, auth_realm_token((int) $frv_realm), !empty($id) ? 'bad_password' : 'no_such_user');
}
}
if (api_plugin_hook_function('custom_login', OPER_MODE_NATIVE) == OPER_MODE_RESKIN) {
return;
}
$selectedTheme = get_selected_theme();
html_auth_header(
'login',
__('Login to Cacti'),
__('User Login'),
__('Enter your Username and Password below'),
[
'error' => $error,
'error_msg' => $error_msg,
'username' => $username,
'user_enabled' => $user_enabled,
'action' => gnrv('action')
]
);
?>
<tr>
<td>
<label for='login_username'><?php print __('Username'); ?></label>
</td>
<td>
<input type='text' class='ui-state-default ui-corner-all' id='login_username' name='login_username' size='15' maxlength='25' value='<?php print htmle($username); ?>' placeholder='<?php print __esc('Username'); ?>'>
</td>
</tr>
<tr>
<td>
<label for='login_password'><?php print __('Password'); ?></label>
</td>
<td>
<input type='password' autocomplete='current-password' class='ui-state-default ui-corner-all' id='login_password' name='login_password' size='15' maxlength='25' placeholder='********'>
</td>
</tr>
<?php
if (read_config_option('auth_method') == AUTH_METHOD_LDAP || read_config_option('auth_method') == AUTH_METHOD_PROVIDERS) {
$realms = get_auth_realms(true);
// try and remember previously selected realm
if ($frv_realm && array_key_exists($frv_realm, $realms)) {
foreach ($realms as $key => $realm) {
$realms[$key]['selected'] = ($frv_realm == $key);
}
}
?>
<tr>
<td>
<label for='realm'><?php print __('Realm'); ?></label>
</td>
<td>
<select id='realm' name='realm' class='ui-state-default ui-corner-all'>
<?php
if (cacti_sizeof($realms)) {
foreach ($realms as $index => $realm) {
print "\t\t\t\t\t<option value='" . $index . "'" . ($realm['selected'] ? ' selected="selected"' : '') . '>' . htmle($realm['name']) . "</option>\n";
}
}
?>
</select>
</td>
</tr>
<?php
}
$is_https = cacti_is_https();
if (read_config_option('auth_cache_enabled') == 'on' && $is_https) { ?>
<tr>
<td colspan='2'>
<input style='vertical-align:-3px;' type='checkbox' id='remember_me' name='remember_me' <?php print(isset($_COOKIE['cacti_remembers']) || !ierv('remember_me') ? 'checked' : ''); ?>>
<label for='remember_me'><?php print __('Keep me signed in'); ?></label>
</td>
</tr>
<?php
} ?>
<tr>
<td colspan='2'>
<button type='submit' class='ui-button ui-corner-all ui-widget ui-state-active' value='login'><?php print __esc('Login'); ?></button>
</td>
</tr>
<?php
$sso_providers = get_sso_login_providers();
if (cacti_sizeof($sso_providers)) {
foreach ($sso_providers as $sso_provider) { ?>
<tr>
<td colspan='2'>
<a class='ui-button ui-corner-all ui-widget sso-login-link' data-realm='<?php print $sso_provider['realm']; ?>' href='login_sso.php?action=login&realm=<?php print $sso_provider['realm']; ?>'><?php print __esc('Login with %s', $sso_provider['label']); ?></a>
</td>
</tr>
<?php
}
}
$error_message = '';
if ($error_msg) {
$error_message = $error_msg;
} else {
if (gnrv('action') == 'login') {
$error_message = __('Invalid User Name/Password Please Retype');
}
if ($user_enabled == '0') {
$error_message = __('User Account Disabled');
}
}
$focus_control = (empty($username)) ? 'username' : 'password';
html_auth_footer('login', $error_message, "
<script>
var storage = Storages.localStorage;
$(function() {
$('body').css('height', $(window).height());
$('.cactiAuthLeft').css('width',parseInt($(window).width()*0.33)+'px');
$('.cactiAuthRight').css('width',parseInt($(window).width()*0.33)+'px');
$('#login_{$focus_control}').focus();
if (storage.isSet('user_realm')) {
var preferredRealm = storage.get('user_realm');
} else {
var preferredRealm = null;
}
if (preferredRealm == null) {
preferredRealm = $('#realm option:selected').val();
}
// Restore the preferred realm
if ($('#realm').length) {
if (preferredRealm !== null) {
$('#realm').val(preferredRealm);
if ($('#realm').selectmenu('instance') !== undefined) {
$('#realm').selectmenu('refresh');
}
}
}
// Control submit in order to store preferred realm
$('#auth').submit(function(event) {
event.preventDefault();
if ($('#realm').length) {
storage.set('user_realm', $('#realm').val());
}
$('#auth').off('submit').trigger('submit');
});
// SSO buttons are plain links (they leave the page for the IdP), so the
// remember_me checkbox state has to be appended to the href by hand.
$('.sso-login-link').on('click', function() {
if ($('#remember_me').is(':checked')) {
this.href += '&remember_me=1';
}
});
});
</script>
", $error && $frv_realm <= 1);