Could you please review this code and check if everything is correct? Should work with J4, 5 and 6.
<?php
/*
* @copyright Copyright (C) 2014 Markus Neubauer. All rights reserved.
* @license http://www.gnu.org/licenses/agpl-3.0.html GNU/AGPL
* @github https://github.com/marneu/joomlatic/tree/master/syslogauthlog
* @homepage http://www.std-soft.com/index.php/hm-service/81-c-std-service-code/9-joomla-plugin-syslogauthlog
* @version 1.7.1 (ported for Joomla 4+/6 compatibility)
*/
defined('_JEXEC') or die();
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Factory;
use Psr\Log\LogLevel;
/*
* Compatibility shims for removed legacy classes in Joomla 4+/6.
* These provide minimal implementations so the plugin can continue
* to create JLogEntry objects and write to syslog using PHP's syslog().
*/
// Ensure JFactory alias exists for older code paths that use JFactory
if (!class_exists('JFactory') && class_exists(\Joomla\CMS\Factory::class)) {
class_alias(\Joomla\CMS\Factory::class, 'JFactory');
}
// Minimal JLog class (constants + add fallback)
if (!class_exists('JLog')) {
class JLog {
const EMERGENCY = LOG_EMERG;
const ALERT = LOG_ALERT;
const CRITICAL = LOG_CRIT;
const ERROR = LOG_ERR;
const WARNING = LOG_WARNING;
const NOTICE = LOG_NOTICE;
const INFO = LOG_INFO;
const DEBUG = LOG_DEBUG;
public static function add($message, $priority = LOG_INFO, $category = '')
{
// write directly to syslog
$prefixed = ($category ? "[$category] " : '') . $message;
syslog($priority, $prefixed);
}
}
}
// Minimal JLogEntry container
if (!class_exists('JLogEntry')) {
class JLogEntry {
public $message;
public $priority;
public $category;
public function __construct($message, $priority = LOG_INFO, $category = '')
{
$this->message = $message;
$this->priority = $priority;
$this->category = $category;
}
}
}
// Minimal syslog logger used by the plugin (replaces legacy JLogLoggerSyslog)
if (!class_exists('JLogLoggerSyslog')) {
class JLogLoggerSyslog {
protected $options = array();
protected $opened = false;
public function __construct($options = array())
{
$defaults = array(
'sys_ident' => 'jauthlog',
'sys_add_pid' => true,
'sys_use_stderr' => false,
'sys_facility' => LOG_AUTH
);
$this->options = array_merge($defaults, (array) $options);
$flags = 0;
if (!empty($this->options['sys_add_pid'])) {
$flags |= LOG_PID;
}
// LOG_PERROR may not exist on all systems, guard it
if (!empty($this->options['sys_use_stderr']) && defined('LOG_PERROR')) {
$flags |= LOG_PERROR;
}
@openlog($this->options['sys_ident'], $flags, $this->options['sys_facility']);
$this->opened = true;
}
public function addEntry($entry)
{
if (!$this->opened) {
@openlog($this->options['sys_ident'], LOG_PID, $this->options['sys_facility']);
$this->opened = true;
}
// $entry->priority expected to be a LOG_* constant
@syslog($entry->priority, $entry->message);
}
public function __destruct()
{
if ($this->opened) {
@closelog();
}
}
}
}
/**
* Plugin class
*/
class PlgSystemSyslogAuthLog extends CMSPlugin {
/**
* Load the language file on instantiation.
*
* @var boolean
* @since 3.1
*/
protected $autoloadLanguage = true;
private $format = '{PRIORITY} {EVENT} {USERNAME} {ADMIN} {MESSAGE} from {CLIENTIP}';
private $syslog_options = array(
'sys_ident' => 'jauthlog',
'sys_add_pid' => true,
'sys_use_stderr' => false,
'sys_facility' => LOG_AUTH
);
private $syslog;
// ranges known as private or virtual IPs (unused in public internet)
private $ignore_ip = array (
array('0.0.0.0','2.255.255.255'),
array('10.0.0.0','10.255.255.255'),
array('127.0.0.0','127.255.255.255'),
array('169.254.0.0','169.254.255.255'),
array('172.16.0.0','172.31.255.255'),
array('192.0.0.0','192.0.0.255'),
array('192.0.2.0','192.0.2.255'),
array('192.88.99.0','192.88.99.255'),
array('192.168.0.0','192.168.255.255'),
array('198.18.0.0','198.19.255.255'),
array('198.51.100.0','198.51.100.255'),
array('203.0.113.0','203.0.113.255'),
array('224.0.0.0','255.255.255.255')
);
public $priorities = array(
JLog::EMERGENCY => 'EMERG',
JLog::ALERT => 'ALERT',
JLog::CRITICAL => 'CRIT',
JLog::ERROR => 'ERR',
JLog::WARNING => 'WARNING',
JLog::NOTICE => 'NOTICE',
JLog::INFO => 'INFO',
JLog::DEBUG => 'DEBUG'
);
private $priority = JLog::INFO;
private $field = array( 'MESSAGE' => '' );
private $version = JVERSION;
public function __construct(&$subject, $config)
{
parent::__construct($subject, $config);
// Note: removed an unused legacy JApplicationHelper::getHash() call.
// If you need a hash here for other reasons, reimplement with the appropriate API.
}
private function check_ip($ip)
{
if (!empty($ip) && ip2long($ip) != -1) {
foreach ($this->ignore_ip as $r) {
$min = ip2long($r[0]);
$max = ip2long($r[1]);
if ((ip2long($ip) >= $min) && (ip2long($ip) <= $max)) return false;
}
return true;
} else {
return false;
}
}
private function getAddr()
{
if (isset($_SERVER["HTTP_CLIENT_IP"]) && $this->check_ip($_SERVER["HTTP_CLIENT_IP"])) {
return $_SERVER["HTTP_CLIENT_IP"];
}
if ( isset($_SERVER["HTTP_X_FORWARDED_FOR"]) ) {
foreach (explode(",",$_SERVER["HTTP_X_FORWARDED_FOR"]) as $ip) {
if ($this->check_ip(trim($ip))) {
return $ip;
}
}
}
if ( isset($_SERVER["HTTP_X_FORWARDED"]) && $this->check_ip($_SERVER["HTTP_X_FORWARDED"])) {
return $_SERVER["HTTP_X_FORWARDED"];
} elseif ( isset($_SERVER["HTTP_FORWARDED_FOR"]) && $this->check_ip($_SERVER["HTTP_FORWARDED_FOR"])) {
return $_SERVER["HTTP_FORWARDED_FOR"];
} elseif ( isset($_SERVER["HTTP_FORWARDED"]) && $this->check_ip($_SERVER["HTTP_FORWARDED"])) {
return $_SERVER["HTTP_FORWARDED"];
} else {
return $_SERVER["REMOTE_ADDR"];
}
}
private function log() {
if (Factory::getApplication()->isClient('administrator')) {
$this->field['ADMIN'] = 'ADMIN';
}
else {
if ( $this->params->def('type', 1) == 1 ) return;
$this->field['ADMIN'] = '';
}
$this->field['CLIENTIP'] = $this->getAddr();
$this->field['PRIORITY'] = $this->priorities[$this->priority];
// Fill in field data for the line.
$message = $this->format;
foreach ($this->field as $tmp => $val)
{
$message = str_replace('{' . $tmp . '}', $val, $message);
}
// setup the new entry for syslog.
$jLogEntry = new JLogEntry($message, $this->priority, getenv('USER'));
// connect to syslog (plugin supports Joomla 3..6 behaviour previously)
if ( substr( $this->version, 0, 1) == '3'
|| substr( $this->version, 0, 1) == '4'
|| substr( $this->version, 0, 1) == '5'
|| substr( $this->version, 0, 1) == '6'
)
{
$this->syslog = new JLogLoggerSyslog($this->syslog_options);
} else {
die ("Unsupported Joomla version for plg syslogauth.");
}
// Write the new entry to syslog.
$this->syslog->addEntry($jLogEntry);
}
public function onUserAfterLogin($options)
{ // JAuthenticationResponse array
if ( $this->params->def('mode', 1) != 0 ) return;
if ( $this->params->def('event', 1) == 2 ) return;
$this->field['EVENT'] = 'login';
$this->field['USERNAME'] = isset($options['user']->username) ? $options['user']->username : (isset($options['username']) ? $options['username'] : 'UNKNOWN');
$this->log();
}
public function onUserLoginFailure($response)
{
// do not log successful logins (status may be a string or a legacy constant)
$success = false;
if (isset($response['status'])) {
if (is_string($response['status']) && strtoupper($response['status']) === 'SUCCESS') {
$success = true;
} elseif (class_exists('JAuthentication') && defined('JAuthentication::STATUS_SUCCESS')) {
// If legacy constant exists, attempt to compare (guarded)
try {
if ($response['status'] == JAuthentication::STATUS_SUCCESS) $success = true;
} catch (\Throwable $e) {
// ignore
}
}
}
if ($success) return;
// log only if configured to do so
if ( $this->params->def('event', 1) == 2 ) return;
$this->field['MESSAGE'] = isset($response['error_message']) ? $response['error_message'] : '';
$this->priority = JLog::WARNING;
$this->field['EVENT'] = 'login';
if ($this->params->get('log_username', 0)) {
$this->field['USERNAME'] = isset($response['username']) ? $response['username'] : 'UNKNOWN';
} else {
$this->field['USERNAME'] = 'UNKNOWN';
}
$this->log();
}
public function onUserAfterLogout($options)
{
if ( $this->params->def('mode', 1) != 0 ) return;
if ( $this->params->def('event', 1) == 1 ) return;
$this->field['EVENT'] = 'logout';
$this->field['USERNAME'] = isset($options['username']) ? $options['username'] : 'UNKNOWN';
$this->log();
}
public function onUserLogoutFailure($parameters)
{
if ( $this->params->def('event', 1) == 1 ) return;
$this->priority = JLog::WARNING;
$this->field['EVENT'] = 'logout';
$this->field['USERNAME'] = isset($parameters['username']) ? $parameters['username'] : 'UNKNOWN';
$this->field['MESSAGE'] = 'failure';
$this->log();
}
}
?>
Could you please review this code and check if everything is correct? Should work with J4, 5 and 6.