generated from smedev/Template-for-SMEServer-Contribs-Package
Add in software files and templates
This commit is contained in:
99
root/opt/dmarc-srg/classes/Admin.php
Normal file
99
root/opt/dmarc-srg/classes/Admin.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains Admin class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg;
|
||||
|
||||
use Liuch\DmarcSrg\Mail\MailBoxes;
|
||||
use Liuch\DmarcSrg\Directories\DirectoryList;
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* It's the main class for accessing administration functions.
|
||||
*/
|
||||
class Admin
|
||||
{
|
||||
private $core = null;
|
||||
|
||||
/**
|
||||
* The constructor of the class
|
||||
*
|
||||
* @param Core $core Instace of the Core class
|
||||
*/
|
||||
public function __construct($core)
|
||||
{
|
||||
$this->core = $core;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the database, directories, and mailboxes as an array.
|
||||
*
|
||||
* @return array Contains fields: `database`, `state`, `mailboxes`, `directories`.
|
||||
*/
|
||||
public function state(): array
|
||||
{
|
||||
$res = [];
|
||||
$res['database'] = $this->core->database()->state();
|
||||
$res['mailboxes'] = (new MailBoxes())->list();
|
||||
$res['directories'] = array_map(function ($dir) {
|
||||
return $dir->toArray();
|
||||
}, (new DirectoryList())->list());
|
||||
|
||||
if ($res['database']['correct'] ?? false) {
|
||||
$res['state'] = 'Ok';
|
||||
} else {
|
||||
$res['state'] = 'Err';
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the availability of report sources.
|
||||
*
|
||||
* @param int $id Id of the checked source. If $id == 0 then all available sources with the passed type
|
||||
* will be checked.
|
||||
* @param string $type Type of the checked source.
|
||||
*
|
||||
* @return array Result array with `error_code` and `message` fields.
|
||||
* For one resource and if there is no error,
|
||||
* a field `status` will be added to the result.
|
||||
*/
|
||||
public function checkSource(int $id, string $type): array
|
||||
{
|
||||
switch ($type) {
|
||||
case 'mailbox':
|
||||
return (new MailBoxes())->check($id);
|
||||
case 'directory':
|
||||
return (new DirectoryList())->check($id);
|
||||
}
|
||||
throw new LogicException('Unknown resource type');
|
||||
}
|
||||
}
|
147
root/opt/dmarc-srg/classes/Auth.php
Normal file
147
root/opt/dmarc-srg/classes/Auth.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains Auth class.
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg;
|
||||
|
||||
use Liuch\DmarcSrg\Exception\AuthException;
|
||||
|
||||
/**
|
||||
* Class for working with authentication data.
|
||||
*/
|
||||
class Auth
|
||||
{
|
||||
private $core = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param Core $core
|
||||
*/
|
||||
public function __construct(object $core)
|
||||
{
|
||||
$this->core = $core;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if authentication is enabled.
|
||||
*
|
||||
* The method examines the key `password` in $admin array from the config file.
|
||||
* It must exist and not be null.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->core->config('admin/password') !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The authetication with a username and password.
|
||||
*
|
||||
* This method checks the password passed in $password and creates a user session.
|
||||
* This method throws an exception if the passed password is wrong.
|
||||
* The password with an empty string is always wrong!
|
||||
*
|
||||
* @param string $username - Must be an empty string, it is currently not used.
|
||||
* @param string $password - Must not be an empty string.
|
||||
*
|
||||
* @return array Array with `error_code` and `message` fields.
|
||||
*/
|
||||
public function login(string $username, string $password): array
|
||||
{
|
||||
if ($username !== '' || $this->core->config('admin/password') === '' || !$this->isAdminPassword($password)) {
|
||||
throw new AuthException('Authentication failed. Try again');
|
||||
}
|
||||
$this->core->userId(0);
|
||||
return [
|
||||
'error_code' => 0,
|
||||
'message' => 'Authentication succeeded'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current user's session.
|
||||
*
|
||||
* @return array Array with `error_code` and `message` fields.
|
||||
*/
|
||||
public function logout(): array
|
||||
{
|
||||
$this->core->destroySession();
|
||||
return [
|
||||
'error_code' => 0,
|
||||
'message' => 'Logged out successfully'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user session exists.
|
||||
*
|
||||
* This method throws an exception if authentication needed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function isAllowed(): void
|
||||
{
|
||||
if ($this->isEnabled()) {
|
||||
if ($this->core->userId() === false) {
|
||||
throw new AuthException('Authentication needed', -2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed password is the admin password.
|
||||
*
|
||||
* Throws an exception if the passed password is not the admin password.
|
||||
*
|
||||
* @param string $password Password to check
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function checkAdminPassword(string $password): void
|
||||
{
|
||||
if (!$this->isAdminPassword($password)) {
|
||||
throw new AuthException('Incorrect password');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $password equals the admin password.
|
||||
*
|
||||
* @param string $password Password to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isAdminPassword(string $password): bool
|
||||
{
|
||||
return $password === $this->core->config('admin/password');
|
||||
}
|
||||
}
|
50
root/opt/dmarc-srg/classes/Common.php
Normal file
50
root/opt/dmarc-srg/classes/Common.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains common classes
|
||||
*
|
||||
* @category Common
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg;
|
||||
|
||||
/**
|
||||
* Static common arrays
|
||||
*/
|
||||
class Common
|
||||
{
|
||||
/**
|
||||
* This array needs for converting the align result text constant to integer value and back
|
||||
* in Report and ReportList classes
|
||||
*/
|
||||
public static $align_res = [ 'fail', 'unknown', 'pass' ];
|
||||
|
||||
/**
|
||||
* This array needs for converting the the disposition result text constant to integer value and back
|
||||
* in Report and ReportList classes
|
||||
*/
|
||||
public static $disposition = [ 'reject', 'quarantine', 'none' ];
|
||||
}
|
89
root/opt/dmarc-srg/classes/Config.php
Normal file
89
root/opt/dmarc-srg/classes/Config.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class Config
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg;
|
||||
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* This class is for storing configuration data to avoid using globas variables
|
||||
*/
|
||||
class Config
|
||||
{
|
||||
private $data = [];
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param string $config_file A php config file to load.
|
||||
*/
|
||||
public function __construct(string $config_file)
|
||||
{
|
||||
require($config_file);
|
||||
foreach ([
|
||||
'debug', 'database', 'mailboxes', 'directories',
|
||||
'admin', 'mailer', 'fetcher', 'cleaner'
|
||||
] as $key
|
||||
) {
|
||||
$this->data[$key] = $$key ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config value by its name
|
||||
*
|
||||
* @param string $name Setting name. Hierarchy supported via '/'
|
||||
* @param mixed $default Value to be returned if the required config item is missing or null
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $name, $default = null)
|
||||
{
|
||||
$nm_i = 0;
|
||||
$path = explode('/', $name);
|
||||
$data = $this->data;
|
||||
do {
|
||||
$key = $path[$nm_i++];
|
||||
if (empty($key)) {
|
||||
throw new LogicException('Incorrect setting name: ' .$name);
|
||||
}
|
||||
if (!isset($data[$key])) {
|
||||
return $default;
|
||||
}
|
||||
$data = $data[$key];
|
||||
if (!isset($path[$nm_i])) {
|
||||
return $data ?? $default;
|
||||
}
|
||||
} while (gettype($data) === 'array');
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
362
root/opt/dmarc-srg/classes/Core.php
Normal file
362
root/opt/dmarc-srg/classes/Core.php
Normal file
@@ -0,0 +1,362 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the Core class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg;
|
||||
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* It's class for accessing to most methods for working with http, json data,
|
||||
* the user session, getting instances of some classes
|
||||
*/
|
||||
class Core
|
||||
{
|
||||
public const APP_VERSION = '1.7';
|
||||
private const SESSION_NAME = 'session';
|
||||
private const HTML_FILE_NAME = 'index.html';
|
||||
|
||||
private $modules = [];
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param array $params Array with modules to be bind to
|
||||
*/
|
||||
public function __construct($params)
|
||||
{
|
||||
foreach ([ 'admin', 'auth', 'config', 'database', 'ehandler', 'status' ] as $key) {
|
||||
if (isset($params[$key])) {
|
||||
$this->modules[$key] = $params[$key];
|
||||
}
|
||||
}
|
||||
self::$instance = $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method of the current http request.
|
||||
*
|
||||
* @return string http method
|
||||
*/
|
||||
public static function method(): string
|
||||
{
|
||||
return $_SERVER['REQUEST_METHOD'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of request headers in lowercase mode.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getHeaders(): array
|
||||
{
|
||||
return array_change_key_case(getallheaders(), CASE_LOWER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or gets the current user's id
|
||||
*
|
||||
* In case $id is null, the method returns the current user's id.
|
||||
* In case $id is integer value, the method sets this value as the current user's id.
|
||||
* It returns false if there is an error.
|
||||
*
|
||||
* @param int|void $id User id to set it.
|
||||
*
|
||||
* @return int|bool User id or false in case of error.
|
||||
*/
|
||||
public function userId($id = null)
|
||||
{
|
||||
$start_f = false;
|
||||
if ((self::cookie(self::SESSION_NAME) !== '' || $id !== null) && session_status() !== PHP_SESSION_ACTIVE) {
|
||||
$start_f = true;
|
||||
self::sessionStart();
|
||||
}
|
||||
$res = null;
|
||||
if (gettype($id) === 'integer') {
|
||||
$_SESSION['user_id'] = $id;
|
||||
}
|
||||
$res = isset($_SESSION['user_id']) ? intval($_SESSION['user_id']) : false;
|
||||
if ($start_f) {
|
||||
session_write_close();
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the session of the current user and the corresponding cookie.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroySession(): void
|
||||
{
|
||||
if (self::cookie(self::SESSION_NAME)) {
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
self::sessionStart();
|
||||
}
|
||||
$_SESSION = [];
|
||||
if (ini_get('session.use_cookies')) {
|
||||
$scp = session_get_cookie_params();
|
||||
$cep = [
|
||||
'expires' => time() - 42000,
|
||||
'path' => $scp['path'],
|
||||
'domain' => $scp['domain'],
|
||||
'secure' => $scp['secure'],
|
||||
'httponly' => $scp['httponly'],
|
||||
'samesite' => $scp['samesite']
|
||||
];
|
||||
setcookie(self::SESSION_NAME, '', $cep);
|
||||
session_write_close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the http request asks for json data.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isJson(): bool
|
||||
{
|
||||
$headers = self::getHeaders();
|
||||
return (isset($headers['accept']) && $headers['accept'] === 'application/json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the html file to the client.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function sendHtml(): void
|
||||
{
|
||||
if (file_exists(Core::HTML_FILE_NAME)) {
|
||||
readfile(Core::HTML_FILE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data from an array as json string to the client.
|
||||
*
|
||||
* @param array $data - Data to send.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function sendJson(array $data): void
|
||||
{
|
||||
$res_str = json_encode($data);
|
||||
if ($res_str === false) {
|
||||
$res_str = '[]';
|
||||
}
|
||||
header('content-type: application/json; charset=UTF-8');
|
||||
echo $res_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a Bad Request response to the client.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function sendBad(): void
|
||||
{
|
||||
http_response_code(400);
|
||||
echo 'Bad request';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves json data from the request and return it as an array.
|
||||
*
|
||||
* Returns an array with data or null if there is an error.
|
||||
*
|
||||
* @return array|null Data from the request
|
||||
*/
|
||||
public static function getJsonData()
|
||||
{
|
||||
$res = null;
|
||||
$headers = self::getHeaders();
|
||||
if (isset($headers['content-type']) && $headers['content-type'] === 'application/json') {
|
||||
$str = file_get_contents('php://input');
|
||||
if ($str) {
|
||||
$res = json_decode($str, true);
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the class Auth.
|
||||
*
|
||||
* @return Auth
|
||||
*/
|
||||
public function auth()
|
||||
{
|
||||
return $this->getModule('auth', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the class Status.
|
||||
*
|
||||
* @return Status instance of Status
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
return $this->getModule('status', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the class Admin.
|
||||
*
|
||||
* @return Admin instance of Admin
|
||||
*/
|
||||
public function admin()
|
||||
{
|
||||
return $this->getModule('admin', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the class Database.
|
||||
*
|
||||
* @return DatabaseController
|
||||
*/
|
||||
public function database()
|
||||
{
|
||||
return $this->getModule('database', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the class ErrorHandler
|
||||
*
|
||||
* @return ErrorHandler
|
||||
*/
|
||||
public function errorHandler()
|
||||
{
|
||||
return $this->getModule('ehandler', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current logger.
|
||||
* Just a proxy method to return the logger from ErrorHandler
|
||||
*
|
||||
* @return LoggerInterface
|
||||
*/
|
||||
public function logger()
|
||||
{
|
||||
return $this->errorHandler()->logger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns instance of the object
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function instance()
|
||||
{
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the config value by its name
|
||||
*
|
||||
* @param string $name Config item name. Hierarchy supported via '/'
|
||||
* @param mixed $default Value to be returned if the required config item is missing or null
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function config(string $name, $default = null)
|
||||
{
|
||||
return $this->getModule('config', false)->get($name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets a cookie with the specified name.
|
||||
*
|
||||
* @param string $name the cookie name to get or to set
|
||||
* @param string|null $value
|
||||
* @param array|null $params
|
||||
*
|
||||
* @return string|boolean The cookie value or false if there is an error
|
||||
*/
|
||||
private static function cookie($name, $value = null, $params = null)
|
||||
{
|
||||
if (!$value) {
|
||||
return isset($_COOKIE[$name]) ? $_COOKIE[$name] : '';
|
||||
}
|
||||
if (setcookie($name, $value, $params)) {
|
||||
return $value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the user session
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function sessionStart(): void
|
||||
{
|
||||
if (!session_start(
|
||||
[
|
||||
'name' => self::SESSION_NAME,
|
||||
'cookie_path' => dirname($_SERVER['REQUEST_URI']),
|
||||
'cookie_httponly' => true,
|
||||
'cookie_samesite' => 'Strict'
|
||||
]
|
||||
)
|
||||
) {
|
||||
throw new SoftException('Failed to start a user session');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a module instance by its name. Lazy initialization is used.
|
||||
*
|
||||
* @param string $name Module name
|
||||
* @param bool $core Whether to pass $this to the constructor
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
private function getModule(string $name, bool $core)
|
||||
{
|
||||
$module = $this->modules[$name] ?? null;
|
||||
switch (gettype($module)) {
|
||||
case 'array':
|
||||
if ($core) {
|
||||
$module = new $module[0]($this, ...($module[1] ?? []));
|
||||
} else {
|
||||
$module = new $module[0](...($module[1] ?? []));
|
||||
}
|
||||
$this->modules[$name] = $module;
|
||||
break;
|
||||
case 'NULL':
|
||||
throw new LogicException('Attempt to initiate an unloaded module ' . $name);
|
||||
}
|
||||
return $module;
|
||||
}
|
||||
}
|
543
root/opt/dmarc-srg/classes/Database/Database.php
Normal file
543
root/opt/dmarc-srg/classes/Database/Database.php
Normal file
@@ -0,0 +1,543 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database;
|
||||
|
||||
use Liuch\DmarcSrg\Settings\SettingString;
|
||||
|
||||
class Database
|
||||
{
|
||||
public const REQUIRED_VERSION = '2.0';
|
||||
|
||||
private $conn;
|
||||
private static $instance = null;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
$this->conn = null;
|
||||
$this->establishConnection();
|
||||
}
|
||||
|
||||
public static function connection()
|
||||
{
|
||||
if (!self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance->conn;
|
||||
}
|
||||
|
||||
public static function type()
|
||||
{
|
||||
global $database;
|
||||
return $database['type'];
|
||||
}
|
||||
|
||||
public static function name()
|
||||
{
|
||||
global $database;
|
||||
return $database['name'];
|
||||
}
|
||||
|
||||
public static function location()
|
||||
{
|
||||
global $database;
|
||||
return $database['host'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prefix for tables of the database
|
||||
*
|
||||
* @param string $postfix String to be concatenated with the prefix.
|
||||
* Usually, this is a table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function tablePrefix(string $postfix = ''): string
|
||||
{
|
||||
global $database;
|
||||
return ($database['table_prefix'] ?? '') . $postfix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the database as an array.
|
||||
*
|
||||
* @return array May contain the following fields:
|
||||
* `tables` - an array of tables with their properties;
|
||||
* `needs_upgrade` - true if the database needs upgrading;
|
||||
* `correct` - true if the database is correct;
|
||||
* `type` - the database type;
|
||||
* `name` - the database name;
|
||||
* `location` - the database location;
|
||||
* `version` - the current version of the database structure;
|
||||
* `message` - a state message;
|
||||
* `error_code` - an error code;
|
||||
*/
|
||||
public static function state(): array
|
||||
{
|
||||
$res = [];
|
||||
try {
|
||||
$prefix = self::tablePrefix();
|
||||
$p_len = strlen($prefix);
|
||||
if ($p_len > 0) {
|
||||
$like_str = ' WHERE NAME LIKE "' . str_replace('_', '\\_', $prefix) . '%"';
|
||||
} else {
|
||||
$like_str = '';
|
||||
}
|
||||
$db = self::connection();
|
||||
$tables = [];
|
||||
$st = $db->query(
|
||||
'SHOW TABLE STATUS FROM `' . str_replace('`', '', self::name()) . '`' . $like_str
|
||||
);
|
||||
while ($row = $st->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$tnm = $row['Name'];
|
||||
$st2 = $db->query('SELECT COUNT(*) FROM `' . $tnm . '`');
|
||||
$rows = $st2->fetch(\PDO::FETCH_NUM)[0];
|
||||
$tables[substr($tnm, $p_len)] = [
|
||||
'engine' => $row['Engine'],
|
||||
'rows' => intval($rows),
|
||||
'data_length' => intval($row['Data_length']),
|
||||
'index_length' => intval($row['Index_length']),
|
||||
'create_time' => $row['Create_time'],
|
||||
'update_time' => $row['Update_time']
|
||||
];
|
||||
}
|
||||
foreach (array_keys(self::$schema) as $table) {
|
||||
if (!isset($tables[$table])) {
|
||||
$tables[$table] = false;
|
||||
}
|
||||
}
|
||||
$exist_sys = false;
|
||||
$exist_cnt = 0;
|
||||
$absent_cnt = 0;
|
||||
$tables_res = [];
|
||||
foreach ($tables as $tname => $tval) {
|
||||
$t = null;
|
||||
if ($tval) {
|
||||
$t = $tval;
|
||||
$t['exists'] = true;
|
||||
if (isset(self::$schema[$tname])) {
|
||||
$exist_cnt += 1;
|
||||
$t['message'] = 'Ok';
|
||||
if (!$exist_sys && $tname === 'system') {
|
||||
$exist_sys = true;
|
||||
}
|
||||
} else {
|
||||
$t['message'] = 'Unknown table';
|
||||
}
|
||||
} else {
|
||||
$absent_cnt += 1;
|
||||
$t = [
|
||||
'error_code' => 1,
|
||||
'message' => 'Not exist'
|
||||
];
|
||||
}
|
||||
$t['name'] = $tname;
|
||||
$tables_res[] = $t;
|
||||
}
|
||||
$res['tables'] = $tables_res;
|
||||
$ver = $exist_sys ? (new SettingString('version'))->value() : null;
|
||||
if ($exist_sys && $ver !== self::REQUIRED_VERSION) {
|
||||
self::setDbMessage('The database structure needs upgrading', 0, $res);
|
||||
$res['needs_upgrade'] = true;
|
||||
} elseif ($absent_cnt == 0) {
|
||||
$res['correct'] = true;
|
||||
self::setDbMessage('Ok', 0, $res);
|
||||
} else {
|
||||
if ($exist_cnt == 0) {
|
||||
self::setDbMessage('The database schema is not initiated', -1, $res);
|
||||
} else {
|
||||
self::setDbMessage('Incomplete set of the tables', -1, $res);
|
||||
}
|
||||
}
|
||||
if ($ver) {
|
||||
$res['version'] = $ver;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$res['error_code'] = $e->getCode();
|
||||
$res['message'] = $e->getMessage();
|
||||
}
|
||||
$res['type'] = self::type();
|
||||
$res['name'] = self::name();
|
||||
$res['location'] = self::location();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inites the database.
|
||||
*
|
||||
* This method creates needed tables and indexes in the database.
|
||||
* The method will fail if the database already have tables with the table prefix.
|
||||
*
|
||||
* @return array Result array with `error_code` and `message` fields.
|
||||
*/
|
||||
public static function initDb(): array
|
||||
{
|
||||
try {
|
||||
$db = self::connection();
|
||||
$st = $db->query(self::sqlShowTablesQuery());
|
||||
try {
|
||||
if ($st->fetch()) {
|
||||
if (empty(self::tablePrefix())) {
|
||||
throw new \Exception('The database is not empty', -4);
|
||||
} else {
|
||||
throw new \Exception('Database tables already exist with the given prefix', -4);
|
||||
}
|
||||
}
|
||||
foreach (array_keys(self::$schema) as $table) {
|
||||
self::createDbTable(self::tablePrefix($table), self::$schema[$table]);
|
||||
}
|
||||
} finally {
|
||||
$st->closeCursor();
|
||||
}
|
||||
$st = $db->prepare(
|
||||
'INSERT INTO `' . self::tablePrefix('system') . '` (`key`, `value`) VALUES ("version", ?)'
|
||||
);
|
||||
$st->bindValue(1, self::REQUIRED_VERSION, \PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
$st->closeCursor();
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'error_code' => $e->getCode(),
|
||||
'message' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
return [ 'message' => 'The database has been initiated' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the database.
|
||||
*
|
||||
* Drops tables with the table prefix in the database or all tables in the database if no table prefix is set.
|
||||
*
|
||||
* @return array Result array with `error_code` and `message` fields.
|
||||
*/
|
||||
public static function dropTables(): array
|
||||
{
|
||||
try {
|
||||
$db = self::connection();
|
||||
$db->query('SET foreign_key_checks = 0');
|
||||
$st = $db->query(self::sqlShowTablesQuery());
|
||||
while ($table = $st->fetchColumn(0)) {
|
||||
$db->query('DROP TABLE `' . $table . '`');
|
||||
}
|
||||
$st->closeCursor();
|
||||
$db->query('SET foreign_key_checks = 1');
|
||||
} catch (\PDOException $e) {
|
||||
return [
|
||||
'error_code' => $e->getCode(),
|
||||
'message' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
return [ 'message' => 'Database tables have been dropped' ];
|
||||
}
|
||||
|
||||
private function establishConnection()
|
||||
{
|
||||
global $database;
|
||||
try {
|
||||
$dsn = "{$database['type']}:host={$database['host']};dbname={$database['name']};charset=utf8";
|
||||
$this->conn = new \PDO(
|
||||
$dsn,
|
||||
$database['user'],
|
||||
$database['password'],
|
||||
[ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION ]
|
||||
);
|
||||
$this->conn->query('SET time_zone = "+00:00"');
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception($e->getMessage(), -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SHOW TABLES SQL query string for tables with the table prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function sqlShowTablesQuery(): string
|
||||
{
|
||||
$res = 'SHOW TABLES';
|
||||
$prefix = self::tablePrefix();
|
||||
if (strlen($prefix) > 0) {
|
||||
$res .= ' WHERE `tables_in_' . str_replace('`', '', self::name())
|
||||
. '` LIKE "' . str_replace('_', '\\_', $prefix) . '%"';
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a table in the database.
|
||||
*
|
||||
* @param string $name Table name
|
||||
* @param array $definitions Table structure
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function createDbTable(string $name, array $definitions): void
|
||||
{
|
||||
$query = 'CREATE TABLE `' . $name . '` (';
|
||||
$col_num = 0;
|
||||
foreach ($definitions['columns'] as $column) {
|
||||
if ($col_num > 0) {
|
||||
$query .= ', ';
|
||||
}
|
||||
$query .= '`' . $column['name'] . '` ' . $column['definition'];
|
||||
$col_num += 1;
|
||||
}
|
||||
$query .= ', ' . $definitions['additional'] . ') ' . $definitions['table_options'];
|
||||
self::connection()->query($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the database message and error code for the state array
|
||||
*
|
||||
* @param string $message Message string
|
||||
* @param int $err_code Error code
|
||||
* @param array $state Database state array
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function setDbMessage(string $message, int $err_code, array &$state): void
|
||||
{
|
||||
$state['message'] = $message;
|
||||
if ($err_code !== 0) {
|
||||
$state['error_code'] = $err_code;
|
||||
}
|
||||
}
|
||||
|
||||
private static $schema = [
|
||||
'system' => [
|
||||
'columns' => [
|
||||
[
|
||||
'name' => 'key',
|
||||
'definition' => 'varchar(64) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'value',
|
||||
'definition' => 'varchar(255) DEFAULT NULL'
|
||||
]
|
||||
],
|
||||
'additional' => 'PRIMARY KEY (`key`)',
|
||||
'table_options' => 'ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||
],
|
||||
'domains' => [
|
||||
'columns' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'definition' => 'int(10) unsigned NOT NULL AUTO_INCREMENT'
|
||||
],
|
||||
[
|
||||
'name' => 'fqdn',
|
||||
'definition' => 'varchar(255) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'active',
|
||||
'definition' => 'boolean NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'description',
|
||||
'definition' => 'TEXT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'created_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'updated_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
]
|
||||
],
|
||||
'additional' => 'PRIMARY KEY (`id`), UNIQUE KEY `fqdn` (`fqdn`)',
|
||||
'table_options' => 'ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||
],
|
||||
'reports' => [
|
||||
'columns' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'definition' => 'int(10) unsigned NOT NULL AUTO_INCREMENT'
|
||||
],
|
||||
[
|
||||
'name' => 'domain_id',
|
||||
'definition' => 'int(10) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'begin_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'end_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'loaded_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'org',
|
||||
'definition' => 'varchar(255) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'external_id',
|
||||
'definition' => 'varchar(255) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'email',
|
||||
'definition' => 'varchar(255) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'extra_contact_info',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'error_string',
|
||||
'definition' => 'text NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_adkim',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_aspf',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_p',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_sp',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_pct',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_fo',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'seen',
|
||||
'definition' => 'boolean NOT NULL'
|
||||
]
|
||||
],
|
||||
'additional' => 'PRIMARY KEY (`id`), UNIQUE KEY `external_id` (`domain_id`, `external_id`), KEY (`begin_time`), KEY (`end_time`), KEY `org` (`org`, `begin_time`)',
|
||||
'table_options' => 'ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||
],
|
||||
'rptrecords' => [
|
||||
'columns' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'definition' => 'int(10) unsigned NOT NULL AUTO_INCREMENT'
|
||||
],
|
||||
[
|
||||
'name' => 'report_id',
|
||||
'definition' => 'int(10) unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'ip',
|
||||
'definition' => 'varbinary(16) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'rcount',
|
||||
'definition' => 'int(10) unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'disposition',
|
||||
'definition' => 'tinyint unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'reason',
|
||||
'definition' => 'text NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'dkim_auth',
|
||||
'definition' => 'text NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'spf_auth',
|
||||
'definition' => 'text NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'dkim_align',
|
||||
'definition' => 'tinyint unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'spf_align',
|
||||
'definition' => 'tinyint unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'envelope_to',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'envelope_from',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'header_from',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
]
|
||||
],
|
||||
'additional' => 'PRIMARY KEY (`id`), KEY (`report_id`), KEY (`ip`)',
|
||||
'table_options' => 'ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||
],
|
||||
'reportlog' => [
|
||||
'columns' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'definition' => 'int(10) unsigned NOT NULL AUTO_INCREMENT'
|
||||
],
|
||||
[
|
||||
'name' => 'domain',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'external_id',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'event_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'filename',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'source',
|
||||
'definition' => 'tinyint unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'success',
|
||||
'definition' => 'boolean NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'message',
|
||||
'definition' => 'text NULL'
|
||||
]
|
||||
],
|
||||
'additional' => 'PRIMARY KEY (`id`), KEY(`event_time`)',
|
||||
'table_options' => 'ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||
]
|
||||
];
|
||||
}
|
131
root/opt/dmarc-srg/classes/Database/DatabaseConnector.php
Normal file
131
root/opt/dmarc-srg/classes/Database/DatabaseConnector.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the abstract DatabaseConnector class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database;
|
||||
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
|
||||
abstract class DatabaseConnector
|
||||
{
|
||||
protected static $names = [
|
||||
'domain' => 'DomainMapper',
|
||||
'report' => 'ReportMapper',
|
||||
'report-log' => 'ReportLogMapper',
|
||||
'setting' => 'SettingMapper',
|
||||
'statistics' => 'StatisticsMapper',
|
||||
'upgrader' => 'UpgraderMapper'
|
||||
];
|
||||
|
||||
protected $host = null;
|
||||
protected $name = null;
|
||||
protected $user = null;
|
||||
protected $password = null;
|
||||
protected $prefix = '';
|
||||
protected $mappers = [];
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param array $conf Configuration data from the conf.php file
|
||||
*/
|
||||
public function __construct(array $conf)
|
||||
{
|
||||
$this->host = $conf['host'] ?? '';
|
||||
$this->name = $conf['name'] ?? '';
|
||||
$this->user = $conf['user'] ?? '';
|
||||
$this->password = $conf['password'] ?? '';
|
||||
$this->prefix = $conf['table_prefix'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of PDO class
|
||||
*
|
||||
* @return PDO
|
||||
*/
|
||||
abstract public function dbh(): object;
|
||||
|
||||
/**
|
||||
* Returns the database state as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function state(): array;
|
||||
|
||||
/**
|
||||
* Returns a data mapper by its name.
|
||||
*
|
||||
* @param string $name Mapper name
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function getMapper(string $name): object
|
||||
{
|
||||
if (isset($this->mappers[$name])) {
|
||||
return $this->mappers[$name];
|
||||
}
|
||||
|
||||
if (!isset(self::$names[$name])) {
|
||||
throw new LogicException('Unknown mapper name: ' . $name);
|
||||
}
|
||||
|
||||
$mapper_name = (new \ReflectionClass($this))->getNamespaceName() . '\\' . self::$names[$name];
|
||||
$mapper = new $mapper_name($this);
|
||||
$this->mappers[$name] = $mapper;
|
||||
return $mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inites the database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function initDb(string $version): void;
|
||||
|
||||
/**
|
||||
* Cleans up the database
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function cleanDb(): void;
|
||||
|
||||
/**
|
||||
* Returns the prefix for tables of the database
|
||||
*
|
||||
* @param string $postfix String to be concatenated with the prefix.
|
||||
* Usually, this is a table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function tablePrefix(string $postfix = ''): string
|
||||
{
|
||||
return $this->prefix . $postfix;
|
||||
}
|
||||
}
|
184
root/opt/dmarc-srg/classes/Database/DatabaseController.php
Normal file
184
root/opt/dmarc-srg/classes/Database/DatabaseController.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the DatabaseController class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database;
|
||||
|
||||
use Liuch\DmarcSrg\Exception\RuntimeException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseFatalException;
|
||||
|
||||
/**
|
||||
* Proxy class for accessing a database of the selected type
|
||||
*/
|
||||
class DatabaseController
|
||||
{
|
||||
public const REQUIRED_VERSION = '3.0';
|
||||
|
||||
private $conf_data = null;
|
||||
private $connector = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param Core $core Instace of the Core class
|
||||
* @param class $connector The connector class of the current database
|
||||
*/
|
||||
public function __construct($core, $connector = null)
|
||||
{
|
||||
$this->conf_data = $core->config('database');
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string
|
||||
{
|
||||
return $this->conf_data['type'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->conf_data['name'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database host
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function location(): string
|
||||
{
|
||||
return $this->conf_data['host'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the database as an array.
|
||||
*
|
||||
* @return array May contain the following fields:
|
||||
* `tables` - an array of tables with their properties;
|
||||
* `needs_upgrade` - true if the database needs upgrading;
|
||||
* `correct` - true if the database is correct;
|
||||
* `type` - the database type;
|
||||
* `name` - the database name;
|
||||
* `location` - the database location;
|
||||
* `version` - the current version of the database structure;
|
||||
* `message` - a state message;
|
||||
* `error_code` - an error code;
|
||||
*/
|
||||
public function state(): array
|
||||
{
|
||||
$this->ensureConnector();
|
||||
|
||||
$res = $this->connector->state();
|
||||
$res['type'] = $this->type();
|
||||
$res['name'] = $this->name();
|
||||
$res['location'] = $this->location();
|
||||
if (($res['correct'] ?? false) && ($res['version'] ?? 'null') !== self::REQUIRED_VERSION) {
|
||||
$res['correct'] = false;
|
||||
$res['message'] = 'The database structure needs upgrading';
|
||||
$res['needs_upgrade'] = true;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inites the database.
|
||||
*
|
||||
* This method creates needed tables and indexes in the database.
|
||||
* The method will fail if the database already have tables with the table prefix.
|
||||
*
|
||||
* @return array Result array with `error_code` and `message` fields.
|
||||
*/
|
||||
public function initDb(): array
|
||||
{
|
||||
$this->ensureConnector();
|
||||
$this->connector->initDb(self::REQUIRED_VERSION);
|
||||
return [ 'message' => 'The database has been initiated' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the database.
|
||||
*
|
||||
* Drops tables with the table prefix in the database or all tables in the database if no table prefix is set.
|
||||
*
|
||||
* @return array Result array with `error_code` and `message` fields.
|
||||
*/
|
||||
public function cleanDb(): array
|
||||
{
|
||||
$this->ensureConnector();
|
||||
$this->connector->cleanDb();
|
||||
return [ 'message' => 'The database tables have been dropped' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a data mapper by its name from the current database connector
|
||||
*
|
||||
* @param string $name Mapper name
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function getMapper(string $name): object
|
||||
{
|
||||
$this->ensureConnector();
|
||||
return $this->connector->getMapper($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the connector of the specified database type and initializes it
|
||||
* if it hasn't already been initialized
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function ensureConnector(): void
|
||||
{
|
||||
if (!$this->connector) {
|
||||
switch ($this->conf_data['type']) {
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
$type = 'mariadb';
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException('Unknown database type: ' . $this->conf_data['type']);
|
||||
$type = null;
|
||||
break;
|
||||
}
|
||||
$c_name = __NAMESPACE__ . '\\' . \ucfirst($type) . '\\Connector';
|
||||
$this->connector = new $c_name($this->conf_data);
|
||||
}
|
||||
}
|
||||
}
|
121
root/opt/dmarc-srg/classes/Database/DatabaseUpgrader.php
Normal file
121
root/opt/dmarc-srg/classes/Database/DatabaseUpgrader.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database;
|
||||
|
||||
use PDO;
|
||||
use Exception;
|
||||
use Liuch\DmarcSrg\Settings\SettingString;
|
||||
|
||||
class DatabaseUpgrader
|
||||
{
|
||||
public static function go()
|
||||
{
|
||||
$ver = (new SettingString('version'))->value();
|
||||
if ($ver == '') {
|
||||
$ver = 'null';
|
||||
}
|
||||
|
||||
while ($ver !== Database::REQUIRED_VERSION) {
|
||||
if (!isset(self::$upways['ver_' . $ver])) {
|
||||
throw new Exception('Upgrading failed: There is no way to upgrade from ' . $ver . ' to ' . Database::REQUIRED_VERSION, -1);
|
||||
}
|
||||
$um = self::$upways['ver_' . $ver];
|
||||
$ver = self::$um();
|
||||
}
|
||||
}
|
||||
|
||||
private static $upways = [
|
||||
'ver_null' => 'upNull',
|
||||
'ver_0.1' => 'up01',
|
||||
'ver_1.0' => 'up10'
|
||||
];
|
||||
|
||||
private static function upNull()
|
||||
{
|
||||
$db = Database::connection();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$db->query(
|
||||
'INSERT INTO `' . Database::tablePrefix('system') . '` (`key`, `value`) VALUES ("version", "0.1")'
|
||||
);
|
||||
$db->commit();
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
return '0.1';
|
||||
}
|
||||
|
||||
private static function up01()
|
||||
{
|
||||
$db = Database::connection();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$dom_tn = Database::tablePrefix('domains');
|
||||
if (!self::columnExists($db, $dom_tn, 'active')) {
|
||||
$db->query('ALTER TABLE `' . $dom_tn . '` ADD COLUMN `active` boolean NOT NULL AFTER `fqdn`');
|
||||
}
|
||||
if (!self::columnExists($db, $dom_tn, 'created_time')) {
|
||||
$db->query('ALTER TABLE `' . $dom_tn . '` ADD COLUMN `created_time` datetime NOT NULL');
|
||||
}
|
||||
if (!self::columnExists($db, $dom_tn, 'updated_time')) {
|
||||
$db->query('ALTER TABLE `' . $dom_tn . '` ADD COLUMN `updated_time` datetime NOT NULL');
|
||||
}
|
||||
$db->query('UPDATE `' . $dom_tn . '` SET `active` = TRUE, `created_time` = NOW(), `updated_time` = NOW()');
|
||||
$db->query('UPDATE `' . Database::tablePrefix('system') . '` SET `value` = "1.0" WHERE `key` = "version"');
|
||||
$db->commit();
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
return '1.0';
|
||||
}
|
||||
|
||||
private static function up10()
|
||||
{
|
||||
$db = Database::connection();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$sys_tn = Database::tablePrefix('system');
|
||||
$db->query('ALTER TABLE `' . $sys_tn . '` MODIFY COLUMN `key` varchar(64) NOT NULL');
|
||||
$db->query('UPDATE `' . $sys_tn . '` SET `value` = "2.0" WHERE `key` = "version"');
|
||||
$db->commit();
|
||||
} catch (Exception $d) {
|
||||
$db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
return '2.0';
|
||||
}
|
||||
|
||||
private static function columnExists($db, $table, $column)
|
||||
{
|
||||
$st = $db->prepare('SELECT NULL FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `table_schema` = ? AND `table_name` = ? AND `column_name` = ?');
|
||||
$st->bindValue(1, Database::name(), PDO::PARAM_STR);
|
||||
$st->bindValue(2, $table, PDO::PARAM_STR);
|
||||
$st->bindValue(3, $column, PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
$res = $st->fetch(PDO::FETCH_NUM);
|
||||
$st->closeCursor();
|
||||
return $res ? true : false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the DomainMapperInterface
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database;
|
||||
|
||||
interface DomainMapperInterface
|
||||
{
|
||||
/**
|
||||
* Return true if the domain exists or false otherwise.
|
||||
*
|
||||
* @param array $data Array with domain data to search
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(array &$data): bool;
|
||||
|
||||
/**
|
||||
* Fetch the domain data from the database by its id or name
|
||||
*
|
||||
* @param array $data Domain data to update
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fetch(array &$data): void;
|
||||
|
||||
/**
|
||||
* Saves domain data to the database (updates or inserts an record)
|
||||
*
|
||||
* @param array $data Domain data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(array &$data): void;
|
||||
|
||||
/**
|
||||
* Deletes the domain from the database
|
||||
*
|
||||
* Deletes the domain if there are no reports for this domain in the database.
|
||||
*
|
||||
* @param array $data Domain data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(array &$data): void;
|
||||
|
||||
/**
|
||||
* Returns a list of domains data from the database
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list(): array;
|
||||
|
||||
/**
|
||||
* Returns an ordered array with domain names from the database
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function names(): array;
|
||||
|
||||
/**
|
||||
* Returns the total number of domains in the database
|
||||
*
|
||||
* @param int $max The maximum number of records to count. 0 means no limitation.
|
||||
*
|
||||
* @return int The total number of domains
|
||||
*/
|
||||
public function count(int $max = 0): int;
|
||||
}
|
511
root/opt/dmarc-srg/classes/Database/Mariadb/Connector.php
Normal file
511
root/opt/dmarc-srg/classes/Database/Mariadb/Connector.php
Normal file
@@ -0,0 +1,511 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the DatabaseConnector class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database\Mariadb;
|
||||
|
||||
use Liuch\DmarcSrg\ErrorHandler;
|
||||
use Liuch\DmarcSrg\Database\DatabaseConnector;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\RuntimeException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseFatalException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseExceptionFactory;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
|
||||
|
||||
class Connector extends DatabaseConnector
|
||||
{
|
||||
protected $dbh = null;
|
||||
|
||||
/**
|
||||
* Returns an instance of PDO class
|
||||
*
|
||||
* @return \PDO
|
||||
*/
|
||||
public function dbh(): object
|
||||
{
|
||||
$this->ensureConnection();
|
||||
return $this->dbh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the database
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function dbName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the database as an array.
|
||||
*
|
||||
* @return array May contain the following fields:
|
||||
* `tables` - an array of tables with their properties;
|
||||
* `correct` - true if the database is correct;
|
||||
* `version` - the current version of the database structure;
|
||||
* `message` - a state message;
|
||||
* `error_code` - an error code;
|
||||
*/
|
||||
public function state(): array
|
||||
{
|
||||
$this->ensureConnection();
|
||||
|
||||
$res = [];
|
||||
$p_len = strlen($this->prefix);
|
||||
if ($p_len > 0) {
|
||||
$like_str = ' WHERE NAME LIKE "' . str_replace('_', '\\_', $this->prefix) . '%"';
|
||||
} else {
|
||||
$like_str = '';
|
||||
}
|
||||
|
||||
try {
|
||||
$tables = [];
|
||||
$st = $this->dbh->query(
|
||||
'SHOW TABLE STATUS FROM `' . str_replace('`', '', $this->name) . '`' . $like_str
|
||||
);
|
||||
while ($row = $st->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$tname = $row['Name'];
|
||||
$rcnt = $this->dbh->query('SELECT COUNT(*) FROM `' . $tname . '`')->fetch(\PDO::FETCH_NUM)[0];
|
||||
$tables[substr($tname, $p_len)] = [
|
||||
'engine' => $row['Engine'],
|
||||
'rows' => intval($rcnt),
|
||||
'data_length' => intval($row['Data_length']),
|
||||
'index_length' => intval($row['Index_length']),
|
||||
'create_time' => $row['Create_time'],
|
||||
'update_time' => $row['Update_time']
|
||||
];
|
||||
}
|
||||
foreach (array_keys(self::$schema) as $table) {
|
||||
if (!isset($tables[$table])) {
|
||||
$tables[$table] = false;
|
||||
}
|
||||
}
|
||||
$exist_cnt = 0;
|
||||
$absent_cnt = 0;
|
||||
$tables_res = [];
|
||||
foreach ($tables as $tname => $tval) {
|
||||
$t = null;
|
||||
if ($tval) {
|
||||
$t = $tval;
|
||||
$t['exists'] = true;
|
||||
if (isset(self::$schema[$tname])) {
|
||||
++$exist_cnt;
|
||||
$t['message'] = 'Ok';
|
||||
} else {
|
||||
$t['message'] = 'Unknown table';
|
||||
}
|
||||
} else {
|
||||
++$absent_cnt;
|
||||
$t = [
|
||||
'error_code' => 1,
|
||||
'message' => 'Not exist'
|
||||
];
|
||||
}
|
||||
$t['name'] = $tname;
|
||||
$tables_res[] = $t;
|
||||
}
|
||||
$res['tables'] = $tables_res;
|
||||
if ($absent_cnt === 0) {
|
||||
$res['correct'] = true;
|
||||
$res['message'] = 'Ok';
|
||||
try {
|
||||
$res['version'] = $this->getMapper('setting')->value('version');
|
||||
} catch (DatabaseNotFoundException $e) {
|
||||
}
|
||||
} else {
|
||||
$res['error_code'] = -1;
|
||||
if ($exist_cnt == 0) {
|
||||
$res['message'] = 'The database schema is not initiated';
|
||||
} else {
|
||||
$res['message'] = 'Incomplete set of the tables';
|
||||
}
|
||||
}
|
||||
} catch (\PDOException $e) {
|
||||
$res = array_replace($res, ErrorHandler::exceptionResult(
|
||||
new DatabaseFatalException('Failed to get the database information', -1, $e)
|
||||
));
|
||||
} catch (RuntimeException $e) {
|
||||
$res = array_replace($res, ErrorHandler::exceptionResult($e));
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inites the database.
|
||||
*
|
||||
* This method creates needed tables and indexes in the database.
|
||||
* The method will fail if the database already have tables with the table prefix.
|
||||
*
|
||||
* @param $version The current version of the database schema
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initDb(string $version): void
|
||||
{
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$st = $this->dbh->query($this->sqlShowTablesQuery());
|
||||
try {
|
||||
if ($st->fetch()) {
|
||||
if (empty($this->tablePrefix())) {
|
||||
throw new SoftException('The database is not empty', -4);
|
||||
} else {
|
||||
throw new SoftException('Database tables already exist with the given prefix', -4);
|
||||
}
|
||||
}
|
||||
foreach (self::$schema as $t_name => &$t_schema) {
|
||||
$this->createDbTable($this->tablePrefix($t_name), $t_schema);
|
||||
}
|
||||
unset($t_schema);
|
||||
} finally {
|
||||
$st->closeCursor();
|
||||
}
|
||||
$st = $this->dbh->prepare(
|
||||
'INSERT INTO `' . $this->tablePrefix('system') . '` (`key`, `value`) VALUES ("version", ?)'
|
||||
);
|
||||
$st->bindValue(1, $version, \PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to create required tables in the database', -1, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the database
|
||||
*
|
||||
* Drops tables with the table prefix in the database or all tables in the database
|
||||
* if no table prefix is set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cleanDb(): void
|
||||
{
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$db = $this->dbh;
|
||||
$db->query('SET foreign_key_checks = 0');
|
||||
$st = $db->query($this->sqlShowTablesQuery());
|
||||
while ($table = $st->fetchColumn(0)) {
|
||||
$db->query('DROP TABLE `' . $table . '`');
|
||||
}
|
||||
$st->closeCursor();
|
||||
$db->query('SET foreign_key_checks = 1');
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to drop the database tables', -1, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the database connection if it hasn't connected yet.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function ensureConnection(): void
|
||||
{
|
||||
if (!$this->dbh) {
|
||||
try {
|
||||
$this->dbh = new \PDO(
|
||||
"mysql:host={$this->host};dbname={$this->name};charset=utf8",
|
||||
$this->user,
|
||||
$this->password,
|
||||
[ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION ]
|
||||
);
|
||||
$this->dbh->query('SET time_zone = "+00:00"');
|
||||
} catch (\PDOException $e) {
|
||||
throw DatabaseExceptionFactory::fromException($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SHOW TABLES SQL query string for tables with the table prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function sqlShowTablesQuery(): string
|
||||
{
|
||||
$res = 'SHOW TABLES';
|
||||
$prefix = $this->tablePrefix();
|
||||
if (strlen($prefix) > 0) {
|
||||
$res .= ' WHERE `tables_in_' . str_replace('`', '', $this->name)
|
||||
. '` LIKE "' . str_replace('_', '\\_', $prefix) . '%"';
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a table in the database.
|
||||
*
|
||||
* @param string $name Table name
|
||||
* @param array $definitions Table structure
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function createDbTable(string $name, array $definitions): void
|
||||
{
|
||||
$query = 'CREATE TABLE `' . $name . '` (';
|
||||
$col_num = 0;
|
||||
foreach ($definitions['columns'] as $column) {
|
||||
if ($col_num > 0) {
|
||||
$query .= ', ';
|
||||
}
|
||||
$query .= '`' . $column['name'] . '` ' . $column['definition'];
|
||||
$col_num += 1;
|
||||
}
|
||||
$query .= ', ' . $definitions['additional'] . ') ' . $definitions['table_options'];
|
||||
$this->dbh->query($query);
|
||||
}
|
||||
|
||||
private static $schema = [
|
||||
'system' => [
|
||||
'columns' => [
|
||||
[
|
||||
'name' => 'key',
|
||||
'definition' => 'varchar(64) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'value',
|
||||
'definition' => 'varchar(255) DEFAULT NULL'
|
||||
]
|
||||
],
|
||||
'additional' => 'PRIMARY KEY (`key`)',
|
||||
'table_options' => 'ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||
],
|
||||
'domains' => [
|
||||
'columns' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'definition' => 'int(10) unsigned NOT NULL AUTO_INCREMENT'
|
||||
],
|
||||
[
|
||||
'name' => 'fqdn',
|
||||
'definition' => 'varchar(255) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'active',
|
||||
'definition' => 'boolean NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'description',
|
||||
'definition' => 'TEXT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'created_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'updated_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
]
|
||||
],
|
||||
'additional' => 'PRIMARY KEY (`id`), UNIQUE KEY `fqdn` (`fqdn`)',
|
||||
'table_options' => 'ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||
],
|
||||
'reports' => [
|
||||
'columns' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'definition' => 'int(10) unsigned NOT NULL AUTO_INCREMENT'
|
||||
],
|
||||
[
|
||||
'name' => 'domain_id',
|
||||
'definition' => 'int(10) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'begin_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'end_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'loaded_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'org',
|
||||
'definition' => 'varchar(255) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'external_id',
|
||||
'definition' => 'varchar(255) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'email',
|
||||
'definition' => 'varchar(255) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'extra_contact_info',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'error_string',
|
||||
'definition' => 'text NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_adkim',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_aspf',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_p',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_sp',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_np',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_pct',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'policy_fo',
|
||||
'definition' => 'varchar(20) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'seen',
|
||||
'definition' => 'boolean NOT NULL'
|
||||
]
|
||||
],
|
||||
'additional' => 'PRIMARY KEY (`id`),' .
|
||||
' UNIQUE KEY `external_id` (`domain_id`, `external_id`),' .
|
||||
' KEY (`begin_time`), KEY (`end_time`),' .
|
||||
' KEY `org` (`org`, `begin_time`)',
|
||||
'table_options' => 'ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||
],
|
||||
'rptrecords' => [
|
||||
'columns' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'definition' => 'int(10) unsigned NOT NULL AUTO_INCREMENT'
|
||||
],
|
||||
[
|
||||
'name' => 'report_id',
|
||||
'definition' => 'int(10) unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'ip',
|
||||
'definition' => 'varbinary(16) NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'rcount',
|
||||
'definition' => 'int(10) unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'disposition',
|
||||
'definition' => 'tinyint unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'reason',
|
||||
'definition' => 'text NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'dkim_auth',
|
||||
'definition' => 'text NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'spf_auth',
|
||||
'definition' => 'text NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'dkim_align',
|
||||
'definition' => 'tinyint unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'spf_align',
|
||||
'definition' => 'tinyint unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'envelope_to',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'envelope_from',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'header_from',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
]
|
||||
],
|
||||
'additional' => 'PRIMARY KEY (`id`), KEY (`report_id`), KEY (`ip`)',
|
||||
'table_options' => 'ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||
],
|
||||
'reportlog' => [
|
||||
'columns' => [
|
||||
[
|
||||
'name' => 'id',
|
||||
'definition' => 'int(10) unsigned NOT NULL AUTO_INCREMENT'
|
||||
],
|
||||
[
|
||||
'name' => 'domain',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'external_id',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'event_time',
|
||||
'definition' => 'datetime NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'filename',
|
||||
'definition' => 'varchar(255) NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'source',
|
||||
'definition' => 'tinyint unsigned NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'success',
|
||||
'definition' => 'boolean NOT NULL'
|
||||
],
|
||||
[
|
||||
'name' => 'message',
|
||||
'definition' => 'text NULL'
|
||||
]
|
||||
],
|
||||
'additional' => 'PRIMARY KEY (`id`), KEY(`event_time`)',
|
||||
'table_options' => 'ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||
]
|
||||
];
|
||||
}
|
332
root/opt/dmarc-srg/classes/Database/Mariadb/DomainMapper.php
Normal file
332
root/opt/dmarc-srg/classes/Database/Mariadb/DomainMapper.php
Normal file
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the DomainMapper class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database\Mariadb;
|
||||
|
||||
use Liuch\DmarcSrg\DateTime;
|
||||
use Liuch\DmarcSrg\Database\DomainMapperInterface;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseFatalException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
|
||||
|
||||
/**
|
||||
* DomainMapper class implementation for MariaDB
|
||||
*/
|
||||
class DomainMapper implements DomainMapperInterface
|
||||
{
|
||||
private $connector = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param Connector $connector DatabaseConnector
|
||||
*/
|
||||
public function __construct(object $connector)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the domain exists or false otherwise.
|
||||
*
|
||||
* @param array $data Array with domain data to search
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(array &$data): bool
|
||||
{
|
||||
try {
|
||||
$st = $this->connector->dbh()->prepare(
|
||||
'SELECT `id` FROM `' . $this->connector->tablePrefix('domains') .
|
||||
'` WHERE ' . $this->sqlCondition($data)
|
||||
);
|
||||
$this->sqlBindValue($st, 1, $data);
|
||||
$st->execute();
|
||||
$res = $st->fetch(\PDO::FETCH_NUM);
|
||||
$st->closeCursor();
|
||||
if (!$res) {
|
||||
return false;
|
||||
}
|
||||
$data['id'] = intval($res[0]);
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get domain ID', -1, $e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the domain data from the database by its id or name
|
||||
*
|
||||
* @param array $data Domain data to update
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fetch(array &$data): void
|
||||
{
|
||||
try {
|
||||
$st = $this->connector->dbh()->prepare(
|
||||
'SELECT `id`, `fqdn`, `active`, `description`, `created_time`, `updated_time` FROM `'
|
||||
. $this->connector->tablePrefix('domains') . '` WHERE ' . $this->sqlCondition($data)
|
||||
);
|
||||
$this->sqlBindValue($st, 1, $data);
|
||||
$st->execute();
|
||||
$res = $st->fetch(\PDO::FETCH_NUM);
|
||||
$st->closeCursor();
|
||||
if (!$res) {
|
||||
throw new DatabaseNotFoundException('Domain not found');
|
||||
}
|
||||
$data['id'] = intval($res[0]);
|
||||
$data['fqdn'] = $res[1];
|
||||
$data['active'] = boolval($res[2]);
|
||||
$data['description'] = $res[3];
|
||||
$data['created_time'] = new DateTime($res[4]);
|
||||
$data['updated_time'] = new DateTime($res[5]);
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to fetch the domain data', -1, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves domain data to the database (updates or inserts an record)
|
||||
*
|
||||
* @param array $data Domain data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(array &$data): void
|
||||
{
|
||||
$db = $this->connector->dbh();
|
||||
$data['updated_time'] = new DateTime();
|
||||
if ($this->exists($data)) {
|
||||
try {
|
||||
$st = $db->prepare(
|
||||
'UPDATE `' . $this->connector->tablePrefix('domains')
|
||||
. '` SET `active` = ?, `description` = ?, `updated_time` = ? WHERE `id` = ?'
|
||||
);
|
||||
$st->bindValue(1, $data['active'], \PDO::PARAM_BOOL);
|
||||
$st->bindValue(2, $data['description'], \PDO::PARAM_STR);
|
||||
$st->bindValue(3, $data['updated_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||||
$st->bindValue(4, $data['id'], \PDO::PARAM_INT);
|
||||
$st->execute();
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DababaseException('Failed to update the domain data', -1, $e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$active = $data['active'] ?? false;
|
||||
$data['created_time'] = $data['updated_time'];
|
||||
if (is_null($data['description'])) {
|
||||
$sql1 = '';
|
||||
$sql2 = '';
|
||||
} else {
|
||||
$sql1 = ', `description`';
|
||||
$sql2 = ', ?';
|
||||
}
|
||||
$st = $db->prepare(
|
||||
'INSERT INTO `' . $this->connector->tablePrefix('domains')
|
||||
. '` (`fqdn`, `active`' . $sql1 . ', `created_time`, `updated_time`)'
|
||||
. ' VALUES (?, ?' . $sql2 . ', ?, ?)'
|
||||
);
|
||||
$idx = 0;
|
||||
$st->bindValue(++$idx, $data['fqdn'], \PDO::PARAM_STR);
|
||||
$st->bindValue(++$idx, $active, \PDO::PARAM_BOOL);
|
||||
if (!is_null($data['description'])) {
|
||||
$st->bindValue(++$idx, $data['description'], \PDO::PARAM_STR);
|
||||
}
|
||||
$st->bindValue(++$idx, $data['created_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||||
$st->bindValue(++$idx, $data['updated_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
$st->closeCursor();
|
||||
$data['id'] = intval($db->lastInsertId());
|
||||
$data['active'] = $active;
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to insert the domain data', -1, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the domain from the database
|
||||
*
|
||||
* Deletes the domain if there are no reports for this domain in the database.
|
||||
*
|
||||
* @param array $data Domain data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(array &$data): void
|
||||
{
|
||||
$db = $this->connector->dbh();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$filter = [ 'domain' => $data['id'] ];
|
||||
$limit = [ 'offset' => 0, 'count' => 0 ];
|
||||
$r_count = $this->connector->getMapper('report')->count($filter, $limit);
|
||||
if ($r_count > 0) {
|
||||
switch ($r_count) {
|
||||
case 1:
|
||||
$s1 = 'is';
|
||||
$s2 = '';
|
||||
break;
|
||||
default:
|
||||
$s1 = 'are';
|
||||
$s2 = 's';
|
||||
break;
|
||||
}
|
||||
throw new SoftException(
|
||||
"Failed to delete: there {$s1} {$r_count} incoming report{$s2} for this domain"
|
||||
);
|
||||
}
|
||||
$st = $db->prepare('DELETE FROM `' . $this->connector->tablePrefix('domains') . '` WHERE `id` = ?');
|
||||
$st->bindValue(1, $data['id'], \PDO::PARAM_INT);
|
||||
$st->execute();
|
||||
$st->closeCursor();
|
||||
$db->commit();
|
||||
} catch (\PDOException $e) {
|
||||
$db->rollBack();
|
||||
throw new DatabaseFatalException('Failed to delete the domain', -1, $e);
|
||||
} catch (\Exception $e) {
|
||||
$db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of domains data from the database
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list(): array
|
||||
{
|
||||
$list = [];
|
||||
try {
|
||||
$st = $this->connector->dbh()->query(
|
||||
'SELECT `id`, `fqdn`, `active`, `description`, `created_time`, `updated_time` FROM `'
|
||||
. $this->connector->tablePrefix('domains') . '`'
|
||||
);
|
||||
while ($row = $st->fetch(\PDO::FETCH_NUM)) {
|
||||
$list[] = [
|
||||
'id' => intval($row[0]),
|
||||
'fqdn' => $row[1],
|
||||
'active' => boolval($row[2]),
|
||||
'description' => $row[3],
|
||||
'created_time' => new DateTime($row[4]),
|
||||
'updated_time' => new DateTime($row[5])
|
||||
];
|
||||
}
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get the domain list', -1, $e);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ordered array with domain names from the database
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function names(): array
|
||||
{
|
||||
$res = [];
|
||||
try {
|
||||
$st = $this->connector->dbh()->query(
|
||||
'SELECT `fqdn` FROM `' . $this->connector->tablePrefix('domains') . '` ORDER BY `fqdn`',
|
||||
\PDO::FETCH_NUM
|
||||
);
|
||||
while ($name = $st->fetchColumn(0)) {
|
||||
$res[] = $name;
|
||||
}
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get a list of domain names', -1, $e);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of domains in the database
|
||||
*
|
||||
* @param int $max The maximum number of records to count. 0 means no limitation.
|
||||
*
|
||||
* @return int The total number of domains
|
||||
*/
|
||||
public function count(int $max = 0): int
|
||||
{
|
||||
$number = 0;
|
||||
try {
|
||||
$query_str = 'SELECT COUNT(*) FROM `' . $this->connector->tablePrefix('domains') . '`';
|
||||
if ($max > 0) {
|
||||
$query_str .= " LIMIT {$max}";
|
||||
}
|
||||
$st = $this->connector->dbh()->query($query_str, \PDO::FETCH_NUM);
|
||||
$number = intval($st->fetchColumn(0));
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get the number of domains', -1, $e);
|
||||
}
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a condition string for a WHERE statement based on existing domain data
|
||||
*
|
||||
* @param array $data Domain data
|
||||
*
|
||||
* @return string Condition string
|
||||
*/
|
||||
private function sqlCondition(array &$data): string
|
||||
{
|
||||
if (isset($data['id'])) {
|
||||
return '`id` = ?';
|
||||
}
|
||||
return '`fqdn` = ?';
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds values for SQL queries based on existing domain data
|
||||
*
|
||||
* @param PDOStatement $st PDO Statement to bind to
|
||||
* @param ind $pos Start position for binding
|
||||
* @param array $data Domain data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function sqlBindValue($st, int $pos, array &$data): void
|
||||
{
|
||||
if (isset($data['id'])) {
|
||||
$st->bindValue($pos, $data['id'], \PDO::PARAM_INT);
|
||||
} else {
|
||||
$st->bindValue($pos, $data['fqdn'], \PDO::PARAM_STR);
|
||||
}
|
||||
}
|
||||
}
|
311
root/opt/dmarc-srg/classes/Database/Mariadb/ReportLogMapper.php
Normal file
311
root/opt/dmarc-srg/classes/Database/Mariadb/ReportLogMapper.php
Normal file
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the ReportLogMapper class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database\Mariadb;
|
||||
|
||||
use Liuch\DmarcSrg\DateTime;
|
||||
use Liuch\DmarcSrg\Database\ReportLogMapperInterface;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseFatalException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
|
||||
|
||||
/**
|
||||
* ReportLogMapper class implementation for MariaDB
|
||||
*/
|
||||
class ReportLogMapper implements ReportLogMapperInterface
|
||||
{
|
||||
private $connector = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param Connector $connector DatabaseConnector
|
||||
*/
|
||||
public function __construct(object $connector)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches data of report log item from the database by id
|
||||
*
|
||||
* @param Report log data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fetch(array &$data): void
|
||||
{
|
||||
try {
|
||||
$st = $this->connector->dbh()->prepare(
|
||||
'SELECT `domain`, `external_id`, `event_time`, `filename`, `source`, `success`, `message` FROM `'
|
||||
. $this->connector->tablePrefix('reportlog') . '` WHERE `id` = ?'
|
||||
);
|
||||
$st->bindValue(1, $data['id'], \PDO::PARAM_INT);
|
||||
$st->execute();
|
||||
if (!($row = $st->fetch(\PDO::FETCH_NUM))) {
|
||||
throw new DatabaseNotFoundException();
|
||||
}
|
||||
$data['domain'] = $row[0];
|
||||
$data['external_id'] = $row[1];
|
||||
$data['event_time'] = new DateTime($row[2]);
|
||||
$data['filename'] = $row[3];
|
||||
$data['source'] = intval($row[4]);
|
||||
$data['success'] = boolval($row[5]);
|
||||
$data['message'] = $row[6];
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get the log item', -1, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves data of report log item to the database
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(array &$data): void
|
||||
{
|
||||
$db = $this->connector->dbh();
|
||||
try {
|
||||
$id = $data['id'];
|
||||
if (is_null($id)) {
|
||||
$st = $db->prepare(
|
||||
'INSERT INTO `' . $this->connector->tablePrefix('reportlog')
|
||||
. '` (`domain`, `external_id`, `event_time`, `filename`, `source`, `success`, `message`)'
|
||||
. ' VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||||
);
|
||||
} else {
|
||||
$st = $db->prepare(
|
||||
'UPDATE `' . $this->connector->tablePrefix('reportlog')
|
||||
. '` SET `domain` = ?, `external_id` = ?, `event_time` = ?, `filename` = ?,'
|
||||
. ' `source` = ?, `success` = ?, `message` = ? WHERE `id` = ?'
|
||||
);
|
||||
$st->bindValue(8, $id, \PDO::PARAM_INT);
|
||||
}
|
||||
$ts = $data['event_time'] ?? (new DateTime());
|
||||
$st->bindValue(1, $data['domain'], \PDO::PARAM_STR);
|
||||
$st->bindValue(2, $data['external_id'], \PDO::PARAM_STR);
|
||||
$st->bindValue(3, $ts->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||||
$st->bindValue(4, $data['filename'], \PDO::PARAM_STR);
|
||||
$st->bindValue(5, $data['source'], \PDO::PARAM_INT);
|
||||
$st->bindValue(6, $data['success'], \PDO::PARAM_BOOL);
|
||||
$st->bindValue(7, $data['message'], \PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
if (is_null($id)) {
|
||||
$data['id'] = intval($db->lastInsertId());
|
||||
}
|
||||
$st->closeCursor();
|
||||
$data['event_time'] = $ts;
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to save a report log item');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of report log items with given criteria
|
||||
*
|
||||
* @param array $filter Key-value array:
|
||||
* 'from_time' => DateTime
|
||||
* 'till_time' => DateTime
|
||||
* @param array $order Key-value array with order options:
|
||||
* 'direction' => string, 'ascent' or 'descent'
|
||||
* @param array $limit Key-value array:
|
||||
* 'offset' => int
|
||||
* 'count' => int
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list(array &$filter, array &$order, array &$limit): array
|
||||
{
|
||||
$list = [];
|
||||
try {
|
||||
$st = $this->connector->dbh()->prepare(
|
||||
'SELECT `id`, `domain`, `event_time`, `source`, `success`, `message` FROM `'
|
||||
. $this->connector->tablePrefix('reportlog') . '`'
|
||||
. $this->sqlCondition($filter)
|
||||
. $this->sqlOrder($order)
|
||||
. $this->sqlLimit($limit)
|
||||
);
|
||||
$this->sqlBindValues($st, $filter, $limit);
|
||||
$st->execute();
|
||||
while ($row = $st->fetch(\PDO::FETCH_NUM)) {
|
||||
$list[] = [
|
||||
'id' => intval($row[0]),
|
||||
'domain' => $row[1],
|
||||
'event_time' => new DateTime($row[2]),
|
||||
'source' => intval($row[3]),
|
||||
'success' => boolval($row[4]),
|
||||
'message' => $row[5]
|
||||
];
|
||||
}
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get the logs', -1, $e);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of report log items matching the specified filter and limits
|
||||
*
|
||||
* @param array $filter Key-value array with filtering parameters
|
||||
* @param array $limit Key-value array with limits
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(array &$filter, array &$limit): int
|
||||
{
|
||||
$cnt = 0;
|
||||
try {
|
||||
$st = $this->connector->dbh()->prepare(
|
||||
'SELECT COUNT(*) FROM `' . $this->connector->tablePrefix('reportlog') . '`'
|
||||
. $this->sqlCondition($filter)
|
||||
. $this->sqlLimit($limit)
|
||||
);
|
||||
$this->sqlBindValues($st, $filter, $limit);
|
||||
$st->execute();
|
||||
$cnt = intval($st->fetch(\PDO::FETCH_NUM)[0]);
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get the log data', -1, $e);
|
||||
}
|
||||
return $cnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes report log items from the database
|
||||
*
|
||||
* @param array $filter Key-value array with filtering parameters
|
||||
* @param array $order Key-value array with order options:
|
||||
* 'direction' => string, 'ascent' or 'descent'
|
||||
* @param array $limit Key-value array with limits
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(array &$filter, array &$order, array &$limit): void
|
||||
{
|
||||
try {
|
||||
$st = $this->connector->dbh()->prepare(
|
||||
'DELETE FROM `' . $this->connector->tablePrefix('reportlog') . '`'
|
||||
. $this->sqlCondition($filter)
|
||||
. $this->sqlOrder($order)
|
||||
. $this->sqlLimit($limit)
|
||||
);
|
||||
$this->sqlBindValues($st, $filter, $limit);
|
||||
$st->execute();
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to remove the log data', -1, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string with an SQL condition 'WHERE ...'
|
||||
*
|
||||
* @param array $filter Key-value with filtering paremeters
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function sqlCondition(array &$filter): string
|
||||
{
|
||||
$res = '';
|
||||
if (!is_null($filter['from_time']) || !is_null($filter['till_time'])) {
|
||||
$res = ' WHERE';
|
||||
$till_time = $filter['till_time'];
|
||||
if (!is_null($filter['from_time'])) {
|
||||
$res .= ' `event_time` >= ?';
|
||||
if (!is_null($till_time)) {
|
||||
$res .= ' AND';
|
||||
}
|
||||
}
|
||||
if (!is_null($till_time)) {
|
||||
$res .= ' `event_time` < ?';
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 'ORDER BY ...' part of the SQL query
|
||||
*
|
||||
* @param array $order Key-value array with ordering options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function sqlOrder(array &$order): string
|
||||
{
|
||||
return ' ORDER BY `event_time` ' . ($order['direction'] === 'descent' ? 'DESC' : 'ASC');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 'LIMIT ...' part of the SQL string
|
||||
*
|
||||
* @param array $limit Key-value array with keys 'offset' and 'count'
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function sqlLimit(array &$limit): string
|
||||
{
|
||||
$res = '';
|
||||
if ($limit['count'] > 0) {
|
||||
$res = ' LIMIT ?';
|
||||
if ($limit['offset'] > 0) {
|
||||
$res .= ', ?';
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the values of the filter and the limit to SQL query
|
||||
*
|
||||
* @param PDOStatement $st Prepared SOL statement to bind to
|
||||
* @param array $filter Key-value array with filter data
|
||||
* @param array $limit Key-value array with limit data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function sqlBindValues($st, array &$filter, array &$limit): void
|
||||
{
|
||||
$pos = 0;
|
||||
if (!is_null($filter['from_time'])) {
|
||||
$st->bindValue(++$pos, $filter['from_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||||
}
|
||||
if (!is_null($filter['till_time'])) {
|
||||
$st->bindValue(++$pos, $filter['till_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||||
}
|
||||
if ($limit['count'] > 0) {
|
||||
if ($limit['offset'] > 0) {
|
||||
$st->bindValue(++$pos, $limit['offset'], \PDO::PARAM_INT);
|
||||
}
|
||||
$st->bindValue(++$pos, $limit['count'], \PDO::PARAM_INT);
|
||||
}
|
||||
}
|
||||
}
|
753
root/opt/dmarc-srg/classes/Database/Mariadb/ReportMapper.php
Normal file
753
root/opt/dmarc-srg/classes/Database/Mariadb/ReportMapper.php
Normal file
@@ -0,0 +1,753 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the ReportMapper class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database\Mariadb;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\Common;
|
||||
use Liuch\DmarcSrg\DateTime;
|
||||
use Liuch\DmarcSrg\Settings\SettingsList;
|
||||
use Liuch\DmarcSrg\Database\ReportMapperInterface;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseFatalException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
|
||||
|
||||
/**
|
||||
* ReportMapper class implementation for MariaDB
|
||||
*/
|
||||
class ReportMapper implements ReportMapperInterface
|
||||
{
|
||||
private $connector = null;
|
||||
|
||||
private static $allowed_domains = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param Connector $connector DatabaseConnector
|
||||
*/
|
||||
public function __construct(object $connector)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches report data from the database and stores it in the passed array
|
||||
*
|
||||
* @param array $data Array with report data. To identify the report,
|
||||
* the array must contain at least two fields:
|
||||
* `report_id` - External report id from the xml file
|
||||
* `domain` - Fully Qualified Domain Name without a trailing dot
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fetch(array &$data): void
|
||||
{
|
||||
$db = $this->connector->dbh();
|
||||
try {
|
||||
$st = $db->prepare(
|
||||
'SELECT `rp`.`id`, `begin_time`, `end_time`, `loaded_time`, `org`, `email`, `extra_contact_info`,'
|
||||
. ' `error_string`, `policy_adkim`, `policy_aspf`, `policy_p`, `policy_sp`, `policy_np`,'
|
||||
. ' `policy_pct`, `policy_fo`'
|
||||
. ' FROM `' . $this->connector->tablePrefix('reports') . '` AS `rp`'
|
||||
. ' INNER JOIN `' . $this->connector->tablePrefix('domains')
|
||||
. '` AS `dom` ON `dom`.`id` = `rp`.`domain_id`'
|
||||
. ' WHERE `fqdn` = ? AND `external_id` = ?'
|
||||
);
|
||||
$st->bindValue(1, $data['domain'], \PDO::PARAM_STR);
|
||||
$st->bindValue(2, $data['report_id'], \PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
if (!($res = $st->fetch(\PDO::FETCH_NUM))) {
|
||||
throw new DatabaseNotFoundException('The report is not found');
|
||||
}
|
||||
$id = intval($res[0]);
|
||||
$data['date'] = [
|
||||
'begin' => new DateTime($res[1]),
|
||||
'end' => new DateTime($res[2])
|
||||
];
|
||||
$data['loaded_time'] = new DateTime($res[3]);
|
||||
$data['org_name'] = $res[4];
|
||||
$data['email'] = $res[5];
|
||||
$data['extra_contact_info'] = $res[6];
|
||||
$data['error_string'] = json_decode($res[7] ?? '', true);
|
||||
$data['policy'] = [
|
||||
'adkim' => $res[8],
|
||||
'aspf' => $res[9],
|
||||
'p' => $res[10],
|
||||
'sp' => $res[11],
|
||||
'np' => $res[12],
|
||||
'pct' => $res[13],
|
||||
'fo' => $res[14]
|
||||
];
|
||||
|
||||
$order_str = $this->sqlOrderRecords();
|
||||
$st = $db->prepare(
|
||||
'SELECT `report_id`, `ip`, `rcount`, `disposition`, `reason`, `dkim_auth` , `spf_auth`, `dkim_align`,'
|
||||
. ' `spf_align`, `envelope_to`, `envelope_from`, `header_from`'
|
||||
. ' FROM `' . $this->connector->tablePrefix('rptrecords') . '` WHERE `report_id` = ?' . $order_str
|
||||
);
|
||||
$st->bindValue(1, $id, \PDO::PARAM_INT);
|
||||
$st->execute();
|
||||
$data['records'] = [];
|
||||
while ($res = $st->fetch(\PDO::FETCH_NUM)) {
|
||||
$data['records'][] = [
|
||||
'ip' => inet_ntop($res[1]),
|
||||
'count' => intval($res[2]),
|
||||
'disposition' => Common::$disposition[$res[3]],
|
||||
'reason' => json_decode($res[4] ?? '', true),
|
||||
'dkim_auth' => json_decode($res[5] ?? '', true),
|
||||
'spf_auth' => json_decode($res[6] ?? '', true),
|
||||
'dkim_align' => Common::$align_res[$res[7]],
|
||||
'spf_align' => Common::$align_res[$res[8]],
|
||||
'envelope_to' => $res[9],
|
||||
'envelope_from' => $res[10],
|
||||
'header_from' => $res[11]
|
||||
];
|
||||
}
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get the report from DB', -1, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts report data into the database.
|
||||
*
|
||||
* @param array $data Report data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(array &$data): void
|
||||
{
|
||||
$db = $this->connector->dbh();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$domain_data = [ 'fqdn' => strtolower($data['domain']) ];
|
||||
$domain_mapper = $this->connector->getMapper('domain');
|
||||
try {
|
||||
$domain_mapper->fetch($domain_data);
|
||||
if (!$domain_data['active']) {
|
||||
throw new SoftException('Failed to add an incoming report: the domain is inactive');
|
||||
}
|
||||
} catch (DatabaseNotFoundException $e) {
|
||||
// The domain is not found. Let's try to add it automatically.
|
||||
$this->insertDomain($domain_data, $domain_mapper);
|
||||
}
|
||||
|
||||
$ct = new DateTime();
|
||||
$st = $db->prepare(
|
||||
'INSERT INTO `' . $this->connector->tablePrefix('reports')
|
||||
. '` (`domain_id`, `begin_time`, `end_time`, `loaded_time`, `org`, `external_id`, `email`,'
|
||||
. ' `extra_contact_info`, `error_string`, `policy_adkim`, `policy_aspf`, `policy_p`,'
|
||||
. ' `policy_sp`, `policy_np`, `policy_pct`, `policy_fo`, `seen`)'
|
||||
. ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)'
|
||||
);
|
||||
$st->bindValue(1, $domain_data['id'], \PDO::PARAM_INT);
|
||||
$st->bindValue(2, $data['begin_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||||
$st->bindValue(3, $data['end_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||||
$st->bindValue(4, $ct->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
|
||||
$st->bindValue(5, $data['org'], \PDO::PARAM_STR);
|
||||
$st->bindValue(6, $data['external_id'], \PDO::PARAM_STR);
|
||||
$st->bindValue(7, $data['email'], \PDO::PARAM_STR);
|
||||
$st->bindValue(8, $data['extra_contact_info'], \PDO::PARAM_STR);
|
||||
self::sqlBindJson($st, 9, $data['error_string']);
|
||||
$st->bindValue(10, $data['policy_adkim'], \PDO::PARAM_STR);
|
||||
$st->bindValue(11, $data['policy_aspf'], \PDO::PARAM_STR);
|
||||
$st->bindValue(12, $data['policy_p'], \PDO::PARAM_STR);
|
||||
$st->bindValue(13, $data['policy_sp'], \PDO::PARAM_STR);
|
||||
$st->bindValue(14, $data['policy_np'], \PDO::PARAM_STR);
|
||||
$st->bindValue(15, $data['policy_pct'], \PDO::PARAM_STR);
|
||||
$st->bindValue(16, $data['policy_fo'], \PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
$new_id = intval($db->lastInsertId());
|
||||
$st->closeCursor();
|
||||
|
||||
$st = $db->prepare(
|
||||
'INSERT INTO `' . $this->connector->tablePrefix('rptrecords')
|
||||
. '` (`report_id`, `ip`, `rcount`, `disposition`, `reason`, `dkim_auth`, `spf_auth`, `dkim_align`,'
|
||||
. ' `spf_align`, `envelope_to`, `envelope_from`, `header_from`)'
|
||||
. ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
);
|
||||
foreach ($data['records'] as &$rec_data) {
|
||||
$st->bindValue(1, $new_id, \PDO::PARAM_INT);
|
||||
$st->bindValue(2, inet_pton($rec_data['ip']), \PDO::PARAM_STR);
|
||||
$st->bindValue(3, $rec_data['rcount'], \PDO::PARAM_INT);
|
||||
$st->bindValue(4, array_search($rec_data['disposition'], Common::$disposition), \PDO::PARAM_INT);
|
||||
self::sqlBindJson($st, 5, $rec_data['reason']);
|
||||
self::sqlBindJson($st, 6, $rec_data['dkim_auth']);
|
||||
self::sqlBindJson($st, 7, $rec_data['spf_auth']);
|
||||
$st->bindValue(8, array_search($rec_data['dkim_align'], Common::$align_res), \PDO::PARAM_INT);
|
||||
$st->bindValue(9, array_search($rec_data['spf_align'], Common::$align_res), \PDO::PARAM_INT);
|
||||
$st->bindValue(10, $rec_data['envelope_to'], \PDO::PARAM_STR);
|
||||
$st->bindValue(11, $rec_data['envelope_from'], \PDO::PARAM_STR);
|
||||
$st->bindValue(12, $rec_data['header_from'], \PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
}
|
||||
unset($rec_data);
|
||||
$db->commit();
|
||||
$data['loaded_time'] = $ct;
|
||||
} catch (\PDOException $e) {
|
||||
$db->rollBack();
|
||||
if ($e->getCode() == '23000') {
|
||||
throw new SoftException('This report has already been loaded');
|
||||
}
|
||||
throw new DatabaseFatalException('Failed to insert the report', -1, $e);
|
||||
} catch (\Exception $e) {
|
||||
$db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets report record property in database.
|
||||
*
|
||||
* It has nothing to do with the fields of the report itself.
|
||||
*
|
||||
* @param array $data Report data
|
||||
* @param string $name Property name. Currently only `seen` is supported.
|
||||
* @param variant $value Property value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setProperty(array &$data, string $name, $value): void
|
||||
{
|
||||
if ($name !== 'seen' && gettype($value) !== 'boolean') {
|
||||
throw new LogicException('Incorrect parameters');
|
||||
}
|
||||
|
||||
try {
|
||||
$st = $this->connector->dbh()->prepare(
|
||||
'UPDATE `' . $this->connector->tablePrefix('reports') . '` AS `rp`'
|
||||
. ' INNER JOIN `' . $this->connector->tablePrefix('domains') . '` AS `dom`'
|
||||
. ' ON `rp`.`domain_id` = `dom`.`id` SET `seen` = ? WHERE `fqdn` = ? AND `external_id` = ?'
|
||||
);
|
||||
$st->bindValue(1, $value, \PDO::PARAM_BOOL);
|
||||
$st->bindValue(2, $data['domain'], \PDO::PARAM_STR);
|
||||
$st->bindValue(3, $data['report_id'], \PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to update the DB record', -1, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of reports with specified parameters
|
||||
*
|
||||
* This method returns a list of reports that depends on the $filter, $order and $limit.
|
||||
*
|
||||
* @param array $filter Key-value array with filtering parameters
|
||||
* @param array $order Key-value array:
|
||||
* 'field' => string, 'begin_time'
|
||||
* 'direction' => string, 'ascent' or 'descent'
|
||||
* @param array $limit Key-value array with two keys: `offset` and `count`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list(array &$filter, array &$order, array &$limit): array
|
||||
{
|
||||
$db = $this->connector->dbh();
|
||||
$list = [];
|
||||
$f_data = $this->prepareFilterData($filter);
|
||||
$order_str = $this->sqlOrderList($order);
|
||||
$cond_str0 = $this->sqlConditionList($f_data, ' AND ', 0);
|
||||
$cond_str1 = $this->sqlConditionList($f_data, ' HAVING ', 1);
|
||||
$limit_str = $this->sqlLimit($limit);
|
||||
try {
|
||||
$st = $db->prepare(
|
||||
'SELECT `org`, `begin_time`, `end_time`, `fqdn`, external_id, `seen`, SUM(`rcount`) AS `rcount`,'
|
||||
. ' MIN(`dkim_align`) AS `dkim_align`, MIN(`spf_align`) AS `spf_align`,'
|
||||
. ' MIN(`disposition`) AS `disposition` FROM `' . $this->connector->tablePrefix('rptrecords')
|
||||
. '` AS `rr` RIGHT JOIN (SELECT `rp`.`id`, `org`, `begin_time`, `end_time`, `external_id`,'
|
||||
. ' `fqdn`, `seen` FROM `' . $this->connector->tablePrefix('reports')
|
||||
. '` AS `rp` INNER JOIN `' . $this->connector->tablePrefix('domains')
|
||||
. '` AS `d` ON `d`.`id` = `rp`.`domain_id`' . $cond_str0 . $order_str
|
||||
. ') AS `rp` ON `rp`.`id` = `rr`.`report_id` GROUP BY `rp`.`id`'
|
||||
. $cond_str1 . $order_str . $limit_str
|
||||
);
|
||||
$this->sqlBindValues($st, $f_data, $limit);
|
||||
$st->execute();
|
||||
while ($row = $st->fetch(\PDO::FETCH_NUM)) {
|
||||
$list[] = [
|
||||
'org_name' => $row[0],
|
||||
'date' => [
|
||||
'begin' => new DateTime($row[1]),
|
||||
'end' => new DateTime($row[2])
|
||||
],
|
||||
'domain' => $row[3],
|
||||
'report_id' => $row[4],
|
||||
'seen' => (bool) $row[5],
|
||||
'messages' => $row[6],
|
||||
'dkim_align' => Common::$align_res[$row[7]],
|
||||
'spf_align' => Common::$align_res[$row[8]],
|
||||
'disposition' => Common::$disposition[$row[9]]
|
||||
];
|
||||
}
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get the report list', -1, $e);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of reports matching the specified filter and limits
|
||||
*
|
||||
* @param array $filter Key-value array with filtering parameters
|
||||
* @param array $limit Key-value array with two keys: `offset` and `count`
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(array &$filter, array &$limit): int
|
||||
{
|
||||
$cnt = 0;
|
||||
$f_data = $this->prepareFilterData($filter);
|
||||
try {
|
||||
$st = $this->connector->dbh()->prepare(
|
||||
'SELECT COUNT(*) FROM `' . $this->connector->tablePrefix('reports') . '` AS `rp`'
|
||||
. $this->sqlConditionList($f_data, ' WHERE ', 0)
|
||||
);
|
||||
$l_empty = [ 'offset' => 0, 'count' => 0 ];
|
||||
$this->sqlBindValues($st, $f_data, $l_empty);
|
||||
$st->execute();
|
||||
$cnt = intval($st->fetch(\PDO::FETCH_NUM)[0]);
|
||||
$st->closeCursor();
|
||||
|
||||
$offset = $limit['offset'];
|
||||
if ($offset > 0) {
|
||||
$cnt -= $offset;
|
||||
if ($cnt < 0) {
|
||||
$cnt = 0;
|
||||
}
|
||||
}
|
||||
$max = $limit['count'];
|
||||
if ($max > 0 && $max < $cnt) {
|
||||
$cnt = $max;
|
||||
}
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get the number of reports', -1, $e);
|
||||
}
|
||||
return $cnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes reports from the database
|
||||
*
|
||||
* It deletes repors form the database. The filter options `dkim` and `spf` do not affect this.
|
||||
*
|
||||
* @param array $filter Key-value array with filtering parameters
|
||||
* @param array $order Key-value array:
|
||||
* 'field' => string, 'begin_time'
|
||||
* 'direction' => string, 'ascent' or 'descent'
|
||||
* @param array $limit Key-value array with two keys: `offset` and `count`
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(array &$filter, array &$order, array &$limit): void
|
||||
{
|
||||
$f_data = $this->prepareFilterData($filter);
|
||||
$cond_str = $this->sqlConditionList($f_data, ' WHERE ', 0);
|
||||
$order_str = $this->sqlOrderList($order);
|
||||
$limit_str = $this->sqlLimit($limit);
|
||||
$db = $this->connector->dbh();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$st = $db->prepare(
|
||||
'DELETE `rr` FROM `' . $this->connector->tablePrefix('rptrecords')
|
||||
. '` AS `rr` INNER JOIN (SELECT `id` FROM `' . $this->connector->tablePrefix('reports') . '`'
|
||||
. $cond_str . $order_str . $limit_str . ') AS `rp` ON `rp`.`id` = `rr`.`report_id`'
|
||||
);
|
||||
$this->sqlBindValues($st, $f_data, $limit);
|
||||
$st->execute();
|
||||
$st->closeCursor();
|
||||
|
||||
$st = $db->prepare(
|
||||
'DELETE FROM `' . $this->connector->tablePrefix('reports') . "`{$cond_str}{$order_str}{$limit_str}"
|
||||
);
|
||||
$this->sqlBindValues($st, $f_data, $limit);
|
||||
$st->execute();
|
||||
$st->closeCursor();
|
||||
|
||||
$db->commit();
|
||||
} catch (\PDOException $e) {
|
||||
$db->rollBack();
|
||||
throw new DatabaseFatalException('Failed to delete reports', -1, $e);
|
||||
} catch (\Exception $e) {
|
||||
$db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of months with years of the form: 'yyyy-mm' for which there is at least one report
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function months(): array
|
||||
{
|
||||
$res = [];
|
||||
$rep_tn = $this->connector->tablePrefix('reports');
|
||||
try {
|
||||
$st = $this->connector->dbh()->query(
|
||||
'SELECT DISTINCT DATE_FORMAT(`date`, "%Y-%m") AS `month` FROM'
|
||||
. ' ((SELECT DISTINCT `begin_time` AS `date` FROM `' . $rep_tn
|
||||
. '`) UNION (SELECT DISTINCT `end_time` AS `date` FROM `' . $rep_tn
|
||||
. '`)) AS `r` ORDER BY `month` DESC'
|
||||
);
|
||||
while ($row = $st->fetch(\PDO::FETCH_NUM)) {
|
||||
$res[] = $row[0];
|
||||
}
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get a list of months', -1, $e);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of reporting organizations from which there is at least one report
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function organizations(): array
|
||||
{
|
||||
$res = [];
|
||||
$rep_tn = $this->connector->tablePrefix('reports');
|
||||
try {
|
||||
$st = $this->connector->dbh()->query(
|
||||
'SELECT DISTINCT `org` FROM `' . $rep_tn . '` ORDER BY `org`'
|
||||
);
|
||||
while ($row = $st->fetch(\PDO::FETCH_NUM)) {
|
||||
$res[] = $row[0];
|
||||
}
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get a list of organizations', -1, $e);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `ORDER BY ...` part of the SQL query for report records
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function sqlOrderRecords(): string
|
||||
{
|
||||
$o_set = explode(',', SettingsList::getSettingByName('report-view.sort-records-by')->value());
|
||||
switch ($o_set[0]) {
|
||||
case 'ip':
|
||||
$fname = 'ip';
|
||||
break;
|
||||
case 'message-count':
|
||||
default:
|
||||
$fname = 'rcount';
|
||||
break;
|
||||
}
|
||||
$dir = $o_set[1] === 'descent' ? 'DESC' : 'ASC';
|
||||
|
||||
return " ORDER BY `{$fname}` {$dir}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the domain exists and adds it to the database if necessary
|
||||
*
|
||||
* It automatically adds the domain if there are no domains in the database
|
||||
* or if the domain match the `allowed_domains` reqular expression in the configuration file.
|
||||
* Otherwise, throws a SoftException.
|
||||
*
|
||||
* @param array $data Domain data
|
||||
* @param object $mapper Domain mapper
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function insertDomain(array &$data, $mapper): void
|
||||
{
|
||||
$mapper = $this->connector->getMapper('domain');
|
||||
if ($mapper->count(1) !== 0) {
|
||||
if (is_null(self::$allowed_domains)) {
|
||||
$allowed = Core::instance()->config('fetcher/allowed_domains', '');
|
||||
if (!empty($allowed)) {
|
||||
self::$allowed_domains = "<{$allowed}>i";
|
||||
}
|
||||
}
|
||||
try {
|
||||
$add = !empty(self::$allowed_domains) && preg_match(self::$allowed_domains, $data['fqdn']) === 1;
|
||||
} catch (\ErrorException $e) {
|
||||
$add = false;
|
||||
Core::instance()->logger()->warning(
|
||||
'The allow_domains parameter in the settings has an incorrect regular expression value.'
|
||||
);
|
||||
}
|
||||
if (!$add) {
|
||||
throw new SoftException('Failed to add an incoming report: unknown domain: ' . $data['fqdn']);
|
||||
}
|
||||
}
|
||||
|
||||
$data['active'] = true;
|
||||
$data['description'] = 'The domain was added automatically.';
|
||||
$mapper->save($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a nullable array to an SQL query as a json string
|
||||
*
|
||||
* @param PDOStatement $st DB statement object
|
||||
* @param int $idx Bind position
|
||||
* @param array $data JSON data or null
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function sqlBindJson($st, int $idx, $data): void
|
||||
{
|
||||
if (is_null($data)) {
|
||||
$val = null;
|
||||
$type = \PDO::PARAM_NULL;
|
||||
} else {
|
||||
$val = json_encode($data);
|
||||
$type = \PDO::PARAM_STR;
|
||||
}
|
||||
$st->bindValue($idx, $val, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `ORDER BY ...` part of the SQL query
|
||||
*
|
||||
* @param array $order Key-value array with ordering options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function sqlOrderList(array &$order): string
|
||||
{
|
||||
|
||||
$dir = $order['direction'] === 'ascent' ? 'ASC' : 'DESC';
|
||||
return " ORDER BY `{$order['field']}` {$dir}";
|
||||
}
|
||||
|
||||
/**
|
||||
* The valid filter item names
|
||||
*/
|
||||
private static $filters_available = [
|
||||
'domain', 'month', 'before_time', 'organization', 'dkim', 'spf', 'status'
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns prepared filter data for sql queries
|
||||
*
|
||||
* @param array $filter Key-value array with filter options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepareFilterData(array &$filter): array
|
||||
{
|
||||
$filters = [];
|
||||
for ($i = 0; $i < 2; ++$i) {
|
||||
$filters[] = [
|
||||
'a_str' => [],
|
||||
'bindings' => []
|
||||
];
|
||||
}
|
||||
foreach (self::$filters_available as $fn) {
|
||||
if (isset($filter[$fn])) {
|
||||
$fv = $filter[$fn];
|
||||
switch (gettype($fv)) {
|
||||
case 'string':
|
||||
if (!empty($fv)) {
|
||||
if ($fn == 'domain') {
|
||||
$filters[0]['a_str'][] = '`rp`.`domain_id` = ?';
|
||||
$d_data = [ 'fqdn' => $fv ];
|
||||
$this->connector->getMapper('domain')->fetch($d_data);
|
||||
$filters[0]['bindings'][] = [ $d_data['id'], \PDO::PARAM_INT ];
|
||||
} elseif ($fn == 'month') {
|
||||
$ma = explode('-', $fv);
|
||||
if (count($ma) != 2) {
|
||||
throw new SoftException('Report list filter: Incorrect date format');
|
||||
}
|
||||
$year = (int)$ma[0];
|
||||
$month = (int)$ma[1];
|
||||
if ($year < 0 || $month < 1 || $month > 12) {
|
||||
throw new SoftException('Report list filter: Incorrect month or year value');
|
||||
}
|
||||
$filters[0]['a_str'][] = '`begin_time` < ? AND `end_time` >= ?';
|
||||
$date1 = new DateTime("{$year}-{$month}-01");
|
||||
$date2 = (clone $date1)->modify('first day of next month');
|
||||
$date1->add(new \DateInterval('PT10S'));
|
||||
$date2->sub(new \DateInterval('PT10S'));
|
||||
$filters[0]['bindings'][] = [ $date2->format('Y-m-d H:i:s'), \PDO::PARAM_STR ];
|
||||
$filters[0]['bindings'][] = [ $date1->format('Y-m-d H:i:s'), \PDO::PARAM_STR ];
|
||||
} elseif ($fn == 'organization') {
|
||||
$filters[0]['a_str'][] = '`org` = ?';
|
||||
$filters[0]['bindings'][] = [ $fv, \PDO::PARAM_STR ];
|
||||
} elseif ($fn == 'dkim') {
|
||||
if ($fv === Common::$align_res[0]) {
|
||||
$val = 0;
|
||||
} else {
|
||||
$val = count(Common::$align_res) - 1;
|
||||
if ($fv !== Common::$align_res[$val]) {
|
||||
throw new SoftException('Report list filter: Incorrect DKIM value');
|
||||
}
|
||||
}
|
||||
$filters[1]['a_str'][] = '`dkim_align` = ?';
|
||||
$filters[1]['bindings'][] = [ $val, \PDO::PARAM_INT ];
|
||||
} elseif ($fn == 'spf') {
|
||||
if ($fv === Common::$align_res[0]) {
|
||||
$val = 0;
|
||||
} else {
|
||||
$val = count(Common::$align_res) - 1;
|
||||
if ($fv !== Common::$align_res[$val]) {
|
||||
throw new SoftException('Report list filter: Incorrect SPF value');
|
||||
}
|
||||
}
|
||||
$filters[1]['a_str'][] = '`spf_align` = ?';
|
||||
$filters[1]['bindings'][] = [ $val, \PDO::PARAM_INT ];
|
||||
} elseif ($fn == 'status') {
|
||||
if ($fv === 'read') {
|
||||
$val = true;
|
||||
} elseif ($fv === 'unread') {
|
||||
$val = false;
|
||||
} else {
|
||||
throw new SoftException('Report list filter: Incorrect status value');
|
||||
}
|
||||
$filters[0]['a_str'][] = '`seen` = ?';
|
||||
$filters[0]['bindings'][] = [ $val, \PDO::PARAM_BOOL ];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'object':
|
||||
if ($fn == 'domain') {
|
||||
$filters[0]['a_str'][] = '`rp`.`domain_id` = ?';
|
||||
$filters[0]['bindings'][] = [ $fv->id(), \PDO::PARAM_INT ];
|
||||
} elseif ($fn == 'before_time') {
|
||||
$filters[0]['a_str'][] = '`begin_time` < ?';
|
||||
$filters[0]['bindings'][] = [ $fv->format('Y-m-d H:i:s'), \PDO::PARAM_STR ];
|
||||
}
|
||||
break;
|
||||
case 'integer':
|
||||
if ($fn == 'domain') {
|
||||
$filters[0]['a_str'][] = '`rp`.`domain_id` = ?';
|
||||
$filters[0]['bindings'][] = [ $fv, \PDO::PARAM_INT ];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$f_data = [];
|
||||
for ($i = 0; $i < count($filters); ++$i) {
|
||||
$filter = &$filters[$i];
|
||||
if (count($filter['a_str']) > 0) {
|
||||
$f_data[$i] = [
|
||||
'str' => implode(' AND ', $filter['a_str']),
|
||||
'bindings' => $filter['bindings']
|
||||
];
|
||||
}
|
||||
unset($filter);
|
||||
}
|
||||
return $f_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SQL condition for a filter by filter id
|
||||
*
|
||||
* @param array $f_data Array with prepared filter data
|
||||
* @param string $prefix Prefix, which will be added to the beginning of the condition string,
|
||||
* but only in the case when the condition string is not empty.
|
||||
* @param int $f_id Index of the filter
|
||||
*
|
||||
* @return string the condition string
|
||||
*/
|
||||
private function sqlConditionList(array &$f_data, string $prefix, int $f_idx): string
|
||||
{
|
||||
return isset($f_data[$f_idx]) ? ($prefix . $f_data[$f_idx]['str']) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `LIMIT ...` part of the SQL query
|
||||
*
|
||||
* @param array $limit Key-value array with two keys: `offset` and `count`
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function sqlLimit(array &$limit): string
|
||||
{
|
||||
$res = '';
|
||||
if ($limit['count'] > 0) {
|
||||
$res = ' LIMIT ?';
|
||||
if ($limit['offset'] > 0) {
|
||||
$res .= ', ?';
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the values of the filter and the limit to SQL query
|
||||
*
|
||||
* @param PDOStatement $st Prepared SQL statement to bind to
|
||||
* @param array $f_data Array with prepared filter data
|
||||
* @param array $limit Key-value array with two keys: `offset` and `count`
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function sqlBindValues($st, array &$f_data, array &$limit): void
|
||||
{
|
||||
$pos = 0;
|
||||
if (isset($f_data[0])) {
|
||||
$this->sqlBindFilterValues($st, $f_data, 0, $pos);
|
||||
}
|
||||
if (isset($f_data[1])) {
|
||||
$this->sqlBindFilterValues($st, $f_data, 1, $pos);
|
||||
}
|
||||
if ($limit['count'] > 0) {
|
||||
if ($limit['offset'] > 0) {
|
||||
$st->bindValue(++$pos, $limit['offset'], \PDO::PARAM_INT);
|
||||
}
|
||||
$st->bindValue(++$pos, $limit['count'], \PDO::PARAM_INT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the values of the specified filter item to SQL query
|
||||
*
|
||||
* @param PDOStatement $st Prepared SQL statement to bind to
|
||||
* @param array $f_data Array with prepared filter data
|
||||
* @param int $filter_idx Index of the filter to bind to
|
||||
* @param int $bind_pos Start bind position (pointer). It will be increaded with each binding.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function sqlBindFilterValues($st, array &$f_data, int $filter_idx, int &$bind_pos): void
|
||||
{
|
||||
foreach ($f_data[$filter_idx]['bindings'] as &$bv) {
|
||||
$st->bindValue(++$bind_pos, $bv[0], $bv[1]);
|
||||
}
|
||||
}
|
||||
}
|
130
root/opt/dmarc-srg/classes/Database/Mariadb/SettingMapper.php
Normal file
130
root/opt/dmarc-srg/classes/Database/Mariadb/SettingMapper.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the SettingMapper class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database\Mariadb;
|
||||
|
||||
use Liuch\DmarcSrg\Database\SettingMapperInterface;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseFatalException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
|
||||
|
||||
/**
|
||||
* SettingMapper class implementation for MariaDB
|
||||
*/
|
||||
class SettingMapper implements SettingMapperInterface
|
||||
{
|
||||
private $connector = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param Connector $connector DatabaseConnector
|
||||
*/
|
||||
public function __construct(object $connector)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns setting value as a string by key
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function value(string $key): string
|
||||
{
|
||||
try {
|
||||
$st = $this->connector->dbh()->prepare(
|
||||
'SELECT `value` FROM `' . $this->connector->tablePrefix('system') . '` WHERE `key` = ?'
|
||||
);
|
||||
$st->bindValue(1, $key, \PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
if (!$res = $st->fetch(\PDO::FETCH_NUM)) {
|
||||
throw new DatabaseNotFoundException('Setting not found: ' . $key);
|
||||
}
|
||||
$st->closeCursor();
|
||||
return $res[0];
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get a setting', -1, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a key-value array of the setting list like this:
|
||||
* [ 'name1' => 'value1', 'name2' => 'value2' ]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list(): array
|
||||
{
|
||||
$res = [];
|
||||
try {
|
||||
$st = $this->connector->dbh()->query(
|
||||
'SELECT `key`, `value` FROM `' . $this->connector->tablePrefix('system') . '` ORDER BY `key`'
|
||||
);
|
||||
while ($row = $st->fetch(\PDO::FETCH_NUM)) {
|
||||
$res[$row[0]] = $row[1];
|
||||
}
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get a list of the settings', -1, $e);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the setting to the database
|
||||
*
|
||||
* Updates the value of the setting in the database if the setting exists there or insert a new record otherwise.
|
||||
*
|
||||
* @param string $name Setting name
|
||||
* @param string $value Setting value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(string $name, string $value): void
|
||||
{
|
||||
$db = $this->connector->dbh();
|
||||
try {
|
||||
$st = $db->prepare(
|
||||
'INSERT INTO `' . $this->connector->tablePrefix('system') .
|
||||
'` (`key`, `value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `value` = ?'
|
||||
);
|
||||
$st->bindValue(1, $name, \PDO::PARAM_STR);
|
||||
$st->bindValue(2, $value, \PDO::PARAM_STR);
|
||||
$st->bindValue(3, $value, \PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to update a setting', -1, $e);
|
||||
}
|
||||
}
|
||||
}
|
222
root/opt/dmarc-srg/classes/Database/Mariadb/StatisticsMapper.php
Normal file
222
root/opt/dmarc-srg/classes/Database/Mariadb/StatisticsMapper.php
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the StatisticsMapper class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database\Mariadb;
|
||||
|
||||
use Liuch\DmarcSrg\Database\StatisticsMapperInterface;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseFatalException;
|
||||
|
||||
/**
|
||||
* StatisticsMapper class implementation for MariaDB
|
||||
*/
|
||||
class StatisticsMapper implements StatisticsMapperInterface
|
||||
{
|
||||
private $connector = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param Connector $connector DatabaseConnector
|
||||
*/
|
||||
public function __construct(object $connector)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns summary information for the specified domain and date range
|
||||
*
|
||||
* @param Domain|null $domain Domain for which the information is needed. Null is for all domains.
|
||||
* @param array $range Array with two dates
|
||||
*
|
||||
* @return array Array with Summary information:
|
||||
* 'emails' => [
|
||||
* 'total' => total email processed (int)
|
||||
* 'dkim_spf_aligned' => Both DKIM and SPF aligned (int)
|
||||
* 'dkim_aligned' => Only DKIM aligned (int)
|
||||
* 'spf_aligned' => Only SPF aligned (int)
|
||||
* ];
|
||||
*/
|
||||
public function summary($domain, array &$range): array
|
||||
{
|
||||
$is_domain = $domain ? true : false;
|
||||
$db = $this->connector->dbh();
|
||||
try {
|
||||
$st = $db->prepare(
|
||||
'SELECT SUM(`rcount`), SUM(IF(`dkim_align` = 2 AND `spf_align` = 2, `rcount`, 0)),'
|
||||
. ' SUM(IF(`dkim_align` = 2 AND `spf_align` <> 2, `rcount`, 0)),'
|
||||
. ' SUM(IF(`dkim_align` <> 2 AND `spf_align` = 2, `rcount`, 0))'
|
||||
. ' FROM `' . $this->connector->tablePrefix('rptrecords') . '` AS `rr`'
|
||||
. ' INNER JOIN `' . $this->connector->tablePrefix('reports')
|
||||
. '` AS `rp` ON `rr`.`report_id` = `rp`.`id`'
|
||||
. $this->sqlCondition($is_domain)
|
||||
);
|
||||
$this->sqlBindValues($st, $domain, $range);
|
||||
$st->execute();
|
||||
$row = $st->fetch(\PDO::FETCH_NUM);
|
||||
$ems = [
|
||||
'total' => intval($row[0]),
|
||||
'dkim_spf_aligned' => intval($row[1]),
|
||||
'dkim_aligned' => intval($row[2]),
|
||||
'spf_aligned' => intval($row[3])
|
||||
];
|
||||
$st->closeCursor();
|
||||
|
||||
$st = $db->prepare(
|
||||
'SELECT COUNT(*) FROM (SELECT `org` FROM `' . $this->connector->tablePrefix('reports') . '`'
|
||||
. $this->sqlCondition($is_domain) . ' GROUP BY `org`) AS `orgs`'
|
||||
);
|
||||
$this->sqlBindValues($st, $domain, $range);
|
||||
$st->execute();
|
||||
$row = $st->fetch(\PDO::FETCH_NUM);
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get summary information', -1, $e);
|
||||
}
|
||||
|
||||
return [
|
||||
'emails' => $ems,
|
||||
'organizations' => intval($row[0])
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of ip-addresses from which the e-mail messages were received, with some statistics for each one
|
||||
*
|
||||
* @param Domain|null $domain Domain for which the information is needed. Null is for all domains.
|
||||
* @param array $range Array with two dates
|
||||
*
|
||||
* @return array A list of ip-addresses with fields `ip`, `emails`, `dkim_aligned`, `spf_aligned`
|
||||
*/
|
||||
public function ips($domain, array &$range): array
|
||||
{
|
||||
try {
|
||||
$st = $this->connector->dbh()->prepare(
|
||||
'SELECT `ip`, SUM(`rcount`) AS `rcount`, SUM(IF(`dkim_align` = 2, `rcount`, 0)) AS `dkim_aligned`,'
|
||||
. ' SUM(IF(`spf_align` = 2, `rcount`, 0)) AS `spf_aligned`'
|
||||
. ' FROM `' . $this->connector->tablePrefix('rptrecords') . '` AS `rr`'
|
||||
. ' INNER JOIN `' . $this->connector->tablePrefix('reports')
|
||||
. '` AS `rp` ON `rr`.`report_id` = `rp`.`id`'
|
||||
. $this->sqlCondition($domain ? true : false) . ' GROUP BY `ip` ORDER BY `rcount` DESC'
|
||||
);
|
||||
$this->sqlBindValues($st, $domain, $range);
|
||||
$st->execute();
|
||||
$res = [];
|
||||
while ($row = $st->fetch(\PDO::FETCH_NUM)) {
|
||||
$res[] = [
|
||||
'ip' => inet_ntop($row[0]),
|
||||
'emails' => intval($row[1]),
|
||||
'dkim_aligned' => intval($row[2]),
|
||||
'spf_aligned' => intval($row[3])
|
||||
];
|
||||
}
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get IPs summary information', -1, $e);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of organizations that sent the reports with some statistics for each one
|
||||
*
|
||||
* @param Domain|null $domain Domain for which the information is needed. Null is for all domains.
|
||||
* @param array $range Array with two dates
|
||||
*
|
||||
* @return array List of organizations with fields `name`, `reports`, `emails`
|
||||
*/
|
||||
public function organizations($domain, array &$range): array
|
||||
{
|
||||
try {
|
||||
$st = $this->connector->dbh()->prepare(
|
||||
'SELECT `org`, COUNT(*), SUM(`rr`.`rcount`) AS `rcount`'
|
||||
. ' FROM `' . $this->connector->tablePrefix('reports') . '` AS `rp`'
|
||||
. ' INNER JOIN (SELECT `report_id`, SUM(`rcount`) AS `rcount` FROM `'
|
||||
. $this->connector->tablePrefix('rptrecords')
|
||||
. '` GROUP BY `report_id`) AS `rr` ON `rp`.`id` = `rr`.`report_id`'
|
||||
. $this->sqlCondition($domain ? true : false)
|
||||
. ' GROUP BY `org` ORDER BY `rcount` DESC'
|
||||
);
|
||||
$this->sqlBindValues($st, $domain, $range);
|
||||
$st->execute();
|
||||
$res = [];
|
||||
while ($row = $st->fetch(\PDO::FETCH_NUM)) {
|
||||
$res[] = [
|
||||
'name' => $row[0],
|
||||
'reports' => intval($row[1]),
|
||||
'emails' => intval($row[2])
|
||||
];
|
||||
}
|
||||
$st->closeCursor();
|
||||
} catch (\PDOException $e) {
|
||||
throw new DatabaseFatalException('Failed to get summary information of reporting organizations', -1, $e);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a condition string for WHERE statement
|
||||
*
|
||||
* @param bool $with_domain Is it needed to add a condition for a domain
|
||||
*
|
||||
* @return string Condition string
|
||||
*/
|
||||
private function sqlCondition($with_domain): string
|
||||
{
|
||||
$res = ' WHERE ';
|
||||
if ($with_domain) {
|
||||
$res .= 'domain_id = ? AND ';
|
||||
}
|
||||
$res .= '`begin_time` < ? AND `end_time` >= ?';
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds values for SQL queries
|
||||
*
|
||||
* @param PDOStatement $st PDO Statement to bind to
|
||||
* @param Domain|null $domain Domain for the condition
|
||||
* @param array $range Date range for the condition
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function sqlBindValues(object $st, $domain, array &$range): void
|
||||
{
|
||||
$pnum = 0;
|
||||
if ($domain) {
|
||||
$st->bindValue(++$pnum, $domain->id(), \PDO::PARAM_INT);
|
||||
}
|
||||
$ds1 = (clone $range['date1'])->add(new \DateInterval('PT10S'))->format('Y-m-d H:i:s');
|
||||
$ds2 = (clone $range['date2'])->sub(new \DateInterval('PT10S'))->format('Y-m-d H:i:s');
|
||||
$st->bindValue(++$pnum, $ds2, \PDO::PARAM_STR);
|
||||
$st->bindValue(++$pnum, $ds1, \PDO::PARAM_STR);
|
||||
}
|
||||
}
|
236
root/opt/dmarc-srg/classes/Database/Mariadb/UpgraderMapper.php
Normal file
236
root/opt/dmarc-srg/classes/Database/Mariadb/UpgraderMapper.php
Normal file
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the UpgraderMapper class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database\Mariadb;
|
||||
|
||||
use Liuch\DmarcSrg\Database\UpgraderMapperInterface;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseFatalException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
|
||||
|
||||
/**
|
||||
* UpgraderMapper class implementation for MariaDB
|
||||
*/
|
||||
class UpgraderMapper implements UpgraderMapperInterface
|
||||
{
|
||||
private $connector = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param Connector $connector DatabaseConnector
|
||||
*/
|
||||
public function __construct(object $connector)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts upgrading the database structure
|
||||
*
|
||||
* @param string $target Target version of the database structure to upgrade to
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function go(string $target): void
|
||||
{
|
||||
try {
|
||||
$cur_ver = $this->connector->getMapper('setting')->value('version');
|
||||
} catch (DatabaseNotFoundException $e) {
|
||||
$cur_ver = 'null';
|
||||
}
|
||||
|
||||
while ($cur_ver !== $target) {
|
||||
if (!isset(self::$upways['ver_' . $cur_ver])) {
|
||||
throw new SoftException(
|
||||
"Upgrading failed: There is no way to upgrade from {$cur_ver} to {$target}"
|
||||
);
|
||||
}
|
||||
$um = self::$upways['ver_' . $cur_ver];
|
||||
$cur_ver = $this->$um();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrades the database structure from None to 0.1
|
||||
*
|
||||
* @return string New version of the database structure
|
||||
*/
|
||||
private function upNull(): string
|
||||
{
|
||||
$db = $this->connector->dbh();
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$db->query(
|
||||
'INSERT INTO `' . $this->connector->tablePrefix('system')
|
||||
. '` (`key`, `value`) VALUES ("version", "0.1")'
|
||||
);
|
||||
$db->commit();
|
||||
} catch (\PDOException $e) {
|
||||
$db->rollBack();
|
||||
throw $this->dbFatalException($e);
|
||||
} catch (\Exception $e) {
|
||||
$db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
return '0.1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrades the database structure from 0.1 to 1.0
|
||||
*
|
||||
* @return string New version of the database structure
|
||||
*/
|
||||
private function up01(): string
|
||||
{
|
||||
$db = $this->connector->dbh();
|
||||
// Transaction would be useful here but it doesn't work with ALTER TABLE in MySQL/MariaDB
|
||||
try {
|
||||
$dom_tn = $this->connector->tablePrefix('domains');
|
||||
if (!$this->columnExists($db, $dom_tn, 'active')) {
|
||||
$db->query(
|
||||
'ALTER TABLE `' . $dom_tn . '` ADD COLUMN `active` boolean NOT NULL AFTER `fqdn`'
|
||||
);
|
||||
}
|
||||
if (!$this->columnExists($db, $dom_tn, 'created_time')) {
|
||||
$db->query(
|
||||
'ALTER TABLE `' . $dom_tn . '` ADD COLUMN `created_time` datetime NOT NULL'
|
||||
);
|
||||
}
|
||||
if (!$this->columnExists($db, $dom_tn, 'updated_time')) {
|
||||
$db->query(
|
||||
'ALTER TABLE `' . $dom_tn . '` ADD COLUMN `updated_time` datetime NOT NULL'
|
||||
);
|
||||
}
|
||||
$db->query(
|
||||
'UPDATE `' . $dom_tn . '` SET `active` = TRUE, `created_time` = NOW(), `updated_time` = NOW()'
|
||||
);
|
||||
$db->query(
|
||||
'UPDATE `' . $this->connector->tablePrefix('system') . '` SET `value` = "1.0" WHERE `key` = "version"'
|
||||
);
|
||||
} catch (\PDOException $e) {
|
||||
throw $this->dbFatalException($e);
|
||||
}
|
||||
return '1.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrades the database structure from 1.0 to 2.0
|
||||
*
|
||||
* @return string New version of the database structure
|
||||
*/
|
||||
private function up10(): string
|
||||
{
|
||||
$db = $this->connector->dbh();
|
||||
// Transaction would be useful here but it doesn't work with ALTER TABLE in MySQL/MariaDB
|
||||
try {
|
||||
$sys_tn = $this->connector->tablePrefix('system');
|
||||
$db->query(
|
||||
'ALTER TABLE `' . $sys_tn . '` MODIFY COLUMN `key` varchar(64) NOT NULL'
|
||||
);
|
||||
$db->query(
|
||||
'UPDATE `' . $sys_tn . '` SET `value` = "2.0" WHERE `key` = "version"'
|
||||
);
|
||||
} catch (\PDOException $e) {
|
||||
throw $this->dbFatalException($e);
|
||||
}
|
||||
return '2.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrades the database structure from v2.0 to v3.0
|
||||
*
|
||||
* @return string New version of the database structure
|
||||
*/
|
||||
private function up20(): string
|
||||
{
|
||||
$db = $this->connector->dbh();
|
||||
// Transaction would be useful here but it doesn't work with ALTER TABLE in MySQL/MariaDB
|
||||
try {
|
||||
$rep_tn = $this->connector->tablePrefix('reports');
|
||||
if (!$this->columnExists($db, $rep_tn, 'policy_np')) {
|
||||
$db->query(
|
||||
'ALTER TABLE `' . $rep_tn . '` ADD COLUMN `policy_np` varchar(20) NULL AFTER `policy_sp`'
|
||||
);
|
||||
}
|
||||
$sys_tn = $this->connector->tablePrefix('system');
|
||||
$db->query(
|
||||
'UPDATE `' . $sys_tn . '` SET `value` = "3.0" WHERE `key` = "version"'
|
||||
);
|
||||
} catch (\PDOException $e) {
|
||||
throw $this->dbFatalException($e);
|
||||
}
|
||||
return '3.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the spefied column exists in the spefied table of the database
|
||||
*
|
||||
* @param object $db Connection handle of the database
|
||||
* @param string $table Table name with the prefix
|
||||
* @param string $columb Column name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function columnExists($db, string $table, string $column): bool
|
||||
{
|
||||
$st = $db->prepare(
|
||||
'SELECT NULL FROM `INFORMATION_SCHEMA`.`COLUMNS`'
|
||||
. ' WHERE `table_schema` = ? AND `table_name` = ? AND `column_name` = ?'
|
||||
);
|
||||
$st->bindValue(1, $this->connector->dbName(), \PDO::PARAM_STR);
|
||||
$st->bindValue(2, $table, \PDO::PARAM_STR);
|
||||
$st->bindValue(3, $column, \PDO::PARAM_STR);
|
||||
$st->execute();
|
||||
$res = $st->fetch(\PDO::FETCH_NUM);
|
||||
$st->closeCursor();
|
||||
return $res ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of DatabaseFatalException
|
||||
*
|
||||
* @param Exception $e The original exception
|
||||
*
|
||||
* @return DatabaseFatalException
|
||||
*/
|
||||
private function dbFatalException($e)
|
||||
{
|
||||
return new DatabaseFatalException('Failed to upgrade the database structure', -1, $e);
|
||||
}
|
||||
|
||||
private static $upways = [
|
||||
'ver_null' => 'upNull',
|
||||
'ver_0.1' => 'up01',
|
||||
'ver_1.0' => 'up10',
|
||||
'ver_2.0' => 'up20'
|
||||
];
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the ReportLogMapperInterface
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database;
|
||||
|
||||
interface ReportLogMapperInterface
|
||||
{
|
||||
/**
|
||||
* Fetches data of report log item from the database by id
|
||||
*
|
||||
* @param Report log data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fetch(array &$data): void;
|
||||
|
||||
/**
|
||||
* Saves data of report log item to the database
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(array &$data): void;
|
||||
|
||||
/**
|
||||
* Returns a list of report log items with given criteria
|
||||
*
|
||||
* @param array $filter Key-value array:
|
||||
* 'from_time' => DateTime
|
||||
* 'till_time' => DateTime
|
||||
* @param array $order Key-value array with order options:
|
||||
* 'direction' => string, 'ascent' or 'descent'
|
||||
* @param array $limit Key-value array:
|
||||
* 'offset' => int
|
||||
* 'count' => int
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list(array &$filter, array &$order, array &$limit): array;
|
||||
|
||||
/**
|
||||
* Returns the number of report log items matching the specified filter and limits
|
||||
*
|
||||
* @param array $filter Key-value array with filtering parameters
|
||||
* @param array $limit Key-value array with limits
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(array &$filter, array &$limit): int;
|
||||
|
||||
/**
|
||||
* Deletes report log items from the database
|
||||
*
|
||||
* @param array $filter Key-value array with filtering parameters
|
||||
* @param array $order Key-value array with order options:
|
||||
* 'direction' => string, 'ascent' or 'descent'
|
||||
* @param array $limit Key-value array with limits
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(array &$filter, array &$order, array &$limit): void;
|
||||
}
|
123
root/opt/dmarc-srg/classes/Database/ReportMapperInterface.php
Normal file
123
root/opt/dmarc-srg/classes/Database/ReportMapperInterface.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the ReportMapperInterface
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database;
|
||||
|
||||
interface ReportMapperInterface
|
||||
{
|
||||
/**
|
||||
* Fetches report data from the database and stores it in the passed array
|
||||
*
|
||||
* @param array $data Array with report data. To identify the report,
|
||||
* the array must contain at least two fields:
|
||||
* `report_id` - External report id from the xml file
|
||||
* `domain` - Fully Qualified Domain Name without a trailing dot
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fetch(array &$data): void;
|
||||
|
||||
/**
|
||||
* Inserts report data into the database.
|
||||
*
|
||||
* @param array $data Report data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(array &$data): void;
|
||||
|
||||
/**
|
||||
* Sets report record property in database.
|
||||
*
|
||||
* It has nothing to do with the fields of the report itself.
|
||||
*
|
||||
* @param array $data Report data
|
||||
* @param string $name Property name. Currently only `seen` is supported.
|
||||
* @param variant $value Property value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setProperty(array &$data, string $name, $value): void;
|
||||
|
||||
/**
|
||||
* Returns a list of reports with specified parameters
|
||||
*
|
||||
* This method returns a list of reports that depends on the $filter, $order and $limit.
|
||||
*
|
||||
* @param array $filter Key-value array with filtering parameters
|
||||
* @param array $order Key-value array:
|
||||
* 'field' => string, 'begin_time'
|
||||
* 'direction' => string, 'ascent' or 'descent'
|
||||
* @param array $limit Key-value array with two keys: `offset` and `count`
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list(array &$filter, array &$order, array &$limit): array;
|
||||
|
||||
/**
|
||||
* Returns the number of reports matching the specified filter and limits
|
||||
*
|
||||
* @param array $filter Key-value array with filtering parameters
|
||||
* @param array $limit Key-value array with two keys: `offset` and `count`
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(array &$filter, array &$limit): int;
|
||||
|
||||
/**
|
||||
* Deletes reports from the database
|
||||
*
|
||||
* It deletes repors form the database. The filter options `dkim` and `spf` do not affect this.
|
||||
*
|
||||
* @param array $filter Key-value array with filtering parameters
|
||||
* @param array $order Key-value array:
|
||||
* 'field' => string, 'begin_time'
|
||||
* 'direction' => string, 'ascent' or 'descent'
|
||||
* @param array $limit Key-value array with two keys: `offset` and `count`
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(array &$filter, array &$order, array &$limit): void;
|
||||
|
||||
/**
|
||||
* Returns a list of months with years of the form: 'yyyy-mm' for which there is at least one report
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function months(): array;
|
||||
|
||||
/**
|
||||
* Returns a list of reporting organizations from which there is at least one report
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function organizations(): array;
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the SettingMapperInterface
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database;
|
||||
|
||||
interface SettingMapperInterface
|
||||
{
|
||||
/**
|
||||
* Returns setting value as a string by key
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function value(string $key): string;
|
||||
|
||||
/**
|
||||
* Returns a key-value array of the setting list like this:
|
||||
* [ 'name1' => 'value1', 'name2' => 'value2' ]
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list(): array;
|
||||
|
||||
/**
|
||||
* Saves the setting to the database
|
||||
*
|
||||
* Updates the value of the setting in the database if the setting exists there or insert a new record otherwise.
|
||||
*
|
||||
* @param string $name Setting name
|
||||
* @param string $value Setting value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(string $name, string $value): void;
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the StatisticsMapperInterface
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database;
|
||||
|
||||
interface StatisticsMapperInterface
|
||||
{
|
||||
/**
|
||||
* Returns summary information for the specified domain and date range
|
||||
*
|
||||
* @param Domain|null $domain Domain for which the information is needed. Null is for all domains.
|
||||
* @param array $range Array with two dates
|
||||
*
|
||||
* @return array Array with Summary information:
|
||||
* 'emails' => [
|
||||
* 'total' => total email processed (int)
|
||||
* 'dkim_spf_aligned' => Both DKIM and SPF aligned (int)
|
||||
* 'dkim_aligned' => Only DKIM aligned (int)
|
||||
* 'spf_aligned' => Only SPF aligned (int)
|
||||
* ];
|
||||
*/
|
||||
public function summary($domain, array &$range): array;
|
||||
|
||||
/**
|
||||
* Returns a list of ip-addresses from which the e-mail messages were received, with some statistics for each one
|
||||
*
|
||||
* @param Domain|null $domain Domain for which the information is needed. Null is for all domains.
|
||||
* @param array $range Array with two dates
|
||||
*
|
||||
* @return array A list of ip-addresses with fields `ip`, `emails`, `dkim_aligned`, `spf_aligned`
|
||||
*/
|
||||
public function ips($domain, array &$range): array;
|
||||
|
||||
/**
|
||||
* Returns a list of organizations that sent the reports with some statistics for each one
|
||||
*
|
||||
* @param Domain|null $domain Domain for which the information is needed. Null is for all domains.
|
||||
* @param array $range Array with two dates
|
||||
*
|
||||
* @return array List of organizations with fields `name`, `reports`, `emails`
|
||||
*/
|
||||
public function organizations($domain, array &$range): array;
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the UpgraderMapperInterface
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Database;
|
||||
|
||||
interface UpgraderMapperInterface
|
||||
{
|
||||
/**
|
||||
* Starts upgrading the database structure
|
||||
*
|
||||
* @param string $target Target version of the database structure to upgrade to
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function go(string $target): void;
|
||||
}
|
43
root/opt/dmarc-srg/classes/DateTime.php
Normal file
43
root/opt/dmarc-srg/classes/DateTime.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class DateTime
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg;
|
||||
|
||||
/**
|
||||
* This class extends the standard class to get ISO 8601 value in json_encode.
|
||||
*/
|
||||
class DateTime extends \DateTime implements \JsonSerializable
|
||||
{
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return $this->format(\DateTime::ATOM);
|
||||
}
|
||||
}
|
165
root/opt/dmarc-srg/classes/Directories/Directory.php
Normal file
165
root/opt/dmarc-srg/classes/Directories/Directory.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class Directory
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Directories;
|
||||
|
||||
use Liuch\DmarcSrg\ErrorHandler;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
use Liuch\DmarcSrg\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* This class is designed to work with the report directories which are listed in the configuration file.
|
||||
*/
|
||||
class Directory
|
||||
{
|
||||
private $id = null;
|
||||
private $name = null;
|
||||
private $location = null;
|
||||
|
||||
/**
|
||||
* It's the constructor of the class
|
||||
*
|
||||
* @param int $id Id of the directory. In fact, it is a serial number in the configuration file.
|
||||
* @param array $data An array with the following fields:
|
||||
* `location` (string) - Location of the directory in the file system.
|
||||
* `name` (string) - Name of the directory. It is optional.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(int $id, array $data)
|
||||
{
|
||||
if (isset($data['name']) && gettype($data['name']) !== 'string') {
|
||||
throw new LogicException('Directory name must be either null or a string value');
|
||||
}
|
||||
if (!isset($data['location']) || gettype($data['location']) !== 'string') {
|
||||
throw new LogicException('Directory location must be a string value');
|
||||
}
|
||||
if (empty($data['location'])) {
|
||||
throw new LogicException('Directory location must not be an empty string');
|
||||
}
|
||||
|
||||
$this->id = $id;
|
||||
$this->name = $data['name'] ?? null;
|
||||
$this->location = $data['location'];
|
||||
if (empty($this->name)) {
|
||||
$this->name = 'Directory ' . $this->id;
|
||||
}
|
||||
if (substr($this->location, -1) !== '/') {
|
||||
$this->location .= '/';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with directory configuration data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'location' => $this->location
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the existence and accessibility of the directory. Returns the result as an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check(): array
|
||||
{
|
||||
try {
|
||||
self::checkPath($this->location, true);
|
||||
self::checkPath($this->location . 'failed/', false);
|
||||
} catch (RuntimeException $e) {
|
||||
return ErrorHandler::exceptionResult($e);
|
||||
}
|
||||
|
||||
return [
|
||||
'error_code' => 0,
|
||||
'message' => 'Successfully',
|
||||
'status' => [
|
||||
'files' => $this->count()
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of files in the directory.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$cnt = 0;
|
||||
try {
|
||||
$fs = new \FilesystemIterator($this->location);
|
||||
} catch (\Exception $e) {
|
||||
throw new RuntimeException("Error accessing directory {$this->location}", -1, $e);
|
||||
}
|
||||
foreach ($fs as $entry) {
|
||||
if ($entry->isFile()) {
|
||||
++$cnt;
|
||||
}
|
||||
}
|
||||
return $cnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks accessibility of a directory by its path. Throws an exception in case of any error.
|
||||
*
|
||||
* @param string $path Path to the directory to check.
|
||||
* @param bool $existence If true, the absence of the directory causes an error.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function checkPath(string $path, bool $existence): void
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
if ($existence) {
|
||||
throw new SoftException($path . ' directory does not exist!');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!is_dir($path)) {
|
||||
throw new SoftException($path . ' is not a directory!');
|
||||
}
|
||||
if (!is_readable($path)) {
|
||||
throw new SoftException($path . ' directory is not readable!');
|
||||
}
|
||||
if (!is_writable($path)) {
|
||||
throw new SoftException($path . ' directory is not writable!');
|
||||
}
|
||||
}
|
||||
}
|
147
root/opt/dmarc-srg/classes/Directories/DirectoryList.php
Normal file
147
root/opt/dmarc-srg/classes/Directories/DirectoryList.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class DirectoryList
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Directories;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* This class is designed to work with the list of report directories which are listed in the configuration file.
|
||||
*/
|
||||
class DirectoryList
|
||||
{
|
||||
private $list = null;
|
||||
|
||||
/**
|
||||
* Returns a list of directories for the setting file
|
||||
*
|
||||
* @return array Array with instances of Directory class
|
||||
*/
|
||||
public function list(): array
|
||||
{
|
||||
$this->ensureList();
|
||||
return $this->list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the Directory class by its Id
|
||||
*
|
||||
* @param int $id Id of the required directory
|
||||
*
|
||||
* @return Directory
|
||||
*/
|
||||
public function directory(int $id)
|
||||
{
|
||||
$this->ensureList();
|
||||
if ($id <= 0 || $id > count($this->list)) {
|
||||
throw new LogicException('Incorrect directory Id');
|
||||
}
|
||||
return $this->list[$id - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the accessibility of the specified directory or all the directories from configuration file if $id is 0.
|
||||
*
|
||||
* @param int $id Directory Id to check
|
||||
*
|
||||
* @return array Result array with `error_code` and `message` fields. For one directory and if there is no error,
|
||||
* a field `status` will be added to the result.
|
||||
*/
|
||||
public function check(int $id): array
|
||||
{
|
||||
if ($id !== 0) {
|
||||
$dir = $this->directory($id);
|
||||
return $dir->check();
|
||||
}
|
||||
|
||||
$this->ensureList();
|
||||
$results = [];
|
||||
$err_cnt = 0;
|
||||
$dir_cnt = count($this->list);
|
||||
for ($i = 0; $i < $dir_cnt; ++$i) {
|
||||
$r = $this->list[$i]->check();
|
||||
if ($r['error_code'] !== 0) {
|
||||
++$err_cnt;
|
||||
}
|
||||
$results[] = $r;
|
||||
}
|
||||
$res = [];
|
||||
if ($err_cnt === 0) {
|
||||
$res['error_code'] = 0;
|
||||
$res['message'] = 'Successfully';
|
||||
} else {
|
||||
$res['error_code'] = -1;
|
||||
$res['message'] = sprintf('%d of %d directories have failed the check', $err_cnt, $dir_cnt);
|
||||
}
|
||||
$res['results'] = $results;
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of directories from the configuration file if it does not exist
|
||||
* for using in other methods of the class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function ensureList(): void
|
||||
{
|
||||
if (!is_null($this->list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$directories = Core::instance()->config('directories');
|
||||
|
||||
$this->list = [];
|
||||
if (is_array($directories)) {
|
||||
$cnt = count($directories);
|
||||
if ($cnt > 0) {
|
||||
if (isset($directories[0])) {
|
||||
$id = 1;
|
||||
for ($i = 0; $i < $cnt; ++$i) {
|
||||
try {
|
||||
$this->list[] = new Directory($id, $directories[$i]);
|
||||
++$id;
|
||||
} catch (LogicException $d) {
|
||||
// Just ignore this directory setting.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$this->list[] = new Directory(1, $directories);
|
||||
} catch (LogicException $e) {
|
||||
// Just ignore this directory setting.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
286
root/opt/dmarc-srg/classes/Domains/Domain.php
Normal file
286
root/opt/dmarc-srg/classes/Domains/Domain.php
Normal file
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class Domain
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Domains;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
|
||||
|
||||
/**
|
||||
* It's a class for accessing to stored domains data
|
||||
*
|
||||
* This class is designed for storing and manipulating domain data.
|
||||
* All queries to the datatabase are made in lazy mode.
|
||||
*/
|
||||
class Domain
|
||||
{
|
||||
private $db = null;
|
||||
private $ex_f = null;
|
||||
private $data = [
|
||||
'id' => null,
|
||||
'fqdn' => null,
|
||||
'active' => null,
|
||||
'description' => null,
|
||||
'created_time' => null,
|
||||
'updated_time' => null
|
||||
];
|
||||
|
||||
/**
|
||||
* It's a constructor of the class
|
||||
*
|
||||
* Some examples of using:
|
||||
* (new Domain(1))->fqdn(); - will return the fully qualified domain name for the domain with id = 1
|
||||
* (new Domain('example.com'))->description(); - will return the description for the domain example.com
|
||||
* (new Domain([ 'fqdn' => 'example.com', 'description' => 'an expample domain' ])->save(); - will add
|
||||
* this domain to the database if it does not exist in it.
|
||||
*
|
||||
* @param int|string|array $data Some domain data to identify it
|
||||
* int value is treated as domain id
|
||||
* string value is treated as a FQDN
|
||||
* array has these fields: `id`, `fqdn`, `active`, `description`
|
||||
* and usually uses for creating a new domain item.
|
||||
* Note: The values of the fields `created_time` and `updated_time`
|
||||
* will be ignored while saving to the database.
|
||||
* @param DatabaseController $db The database controller
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($data, $db = null)
|
||||
{
|
||||
$this->db = $db ?? Core::instance()->database();
|
||||
switch (gettype($data)) {
|
||||
case 'integer':
|
||||
$this->data['id'] = $data;
|
||||
return;
|
||||
case 'string':
|
||||
$this->data['fqdn'] = strtolower($data);
|
||||
$this->checkFqdn();
|
||||
return;
|
||||
case 'array':
|
||||
if (isset($data['id'])) {
|
||||
if (gettype($data['id']) !== 'integer') {
|
||||
break;
|
||||
}
|
||||
$this->data['id'] = $data['id'];
|
||||
}
|
||||
if (isset($data['fqdn'])) {
|
||||
if (gettype($data['fqdn']) !== 'string') {
|
||||
break;
|
||||
}
|
||||
$this->data['fqdn'] = strtolower($data['fqdn']);
|
||||
$this->checkFqdn();
|
||||
}
|
||||
if (isset($data['active'])) {
|
||||
if (gettype($data['active']) !== 'boolean') {
|
||||
break;
|
||||
}
|
||||
$this->data['active'] = $data['active'];
|
||||
} else {
|
||||
$this->data['active'] = false;
|
||||
}
|
||||
if (isset($data['description'])) {
|
||||
if (gettype($data['description']) !== 'string') {
|
||||
break;
|
||||
}
|
||||
$this->data['description'] = $data['description'];
|
||||
}
|
||||
if (isset($data['created_time'])) {
|
||||
if (gettype($data['created_time']) !== 'object') {
|
||||
break;
|
||||
}
|
||||
$this->data['created_time'] = $data['created_time'];
|
||||
}
|
||||
if (isset($data['updated_time'])) {
|
||||
if (gettype($data['updated_time']) !== 'object') {
|
||||
break;
|
||||
}
|
||||
$this->data['updated_time'] = $data['updated_time'];
|
||||
}
|
||||
if (!is_null($this->data['id']) || !is_null($this->data['fqdn'])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new LogicException('Wrong domain data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the domain exists in the database or false otherwise
|
||||
*
|
||||
* @return bool Whether the domain exists
|
||||
*/
|
||||
public function exists(): bool
|
||||
{
|
||||
if (is_null($this->ex_f)) {
|
||||
$this->ex_f = $this->db->getMapper('domain')->exists($this->data);
|
||||
}
|
||||
return $this->ex_f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain id
|
||||
*
|
||||
* @return int The domain id
|
||||
*/
|
||||
public function id(): int
|
||||
{
|
||||
if (is_null($this->data['id'])) {
|
||||
$this->fetchData();
|
||||
}
|
||||
return $this->data['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain's FQDN
|
||||
*
|
||||
* @return string FQDN for the domain
|
||||
*/
|
||||
public function fqdn(): string
|
||||
{
|
||||
if (is_null($this->data['fqdn'])) {
|
||||
$this->fetchData();
|
||||
}
|
||||
return $this->data['fqdn'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the domain is active or not
|
||||
*
|
||||
* When the domain is inactive, all incoming reports for it are ignored
|
||||
* but the domain will still be included in summary reports.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function active(): bool
|
||||
{
|
||||
if (is_null($this->data['active'])) {
|
||||
$this->fetchData();
|
||||
}
|
||||
return $this->data['active'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain's description
|
||||
*
|
||||
* @return string|null The description of the domain if it exists or null otherwise
|
||||
*/
|
||||
public function description()
|
||||
{
|
||||
if (is_null($this->data['id']) || is_null($this->data['fqdn'])) {
|
||||
$this->fetchData();
|
||||
}
|
||||
return $this->data['description'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with domain data
|
||||
*
|
||||
* @return array Domain data
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
if (is_null($this->data['id']) || is_null($this->data['fqdn'])) {
|
||||
$this->fetchData();
|
||||
}
|
||||
$res = $this->data;
|
||||
unset($res['id']);
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the domain to the database
|
||||
*
|
||||
* Updates the domain's description in the database if the domain exists or insert a new record otherwise.
|
||||
* The domain id is ignored in the insert mode.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
$this->db->getMapper('domain')->save($this->data);
|
||||
$this->ex_f = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the domain from the database
|
||||
*
|
||||
* Deletes the domain if there are no reports for this domain in the database.
|
||||
* If you want to stop handling reports for this domain, just make it inactive.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
if (is_null($this->data['id'])) {
|
||||
$this->fetchData();
|
||||
}
|
||||
$this->db->getMapper('domain')->delete($this->data);
|
||||
$this->ex_f = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the trailing dot from the domain name and checks it for an empty value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function checkFqdn(): void
|
||||
{
|
||||
$fqdn = trim($this->data['fqdn']);
|
||||
if (substr($fqdn, -1) === '.') {
|
||||
$fqdn = trim(substr($fqdn, 0, -1));
|
||||
}
|
||||
if ($fqdn === '') {
|
||||
throw new SoftException('The domain name must not be an empty string');
|
||||
}
|
||||
$this->data['fqdn'] = $fqdn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the domain data from the database by its id or name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function fetchData(): void
|
||||
{
|
||||
if ($this->ex_f === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->db->getMapper('domain')->fetch($this->data);
|
||||
$this->ex_f = true;
|
||||
} catch (DatabaseNotFoundException $e) {
|
||||
$this->ex_f = false;
|
||||
throw new SoftException('Domain not found');
|
||||
}
|
||||
}
|
||||
}
|
79
root/opt/dmarc-srg/classes/Domains/DomainList.php
Normal file
79
root/opt/dmarc-srg/classes/Domains/DomainList.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class DomainList
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Domains;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
|
||||
/**
|
||||
* This class is designed to work with the list of domains
|
||||
*/
|
||||
class DomainList
|
||||
{
|
||||
private $db = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param DatabaseController $db The database controller
|
||||
*/
|
||||
public function __construct($db = null)
|
||||
{
|
||||
$this->db = $db ?? Core::instance()->database();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of domains from the database
|
||||
*
|
||||
* @return array Array with instances of Domain class
|
||||
*/
|
||||
public function getList(): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->db->getMapper('domain')->list() as $dd) {
|
||||
$list[] = new Domain($dd, $this->db);
|
||||
}
|
||||
return [
|
||||
'domains' => $list,
|
||||
'more' => false
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ordered array with domain names from the database
|
||||
*
|
||||
* @return array Array of strings
|
||||
*/
|
||||
public function names(): array
|
||||
{
|
||||
return $this->db->getMapper('domain')->names();
|
||||
}
|
||||
}
|
156
root/opt/dmarc-srg/classes/ErrorHandler.php
Normal file
156
root/opt/dmarc-srg/classes/ErrorHandler.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains ErrorHandler class
|
||||
*
|
||||
* @category Common
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg;
|
||||
|
||||
use Liuch\DmarcSrg\Log\LoggerInterface;
|
||||
use Liuch\DmarcSrg\Log\LoggerAwareInterface;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
|
||||
/**
|
||||
* Uncaught exception handler
|
||||
*/
|
||||
class ErrorHandler implements LoggerAwareInterface
|
||||
{
|
||||
private $core = null;
|
||||
private $logger = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param Core $core
|
||||
*/
|
||||
public function __construct(object $core)
|
||||
{
|
||||
$this->core = $core;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle uncaught exceptions. Used by set_exception_handler and set_error_handler functions
|
||||
*
|
||||
* @param Throwable $e an exception to handle. For set_error_handler it is ErrorException.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handleException(\Throwable $e): void
|
||||
{
|
||||
$debug = $this->core->config('debug', 0);
|
||||
|
||||
if ($this->logger) {
|
||||
$this->logger->error(strval($e));
|
||||
}
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
echo self::getText($e, $debug);
|
||||
exit(1);
|
||||
} else {
|
||||
Core::sendJson(self::getResult($e, $debug));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an result array based on the passed exception's data.
|
||||
* If the debug mode is enabled, the `debug_info` field will be added to the result.
|
||||
*
|
||||
* @param Throwable $e an exception for which the result is generated
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function exceptionResult(\Throwable $e): array
|
||||
{
|
||||
return self::getResult($e, Core::instance()->config('debug', 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about the passed exception as text.
|
||||
* If the debug is enabled, debug information will be added.
|
||||
*
|
||||
* @param Throwable $e an exception for which the text is generated
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function exceptionText(\Throwable $e): string
|
||||
{
|
||||
return self::getText($e, Core::instance()->config('debug', 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a logger to log uncaught exceptions and errors
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger): void
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current logger
|
||||
*/
|
||||
public function logger()
|
||||
{
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
private static function getResult(\Throwable $e, int $debug): array
|
||||
{
|
||||
$code = $e->getCode();
|
||||
if ($code === 0) {
|
||||
$code = -1;
|
||||
}
|
||||
$res = [
|
||||
'error_code' => $code,
|
||||
'message' => $e->getMessage()
|
||||
];
|
||||
if ($debug &&
|
||||
(Core::instance()->userId() !== false || php_sapi_name() === 'cli') &&
|
||||
!($e instanceof SoftException)
|
||||
) {
|
||||
$prev = $e->getPrevious();
|
||||
$res['debug_info'] = [
|
||||
'code' => ($prev ?? $e)->getCode(),
|
||||
'content' => strval($prev ?? $e)
|
||||
];
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
private static function getText(\Throwable $e, int $debug): string
|
||||
{
|
||||
$msg = 'Error: ' . $e->getMessage() . ' (' . $e->getCode() . ')' . PHP_EOL;
|
||||
if (!$debug) {
|
||||
return $msg;
|
||||
}
|
||||
|
||||
return '-----' . PHP_EOL
|
||||
. $msg
|
||||
. '-----' . PHP_EOL
|
||||
. $e . PHP_EOL;
|
||||
}
|
||||
}
|
39
root/opt/dmarc-srg/classes/Exception/AuthException.php
Normal file
39
root/opt/dmarc-srg/classes/Exception/AuthException.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains AuthException class
|
||||
*
|
||||
* @category Common
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Exception;
|
||||
|
||||
/**
|
||||
* Represents an authentication error
|
||||
*/
|
||||
class AuthException extends SoftException
|
||||
{
|
||||
}
|
39
root/opt/dmarc-srg/classes/Exception/DatabaseException.php
Normal file
39
root/opt/dmarc-srg/classes/Exception/DatabaseException.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains DatabaseException class
|
||||
*
|
||||
* @category Common
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Exception;
|
||||
|
||||
/**
|
||||
* Represents an error in the database
|
||||
*/
|
||||
class DatabaseException extends RuntimeException
|
||||
{
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains DatabaseExceptionFactory class
|
||||
*
|
||||
* @category Common
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Exception;
|
||||
|
||||
/**
|
||||
* Factory class for DatabaseException
|
||||
*/
|
||||
class DatabaseExceptionFactory
|
||||
{
|
||||
/**
|
||||
* Creates a DatabaseException instance with an appropriate message based on the passed class's name and error code.
|
||||
*
|
||||
* @param Exception $origin The original exception
|
||||
*
|
||||
* @return DatabaseException
|
||||
*/
|
||||
public static function fromException(\Throwable $origin)
|
||||
{
|
||||
$msg = null;
|
||||
if (get_class($origin) === 'PDOException') {
|
||||
switch ($origin->getCode()) {
|
||||
case 1044:
|
||||
case 1045:
|
||||
$msg = 'Database access denied';
|
||||
break;
|
||||
case 2002:
|
||||
case 2006:
|
||||
$msg = 'Database connection error';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$msg) {
|
||||
$msg = 'Database error';
|
||||
}
|
||||
return new DatabaseException($msg, -1, $origin);
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains DatabaseFatalException class
|
||||
*
|
||||
* @category Common
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Exception;
|
||||
|
||||
/**
|
||||
* Represents an error in the database
|
||||
*/
|
||||
class DatabaseFatalException extends LogicException
|
||||
{
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains DatabaseNotFoundException class
|
||||
*
|
||||
* @category Common
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Exception;
|
||||
|
||||
/**
|
||||
* Represents the case where a row was not found in the database
|
||||
*/
|
||||
class DatabaseNotFoundException extends DatabaseException
|
||||
{
|
||||
}
|
39
root/opt/dmarc-srg/classes/Exception/LogicException.php
Normal file
39
root/opt/dmarc-srg/classes/Exception/LogicException.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains LogicException class
|
||||
*
|
||||
* @category Common
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Exception;
|
||||
|
||||
/**
|
||||
* Base LogicException
|
||||
*/
|
||||
class LogicException extends \LogicException
|
||||
{
|
||||
}
|
39
root/opt/dmarc-srg/classes/Exception/MailboxException.php
Normal file
39
root/opt/dmarc-srg/classes/Exception/MailboxException.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains MailboxException class
|
||||
*
|
||||
* @category Common
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Exception;
|
||||
|
||||
/**
|
||||
* Represents an error of a mailbox
|
||||
*/
|
||||
class MailboxException extends RuntimeException
|
||||
{
|
||||
}
|
43
root/opt/dmarc-srg/classes/Exception/RuntimeException.php
Normal file
43
root/opt/dmarc-srg/classes/Exception/RuntimeException.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains RuntimeException class
|
||||
*
|
||||
* @category Common
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Exception;
|
||||
|
||||
/**
|
||||
* Base RuntimeException
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException
|
||||
{
|
||||
public function __construct(string $message = '', int $code = -1, \Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
39
root/opt/dmarc-srg/classes/Exception/SoftException.php
Normal file
39
root/opt/dmarc-srg/classes/Exception/SoftException.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains SoftException class
|
||||
*
|
||||
* @category Common
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Exception;
|
||||
|
||||
/**
|
||||
* This exception is ignored in the debug mode
|
||||
*/
|
||||
class SoftException extends RuntimeException
|
||||
{
|
||||
}
|
47
root/opt/dmarc-srg/classes/Log/LogLevel.php
Normal file
47
root/opt/dmarc-srg/classes/Log/LogLevel.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class LogLevel
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Log;
|
||||
|
||||
/**
|
||||
* Describes log levels
|
||||
*/
|
||||
class LogLevel
|
||||
{
|
||||
const EMERGENCY = 'emergency';
|
||||
const ALERT = 'alert';
|
||||
const CRITICAL = 'critical';
|
||||
const ERROR = 'error';
|
||||
const WARNING = 'warning';
|
||||
const NOTICE = 'notice';
|
||||
const INFO = 'info';
|
||||
const DEBUG = 'debug';
|
||||
}
|
47
root/opt/dmarc-srg/classes/Log/LoggerAwareInterface.php
Normal file
47
root/opt/dmarc-srg/classes/Log/LoggerAwareInterface.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the interface LoggerAwareInterface
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Log;
|
||||
|
||||
/**
|
||||
* Describes a logger-aware instance
|
||||
*/
|
||||
interface LoggerAwareInterface
|
||||
{
|
||||
/**
|
||||
* Sets a logger instance on the object
|
||||
*
|
||||
* @param LoggerInterface $logger
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger): void;
|
||||
}
|
131
root/opt/dmarc-srg/classes/Log/LoggerInterface.php
Normal file
131
root/opt/dmarc-srg/classes/Log/LoggerInterface.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the interface LoggerInterface
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Log;
|
||||
|
||||
/**
|
||||
* Describes a logger instance
|
||||
* The message MUST be a string or object implementing __toString().
|
||||
*/
|
||||
interface LoggerInterface
|
||||
{
|
||||
/**
|
||||
* System is unusable
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function emergency($message, array $context = []): void;
|
||||
|
||||
/**
|
||||
* Action must be taken immediately
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function alert($message, array $context = []): void;
|
||||
|
||||
/**
|
||||
* Critical conditions
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function critical($message, array $context = []): void;
|
||||
|
||||
/**
|
||||
* Runtime errors that do not require immediate action but should typically
|
||||
* be logged and monitored
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function error($message, array $context = []): void;
|
||||
|
||||
/**
|
||||
* Exceptional occurrences that are not errors
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function warning($message, array $context = []): void;
|
||||
|
||||
/**
|
||||
* Normal but significant events
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function notice($message, array $context = []): void;
|
||||
|
||||
/**
|
||||
* Interesting events
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function info($message, array $context = []): void;
|
||||
|
||||
/**
|
||||
* Detailed debug information
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function debug($message, array $context = []): void;
|
||||
|
||||
/**
|
||||
* Logs with an arbitrary level
|
||||
*
|
||||
* @param mixed $level
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function log($level, $message, array $context = []): void;
|
||||
}
|
172
root/opt/dmarc-srg/classes/Log/PhpSystemLogger.php
Normal file
172
root/opt/dmarc-srg/classes/Log/PhpSystemLogger.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2022 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class PhpSystemLogger;
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Log;
|
||||
|
||||
/**
|
||||
* Implements a logger to log messages via PHP's system logger,
|
||||
* using the OS's system logging mechanism or a file, depending on
|
||||
* what the error_log PHP configuration directive is set to.
|
||||
*/
|
||||
class PhpSystemLogger implements LoggerInterface
|
||||
{
|
||||
/**
|
||||
* System is unusable
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function emergency($message, array $context = []): void
|
||||
{
|
||||
$this->log(LogLevel::EMERGENCY, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action must be taken immediately
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function alert($message, array $context = []): void
|
||||
{
|
||||
$this->log(LogLevel::ALERT, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Critical conditions
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function critical($message, array $context = []): void
|
||||
{
|
||||
$this->log(LogLevel::CRITICAL, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime errors that do not require immediate action but should typically
|
||||
* be logged and monitored
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function error($message, array $context = []): void
|
||||
{
|
||||
$this->log(LogLevel::ERROR, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exceptional occurrences that are not errors
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function warning($message, array $context = []): void
|
||||
{
|
||||
$this->log(LogLevel::WARNING, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal but significant events
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function notice($message, array $context = []): void
|
||||
{
|
||||
$this->log(LogLevel::NOTICE, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interesting events
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function info($message, array $context = []): void
|
||||
{
|
||||
$this->log(LogLevel::INFO, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed debug information
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function debug($message, array $context = []): void
|
||||
{
|
||||
$this->log(LogLevel::DEBUG, $message, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs with an arbitrary level
|
||||
*
|
||||
* @param mixed $level
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function log($level, $message, array $context = []): void
|
||||
{
|
||||
switch ($level) {
|
||||
case LogLevel::EMERGENCY:
|
||||
case LogLevel::ALERT:
|
||||
case LogLevel::CRITICAL:
|
||||
case LogLevel::ERROR:
|
||||
case LogLevel::WARNING:
|
||||
case LogLevel::NOTICE:
|
||||
case LogLevel::INFO:
|
||||
case LogLevel::DEBUG:
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException('Invalid log level argument');
|
||||
}
|
||||
\error_log("dmarc-srg [{$level}]: {$message}");
|
||||
}
|
||||
}
|
110
root/opt/dmarc-srg/classes/Mail/MailAttachment.php
Normal file
110
root/opt/dmarc-srg/classes/Mail/MailAttachment.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Mail;
|
||||
|
||||
use Liuch\DmarcSrg\ReportFile\ReportFile;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
|
||||
class MailAttachment
|
||||
{
|
||||
private $conn;
|
||||
private $filename;
|
||||
private $bytes;
|
||||
private $number;
|
||||
private $mnumber;
|
||||
private $encoding;
|
||||
private $stream;
|
||||
private $mime_type;
|
||||
|
||||
public function __construct($conn, $params)
|
||||
{
|
||||
$this->conn = $conn;
|
||||
$this->filename = $params['filename'];
|
||||
$this->bytes = $params['bytes'];
|
||||
$this->number = $params['number'];
|
||||
$this->mnumber = $params['mnumber'];
|
||||
$this->encoding = $params['encoding'];
|
||||
$this->stream = null;
|
||||
$this->mime_type = null;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (!is_null($this->stream) && get_resource_type($this->stream) == 'stream') {
|
||||
fclose($this->stream);
|
||||
}
|
||||
}
|
||||
|
||||
public function mimeType()
|
||||
{
|
||||
if (is_null($this->mime_type)) {
|
||||
$this->mime_type = ReportFile::getMimeType($this->filename, $this->datastream());
|
||||
}
|
||||
return $this->mime_type;
|
||||
}
|
||||
|
||||
public function size()
|
||||
{
|
||||
return $this->bytes;
|
||||
}
|
||||
|
||||
public function filename()
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function extension()
|
||||
{
|
||||
return pathinfo($this->filename, PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
public function datastream()
|
||||
{
|
||||
if (is_null($this->stream)) {
|
||||
$this->stream = fopen('php://temp', 'r+');
|
||||
fwrite($this->stream, $this->toString());
|
||||
}
|
||||
rewind($this->stream);
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
private function fetchBody()
|
||||
{
|
||||
return imap_fetchbody($this->conn, $this->mnumber, strval($this->number), FT_PEEK);
|
||||
}
|
||||
|
||||
private function toString()
|
||||
{
|
||||
switch ($this->encoding) {
|
||||
case ENC7BIT:
|
||||
case ENC8BIT:
|
||||
case ENCBINARY:
|
||||
return $this->fetchBody();
|
||||
case ENCBASE64:
|
||||
return base64_decode($this->fetchBody());
|
||||
case ENCQUOTEDPRINTABLE:
|
||||
return imap_qprint($this->fetchBody());
|
||||
}
|
||||
throw new SoftException('Encoding failed: Unknown encoding');
|
||||
}
|
||||
}
|
142
root/opt/dmarc-srg/classes/Mail/MailBody.php
Normal file
142
root/opt/dmarc-srg/classes/Mail/MailBody.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Mail;
|
||||
|
||||
/**
|
||||
* The class is designed to easily create multipart/alternative message bodies.
|
||||
*/
|
||||
class MailBody
|
||||
{
|
||||
private $text = null;
|
||||
private $html = null;
|
||||
private $boundary = null;
|
||||
|
||||
/**
|
||||
* Sets text content as a part of the message body
|
||||
*
|
||||
* @param array $text Text part of the message as an array of strings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setText(array &$text): void
|
||||
{
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets html content as a part of the message body
|
||||
*
|
||||
* @param array $html Html part of the message as and array of strings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setHtml(array $html): void
|
||||
{
|
||||
$this->html = $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Content-Type header value for the whole message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function contentType(): string
|
||||
{
|
||||
if ($this->boundary()) {
|
||||
$ctype = 'multipart/alternative; boundary="' . $this->boundary() . '"';
|
||||
} else {
|
||||
if (!is_null($this->html)) {
|
||||
$ctype = 'text/html';
|
||||
} else {
|
||||
$ctype = 'text/plain';
|
||||
}
|
||||
$ctype .= '; charset=utf-8';
|
||||
}
|
||||
return $ctype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the message parts with required headers as an array of strings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function content(): array
|
||||
{
|
||||
$content = [];
|
||||
if ($this->text) {
|
||||
$this->addBodyPart('text', $this->text, $content);
|
||||
}
|
||||
if ($this->html) {
|
||||
$this->addBodyPart('html', $this->html, $content);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a boundary string of the message. If the body has only one part of the content
|
||||
* it returns null
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function boundary()
|
||||
{
|
||||
if (!$this->boundary) {
|
||||
if ($this->text && $this->html) {
|
||||
$this->boundary = '==========' . sha1(uniqid()) . '=====';
|
||||
}
|
||||
}
|
||||
return $this->boundary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified part of the content to the array passed as the third parameter
|
||||
* with the required headers.
|
||||
*
|
||||
* @param string $type Type of the content to add
|
||||
* @param array $part Part of the content to add
|
||||
* @param array $content Where the data with headers should be added
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addBodyPart(string $type, array &$part, array &$content): void
|
||||
{
|
||||
if ($this->boundary()) {
|
||||
$content[] = '--' . $this->boundary();
|
||||
switch ($type) {
|
||||
case 'text':
|
||||
$ctype = 'text/plain';
|
||||
break;
|
||||
case 'html':
|
||||
$ctype = 'text/html';
|
||||
break;
|
||||
}
|
||||
$content[] = 'Content-Type: ' . $ctype . '; charset=utf-8';
|
||||
$content[] = 'Content-Transfer-Encoding: 7bit';
|
||||
$content[] = '';
|
||||
}
|
||||
foreach ($part as $row) {
|
||||
$content[] = $row;
|
||||
}
|
||||
unset($part);
|
||||
}
|
||||
}
|
468
root/opt/dmarc-srg/classes/Mail/MailBox.php
Normal file
468
root/opt/dmarc-srg/classes/Mail/MailBox.php
Normal file
@@ -0,0 +1,468 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Mail;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\ErrorHandler;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
use Liuch\DmarcSrg\Exception\MailboxException;
|
||||
|
||||
class MailBox
|
||||
{
|
||||
private $conn;
|
||||
private $server;
|
||||
private $host;
|
||||
private $mbox;
|
||||
private $name;
|
||||
private $uname;
|
||||
private $passw;
|
||||
private $delim;
|
||||
private $attrs;
|
||||
private $expunge;
|
||||
private $options;
|
||||
|
||||
public function __construct($params)
|
||||
{
|
||||
if (!is_array($params)) {
|
||||
throw new LogicException('Incorrect mailbox params');
|
||||
}
|
||||
|
||||
$this->conn = null;
|
||||
$this->uname = $params['username'];
|
||||
$this->passw = $params['password'];
|
||||
if (isset($params['name']) && is_string($params['name']) && strlen($params['name']) > 0) {
|
||||
$this->name = $params['name'];
|
||||
} else {
|
||||
$name = $this->uname;
|
||||
$pos = strpos($name, '@');
|
||||
if ($pos !== false && $pos !== 0) {
|
||||
$name = substr($name, 0, $pos);
|
||||
}
|
||||
$this->name = $name;
|
||||
}
|
||||
$this->mbox = $params['mailbox'];
|
||||
$this->host = $params['host'];
|
||||
|
||||
$flags = $params['encryption'] ?? '';
|
||||
switch ($flags) {
|
||||
case 'ssl':
|
||||
default:
|
||||
$flags = '/ssl';
|
||||
break;
|
||||
case 'none':
|
||||
$flags = '/notls';
|
||||
break;
|
||||
case 'starttls':
|
||||
$flags = '/tls';
|
||||
break;
|
||||
}
|
||||
if (isset($params['novalidate-cert']) && $params['novalidate-cert'] === true) {
|
||||
$flags .= '/novalidate-cert';
|
||||
}
|
||||
|
||||
$this->server = sprintf('{%s/imap%s}', $this->host, $flags);
|
||||
$this->expunge = false;
|
||||
$this->options = [];
|
||||
|
||||
if (isset($params['auth_exclude'])) {
|
||||
$auth_exclude = $params['auth_exclude'];
|
||||
switch (gettype($auth_exclude)) {
|
||||
case 'string':
|
||||
$auth_exclude = [ $auth_exclude ];
|
||||
break;
|
||||
case 'array':
|
||||
break;
|
||||
default:
|
||||
$auth_exclude = null;
|
||||
break;
|
||||
}
|
||||
if ($auth_exclude) {
|
||||
$this->options['DISABLE_AUTHENTICATOR'] = $auth_exclude;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (extension_loaded('imap')) {
|
||||
$this->cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
public function childMailbox(string $mailbox_name)
|
||||
{
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$mb_list = imap_list(
|
||||
$this->conn,
|
||||
self::utf8ToMutf7($this->server),
|
||||
self::utf8ToMutf7($this->mbox) . $this->delim . self::utf8ToMutf7($mailbox_name)
|
||||
);
|
||||
} catch (\ErrorException $e) {
|
||||
$mb_list = false;
|
||||
}
|
||||
$this->ensureErrorLog('imap_list');
|
||||
if (!$mb_list) {
|
||||
return null;
|
||||
}
|
||||
$child = clone $this;
|
||||
$child->mbox .= $this->delim . $mailbox_name;
|
||||
$child->conn = null;
|
||||
$child->expunge = false;
|
||||
return $child;
|
||||
}
|
||||
|
||||
public function name()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function host()
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
public function mailbox()
|
||||
{
|
||||
return $this->mbox;
|
||||
}
|
||||
|
||||
public function check()
|
||||
{
|
||||
try {
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$res = imap_status(
|
||||
$this->conn,
|
||||
self::utf8ToMutf7($this->server . $this->mbox),
|
||||
SA_MESSAGES | SA_UNSEEN
|
||||
);
|
||||
} catch (\ErrorException $e) {
|
||||
$res = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog();
|
||||
if (!$res) {
|
||||
throw new MailboxException($error_message ?? 'Failed to get the mail box status');
|
||||
}
|
||||
|
||||
if ($this->attrs & \LATT_NOSELECT) {
|
||||
throw new MailboxException('The resource is not a mailbox');
|
||||
}
|
||||
|
||||
$this->checkRights();
|
||||
} catch (MailboxException $e) {
|
||||
return ErrorHandler::exceptionResult($e);
|
||||
}
|
||||
return [
|
||||
'error_code' => 0,
|
||||
'message' => 'Successfully',
|
||||
'status' => [
|
||||
'messages' => $res->messages,
|
||||
'unseen' => $res->unseen
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function search($criteria)
|
||||
{
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$res = imap_search($this->conn, $criteria);
|
||||
} catch (\ErrorException $e) {
|
||||
$res = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog('imap_search');
|
||||
if ($res === false) {
|
||||
if (!$error_message) {
|
||||
return [];
|
||||
}
|
||||
throw new MailboxException(
|
||||
'Failed to search email messages',
|
||||
-1,
|
||||
new \ErrorException($error_message)
|
||||
);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function sort($criteria, $search_criteria, $reverse)
|
||||
{
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$res = imap_sort($this->conn, $criteria, $reverse ? 1 : 0, SE_NOPREFETCH, $search_criteria);
|
||||
} catch (\ErrorException $e) {
|
||||
$res = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog('imap_sort');
|
||||
if ($res === false) {
|
||||
if (!$error_message) {
|
||||
return [];
|
||||
}
|
||||
throw new MailboxException(
|
||||
'Failed to sort email messages',
|
||||
-1,
|
||||
new \ErrorException($error_message)
|
||||
);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function message($number)
|
||||
{
|
||||
return new MailMessage($this->conn, $number);
|
||||
}
|
||||
|
||||
public function ensureMailbox($mailbox_name)
|
||||
{
|
||||
$mbn = self::utf8ToMutf7($mailbox_name);
|
||||
$srv = self::utf8ToMutf7($this->server);
|
||||
$mbo = self::utf8ToMutf7($this->mbox);
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
$mb_list = imap_list($this->conn, $srv, $mbo . $this->delim . $mbn);
|
||||
} catch (\ErrorException $e) {
|
||||
$mb_list = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog('imap_list');
|
||||
if (empty($mb_list)) {
|
||||
if ($error_message) {
|
||||
throw new MailboxException(
|
||||
'Failed to get the list of mailboxes',
|
||||
-1,
|
||||
new \ErrorException($error_message)
|
||||
);
|
||||
}
|
||||
|
||||
$new_mailbox = "{$srv}{$mbo}{$this->delim}{$mbn}";
|
||||
try {
|
||||
$res = imap_createmailbox($this->conn, $new_mailbox);
|
||||
} catch (\ErrorException $e) {
|
||||
$res = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog('imap_createmailbox');
|
||||
if (!$res) {
|
||||
throw new MailboxException(
|
||||
'Failed to create a new mailbox',
|
||||
-1,
|
||||
new \ErrorException($error_message ?? 'Unknown')
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
imap_subscribe($this->conn, $new_mailbox);
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
$this->ensureErrorLog('imap_subscribe');
|
||||
}
|
||||
}
|
||||
|
||||
public function moveMessage($number, $mailbox_name)
|
||||
{
|
||||
$this->ensureConnection();
|
||||
$target = self::utf8ToMutf7($this->mbox) . $this->delim . self::utf8ToMutf7($mailbox_name);
|
||||
try {
|
||||
$res = imap_mail_move($this->conn, strval($number), $target);
|
||||
} catch (\ErrorException $e) {
|
||||
$res = false;
|
||||
}
|
||||
$error_message = $this->ensureErrorLog('imap_mail_move');
|
||||
if (!$res) {
|
||||
throw new MailboxException(
|
||||
'Failed to move a message',
|
||||
-1,
|
||||
new \ErrorException($error_message ?? 'Unknown')
|
||||
);
|
||||
}
|
||||
$this->expunge = true;
|
||||
}
|
||||
|
||||
public function deleteMessage($number)
|
||||
{
|
||||
$this->ensureConnection();
|
||||
try {
|
||||
imap_delete($this->conn, strval($number));
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
$this->ensureErrorLog('imap_delete');
|
||||
$this->expunge = true;
|
||||
}
|
||||
|
||||
public static function resetErrorStack()
|
||||
{
|
||||
imap_errors();
|
||||
imap_alerts();
|
||||
}
|
||||
|
||||
private function ensureConnection()
|
||||
{
|
||||
if (is_null($this->conn)) {
|
||||
$error_message = null;
|
||||
$srv = self::utf8ToMutf7($this->server);
|
||||
try {
|
||||
$this->conn = imap_open(
|
||||
$srv,
|
||||
$this->uname,
|
||||
$this->passw,
|
||||
OP_HALFOPEN,
|
||||
0,
|
||||
$this->options
|
||||
);
|
||||
} catch (\ErrorException $e) {
|
||||
$this->conn = null;
|
||||
}
|
||||
if ($this->conn) {
|
||||
$mbx = self::utf8ToMutf7($this->mbox);
|
||||
try {
|
||||
$mb_list = imap_getmailboxes($this->conn, $srv, $mbx);
|
||||
} catch (\ErrorException $e) {
|
||||
$mb_list = null;
|
||||
}
|
||||
if ($mb_list && count($mb_list) === 1) {
|
||||
$this->delim = $mb_list[0]->delimiter ?? '/';
|
||||
$this->attrs = $mb_list[0]->attributes ?? 0;
|
||||
try {
|
||||
if (imap_reopen($this->conn, $srv . $mbx)) {
|
||||
return;
|
||||
}
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
} else {
|
||||
$error_message = "Mailbox `{$this->mbox}` not found";
|
||||
}
|
||||
}
|
||||
if (!$error_message) {
|
||||
$error_message = imap_last_error();
|
||||
if (!$error_message) {
|
||||
$error_message = 'Cannot connect to the mail server';
|
||||
}
|
||||
}
|
||||
Core::instance()->logger()->error("IMAP error: {$error_message}");
|
||||
self::resetErrorStack();
|
||||
if ($this->conn) {
|
||||
try {
|
||||
imap_close($this->conn);
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
$this->ensureErrorLog('imap_close');
|
||||
}
|
||||
$this->conn = null;
|
||||
throw new MailboxException($error_message);
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureErrorLog(string $prefix = 'IMAP error')
|
||||
{
|
||||
if ($error_message = imap_last_error()) {
|
||||
self::resetErrorStack();
|
||||
$error_message = "{$prefix}: {$error_message}";
|
||||
Core::instance()->logger()->error($error_message);
|
||||
return $error_message;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function checkRights(): void
|
||||
{
|
||||
if ($this->attrs & \LATT_NOINFERIORS) {
|
||||
throw new SoftException('The mailbox may not have any children mailboxes');
|
||||
}
|
||||
|
||||
if (!function_exists('imap_getacl')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mbox = self::utf8ToMutf7($this->mbox);
|
||||
try {
|
||||
$acls = imap_getacl($this->conn, $mbox);
|
||||
} catch (\ErrorException $e) {
|
||||
// It's not possible to get the ACLs information
|
||||
$acls = false;
|
||||
}
|
||||
$this->ensureErrorLog('imap_getacl');
|
||||
if ($acls !== false) {
|
||||
$needed_rights_map = [
|
||||
'l' => 'LOOKUP',
|
||||
'r' => 'READ',
|
||||
's' => 'WRITE-SEEN',
|
||||
't' => 'WRITE-DELETE',
|
||||
'k' => 'CREATE'
|
||||
];
|
||||
$result = [];
|
||||
$needed_rights = array_keys($needed_rights_map);
|
||||
foreach ([ "#{$this->uname}", '#authenticated', '#anyone' ] as $identifier) {
|
||||
if (isset($acls[$identifier])) {
|
||||
$rights = $acls[$identifier];
|
||||
foreach ($needed_rights as $r) {
|
||||
if (!str_contains($rights, $r)) {
|
||||
$result[] = $needed_rights_map[$r];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (count($result) > 0) {
|
||||
throw new SoftException(
|
||||
'Not enough rights. Additionally, these rights are required: ' . implode(', ', $result)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes messages marked for deletion, if any, and closes the connection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cleanup(): void
|
||||
{
|
||||
self::resetErrorStack();
|
||||
if (!is_null($this->conn)) {
|
||||
try {
|
||||
if ($this->expunge) {
|
||||
imap_expunge($this->conn);
|
||||
}
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
$this->ensureErrorLog('imap_expunge');
|
||||
|
||||
try {
|
||||
imap_close($this->conn);
|
||||
} catch (\ErrorException $e) {
|
||||
}
|
||||
$this->ensureErrorLog('imap_close');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a replacement for the standard function imap_utf8_to_mutf7
|
||||
*
|
||||
* @param string $s A UTF-8 encoded string
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
private static function utf8ToMutf7(string $s)
|
||||
{
|
||||
return mb_convert_encoding($s, 'UTF7-IMAP', 'UTF-8');
|
||||
}
|
||||
}
|
137
root/opt/dmarc-srg/classes/Mail/MailBoxes.php
Normal file
137
root/opt/dmarc-srg/classes/Mail/MailBoxes.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Mail;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\ErrorHandler;
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
use Liuch\DmarcSrg\Exception\RuntimeException;
|
||||
use Liuch\DmarcSrg\Exception\MailboxException;
|
||||
|
||||
class MailBoxes implements \Iterator
|
||||
{
|
||||
private $box_list;
|
||||
private $index = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$mailboxes = Core::instance()->config('mailboxes');
|
||||
|
||||
$this->box_list = [];
|
||||
if (is_array($mailboxes)) {
|
||||
$cnt = count($mailboxes);
|
||||
if ($cnt > 0) {
|
||||
if (isset($mailboxes[0])) {
|
||||
for ($i = 0; $i < $cnt; ++$i) {
|
||||
$this->box_list[] = new MailBox($mailboxes[$i]);
|
||||
}
|
||||
} else {
|
||||
$this->box_list[] = new MailBox($mailboxes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return count($this->box_list);
|
||||
}
|
||||
|
||||
public function list()
|
||||
{
|
||||
$id = 0;
|
||||
$res = [];
|
||||
foreach ($this->box_list as &$mbox) {
|
||||
$id += 1;
|
||||
$res[] = [
|
||||
'id' => $id,
|
||||
'name' => $mbox->name(),
|
||||
'host' => $mbox->host(),
|
||||
'mailbox' => $mbox->mailbox()
|
||||
];
|
||||
}
|
||||
unset($mbox);
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function mailbox($id)
|
||||
{
|
||||
if (!is_int($id) || $id <= 0 || $id > count($this->box_list)) {
|
||||
throw new LogicException("Incorrect mailbox Id: {$i}");
|
||||
}
|
||||
return $this->box_list[$id - 1];
|
||||
}
|
||||
|
||||
public function check($id)
|
||||
{
|
||||
if ($id !== 0) {
|
||||
return $this->mailbox($id)->check();
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$err_cnt = 0;
|
||||
$box_cnt = count($this->box_list);
|
||||
for ($i = 0; $i < $box_cnt; ++$i) {
|
||||
$r = $this->box_list[$i]->check();
|
||||
if ($r['error_code'] !== 0) {
|
||||
++$err_cnt;
|
||||
}
|
||||
$results[] = $r;
|
||||
}
|
||||
$res = [];
|
||||
if ($err_cnt == 0) {
|
||||
$res['error_code'] = 0;
|
||||
$res['message'] = 'Success';
|
||||
} else {
|
||||
$res['error_code'] = -1;
|
||||
$res['message'] = sprintf('%d of the %d mailboxes failed the check', $err_cnt, $box_cnt);
|
||||
}
|
||||
$res['results'] = $results;
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function current(): object
|
||||
{
|
||||
return $this->box_list[$this->index];
|
||||
}
|
||||
|
||||
public function key(): int
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
++$this->index;
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->index = 0;
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return isset($this->box_list[$this->index]);
|
||||
}
|
||||
}
|
154
root/opt/dmarc-srg/classes/Mail/MailMessage.php
Normal file
154
root/opt/dmarc-srg/classes/Mail/MailMessage.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Mail;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\MailboxException;
|
||||
|
||||
class MailMessage
|
||||
{
|
||||
private $conn;
|
||||
private $number;
|
||||
private $attachment;
|
||||
private $attachments_cnt;
|
||||
|
||||
public function __construct($conn, $number)
|
||||
{
|
||||
$this->conn = $conn;
|
||||
$this->number = $number;
|
||||
$this->attachment = null;
|
||||
$this->attachments_cnt = -1;
|
||||
}
|
||||
|
||||
public function overview()
|
||||
{
|
||||
$res = @imap_fetch_overview($this->conn, strval($this->number));
|
||||
if (!isset($res[0])) {
|
||||
if ($error_message = imap_last_error()) {
|
||||
Core::instance()->logger()->error("imap_fetch_overview failed: {$error_message}");
|
||||
}
|
||||
MailBox::resetErrorStack();
|
||||
return false;
|
||||
}
|
||||
return $res[0];
|
||||
}
|
||||
|
||||
public function setSeen()
|
||||
{
|
||||
if (!@imap_setflag_full($this->conn, strval($this->number), '\\Seen')) {
|
||||
if ($error_message = imap_last_error()) {
|
||||
$error_message = '?';
|
||||
}
|
||||
MailBox::resetErrorStack();
|
||||
Core::instance()->logger()->error("imap_setflag_full failed: {$error_message}");
|
||||
throw new MailboxException("Failed to make a message seen: {$error_message}");
|
||||
}
|
||||
}
|
||||
|
||||
public function validate()
|
||||
{
|
||||
$this->ensureAttachment();
|
||||
if ($this->attachments_cnt !== 1) {
|
||||
throw new SoftException("Attachment count is not valid ({$this->attachments_cnt})");
|
||||
}
|
||||
|
||||
$bytes = $this->attachment->size();
|
||||
if ($bytes === -1) {
|
||||
throw new SoftException("Failed to get attached file size. Wrong message format?");
|
||||
}
|
||||
if ($bytes < 50 || $bytes > 1 * 1024 * 1024) {
|
||||
throw new SoftException("Attachment file size is not valid ({$bytes} bytes)");
|
||||
}
|
||||
|
||||
$mime_type = $this->attachment->mimeType();
|
||||
if (!in_array($mime_type, [ 'application/zip', 'application/gzip', 'application/x-gzip', 'text/xml' ])) {
|
||||
throw new SoftException("Attachment file type is not valid ({$mime_type})");
|
||||
}
|
||||
}
|
||||
|
||||
public function attachment()
|
||||
{
|
||||
return $this->attachment;
|
||||
}
|
||||
|
||||
private function ensureAttachment()
|
||||
{
|
||||
if ($this->attachments_cnt === -1) {
|
||||
$structure = imap_fetchstructure($this->conn, $this->number);
|
||||
if ($structure === false) {
|
||||
throw new MailboxException('FetchStructure failed: ' . imap_last_error());
|
||||
}
|
||||
$this->attachments_cnt = 0;
|
||||
$parts = isset($structure->parts) ? $structure->parts : [ $structure ];
|
||||
foreach ($parts as $index => &$part) {
|
||||
$att_part = $this->scanAttachmentPart($part, $index + 1);
|
||||
if ($att_part) {
|
||||
++$this->attachments_cnt;
|
||||
if (!$this->attachment) {
|
||||
$this->attachment = new MailAttachment($this->conn, $att_part);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($part);
|
||||
}
|
||||
}
|
||||
|
||||
private function scanAttachmentPart(&$part, $number)
|
||||
{
|
||||
$filename = null;
|
||||
if ($part->ifdparameters) {
|
||||
$filename = $this->getAttribute($part->dparameters, 'filename');
|
||||
}
|
||||
|
||||
if (empty($filename) && $part->ifparameters) {
|
||||
$filename = $this->getAttribute($part->parameters, 'name');
|
||||
}
|
||||
|
||||
if (empty($filename)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'filename' => imap_utf8($filename),
|
||||
'bytes' => isset($part->bytes) ? $part->bytes : -1,
|
||||
'number' => $number,
|
||||
'mnumber' => $this->number,
|
||||
'encoding' => $part->encoding
|
||||
];
|
||||
}
|
||||
|
||||
private function getAttribute(&$params, $name)
|
||||
{
|
||||
// need to check all objects as imap_fetchstructure
|
||||
// returns multiple objects with the same attribute name,
|
||||
// but first entry contains a truncated value
|
||||
$value = null;
|
||||
foreach ($params as &$obj) {
|
||||
if (strcasecmp($obj->attribute, $name) === 0) {
|
||||
$value = $obj->value;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
}
|
165
root/opt/dmarc-srg/classes/Report/Report.php
Normal file
165
root/opt/dmarc-srg/classes/Report/Report.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Report;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
|
||||
|
||||
class Report
|
||||
{
|
||||
private $db = null;
|
||||
private $data = null;
|
||||
|
||||
public function __construct($data, $db = null)
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->db = $db ?? Core::instance()->database();
|
||||
}
|
||||
|
||||
public static function fromXmlFile($fd)
|
||||
{
|
||||
$data = ReportData::fromXmlFile($fd);
|
||||
if (!self::checkData($data)) {
|
||||
throw new SoftException('Incorrect or incomplete report data');
|
||||
}
|
||||
return new Report($data);
|
||||
}
|
||||
|
||||
public function fetch()
|
||||
{
|
||||
$domain = $this->data['domain'];
|
||||
$report_id = $this->data['report_id'];
|
||||
if (empty($domain) || empty($report_id)) {
|
||||
throw new SoftException('Not specified report\'s domain or id');
|
||||
}
|
||||
$this->data = [ 'domain' => $domain, 'report_id' => $report_id ];
|
||||
try {
|
||||
$this->db->getMapper('report')->fetch($this->data);
|
||||
} catch (DatabaseNotFoundException $e) {
|
||||
throw new SoftException('The report is not found');
|
||||
}
|
||||
}
|
||||
|
||||
public function save(string $real_fname)
|
||||
{
|
||||
$b_ts = $this->data['begin_time'];
|
||||
$e_ts = $this->data['end_time'];
|
||||
if (!$b_ts->getTimestamp() || !$e_ts->getTimestamp() || $b_ts > $e_ts) {
|
||||
throw new SoftException('Failed to add an incoming report: wrong date range');
|
||||
}
|
||||
|
||||
$this->db->getMapper('report')->save($this->data);
|
||||
return [ 'message' => 'The report is loaded successfully' ];
|
||||
}
|
||||
|
||||
public function get()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function set($name, $value)
|
||||
{
|
||||
if (empty($this->data['domain']) || empty($this->data['report_id'])) {
|
||||
throw new SoftException('Not specified report\'s domain or id');
|
||||
}
|
||||
|
||||
$this->db->getMapper('report')->setProperty($this->data, $name, $value);
|
||||
return [ 'message' => 'Ok' ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks report data for correctness and completeness
|
||||
*
|
||||
* @param array $data Report data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function checkData(array $data): bool
|
||||
{
|
||||
static $fields = [
|
||||
'domain' => [ 'required' => true, 'type' => 'string' ],
|
||||
'begin_time' => [ 'required' => true, 'type' => 'object' ],
|
||||
'end_time' => [ 'required' => true, 'type' => 'object' ],
|
||||
'org' => [ 'required' => true, 'type' => 'string' ],
|
||||
'external_id' => [ 'required' => true, 'type' => 'string' ],
|
||||
'email' => [ 'required' => false, 'type' => 'string' ],
|
||||
'extra_contact_info' => [ 'required' => false, 'type' => 'string' ],
|
||||
'error_string' => [ 'required' => false, 'type' => 'array' ],
|
||||
'policy_adkim' => [ 'required' => false, 'type' => 'string' ],
|
||||
'policy_aspf' => [ 'required' => false, 'type' => 'string' ],
|
||||
'policy_p' => [ 'required' => false, 'type' => 'string' ],
|
||||
'policy_sp' => [ 'required' => false, 'type' => 'string' ],
|
||||
'policy_np' => [ 'required' => false, 'type' => 'string' ],
|
||||
'policy_pct' => [ 'required' => false, 'type' => 'string' ],
|
||||
'policy_fo' => [ 'required' => false, 'type' => 'string' ],
|
||||
'records' => [ 'required' => true, 'type' => 'array' ]
|
||||
];
|
||||
if (!self::checkRow($data, $fields) || count($data['records']) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static $rfields = [
|
||||
'ip' => [ 'required' => true, 'type' => 'string' ],
|
||||
'rcount' => [ 'required' => true, 'type' => 'integer' ],
|
||||
'disposition' => [ 'required' => true, 'type' => 'string' ],
|
||||
'reason' => [ 'required' => false, 'type' => 'array' ],
|
||||
'dkim_auth' => [ 'required' => false, 'type' => 'array' ],
|
||||
'spf_auth' => [ 'required' => false, 'type' => 'array' ],
|
||||
'dkim_align' => [ 'required' => true, 'type' => 'string' ],
|
||||
'spf_align' => [ 'required' => true, 'type' => 'string' ],
|
||||
'envelope_to' => [ 'required' => false, 'type' => 'string' ],
|
||||
'envelope_from' => [ 'required' => false, 'type' => 'string' ],
|
||||
'header_from' => [ 'required' => false, 'type' => 'string' ]
|
||||
];
|
||||
foreach ($data['records'] as &$rec) {
|
||||
if (gettype($rec) !== 'array' || !self::checkRow($rec, $rfields)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks one row of report data
|
||||
*
|
||||
* @param array $row Data row
|
||||
* @param array $def Row definition
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function checkRow(array &$row, array &$def): bool
|
||||
{
|
||||
foreach ($def as $key => &$dd) {
|
||||
if (isset($row[$key])) {
|
||||
if (gettype($row[$key]) !== $dd['type']) {
|
||||
return false;
|
||||
}
|
||||
} elseif ($dd['required']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
370
root/opt/dmarc-srg/classes/Report/ReportData.php
Normal file
370
root/opt/dmarc-srg/classes/Report/ReportData.php
Normal file
@@ -0,0 +1,370 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Report;
|
||||
|
||||
use Liuch\DmarcSrg\DateTime;
|
||||
use Liuch\DmarcSrg\Exception\RuntimeException;
|
||||
|
||||
class ReportData
|
||||
{
|
||||
public static $rep_data = null;
|
||||
public static $tag_id = null;
|
||||
|
||||
public static function fromXmlFile($fd)
|
||||
{
|
||||
self::$tag_id = '<root>';
|
||||
self::$rep_data = [ 'records' => [] ];
|
||||
|
||||
$parser = xml_parser_create();
|
||||
xml_set_element_handler(
|
||||
$parser,
|
||||
'Liuch\DmarcSrg\Report\ReportData::xmlStartTag',
|
||||
'Liuch\DmarcSrg\Report\ReportData::xmlEndTag'
|
||||
);
|
||||
xml_set_character_data_handler($parser, 'Liuch\DmarcSrg\Report\ReportData::xmlTagData');
|
||||
xml_set_external_entity_ref_handler($parser, function () {
|
||||
throw new RuntimeException('The XML document has an external entity!');
|
||||
});
|
||||
try {
|
||||
while ($file_data = fread($fd, 4096)) {
|
||||
if (!xml_parse($parser, $file_data, feof($fd))) {
|
||||
throw new RuntimeException('XML error!');
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
xml_parser_free($parser);
|
||||
unset($parser);
|
||||
}
|
||||
return self::$rep_data;
|
||||
}
|
||||
|
||||
public static function xmlStartTag($parser, $name, $attrs)
|
||||
{
|
||||
self::xmlEnterTag($name);
|
||||
|
||||
switch (self::$tag_id) {
|
||||
case 'rec':
|
||||
self::$rep_data['records'][] = [];
|
||||
break;
|
||||
case 'error_string':
|
||||
if (!isset(self::$rep_data['error_string'])) {
|
||||
self::$rep_data['error_string'] = [];
|
||||
}
|
||||
break;
|
||||
case 'reason':
|
||||
$idx = count(self::$rep_data['records']) - 1;
|
||||
if (!isset(self::$rep_data['records'][$idx]['reason'])) {
|
||||
self::$rep_data['records'][$idx]['reason'] = [];
|
||||
}
|
||||
self::$report_tags['reason']['tmp_data'] = [];
|
||||
break;
|
||||
case 'dkim_auth':
|
||||
$idx = count(self::$rep_data['records']) - 1;
|
||||
if (!isset(self::$rep_data['records'][$idx]['dkim_auth'])) {
|
||||
self::$rep_data['records'][$idx]['dkim_auth'] = [];
|
||||
}
|
||||
self::$report_tags['dkim_auth']['tmp_data'] = [];
|
||||
break;
|
||||
case 'spf_auth':
|
||||
$idx = count(self::$rep_data['records']) - 1;
|
||||
if (!isset(self::$rep_data['records'][$idx]['spf_auth'])) {
|
||||
self::$rep_data['records'][$idx]['spf_auth'] = [];
|
||||
}
|
||||
self::$report_tags['spf_auth']['tmp_data'] = [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static function xmlEndTag($parser, $name)
|
||||
{
|
||||
switch (self::$tag_id) {
|
||||
case 'reason':
|
||||
$idx = count(self::$rep_data['records']) - 1;
|
||||
self::$rep_data['records'][$idx]['reason'][] = self::$report_tags['reason']['tmp_data'];
|
||||
unset(self::$report_tags['reason']['tmp_data']);
|
||||
break;
|
||||
case 'dkim_auth':
|
||||
$idx = count(self::$rep_data['records']) - 1;
|
||||
self::$rep_data['records'][$idx]['dkim_auth'][] = self::$report_tags['dkim_auth']['tmp_data'];
|
||||
unset(self::$report_tags['dkim_auth']['tmp_data']);
|
||||
break;
|
||||
case 'spf_auth':
|
||||
$idx = count(self::$rep_data['records']) - 1;
|
||||
self::$rep_data['records'][$idx]['spf_auth'][] = self::$report_tags['spf_auth']['tmp_data'];
|
||||
unset(self::$report_tags['spf_auth']['tmp_data']);
|
||||
break;
|
||||
case 'feedback':
|
||||
// Set the default value if it's necessary and there is no data
|
||||
foreach (self::$report_tags as $tag_id => &$tag_data) {
|
||||
if (array_key_exists('default', $tag_data)) { // not isset() because of null values
|
||||
if (isset($tag_data['header']) && $tag_data['header']) {
|
||||
if (!isset(self::$rep_data[$tag_id])) {
|
||||
self::$rep_data[$tag_id] = $tag_data['default'];
|
||||
}
|
||||
} else {
|
||||
foreach (self::$rep_data['records'] as $idx => &$rec_val) {
|
||||
if (!isset($rec_val[$tag_id])) {
|
||||
$rec_val[$tag_id] = $tag_data['default'];
|
||||
}
|
||||
}
|
||||
unset($rec_val);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($tag_data);
|
||||
$b_ts = intval(self::$rep_data['begin_time']);
|
||||
$e_ts = intval(self::$rep_data['end_time']);
|
||||
self::$rep_data['begin_time'] = new DateTime('@' . ($b_ts < 0 ? 0 : $b_ts));
|
||||
self::$rep_data['end_time'] = new DateTime('@' . ($e_ts < 0 ? 0 : $e_ts));
|
||||
foreach (self::$rep_data['records'] as &$rec_data) {
|
||||
$rec_data['rcount'] = intval($rec_data['rcount']);
|
||||
}
|
||||
unset($rec_data);
|
||||
break;
|
||||
}
|
||||
self::xmlLeaveTag();
|
||||
}
|
||||
|
||||
public static function xmlTagData($parser, $data)
|
||||
{
|
||||
switch (self::$tag_id) {
|
||||
case 'error_string':
|
||||
if (self::$tag_id === 'error_string') {
|
||||
self::$rep_data['error_string'][] = $data;
|
||||
}
|
||||
break;
|
||||
case 'reason_type':
|
||||
self::$report_tags['reason']['tmp_data']['type'] = $data;
|
||||
break;
|
||||
case 'reason_comment':
|
||||
self::$report_tags['reason']['tmp_data']['comment'] = $data;
|
||||
break;
|
||||
case 'dkim_domain':
|
||||
self::$report_tags['dkim_auth']['tmp_data']['domain'] = $data;
|
||||
break;
|
||||
case 'dkim_selector':
|
||||
self::$report_tags['dkim_auth']['tmp_data']['selector'] = $data;
|
||||
break;
|
||||
case 'dkim_result':
|
||||
self::$report_tags['dkim_auth']['tmp_data']['result'] = $data;
|
||||
break;
|
||||
case 'dkim_human_result':
|
||||
self::$report_tags['dkim_auth']['tmp_data']['human_result'] = $data;
|
||||
break;
|
||||
case 'spf_domain':
|
||||
self::$report_tags['spf_auth']['tmp_data']['domain'] = $data;
|
||||
break;
|
||||
case 'spf_scope':
|
||||
self::$report_tags['spf_auth']['tmp_data']['scope'] = $data;
|
||||
break;
|
||||
case 'spf_result':
|
||||
self::$report_tags['spf_auth']['tmp_data']['result'] = $data;
|
||||
break;
|
||||
default:
|
||||
if (!isset(self::$report_tags[self::$tag_id]['children'])) {
|
||||
if (isset(self::$report_tags[self::$tag_id]['header']) &&
|
||||
self::$report_tags[self::$tag_id]['header']
|
||||
) {
|
||||
if (!isset(self::$rep_data[self::$tag_id])) {
|
||||
self::$rep_data[self::$tag_id] = $data;
|
||||
} else {
|
||||
self::$rep_data[self::$tag_id] .= $data;
|
||||
}
|
||||
} else {
|
||||
$last_idx = count(self::$rep_data['records']) - 1;
|
||||
$last_rec =& self::$rep_data['records'][$last_idx];
|
||||
if (!isset($last_rec[self::$tag_id])) {
|
||||
$last_rec[self::$tag_id] = $data;
|
||||
} else {
|
||||
$last_rec[self::$tag_id] .= $data;
|
||||
}
|
||||
unset($last_rec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function xmlEnterTag($name)
|
||||
{
|
||||
if (!isset(self::$report_tags[self::$tag_id]['children']) ||
|
||||
!isset(self::$report_tags[self::$tag_id]['children'][$name])
|
||||
) {
|
||||
throw new RuntimeException("Unknown tag: {$name}");
|
||||
}
|
||||
|
||||
self::$tag_id = self::$report_tags[self::$tag_id]['children'][$name];
|
||||
}
|
||||
|
||||
public static function xmlLeaveTag()
|
||||
{
|
||||
self::$tag_id = self::$report_tags[self::$tag_id]['parent'];
|
||||
}
|
||||
|
||||
public static $report_tags = [
|
||||
'<root>' => [
|
||||
'children' => [
|
||||
'FEEDBACK' => 'feedback'
|
||||
]
|
||||
],
|
||||
'feedback' => [
|
||||
'parent' => '<root>',
|
||||
'children' => [
|
||||
'VERSION' => 'ver',
|
||||
'REPORT_METADATA' => 'rmd',
|
||||
'POLICY_PUBLISHED' => 'p_p',
|
||||
'RECORD' => 'rec'
|
||||
]
|
||||
],
|
||||
'ver' => [ 'parent' => 'feedback', 'header' => true, 'default' => null ],
|
||||
'rmd' => [
|
||||
'parent' => 'feedback',
|
||||
'children' => [
|
||||
'ORG_NAME' => 'org',
|
||||
'EMAIL' => 'email',
|
||||
'EXTRA_CONTACT_INFO' => 'extra_contact_info',
|
||||
'REPORT_ID' => 'external_id',
|
||||
'DATE_RANGE' => 'd_range',
|
||||
'ERROR' => 'error_string'
|
||||
]
|
||||
],
|
||||
'p_p' => [
|
||||
'parent' => 'feedback',
|
||||
'children' => [
|
||||
'DOMAIN' => 'domain',
|
||||
'ADKIM' => 'policy_adkim',
|
||||
'ASPF' => 'policy_aspf',
|
||||
'P' => 'policy_p',
|
||||
'SP' => 'policy_sp',
|
||||
'NP' => 'policy_np',
|
||||
'PCT' => 'policy_pct',
|
||||
'FO' => 'policy_fo'
|
||||
]
|
||||
],
|
||||
'rec' => [
|
||||
'parent' => 'feedback',
|
||||
'children' => [
|
||||
'ROW' => 'row',
|
||||
'IDENTIFIERS' => 'ident',
|
||||
'AUTH_RESULTS' => 'au_res'
|
||||
]
|
||||
],
|
||||
'org' => [ 'parent' => 'rmd', 'header' => true ],
|
||||
'email' => [ 'parent' => 'rmd', 'header' => true, 'default' => null ],
|
||||
'extra_contact_info' => [ 'parent' => 'rmd', 'header' => true, 'default' => null ],
|
||||
'external_id' => [ 'parent' => 'rmd', 'header' => true ],
|
||||
'd_range' => [
|
||||
'parent' => 'rmd',
|
||||
'children' => [
|
||||
'BEGIN' => 'begin_time',
|
||||
'END' => 'end_time'
|
||||
]
|
||||
],
|
||||
'error_string' => [ 'parent' => 'rmd', 'header' => true, 'default' => null ],
|
||||
'begin_time' => [ 'parent' => 'd_range', 'header' => true ],
|
||||
'end_time' => [ 'parent' => 'd_range', 'header' => true ],
|
||||
'domain' => [ 'parent' => 'p_p', 'header' => true ],
|
||||
'policy_adkim' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ],
|
||||
'policy_aspf' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ],
|
||||
'policy_p' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ],
|
||||
'policy_sp' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ],
|
||||
'policy_np' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ],
|
||||
'policy_pct' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ],
|
||||
'policy_fo' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ],
|
||||
'row' => [
|
||||
'parent' => 'rec',
|
||||
'children' => [
|
||||
'SOURCE_IP' => 'ip',
|
||||
'COUNT' => 'rcount',
|
||||
'POLICY_EVALUATED' => 'p_e'
|
||||
]
|
||||
],
|
||||
'ident' => [
|
||||
'parent' => 'rec',
|
||||
'children' => [
|
||||
'ENVELOPE_TO' => 'envelope_to',
|
||||
'ENVELOPE_FROM' => 'envelope_from',
|
||||
'HEADER_FROM' => 'header_from'
|
||||
]
|
||||
],
|
||||
'au_res' => [
|
||||
'parent' => 'rec',
|
||||
'children' => [
|
||||
'DKIM' => 'dkim_auth',
|
||||
'SPF' => 'spf_auth'
|
||||
]
|
||||
],
|
||||
'ip' => [ 'parent' => 'row' ],
|
||||
'rcount' => [ 'parent' => 'row' ],
|
||||
'p_e' => [
|
||||
'parent' => 'row',
|
||||
'children' => [
|
||||
'DISPOSITION' => 'disposition',
|
||||
'DKIM' => 'dkim_align',
|
||||
'SPF' => 'spf_align',
|
||||
'REASON' => 'reason'
|
||||
]
|
||||
],
|
||||
'disposition' => [ 'parent' => 'p_e' ],
|
||||
'dkim_align' => [ 'parent' => 'p_e' ],
|
||||
'spf_align' => [ 'parent' => 'p_e' ],
|
||||
'reason' => [
|
||||
'parent' => 'p_e',
|
||||
'default' => null,
|
||||
'children' => [
|
||||
'TYPE' => 'reason_type',
|
||||
'COMMENT' => 'reason_comment'
|
||||
]
|
||||
],
|
||||
'envelope_to' => [ 'parent' => 'ident', 'default' => null ],
|
||||
'envelope_from' => [ 'parent' => 'ident', 'default' => null ],
|
||||
'header_from' => [ 'parent' => 'ident', 'default' => null ],
|
||||
'dkim_auth' => [
|
||||
'parent' => 'au_res',
|
||||
'default' => null,
|
||||
'children' => [
|
||||
'DOMAIN' => 'dkim_domain',
|
||||
'SELECTOR' => 'dkim_selector',
|
||||
'RESULT' => 'dkim_result',
|
||||
'HUMAN_RESULT' => 'dkim_human_result'
|
||||
]
|
||||
],
|
||||
'spf_auth' => [
|
||||
'parent' => 'au_res',
|
||||
'default' => null,
|
||||
'children' => [
|
||||
'DOMAIN' => 'spf_domain',
|
||||
'SCOPE' => 'spf_scope',
|
||||
'RESULT' => 'spf_result'
|
||||
]
|
||||
],
|
||||
'reason_type' => [ 'parent' => 'reason' ],
|
||||
'reason_comment' => [ 'parent' => 'reason' ],
|
||||
'dkim_domain' => [ 'parent' => 'dkim_auth' ],
|
||||
'dkim_selector' => [ 'parent' => 'dkim_auth' ],
|
||||
'dkim_result' => [ 'parent' => 'dkim_auth' ],
|
||||
'dkim_human_result' => [ 'parent' => 'dkim_auth' ],
|
||||
'spf_domain' => [ 'parent' => 'spf_auth' ],
|
||||
'spf_scope' => [ 'parent' => 'spf_auth' ],
|
||||
'spf_result' => [ 'parent' => 'spf_auth' ]
|
||||
];
|
||||
}
|
242
root/opt/dmarc-srg/classes/Report/ReportFetcher.php
Normal file
242
root/opt/dmarc-srg/classes/Report/ReportFetcher.php
Normal file
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class ReportFetcher
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Report;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\ErrorHandler;
|
||||
use Liuch\DmarcSrg\Report\Report;
|
||||
use Liuch\DmarcSrg\Sources\Source;
|
||||
use Liuch\DmarcSrg\ReportLog\ReportLogItem;
|
||||
use Liuch\DmarcSrg\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* This class is designed to fetch report files from report sources and store them to the database.
|
||||
*/
|
||||
class ReportFetcher
|
||||
{
|
||||
private $source = null;
|
||||
|
||||
/**
|
||||
* It's the constructor of the class.
|
||||
*
|
||||
* @param Source $sou Source for fetching report files.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($sou)
|
||||
{
|
||||
$this->source = $sou;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves report files from the source and stores them in the database
|
||||
* taking into account the limits from the configuration file.
|
||||
*
|
||||
* @return array Array of results.
|
||||
*/
|
||||
public function fetch(): array
|
||||
{
|
||||
try {
|
||||
$this->source->rewind();
|
||||
} catch (RuntimeException $e) {
|
||||
return [[ 'source_error' => $e->getMessage() ]];
|
||||
}
|
||||
|
||||
$core = Core::instance();
|
||||
$limit = 0;
|
||||
$stype = $this->source->type();
|
||||
switch ($stype) {
|
||||
case Source::SOURCE_MAILBOX:
|
||||
$s_act = $core->config('fetcher/mailboxes/when_done', '');
|
||||
$f_act = $core->config('fetcher/mailboxes/when_failed', '');
|
||||
$limit = $core->config('fetcher/mailboxes/messages_maximum', 0);
|
||||
break;
|
||||
case Source::SOURCE_DIRECTORY:
|
||||
$s_act = $core->config('fetcher/directories/when_done', '');
|
||||
$f_act = $core->config('fetcher/directories/when_failed', '');
|
||||
$limit = $core->config('fetcher/directories/files_maximum', 0);
|
||||
break;
|
||||
}
|
||||
$limit = intval($limit);
|
||||
if ($stype === Source::SOURCE_MAILBOX || $stype === Source::SOURCE_DIRECTORY) {
|
||||
$this->source->setParams([
|
||||
'when_done' => $s_act,
|
||||
'when_failed' => $f_act
|
||||
]);
|
||||
}
|
||||
|
||||
$results = [];
|
||||
while ($this->source->valid()) {
|
||||
$result = null;
|
||||
$fname = null;
|
||||
$report = null;
|
||||
$success = false;
|
||||
$err_msg = null;
|
||||
|
||||
// Extracting and saving reports
|
||||
try {
|
||||
$rfile = $this->source->current();
|
||||
$fname = $rfile->filename();
|
||||
$report = Report::fromXmlFile($rfile->datastream());
|
||||
$result = $report->save($fname);
|
||||
$success = true;
|
||||
} catch (RuntimeException $e) {
|
||||
$err_msg = $e->getMessage();
|
||||
$result = ErrorHandler::exceptionResult($e);
|
||||
}
|
||||
unset($rfile);
|
||||
|
||||
// Post processing
|
||||
try {
|
||||
if ($success) {
|
||||
$this->source->accepted();
|
||||
} else {
|
||||
$this->source->rejected();
|
||||
}
|
||||
} catch (RuntimeException $e) {
|
||||
$err_msg = $e->getMessage();
|
||||
$result['post_processing_message'] = $err_msg;
|
||||
}
|
||||
|
||||
// Adding a record to the log.
|
||||
if (!$err_msg) {
|
||||
$log = ReportLogItem::success($stype, $report, $fname, null)->save();
|
||||
} else {
|
||||
$log = ReportLogItem::failed($stype, $report, $fname, $err_msg)->save();
|
||||
if ($this->source->type() === Source::SOURCE_MAILBOX) {
|
||||
$msg = $this->source->mailMessage();
|
||||
$ov = $msg->overview();
|
||||
if ($ov) {
|
||||
if (property_exists($ov, 'from')) {
|
||||
$result['emailed_from'] = $ov->from;
|
||||
}
|
||||
if (property_exists($ov, 'date')) {
|
||||
$result['emailed_date'] = $ov->date;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($report) {
|
||||
$rd = $report->get();
|
||||
if (isset($rd['external_id'])) {
|
||||
$result['report_id'] = $rd['external_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($report);
|
||||
|
||||
// Adding result to the results array.
|
||||
$results[] = $result;
|
||||
|
||||
// Checking the fetcher limits
|
||||
if ($limit > 0) {
|
||||
if (--$limit === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->source->next();
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the final result based on the results of loading individual report files.
|
||||
*
|
||||
* @param array $results Array with results of loading report files.
|
||||
*
|
||||
* @return array Array of the final result to be sent to the client.
|
||||
*/
|
||||
public static function makeSummaryResult(array $results): array
|
||||
{
|
||||
$reps = [];
|
||||
$others = [];
|
||||
$r_count = 0;
|
||||
$loaded = 0;
|
||||
foreach ($results as &$r) {
|
||||
if (isset($r['source_error'])) {
|
||||
$others[] = $r['source_error'];
|
||||
} else {
|
||||
$reps[] = $r;
|
||||
++$r_count;
|
||||
if (!isset($r['error_code']) || $r['error_code'] === 0) {
|
||||
++$loaded;
|
||||
}
|
||||
if (isset($r['post_processing_message'])) {
|
||||
$others[] = $r['post_processing_message'];
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($r);
|
||||
|
||||
$result = null;
|
||||
$o_count = count($others);
|
||||
if ($r_count + $o_count === 1) {
|
||||
if ($r_count === 1) {
|
||||
$result = $reps[0];
|
||||
} else {
|
||||
$result = [
|
||||
'error_code' => -1,
|
||||
'message' => $others[0]
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$err_code = null;
|
||||
$message = null;
|
||||
if ($loaded === $r_count) {
|
||||
$err_code = 0;
|
||||
if ($r_count > 0) {
|
||||
$message = strval($r_count) . ' report files have been loaded successfully';
|
||||
} elseif ($o_count === 0) {
|
||||
$message = 'There are no report files to load';
|
||||
} else {
|
||||
$err_code = -1;
|
||||
}
|
||||
} else {
|
||||
$err_code = -1;
|
||||
if ($loaded > 0) {
|
||||
$message = "Only {$loaded} of the {$r_count} report files have been loaded";
|
||||
} else {
|
||||
$message = "None of the {$r_count} report files has been loaded";
|
||||
}
|
||||
}
|
||||
$result['error_code'] = $err_code;
|
||||
$result['message'] = $message;
|
||||
if ($r_count > 0) {
|
||||
$result['results'] = $reps;
|
||||
}
|
||||
if ($o_count > 0) {
|
||||
$result['other_errors'] = $others;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
212
root/opt/dmarc-srg/classes/Report/ReportList.php
Normal file
212
root/opt/dmarc-srg/classes/Report/ReportList.php
Normal file
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains ReportList class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Report;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
|
||||
/**
|
||||
* It's the main class for working with the incoming reports, such as:
|
||||
* - getting a list of reports
|
||||
* - deleting several reports at once
|
||||
* - getting the number of reports stored in the database
|
||||
*/
|
||||
class ReportList
|
||||
{
|
||||
public const ORDER_NONE = 0;
|
||||
public const ORDER_BEGIN_TIME = 1;
|
||||
public const ORDER_ASCENT = 2;
|
||||
public const ORDER_DESCENT = 3;
|
||||
|
||||
private $db = null;
|
||||
private $limit = 0;
|
||||
private $filter = [];
|
||||
private $order = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param DatabaseController $db The database controller
|
||||
*/
|
||||
public function __construct($db = null)
|
||||
{
|
||||
$this->db = $db ?? Core::instance()->database();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of reports with specified parameters from position $pos
|
||||
*
|
||||
* This method returns a list of reports that depends on the filter and order.
|
||||
* The filter, order, and limit for the list can be set using the setFilter, setOrder and setMaxCount methods.
|
||||
*
|
||||
* @param int $pos The starting position from which the list will be returned
|
||||
*
|
||||
* @return array An array with keys `reports` and `more`.
|
||||
* `reports` is an array of incoming reports which contains maximum 25 records by default.
|
||||
* Another value of the number of records can be specified by calling
|
||||
* the method setMaxCount.
|
||||
* `more` is true if there are more records in the database, false otherwise.
|
||||
*/
|
||||
public function getList(int $pos): array
|
||||
{
|
||||
$order = [
|
||||
'field' => 'begin_time',
|
||||
'direction' => ($this->order ?? self::ORDER_DESCENT) === self::ORDER_ASCENT ? 'ascent' : 'descent'
|
||||
];
|
||||
|
||||
$max_rec = $this->limit > 0 ? $this->limit : 25;
|
||||
$limit = [
|
||||
'offset' => $pos,
|
||||
'count' => $max_rec + 1
|
||||
];
|
||||
|
||||
$list = $this->db->getMapper('report')->list($this->filter, $order, $limit);
|
||||
if (count($list) > $max_rec) {
|
||||
$more = true;
|
||||
unset($list[$max_rec]);
|
||||
} else {
|
||||
$more = false;
|
||||
}
|
||||
return [
|
||||
'reports' => $list,
|
||||
'more' => $more
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sort order for the list and for deleting several reports at once
|
||||
*
|
||||
* @param int $field The field to sort by. Currently only ORDER_BEGIN_TIME is available.
|
||||
* @param int $direction The sorting direction. ORDER_ASCENT or ORDER_DESCENT must be used here.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setOrder(int $field, int $direction): void
|
||||
{
|
||||
$this->order = null;
|
||||
if ($field > self::ORDER_NONE && $field < self::ORDER_ASCENT) {
|
||||
if ($direction !== self::ORDER_ASCENT) {
|
||||
$direction = self::ORDER_DESCENT;
|
||||
}
|
||||
$this->order = [
|
||||
'field' => $field,
|
||||
'direction' => $direction
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets maximum numbers of records in the list and for deleting reports
|
||||
*
|
||||
* @param int $num Maximum number of records in the list
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setMaxCount(int $num): void
|
||||
{
|
||||
if ($num > 0) {
|
||||
$this->limit = $num;
|
||||
} else {
|
||||
$this->limit = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets filter values for the list and for deleting reports
|
||||
*
|
||||
* @param array $filter Key-value array:
|
||||
* 'before_time' => DateTime, timestamp
|
||||
* 'dkim' => string, 'fail' or 'pass'
|
||||
* 'domain' => string or instance of Domain class
|
||||
* 'month' => string, yyyy-mm format
|
||||
* 'organization' => string
|
||||
* 'spf' => string, 'fail' or 'pass'
|
||||
* 'status' => string, 'read' or 'unread'
|
||||
* Note! 'dkim' and 'spf' do not affect the delete and count methods
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setFilter(array $filter): void
|
||||
{
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of reports in the database
|
||||
*
|
||||
* It returns the number of reports in the database.
|
||||
* The limit and some filter items (`dkim`, `spf`) do not affect this.
|
||||
*
|
||||
* @return int The number of reports in the database
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$limit = [ 'offset' => 0, 'count' => $this->limit ];
|
||||
return $this->db->getMapper('report')->count($this->filter, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes reports from the database
|
||||
*
|
||||
* It deletes repors form the database. The filter items `dkim` and `spf` do not affect this.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
$order = [
|
||||
'field' => 'begin_time',
|
||||
'direction' => ($this->order ?? self::ORDER_DESCENT) === self::ORDER_ASCENT ? 'ascent' : 'descent'
|
||||
];
|
||||
$limit = [ 'offset' => 0, 'count' => $this->limit ];
|
||||
$this->db->getMapper('report')->delete($this->filter, $order, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of values for each filter item except for `before_time`
|
||||
*
|
||||
* @return array An key-value array, where the key is the filter item name
|
||||
* and the value is an array of possible values for the item
|
||||
*/
|
||||
public function getFilterList(): array
|
||||
{
|
||||
$domainMapper = $this->db->getMapper('domain');
|
||||
$reportMapper = $this->db->getMapper('report');
|
||||
return [
|
||||
'domain' => $domainMapper->names(),
|
||||
'month' => $reportMapper->months(),
|
||||
'organization' => $reportMapper->organizations(),
|
||||
'dkim' => [ 'pass', 'fail' ],
|
||||
'spf' => [ 'pass', 'fail' ],
|
||||
'status' => [ 'read', 'unread' ]
|
||||
];
|
||||
}
|
||||
}
|
400
root/opt/dmarc-srg/classes/Report/SummaryReport.php
Normal file
400
root/opt/dmarc-srg/classes/Report/SummaryReport.php
Normal file
@@ -0,0 +1,400 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains SummaryReport class
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Report;
|
||||
|
||||
use Liuch\DmarcSrg\Statistics;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* This class is for generating summary data for the specified period and domain
|
||||
*/
|
||||
class SummaryReport
|
||||
{
|
||||
private const LAST_WEEK = -1;
|
||||
private const LAST_MONTH = -2;
|
||||
|
||||
private $period = 0;
|
||||
private $domain = null;
|
||||
private $stat = null;
|
||||
private $subject = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $period The period for which the report is created
|
||||
* Must me one of the following values: `lastweek`, `lastmonth`, and `lastndays:N`
|
||||
* where N is the number of days the report is created for
|
||||
*/
|
||||
public function __construct(string $period)
|
||||
{
|
||||
switch ($period) {
|
||||
case 'lastweek':
|
||||
$period = self::LAST_WEEK;
|
||||
$subject = ' weekly';
|
||||
break;
|
||||
case 'lastmonth':
|
||||
$period = self::LAST_MONTH;
|
||||
$subject = ' monthly';
|
||||
break;
|
||||
default:
|
||||
$ndays = 0;
|
||||
$av = explode(':', $period);
|
||||
if (count($av) === 2 && $av[0] === 'lastndays') {
|
||||
$ndays = intval($av[1]);
|
||||
if ($ndays <= 0) {
|
||||
throw new SoftException('The parameter "days" has an incorrect value');
|
||||
}
|
||||
$subject = sprintf(' %d day%s', $ndays, ($ndays > 1 ? 's' : ''));
|
||||
}
|
||||
$period = $ndays;
|
||||
break;
|
||||
}
|
||||
if (empty($subject)) {
|
||||
throw new SoftException('The parameter "period" has an incorrect value');
|
||||
}
|
||||
$this->period = $period;
|
||||
$this->subject = "DMARC{$subject} digest";
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a domain to the report
|
||||
*
|
||||
* @param Domain $domain The domain for which the report is created
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDomain($domain)
|
||||
{
|
||||
$this->domain = $domain;
|
||||
$this->stat = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report data as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$this->ensureData();
|
||||
|
||||
$res = [];
|
||||
$stat = $this->stat;
|
||||
$range = $stat->range();
|
||||
$res['date_range'] = [ 'begin' => $range[0], 'end' => $range[1] ];
|
||||
$res['summary'] = $stat->summary();
|
||||
$res['sources'] = $stat->ips();
|
||||
$res['organizations'] = $stat->organizations();
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subject string. It is used in email messages.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function subject(): string
|
||||
{
|
||||
return $this->subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report as an array of text strings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function text(): array
|
||||
{
|
||||
$rdata = $this->reportData();
|
||||
|
||||
$res = [];
|
||||
$res[] = '# Domain: ' . $this->domain->fqdn();
|
||||
|
||||
$res[] = ' Range: ' . $rdata['range'];
|
||||
$res[] = '';
|
||||
|
||||
$res[] = '## Summary';
|
||||
$total = $rdata['summary']['total'];
|
||||
$res[] = sprintf(' Total: %d', $total);
|
||||
$res[] = sprintf(' DKIM or SPF aligned: %s', self::num2percent($rdata['summary']['aligned'], $total));
|
||||
$res[] = sprintf(' Not aligned: %s', self::num2percent($rdata['summary']['n_aligned'], $total));
|
||||
$res[] = sprintf(' Organizations: %d', $rdata['summary']['organizations']);
|
||||
$res[] = '';
|
||||
|
||||
if (count($rdata['sources']) > 0) {
|
||||
$res[] = '## Sources';
|
||||
$res[] = sprintf(
|
||||
' %-25s %13s %13s %13s',
|
||||
'',
|
||||
'Total',
|
||||
'SPF aligned',
|
||||
'DKIM aligned'
|
||||
);
|
||||
foreach ($rdata['sources'] as &$it) {
|
||||
$total = $it['emails'];
|
||||
$spf_a = $it['spf_aligned'];
|
||||
$dkim_a = $it['dkim_aligned'];
|
||||
$spf_str = self::num2percent($spf_a, $total);
|
||||
$dkim_str = self::num2percent($dkim_a, $total);
|
||||
$res[] = sprintf(
|
||||
' %-25s %13d %13s %13s',
|
||||
$it['ip'],
|
||||
$total,
|
||||
$spf_str,
|
||||
$dkim_str
|
||||
);
|
||||
}
|
||||
unset($it);
|
||||
$res[] = '';
|
||||
}
|
||||
|
||||
if (count($rdata['organizations']) > 0) {
|
||||
$res[] = '## Organizations';
|
||||
|
||||
$org_len = 15;
|
||||
foreach ($rdata['organizations'] as &$org) {
|
||||
$org_len = max($org_len, mb_strlen($org['name']));
|
||||
}
|
||||
unset($org);
|
||||
|
||||
$org_len = min($org_len, 55);
|
||||
$res[] = sprintf(" %-{$org_len}s %8s %8s", '', 'emails', 'reports');
|
||||
$frm_str = " %-{$org_len}s %8d %8d";
|
||||
foreach ($rdata['organizations'] as &$org) {
|
||||
$res[] = sprintf(
|
||||
$frm_str,
|
||||
trim($org['name']),
|
||||
$org['emails'],
|
||||
$org['reports']
|
||||
);
|
||||
}
|
||||
unset($org);
|
||||
$res[] = '';
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the report as an array of html strings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function html(): array
|
||||
{
|
||||
$h2a = 'style="margin:15px 0 5px;"';
|
||||
$t2a = 'style="border-collapse:collapse;border-spacing:0;"';
|
||||
$c1a = 'style="font-style:italic;"';
|
||||
$d1s = 'padding-left:1em;';
|
||||
$d2s = 'min-width:4em;';
|
||||
$d3s = 'border:1px solid #888;';
|
||||
$d4s = 'text-align:right;';
|
||||
$d5s = 'padding:.3em;';
|
||||
|
||||
$add_red = function (int $num) {
|
||||
return $num > 0 ? 'color:#f00;' : '';
|
||||
};
|
||||
$add_green = function (int $num) {
|
||||
return $num > 0 ? 'color:#080;' : '';
|
||||
};
|
||||
|
||||
$rdata = $this->reportData();
|
||||
$res = [];
|
||||
$res[] = "<h2 {$h2a}>Domain: " . htmlspecialchars($this->domain->fqdn()) . '</h2>';
|
||||
$res[] = '<p style="margin:0;">Range: ' . htmlspecialchars($rdata['range']) . '</p>';
|
||||
|
||||
$res[] = "<h3 {$h2a}>Summary</h3>";
|
||||
$res[] = '<table>';
|
||||
$total = $rdata['summary']['total'];
|
||||
$a_cnt = $rdata['summary']['aligned'];
|
||||
$n_cnt = $rdata['summary']['n_aligned'];
|
||||
$res[] = " <tr><td>Total: </td><td style=\"{$d1s}\">" . $total . '</td></tr>';
|
||||
$color = $add_green($a_cnt);
|
||||
$res[] = " <tr><td>DKIM or SPF aligned: </td><td style=\"{$d1s}{$color}\">{$a_cnt}</td></tr>";
|
||||
$color = $add_red($n_cnt);
|
||||
$res[] = " <tr><td>Not aligned: </td><td style=\"{$d1s}{$color}\">{$n_cnt}</td></tr>";
|
||||
$res[] = " <tr><td>Organizations: </td><td style=\"{$d1s}\">" .
|
||||
$rdata['summary']['organizations'] .
|
||||
'</td></tr>';
|
||||
$res[] = '</table>';
|
||||
|
||||
$rs2 = 'rowspan="2"';
|
||||
$cs3 = 'colspan="3"';
|
||||
$s_cnt = count($rdata['sources']);
|
||||
if ($s_cnt > 0) {
|
||||
$res[] = "<h3 {$h2a}>Sources</h3>";
|
||||
$res[] = "<table {$t2a}>";
|
||||
$res[] = " <caption {$c1a}>Total records: {$s_cnt}</caption>";
|
||||
$res[] = ' <thead>';
|
||||
$style = "style=\"{$d3s}{$d5s}\"";
|
||||
$res[] = " <tr><th {$rs2} {$style}>IP address</th><th {$rs2} {$style}>Email volume</th>" .
|
||||
"<th {$cs3} {$style}>SPF</th><th {$cs3} {$style}>DKIM</th></tr>";
|
||||
$style = "style=\"{$d2s}{$d3s}{$d5s}\"";
|
||||
$res[] = " <tr><th {$style}>pass</th><th {$style}>fail</th><th {$style}>rate</th>" .
|
||||
"<th {$style}>pass</th><th {$style}>fail</th><th {$style}>rate</th></tr>";
|
||||
$res[] = ' </thead>';
|
||||
$res[] = ' <tbody>';
|
||||
foreach ($rdata['sources'] as &$row) {
|
||||
$ip = htmlspecialchars($row['ip']);
|
||||
$total = $row['emails'];
|
||||
$spf_a = $row['spf_aligned'];
|
||||
$spf_n = $total - $spf_a;
|
||||
$spf_p = sprintf('%.0f%%', $spf_a / $total * 100);
|
||||
$dkim_a = $row['dkim_aligned'];
|
||||
$dkim_n = $total - $dkim_a;
|
||||
$dkim_p = sprintf('%.0f%%', $dkim_a / $total * 100);
|
||||
$style = "style=\"{$d3s}{$d5s}";
|
||||
|
||||
$row_str = " <tr><td {$style}\">{$ip}</td><td {$style}{$d4s}\">{$total}</td>";
|
||||
$row_str .= "<td {$style}{$d4s}{$add_green($spf_a)}\">{$spf_a}</td>";
|
||||
$row_str .= "<td {$style}{$d4s}{$add_red($spf_n)}\">{$spf_n}</td>";
|
||||
$row_str .= "<td {$style}{$d4s}\">{$spf_p}</td>";
|
||||
$row_str .= "<td {$style}{$d4s}{$add_green($dkim_a)}\">{$dkim_a}</td>";
|
||||
$row_str .= "<td {$style}{$d4s}{$add_red($dkim_n)}\">{$dkim_n}</td>";
|
||||
$row_str .= "<td {$style}{$d4s}\">{$dkim_p}</td>";
|
||||
$res[] = $row_str . '</tr>';
|
||||
}
|
||||
unset($row);
|
||||
$res[] = ' </tbody>';
|
||||
$res[] = '</table>';
|
||||
}
|
||||
|
||||
$o_cnt = count($rdata['organizations']);
|
||||
if ($o_cnt) {
|
||||
$res[] = "<h3 {$h2a}>Organizations</h3>";
|
||||
$res[] = "<table {$t2a}>";
|
||||
$res[] = " <caption {$c1a}>Total records: {$o_cnt}</caption>";
|
||||
$res[] = ' <thead>';
|
||||
$style = "style=\"{$d3s}{$d5s}\"";
|
||||
$res[] = " <tr><th {$style}>Name</th><th {$style}>Emails</th><th {$style}>Reports</th></tr>";
|
||||
$res[] = ' </thead>';
|
||||
$res[] = ' <tbody>';
|
||||
foreach ($rdata['organizations'] as &$row) {
|
||||
$name = htmlspecialchars($row['name']);
|
||||
$style2 = "style=\"{$d3s}{$d4s}{$d5s}\"";
|
||||
$res[] = " <tr><td {$style}>{$name}</td>" .
|
||||
"<td {$style2}>{$row['emails']}</td>" .
|
||||
"<td {$style2}>{$row['reports']}</td></tr>";
|
||||
}
|
||||
unset($row);
|
||||
$res[] = ' </tbody>';
|
||||
$res[] = '</table>';
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the percentage with the original number. If $per is 0 then '0' is returned.
|
||||
*
|
||||
* @param int $per Value
|
||||
* @param int $cent Divisor for percentage calculation
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function num2percent(int $per, int $cent): string
|
||||
{
|
||||
if (!$per) {
|
||||
return '0';
|
||||
}
|
||||
return sprintf('%.0f%%(%d)', $per / $cent * 100, $per);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the report if it has not already been done
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function ensureData(): void
|
||||
{
|
||||
if (!$this->domain) {
|
||||
throw new LogicException('No one domain was specified');
|
||||
}
|
||||
|
||||
if (!$this->stat) {
|
||||
switch ($this->period) {
|
||||
case self::LAST_WEEK:
|
||||
$this->stat = Statistics::lastWeek($this->domain);
|
||||
break;
|
||||
case self::LAST_MONTH:
|
||||
$this->stat = Statistics::lastMonth($this->domain);
|
||||
break;
|
||||
default:
|
||||
$this->stat = Statistics::lastNDays($this->domain, $this->period);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns prepared data for the report
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function reportData(): array
|
||||
{
|
||||
$this->ensureData();
|
||||
|
||||
$stat = $this->stat;
|
||||
$rdata = [];
|
||||
$range = $stat->range();
|
||||
$cyear = (new \Datetime())->format('Y');
|
||||
$dform = ($range[0]->format('Y') !== $cyear || $range[1]->format('Y') !== $cyear) ? 'M d Y' : 'M d';
|
||||
$rdata['range'] = $range[0]->format($dform) . ' - ' . $range[1]->format($dform);
|
||||
|
||||
$summ = $stat->summary();
|
||||
$total = $summ['emails']['total'];
|
||||
$aligned = $summ['emails']['dkim_spf_aligned'] +
|
||||
$summ['emails']['dkim_aligned'] +
|
||||
$summ['emails']['spf_aligned'];
|
||||
$n_aligned = $total - $aligned;
|
||||
$rdata['summary'] = [
|
||||
'total' => $total,
|
||||
'organizations' => $summ['organizations']
|
||||
];
|
||||
if ($total > 0) {
|
||||
$rdata['summary']['aligned'] = $aligned;
|
||||
$rdata['summary']['n_aligned'] = $n_aligned;
|
||||
} else {
|
||||
$rdata['summary']['aligned'] = $aligned;
|
||||
$rdata['summary']['n_aligned'] = $aligned;
|
||||
}
|
||||
|
||||
$rdata['sources'] = $stat->ips();
|
||||
|
||||
$rdata['organizations'] = $stat->organizations();
|
||||
|
||||
return $rdata;
|
||||
}
|
||||
}
|
194
root/opt/dmarc-srg/classes/ReportFile/ReportFile.php
Normal file
194
root/opt/dmarc-srg/classes/ReportFile/ReportFile.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\ReportFile;
|
||||
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
|
||||
class ReportFile
|
||||
{
|
||||
private $fd = null;
|
||||
private $zip = null;
|
||||
private $type = null;
|
||||
private $remove = false;
|
||||
private $filename = null;
|
||||
private $filepath = null;
|
||||
private $gzcutfilter = null;
|
||||
private $gzinflatefilter = null;
|
||||
private static $filters = [];
|
||||
private static $ext_mime_map = [
|
||||
'xml' => 'text/xml',
|
||||
'gz' => 'application/gzip',
|
||||
'zip' => 'application/zip'
|
||||
];
|
||||
|
||||
private function __construct($filename, $type = null, $fd = null, $remove = false, $filepath = null)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
$this->type = $type ?? self::getMimeType($filename, $fd, $filepath);
|
||||
$this->remove = $remove;
|
||||
$this->filepath = $filepath;
|
||||
|
||||
switch ($this->type) {
|
||||
case 'application/gzip':
|
||||
case 'application/x-gzip':
|
||||
$this->fd = $fd;
|
||||
break;
|
||||
case 'application/zip':
|
||||
if ($fd) {
|
||||
$tmpfname = tempnam(sys_get_temp_dir(), 'dmarc_');
|
||||
if ($tmpfname === false) {
|
||||
throw new SoftException('Failed to create a temporary file');
|
||||
}
|
||||
rewind($fd);
|
||||
if (file_put_contents($tmpfname, $fd) === false) {
|
||||
throw new SoftException('Failed to copy data to a temporary file');
|
||||
}
|
||||
$this->filepath = $tmpfname;
|
||||
$this->remove = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->fd) {
|
||||
if ($this->isGzipStream()) {
|
||||
$this->enableGzFilter(false);
|
||||
}
|
||||
gzclose($this->fd);
|
||||
}
|
||||
if ($this->zip) {
|
||||
$this->zip->close();
|
||||
}
|
||||
if ($this->remove && $this->filepath) {
|
||||
unlink($this->filepath);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMimeType($filename, $fd = null, $filepath = null)
|
||||
{
|
||||
if (function_exists('mime_content_type')) {
|
||||
if ($fd && ($res = mime_content_type($fd))) {
|
||||
return $res;
|
||||
}
|
||||
if ($filepath && ($res = mime_content_type($filepath))) {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
$ext = pathinfo(basename($filename), PATHINFO_EXTENSION);
|
||||
return self::$ext_mime_map[$ext] ?? 'application/octet-stream';
|
||||
}
|
||||
|
||||
public static function fromFile($filepath, $filename = null, $remove = false)
|
||||
{
|
||||
if (!is_file($filepath)) {
|
||||
throw new SoftException('ReportFile: it is not a file');
|
||||
}
|
||||
|
||||
return new ReportFile(
|
||||
$filename ? basename($filename) : basename($filepath),
|
||||
null,
|
||||
null,
|
||||
$remove,
|
||||
$filepath
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromStream($fd, $filename, $type)
|
||||
{
|
||||
return new ReportFile($filename, $type, $fd);
|
||||
}
|
||||
|
||||
public function filename()
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function datastream()
|
||||
{
|
||||
if (!$this->fd) {
|
||||
$fd = null;
|
||||
switch ($this->type) {
|
||||
case 'application/zip':
|
||||
$this->zip = new \ZipArchive();
|
||||
$this->zip->open($this->filepath);
|
||||
if ($this->zip->count() !== 1) {
|
||||
throw new SoftException('The archive must have only one file in it');
|
||||
}
|
||||
$zfn = $this->zip->getNameIndex(0);
|
||||
if ($zfn !== pathinfo($zfn, PATHINFO_BASENAME)) {
|
||||
throw new SoftException('There must not be any directories in the archive');
|
||||
}
|
||||
$fd = $this->zip->getStream($zfn);
|
||||
break;
|
||||
default:
|
||||
// gzopen() can be used to read a file which is not in gzip format;
|
||||
// in this case gzread() will directly read from the file without decompression.
|
||||
$fd = gzopen($this->filepath, 'r');
|
||||
break;
|
||||
}
|
||||
if (!$fd) {
|
||||
throw new SoftException('Failed to open a report file');
|
||||
}
|
||||
$this->fd = $fd;
|
||||
}
|
||||
if ($this->isGzipStream()) {
|
||||
ReportFile::ensureRegisterFilter(
|
||||
'report_gzfile_cut_filter',
|
||||
'Liuch\DmarcSrg\ReportFile\ReportGZFileCutFilter'
|
||||
);
|
||||
$this->enableGzFilter(true);
|
||||
}
|
||||
return $this->fd;
|
||||
}
|
||||
|
||||
private static function ensureRegisterFilter($filtername, $classname)
|
||||
{
|
||||
if (!isset(ReportFile::$filters[$filtername])) {
|
||||
stream_filter_register($filtername, $classname);
|
||||
ReportFile::$filters[$filtername] = true;
|
||||
}
|
||||
}
|
||||
|
||||
private function enableGzFilter($enable)
|
||||
{
|
||||
if ($enable) {
|
||||
if (!$this->gzcutfilter) {
|
||||
$this->gzcutfilter = stream_filter_append($this->fd, 'report_gzfile_cut_filter', STREAM_FILTER_READ);
|
||||
$this->gzinflatefilter = stream_filter_append($this->fd, 'zlib.inflate', STREAM_FILTER_READ);
|
||||
}
|
||||
} else {
|
||||
if ($this->gzcutfilter) {
|
||||
stream_filter_remove($this->gzinflatefilter);
|
||||
stream_filter_remove($this->gzcutfilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isGzipStream(): bool
|
||||
{
|
||||
return (in_array($this->type, [ 'application/gzip', 'application/x-gzip' ]) && !$this->filepath);
|
||||
}
|
||||
}
|
114
root/opt/dmarc-srg/classes/ReportFile/ReportGZFileCutFilter.php
Normal file
114
root/opt/dmarc-srg/classes/ReportFile/ReportGZFileCutFilter.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\ReportFile;
|
||||
|
||||
use php_user_filter;
|
||||
|
||||
class ReportGZFileCutFilter extends php_user_filter
|
||||
{
|
||||
private $head = true;
|
||||
private $header_data = '';
|
||||
private $tail_data = '';
|
||||
|
||||
public function filter($in, $out, &$consumed, $closing): int
|
||||
{
|
||||
$b_cnt = 0;
|
||||
while ($bucket = stream_bucket_make_writeable($in)) {
|
||||
$consumed += $bucket->datalen;
|
||||
$data = null;
|
||||
if ($this->head) {
|
||||
$data = $this->skipGzHeader($bucket->data);
|
||||
} else {
|
||||
$data = $bucket->data;
|
||||
}
|
||||
$data = $this->cutGzTail($data);
|
||||
if (strlen($data) > 0) {
|
||||
$bucket->data = $data;
|
||||
stream_bucket_append($out, $bucket);
|
||||
$b_cnt += 1;
|
||||
}
|
||||
}
|
||||
return ($b_cnt > 0) ? PSFS_PASS_ON : PSFS_FEED_ME;
|
||||
}
|
||||
|
||||
private function skipGzHeader($data)
|
||||
{
|
||||
// https://tools.ietf.org/html/rfc1952
|
||||
$this->header_data .= $data;
|
||||
$len = strlen($this->header_data);
|
||||
if ($len < 10) { // minimal gz header
|
||||
return '';
|
||||
}
|
||||
|
||||
$pos = 10;
|
||||
$flags = ord($this->header_data[3]);
|
||||
if ($flags & 4) { // FLG.FEXTRA
|
||||
$pos += (ord($this->header_data[$pos + 1]) | (ord($this->header_data[$pos + 2]) << 8)) + 2;
|
||||
if ($pos > $len) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
if ($flags & 8) { // FLG.FNAME
|
||||
$pos = $this->skipZeroTerminatedString($this->header_data, $len, $pos);
|
||||
if ($pos > $len) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
if ($flags & 16) { // FLG.FCOMMENT
|
||||
$pos = $this->skipZeroTerminatedString($this->header_data, $len, $pos);
|
||||
if ($pos > $len) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
if ($flags & 2) { // FLG.FHCRC
|
||||
$pos += 2;
|
||||
if ($pos > $len) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
$res = substr($this->header_data, $pos);
|
||||
$this->head = false;
|
||||
$this->header_data = '';
|
||||
return $res;
|
||||
}
|
||||
|
||||
private function cutGzTail($data)
|
||||
{
|
||||
$res = $this->tail_data . $data;
|
||||
$this->tail_data = substr($res, -8);
|
||||
if (strlen($res) <= 8) {
|
||||
return '';
|
||||
}
|
||||
return substr($res, 0, -8);
|
||||
}
|
||||
|
||||
private function skipZeroTerminatedString($str, $len, $pos)
|
||||
{
|
||||
for ($i = $pos; $i < $len; ++$i) {
|
||||
if ($str[$i] === "\0") {
|
||||
return $i + 1;
|
||||
}
|
||||
}
|
||||
return $len + 1;
|
||||
}
|
||||
}
|
96
root/opt/dmarc-srg/classes/ReportLog/ReportLog.php
Normal file
96
root/opt/dmarc-srg/classes/ReportLog/ReportLog.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\ReportLog;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
|
||||
class ReportLog
|
||||
{
|
||||
public const ORDER_ASCENT = 1;
|
||||
public const ORDER_DESCENT = 2;
|
||||
|
||||
private $db = null;
|
||||
private $filter = [
|
||||
'from_time' => null,
|
||||
'till_time' => null
|
||||
];
|
||||
private $order = [
|
||||
'direction' => 'ascent'
|
||||
];
|
||||
private $rec_limit = 0;
|
||||
private $position = 0;
|
||||
|
||||
public function __construct($from_time, $till_time, $db = null)
|
||||
{
|
||||
$this->filter['from_time'] = $from_time;
|
||||
$this->filter['till_time'] = $till_time;
|
||||
$this->db = $db ?? Core::instance()->database();
|
||||
}
|
||||
|
||||
public function setOrder(int $dir)
|
||||
{
|
||||
$this->order['direction'] = ($dir === self::ORDER_DESCENT ? 'descent' : 'ascent');
|
||||
}
|
||||
|
||||
public function setMaxCount(int $n)
|
||||
{
|
||||
$this->rec_limit = $n;
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
$limit = [ 'offset' => 0, 'count' => $this->rec_limit ];
|
||||
return $this->db->getMapper('report-log')->count($this->filter, $limit);
|
||||
}
|
||||
|
||||
public function getList(int $pos)
|
||||
{
|
||||
$this->position = $pos;
|
||||
$max_rec = $this->rec_limit > 0 ? $this->rec_limit : 25;
|
||||
|
||||
$limit = [ 'offset' => $pos, 'count' => $max_rec + 1 ];
|
||||
|
||||
$list = $this->db->getMapper('report-log')->list($this->filter, $this->order, $limit);
|
||||
if (count($list) > $max_rec) {
|
||||
$more = true;
|
||||
unset($list[$max_rec]);
|
||||
} else {
|
||||
$more = false;
|
||||
}
|
||||
foreach ($list as &$it) {
|
||||
$it['source'] = ReportLogItem::sourceToString($it['source']);
|
||||
}
|
||||
unset($it);
|
||||
|
||||
return [
|
||||
'items' => $list,
|
||||
'more' => $more
|
||||
];
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$limit = [ 'offset' => 0, 'count' => $this->rec_limit ];
|
||||
$this->db->getMapper('report-log')->delete($this->filter, $this->order, $limit);
|
||||
}
|
||||
}
|
147
root/opt/dmarc-srg/classes/ReportLog/ReportLogItem.php
Normal file
147
root/opt/dmarc-srg/classes/ReportLog/ReportLogItem.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\ReportLog;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\Sources\Source;
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
|
||||
|
||||
class ReportLogItem
|
||||
{
|
||||
private $db = null;
|
||||
private $data = [
|
||||
'id' => null,
|
||||
'domain' => null,
|
||||
'external_id' => null,
|
||||
'event_time' => null,
|
||||
'filename' => null,
|
||||
'source' => 0,
|
||||
'success' => false,
|
||||
'message' => null
|
||||
];
|
||||
|
||||
private function __construct($source, $filename, $db)
|
||||
{
|
||||
if (!is_null($source)) {
|
||||
if (gettype($source) !== 'integer' || $source <= 0) {
|
||||
throw new LogicException('Invalid parameter passed');
|
||||
}
|
||||
}
|
||||
$this->data['source'] = $source;
|
||||
$this->data['filename'] = gettype($filename) == 'string' ? $filename : null;
|
||||
$this->db = $db ?? Core::instance()->database();
|
||||
}
|
||||
|
||||
public static function success(int $source, $report, $filename, $message, $db = null)
|
||||
{
|
||||
$li = new ReportLogItem($source, $filename, $db);
|
||||
$li->data['success'] = true;
|
||||
$rdata = $report->get();
|
||||
$li->data['domain'] = $rdata['domain'];
|
||||
$li->data['external_id'] = $rdata['external_id'];
|
||||
$li->data['message'] = $message;
|
||||
return $li;
|
||||
}
|
||||
|
||||
public static function failed(int $source, $report, $filename, $message, $db = null)
|
||||
{
|
||||
$li = new ReportLogItem($source, $filename, $db);
|
||||
$li->data['success'] = false;
|
||||
if (!is_null($report)) {
|
||||
$rdata = $report->get();
|
||||
$li->data['domain'] = $rdata['domain'];
|
||||
$li->data['external_id'] = $rdata['external_id'];
|
||||
} else {
|
||||
$li->data['domain'] = null;
|
||||
$li->data['external_id'] = null;
|
||||
}
|
||||
$li->data['message'] = $message;
|
||||
return $li;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of ReportLogItem with the passed Id
|
||||
*
|
||||
* @param int $id Item Id to return
|
||||
* @param DatabaseController $db The database controller
|
||||
*
|
||||
* @return ReportLogItem an instance of ReportLogItem with the specified Id.
|
||||
*/
|
||||
public static function byId(int $id, $db = null)
|
||||
{
|
||||
$li = new ReportLogItem(null, null, $db);
|
||||
$li->data['id'] = $id;
|
||||
try {
|
||||
$li->db->getMapper('report-log')->fetch($li->data);
|
||||
} catch (DatabaseNotFoundException $e) {
|
||||
throw new SoftException('The log item is not found');
|
||||
}
|
||||
return $li;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an integer source value to a string representation
|
||||
*
|
||||
* Returns a string with the source name or an empty string if the integer value is incorrect.
|
||||
*
|
||||
* @param int $source - an integer value to convert
|
||||
*
|
||||
* @return string A string value of the passed source
|
||||
*/
|
||||
public static function sourceToString(int $source): string
|
||||
{
|
||||
switch ($source) {
|
||||
case Source::SOURCE_UPLOADED_FILE:
|
||||
return 'uploaded_file';
|
||||
case Source::SOURCE_MAILBOX:
|
||||
return 'email';
|
||||
case Source::SOURCE_DIRECTORY:
|
||||
return 'directory';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with log item data
|
||||
*
|
||||
* @return array Log item data
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$res = $this->data;
|
||||
$res['source'] = static::sourceToString($this->data['source']);
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the report log item to the database
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
$this->db->getMapper('report-log')->save($this->data);
|
||||
}
|
||||
}
|
242
root/opt/dmarc-srg/classes/Settings/Setting.php
Normal file
242
root/opt/dmarc-srg/classes/Settings/Setting.php
Normal file
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains implementation of the class Setting
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Settings;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
|
||||
|
||||
/**
|
||||
* It's a class for accessing to settings item data
|
||||
*
|
||||
* This class is designed for storing and manipulating one item of settings data.
|
||||
* All queries to the datatabase are made in lazy mode.
|
||||
*/
|
||||
abstract class Setting
|
||||
{
|
||||
public const TYPE_STRING = 1;
|
||||
public const TYPE_INTEGER = 2;
|
||||
public const TYPE_STRING_SELECT = 3;
|
||||
|
||||
protected $db = null;
|
||||
protected $name = null;
|
||||
protected $value = null;
|
||||
protected $wignore = false;
|
||||
|
||||
/**
|
||||
* Returns the type of the setting
|
||||
*
|
||||
* @return int Type of the setting
|
||||
*/
|
||||
abstract public function type(): int;
|
||||
|
||||
/**
|
||||
* Checks if the value is correct
|
||||
*
|
||||
* @return bool True if the value is correct or false otherwise
|
||||
*/
|
||||
abstract protected function checkValue(): bool;
|
||||
|
||||
/**
|
||||
* Converts a string to the value
|
||||
*
|
||||
* @param string $s String for conversion
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function stringToValue(string $s): void;
|
||||
|
||||
/**
|
||||
* Returns a string representation of the value
|
||||
*
|
||||
* @return string The string value
|
||||
*/
|
||||
abstract protected function valueToString(): string;
|
||||
|
||||
/**
|
||||
* It's a constructor of the class
|
||||
*
|
||||
* Some examples of using:
|
||||
* (new Setting('some.setting'))->value(); - will return the value of the setting 'some.setting'.
|
||||
* (new Setting([ 'name' => 'some.setting', 'value' => 'some string value' ])->save(); - will add
|
||||
* this setting to the database if it does not exist in it or update the value of the setting.
|
||||
*
|
||||
* @param string|array $data Some setting data to identify it
|
||||
* string value is treated as a name
|
||||
* array has these fields: `name`, `value`
|
||||
* and usually uses for creating a new setting item.
|
||||
* @param boolean $wignore If true the wrong value is reset to the default
|
||||
* or it throws an exception otherwise.
|
||||
* @param DatabaseController $db The database controller
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($data, bool $wignore = false, $db = null)
|
||||
{
|
||||
$this->wignore = $wignore;
|
||||
$this->db = $db ?? Core::instance()->database();
|
||||
switch (gettype($data)) {
|
||||
case 'string':
|
||||
$this->name = $data;
|
||||
SettingsList::checkName($this->name);
|
||||
return;
|
||||
case 'array':
|
||||
if (!isset($data['name']) || gettype($data['name']) !== 'string') {
|
||||
break;
|
||||
}
|
||||
$this->name = $data['name'];
|
||||
SettingsList::checkName($this->name);
|
||||
if (isset($data['value'])) {
|
||||
$this->value = $data['value'];
|
||||
if (!$this->checkValue()) {
|
||||
if (!$wignore) {
|
||||
break;
|
||||
}
|
||||
$this->resetToDefault();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw new SoftException('Wrong setting data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the setting
|
||||
*
|
||||
* @return string The name of the setting
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the setting
|
||||
*
|
||||
* @return mixed The value of the setting
|
||||
*/
|
||||
public function value()
|
||||
{
|
||||
if (is_null($this->value)) {
|
||||
$this->fetchData();
|
||||
}
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the passed value to the setting
|
||||
*
|
||||
* @param mixed Value to assign
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setValue($value): void
|
||||
{
|
||||
$this->value = $value;
|
||||
if (!$this->checkValue()) {
|
||||
if (!$this->wignore) {
|
||||
throw new SoftException('Wrong setting value');
|
||||
}
|
||||
$this->resetToDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with setting data
|
||||
*
|
||||
* @return array Setting data
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
if (is_null($this->value)) {
|
||||
$this->fetchData();
|
||||
}
|
||||
switch ($this->type()) {
|
||||
case self::TYPE_STRING:
|
||||
$type = 'string';
|
||||
break;
|
||||
case self::TYPE_INTEGER:
|
||||
$type = 'integer';
|
||||
break;
|
||||
case self::TYPE_STRING_SELECT:
|
||||
$type = 'select';
|
||||
break;
|
||||
}
|
||||
return [
|
||||
'type' => $type,
|
||||
'name' => $this->name,
|
||||
'value' => $this->value,
|
||||
'default' => SettingsList::$schema[$this->name]['default']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the setting to the database
|
||||
*
|
||||
* Updates the value of the setting in the database if the setting exists there or insert a new record otherwise.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function save(): void
|
||||
{
|
||||
$this->db->getMapper('setting')->save($this->name, $this->valueToString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the setting data from the database by its name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function fetchData(): void
|
||||
{
|
||||
try {
|
||||
$res = $this->db->getMapper('setting')->value($this->name);
|
||||
} catch (DatabaseNotFoundException $e) {
|
||||
$this->resetToDefault();
|
||||
return;
|
||||
}
|
||||
$this->stringToValue($res);
|
||||
if (!$this->checkValue()) {
|
||||
$this->resetToDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the setting value to its default value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function resetToDefault(): void
|
||||
{
|
||||
$this->value = SettingsList::$schema[$this->name]['default'];
|
||||
}
|
||||
}
|
109
root/opt/dmarc-srg/classes/Settings/SettingInteger.php
Normal file
109
root/opt/dmarc-srg/classes/Settings/SettingInteger.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains implementation of the class SettingInteger
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Settings;
|
||||
|
||||
/**
|
||||
* It's a class for accessing to settings item data
|
||||
*
|
||||
* This class contains the implementation of the setting for integer values.
|
||||
*/
|
||||
class SettingInteger extends Setting
|
||||
{
|
||||
/**
|
||||
* Returns the type of the setting
|
||||
*
|
||||
* @return int Type of the setting
|
||||
*/
|
||||
public function type(): int
|
||||
{
|
||||
return Setting::TYPE_INTEGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is correct
|
||||
*
|
||||
* @return bool True if the value is correct or false otherwise
|
||||
*/
|
||||
protected function checkValue(): bool
|
||||
{
|
||||
if (gettype($this->value) === 'integer') {
|
||||
$sch = &SettingsList::$schema[$this->name];
|
||||
if (!isset($sch['minimum']) || $this->value >= $sch['minimum']) {
|
||||
if (!isset($sch['maximum']) || $this->value <= $sch['maximum']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to the value
|
||||
*
|
||||
* @param string $s String for conversion
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function stringToValue(string $s): void
|
||||
{
|
||||
$this->value = intval($s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the value
|
||||
*
|
||||
* @return string The string value
|
||||
*/
|
||||
protected function valueToString(): string
|
||||
{
|
||||
return strval($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with setting data
|
||||
*
|
||||
* @return array Setting data
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$res = parent::toArray();
|
||||
$sch = &SettingsList::$schema[$this->name];
|
||||
if (isset($sch['minimum'])) {
|
||||
$res['minimum'] = $sch['minimum'];
|
||||
}
|
||||
if (isset($sch['maximum'])) {
|
||||
$res['maximum'] = $sch['maximum'];
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
83
root/opt/dmarc-srg/classes/Settings/SettingString.php
Normal file
83
root/opt/dmarc-srg/classes/Settings/SettingString.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains implementation of the class SettingString
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Settings;
|
||||
|
||||
/**
|
||||
* It's a class for accessing to settings item data
|
||||
*
|
||||
* This class contains the implementation of the setting for string values.
|
||||
*/
|
||||
class SettingString extends Setting
|
||||
{
|
||||
/**
|
||||
* Returns the type of the setting
|
||||
*
|
||||
* @return int Type of the setting
|
||||
*/
|
||||
public function type(): int
|
||||
{
|
||||
return Setting::TYPE_STRING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is correct
|
||||
*
|
||||
* @return bool True if the value is correct or false otherwise
|
||||
*/
|
||||
protected function checkValue(): bool
|
||||
{
|
||||
return (gettype($this->value) === 'string');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to the value
|
||||
*
|
||||
* @param string $s String for conversion
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function stringToValue(string $s): void
|
||||
{
|
||||
$this->value = $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the value
|
||||
*
|
||||
* @return string The string value
|
||||
*/
|
||||
protected function valueToString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
|
78
root/opt/dmarc-srg/classes/Settings/SettingStringSelect.php
Normal file
78
root/opt/dmarc-srg/classes/Settings/SettingStringSelect.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains implementation of the class SettingStringSelect
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Settings;
|
||||
|
||||
/**
|
||||
* It's a class for accessing to settings item data
|
||||
*
|
||||
* This class contains the implementation of the setting for string with a limited set of values.
|
||||
*/
|
||||
class SettingStringSelect extends SettingString
|
||||
{
|
||||
/**
|
||||
* Returns the type of the setting
|
||||
*
|
||||
* @return int Type of the setting
|
||||
*/
|
||||
public function type(): int
|
||||
{
|
||||
return Setting::TYPE_STRING_SELECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is correct
|
||||
*
|
||||
* @return bool True if the value is correct or false otherwise
|
||||
*/
|
||||
protected function checkValue(): bool
|
||||
{
|
||||
if (parent::checkValue()) {
|
||||
if (in_array($this->value, SettingsList::$schema[$this->name]['options'])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with setting data
|
||||
*
|
||||
* @return array Setting data
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$res = parent::toArray();
|
||||
$res['options'] = SettingsList::$schema[$this->name]['options'];
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
207
root/opt/dmarc-srg/classes/Settings/SettingsList.php
Normal file
207
root/opt/dmarc-srg/classes/Settings/SettingsList.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class SettingsList
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Settings;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
|
||||
/**
|
||||
* This class is designed to work with the list of the settings
|
||||
*/
|
||||
class SettingsList
|
||||
{
|
||||
public const ORDER_ASCENT = 0;
|
||||
public const ORDER_DESCENT = 1;
|
||||
|
||||
private $db = null;
|
||||
private $order = self::ORDER_ASCENT;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param DatabaseController $db Connector to the current database
|
||||
*/
|
||||
public function __construct($db = null)
|
||||
{
|
||||
$this->db = $db ?? Core::instance()->database();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the settings
|
||||
*
|
||||
* It returns a list of the settings that are marked public.
|
||||
* The value is taken from the database, if any, or the default.
|
||||
*
|
||||
* @return array Array with instances of Setting class
|
||||
*/
|
||||
public function getList(): array
|
||||
{
|
||||
$db_map = $this->db->getMapper('setting')->list();
|
||||
foreach (static::$schema as $name => &$sch_data) {
|
||||
if ($sch_data['public'] ?? false) {
|
||||
$value = $db_map[$name] ?? $sch_data['default'];
|
||||
switch ($sch_data['type']) {
|
||||
case 'select':
|
||||
$list[] = new SettingStringSelect([
|
||||
'name' => $name,
|
||||
'value' => $value
|
||||
], true, $this->db);
|
||||
break;
|
||||
case 'integer':
|
||||
$list[] = new SettingInteger([
|
||||
'name' => $name,
|
||||
'value' => intval($value)
|
||||
], true, $this->db);
|
||||
break;
|
||||
case 'string':
|
||||
$list[] = new SettingString([
|
||||
'name' => $name,
|
||||
'value' => $value
|
||||
], true, $this->db);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($sch_data);
|
||||
|
||||
$dir = $this->order == self::ORDER_ASCENT ? 1 : -1;
|
||||
usort($list, static function ($a, $b) use ($dir) {
|
||||
return ($a->name() <=> $b->name()) * $dir;
|
||||
});
|
||||
|
||||
return [
|
||||
'list' => $list,
|
||||
'more' => false
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sorting direction for the list
|
||||
*
|
||||
* @param int $direction The sorting direction. ORDER_ASCENT or ORDER_DESCENT must be used here.
|
||||
*
|
||||
* @return SettingsList $this
|
||||
*/
|
||||
public function setOrder(int $direction)
|
||||
{
|
||||
if ($direction !== self::ORDER_DESCENT) {
|
||||
$direction = self::ORDER_ASCENT;
|
||||
}
|
||||
$this->order = $direction;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if there is no setting with name $name
|
||||
*
|
||||
* @param string $name Setting name to check
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function checkName($name): void
|
||||
{
|
||||
if (!isset(self::$schema[$name])) {
|
||||
throw new SoftException('Unknown setting name: ' . $name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the Setting class by its name
|
||||
*
|
||||
* It returns an instance of the Setting class but only if it is marked public.
|
||||
*
|
||||
* @param string $name Setting name
|
||||
*
|
||||
* @return Setting
|
||||
*/
|
||||
public static function getSettingByName(string $name)
|
||||
{
|
||||
self::checkName($name);
|
||||
if (!(self::$schema[$name]['public'] ?? false)) {
|
||||
throw new SoftException('Attempt to access an internal variable');
|
||||
}
|
||||
|
||||
switch (self::$schema[$name]['type']) {
|
||||
case 'string':
|
||||
return new SettingString($name);
|
||||
case 'select':
|
||||
return new SettingStringSelect($name);
|
||||
case 'integer':
|
||||
return new SettingInteger($name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List of the possible setting items that must be returned in getList method, their types and other data
|
||||
*/
|
||||
public static $schema = [
|
||||
'version' => [
|
||||
'type' => 'string',
|
||||
'default' => ''
|
||||
],
|
||||
'status.emails-for-last-n-days' => [
|
||||
'type' => 'integer',
|
||||
'public' => true,
|
||||
'minimum' => 1,
|
||||
'maximum' => 365,
|
||||
'default' => 30
|
||||
],
|
||||
'report-view.sort-records-by' => [
|
||||
'type' => 'select',
|
||||
'public' => true,
|
||||
'options' => [ 'ip,ascent', 'ip,descent', 'message-count,ascent', 'message-count,descent' ],
|
||||
'default' => 'message-count,descent'
|
||||
],
|
||||
'log-view.sort-list-by' => [
|
||||
'type' => 'select',
|
||||
'public' => true,
|
||||
'options' => [ 'event-time,ascent', 'event-time,descent' ],
|
||||
'default' => 'event-time,ascent'
|
||||
],
|
||||
'ui.datetime.offset' => [
|
||||
'type' => 'select',
|
||||
'public' => true,
|
||||
'options' => [ 'auto', 'utc', 'local' ],
|
||||
'default' => 'auto'
|
||||
],
|
||||
'ui.ipv4.url' => [
|
||||
'type' => 'string',
|
||||
'public' => true,
|
||||
'default' => 'https://who.is/whois-ip/ip-address/{$ip}'
|
||||
],
|
||||
'ui.ipv6.url' => [
|
||||
'type' => 'string',
|
||||
'public' => true,
|
||||
'default' => 'https://who.is/whois-ip/ip-address/{$ip}'
|
||||
]
|
||||
];
|
||||
}
|
255
root/opt/dmarc-srg/classes/Sources/DirectorySource.php
Normal file
255
root/opt/dmarc-srg/classes/Sources/DirectorySource.php
Normal file
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class DirectorySource
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Sources;
|
||||
|
||||
use Liuch\DmarcSrg\Core;
|
||||
use Liuch\DmarcSrg\ReportFile\ReportFile;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* This class is designed to process report files from local server directories.
|
||||
*/
|
||||
class DirectorySource extends Source
|
||||
{
|
||||
private $path = null;
|
||||
private $list = null;
|
||||
private $index = 0;
|
||||
private $params = null;
|
||||
|
||||
/**
|
||||
* Sets parameters that difine the behavior of the source
|
||||
*
|
||||
* @param $params Key-value array
|
||||
* 'when_done' => one or more rules to be executed after successful report processing
|
||||
* (array|string)
|
||||
* 'when_failed' => one or more rules to be executed after report processing fails
|
||||
* (array|string)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setParams(array $params): void
|
||||
{
|
||||
$this->params = [];
|
||||
$this->params['when_done'] = SourceAction::fromSetting(
|
||||
$params['when_done'] ?? [],
|
||||
SourceAction::FLAG_BASENAME,
|
||||
'delete'
|
||||
);
|
||||
$this->params['when_failed'] = SourceAction::fromSetting(
|
||||
$params['when_failed'] ?? [],
|
||||
SourceAction::FLAG_BASENAME,
|
||||
'move_to:failed'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the ReportFile class for the current file.
|
||||
*
|
||||
* @return ReportFile
|
||||
*/
|
||||
public function current(): object
|
||||
{
|
||||
return ReportFile::fromFile($this->list[$this->index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the currect file.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function key(): int
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves forward to the next file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
++$this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewinds the position to the first file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
if (is_null($this->list)) {
|
||||
$this->path = $this->data->toArray()['location'];
|
||||
if (!is_dir($this->path)) {
|
||||
throw new SoftException("The {$this->path} directory does not exist!");
|
||||
}
|
||||
try {
|
||||
$fs = new \FilesystemIterator($this->path);
|
||||
} catch (\Exception $e) {
|
||||
throw new RuntimeException("Error accessing directory {$this->path}", -1, $e);
|
||||
}
|
||||
$this->list = [];
|
||||
foreach ($fs as $entry) {
|
||||
if ($entry->isFile()) {
|
||||
$this->list[] = $entry->getPathname();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_null($this->params)) {
|
||||
$this->setParams([]);
|
||||
}
|
||||
$this->index = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current postion is valid
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return isset($this->list[$this->index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the accepted report file according to the settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function accepted(): void
|
||||
{
|
||||
$this->processReportFileActions($this->params['when_done']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the rejected report file according to the settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rejected(): void
|
||||
{
|
||||
$this->processReportFileActions($this->params['when_failed']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns type of the source.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function type(): int
|
||||
{
|
||||
return Source::SOURCE_DIRECTORY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error message
|
||||
*
|
||||
* @param string $message
|
||||
*/
|
||||
private function logError(string $message): void
|
||||
{
|
||||
Core::instance()->logger()->error($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the current report file according to settings
|
||||
*
|
||||
* @param array $actions List of actions to apply to the file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function processReportFileActions(array &$actions): void
|
||||
{
|
||||
foreach ($actions as $sa) {
|
||||
switch ($sa->type) {
|
||||
case SourceAction::ACTION_DELETE:
|
||||
$this->deleteReportFile();
|
||||
break;
|
||||
case SourceAction::ACTION_MOVE:
|
||||
$this->moveReportFile($sa->param);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current report file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function deleteReportFile(): void
|
||||
{
|
||||
try {
|
||||
unlink($this->list[$this->index]);
|
||||
} catch (\ErrorException $e) {
|
||||
$error_message = "Error deleting file from directory {$this->path}";
|
||||
$this->logError($error_message);
|
||||
throw new RuntimeException($error_message, -1, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the current report file
|
||||
*
|
||||
* @param string $dir_name Directory name where to move the report file to
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function moveReportFile(string $dir_name): void
|
||||
{
|
||||
$fdir = $this->path . $dir_name;
|
||||
if (!is_dir($fdir)) {
|
||||
try {
|
||||
mkdir($fdir);
|
||||
} catch (\ErrorException $e) {
|
||||
$e = new RuntimeException("Error creating directory {$fdir}/", -1, $e);
|
||||
$this->logError(strval($e));
|
||||
throw $e;
|
||||
}
|
||||
try {
|
||||
chmod($fdir, 0700);
|
||||
} catch (\ErrorException $e) {
|
||||
$this->logError(strval($e));
|
||||
}
|
||||
}
|
||||
$file = $this->list[$this->index];
|
||||
try {
|
||||
rename($file, $fdir . '/' . basename($file));
|
||||
} catch (\ErrorException $e) {
|
||||
$e = new RuntimeException("Error moving file to directory {$fdir}/", -1, $e);
|
||||
$this->logError(strval($e));
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
238
root/opt/dmarc-srg/classes/Sources/MailboxSource.php
Normal file
238
root/opt/dmarc-srg/classes/Sources/MailboxSource.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class MailboxSource
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Sources;
|
||||
|
||||
use Liuch\DmarcSrg\ReportFile\ReportFile;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
use Liuch\DmarcSrg\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* This class is designed to process report files from an mail box.
|
||||
*/
|
||||
class MailboxSource extends Source
|
||||
{
|
||||
private $list = null;
|
||||
private $index = 0;
|
||||
private $msg = null;
|
||||
private $params = null;
|
||||
|
||||
/**
|
||||
* Sets parameters that difine the behavior of the source
|
||||
*
|
||||
* @param $params Key-value array
|
||||
* 'when_done' => one or more rules to be executed after successful report processing
|
||||
* (array|string)
|
||||
* 'when_failed' => one or more rules to be executed after report processing fails
|
||||
* (array|string)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setParams(array $params): void
|
||||
{
|
||||
$this->params = [];
|
||||
$this->params['when_done'] = SourceAction::fromSetting(
|
||||
$params['when_done'] ?? [],
|
||||
0,
|
||||
'mark_seen'
|
||||
);
|
||||
$this->params['when_failed'] = SourceAction::fromSetting(
|
||||
$params['when_failed'] ?? [],
|
||||
0,
|
||||
'move_to:failed'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the ReportFile class for the current email message.
|
||||
*
|
||||
* @return ReportFile
|
||||
*/
|
||||
public function current(): object
|
||||
{
|
||||
$this->msg = $this->data->message($this->list[$this->index]);
|
||||
try {
|
||||
$this->msg->validate();
|
||||
} catch (SoftException $e) {
|
||||
throw new SoftException('Incorrect message: ' . $e->getMessage(), $e->getCode());
|
||||
} catch (RuntimeException $e) {
|
||||
throw new RuntimeException('Incorrect message', -1, $e);
|
||||
}
|
||||
$att = $this->msg->attachment();
|
||||
return ReportFile::fromStream($att->datastream(), $att->filename(), $att->mimeType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the currect email message.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function key(): int
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves forward to the next email message
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
$this->msg = null;
|
||||
++$this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of unread messages and rewinds the position to the first email message.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->msg = null;
|
||||
$this->list = $this->data->sort(SORTDATE, 'UNSEEN', false);
|
||||
$this->index = 0;
|
||||
if (is_null($this->params)) {
|
||||
$this->setParams([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current postion is valid
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return isset($this->list[$this->index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the accepted email messages according to the settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function accepted(): void
|
||||
{
|
||||
if ($this->msg) {
|
||||
$this->processMessageActions($this->params['when_done']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the rejected email messages according to the settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rejected(): void
|
||||
{
|
||||
$this->processMessageActions($this->params['when_failed']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns type of the source.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function type(): int
|
||||
{
|
||||
return Source::SOURCE_MAILBOX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current email message.
|
||||
*
|
||||
* @return MailMessage|null
|
||||
*/
|
||||
public function mailMessage()
|
||||
{
|
||||
return $this->msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the current report message according to settings
|
||||
*
|
||||
* @param array $actions List of actions to apply to the message
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function processMessageActions(array &$actions): void
|
||||
{
|
||||
foreach ($actions as $sa) {
|
||||
switch ($sa->type) {
|
||||
case SourceAction::ACTION_SEEN:
|
||||
$this->markMessageSeen();
|
||||
break;
|
||||
case SourceAction::ACTION_MOVE:
|
||||
$this->moveMessage($sa->param);
|
||||
break;
|
||||
case SourceAction::ACTION_DELETE:
|
||||
$this->deleteMessage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the current report message as seen
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function markMessageSeen(): void
|
||||
{
|
||||
$this->msg->setSeen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the current report message
|
||||
*
|
||||
* @param string $mbox_name Child mailbox name where to move the current message to.
|
||||
* If the target mailbox does not exists, it will be created.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function moveMessage(string $mbox_name): void
|
||||
{
|
||||
$this->data->ensureMailbox($mbox_name);
|
||||
$this->data->moveMessage($this->list[$this->index], $mbox_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current report message
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function deleteMessage(): void
|
||||
{
|
||||
$this->data->deleteMessage($this->list[$this->index]);
|
||||
}
|
||||
}
|
115
root/opt/dmarc-srg/classes/Sources/Source.php
Normal file
115
root/opt/dmarc-srg/classes/Sources/Source.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class Source
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Sources;
|
||||
|
||||
/**
|
||||
* It's an abstract class for easy access to reports of a report source
|
||||
*/
|
||||
abstract class Source implements \Iterator
|
||||
{
|
||||
public const SOURCE_UPLOADED_FILE = 1;
|
||||
public const SOURCE_MAILBOX = 2;
|
||||
public const SOURCE_DIRECTORY = 3;
|
||||
|
||||
protected $data = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param mixed Data to reach report files
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets parameters that difine the behavior of the source
|
||||
*
|
||||
* @param $params Key-value array
|
||||
* 'when_done' => one or more rules to be executed after successful report processing
|
||||
* (array|string)
|
||||
* 'when_failed' => one or more rules to be executed after report processing fails
|
||||
* (array|string)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setParams(array $params): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator interface methods
|
||||
*/
|
||||
abstract public function current(): object;
|
||||
abstract public function key(): int;
|
||||
abstract public function next(): void;
|
||||
abstract public function rewind(): void;
|
||||
abstract public function valid(): bool;
|
||||
|
||||
/**
|
||||
* Called when the current report has been successfully processed.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function accepted(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the current report has been rejected.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rejected(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns type of source, i.e. one of Source::SOURCE_* values
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
abstract public function type(): int;
|
||||
|
||||
/**
|
||||
* Returns the source itself that was passed to the constructor
|
||||
*
|
||||
* @return class
|
||||
*/
|
||||
public function container()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
156
root/opt/dmarc-srg/classes/Sources/SourceAction.php
Normal file
156
root/opt/dmarc-srg/classes/Sources/SourceAction.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class SourceAction
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Sources;
|
||||
|
||||
use Liuch\DmarcSrg\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* It's a class for describing one action of source
|
||||
*/
|
||||
class SourceAction
|
||||
{
|
||||
public const ACTION_SEEN = 1;
|
||||
public const ACTION_MOVE = 2;
|
||||
public const ACTION_DELETE = 3;
|
||||
public const FLAG_BASENAME = 1;
|
||||
|
||||
private $valid = false;
|
||||
private $type = 0;
|
||||
private $param = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param string $action Action name with parameter separated by colon
|
||||
* Examples: 'move_to:failed', 'delete'
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function __construct(string $action)
|
||||
{
|
||||
if (($delim_offset = mb_strpos($action, ':')) === false) {
|
||||
$name = $action;
|
||||
$param = null;
|
||||
} else {
|
||||
$name = mb_substr($action, 0, $delim_offset);
|
||||
$param = mb_substr($action, $delim_offset + 1);
|
||||
}
|
||||
switch ($name) {
|
||||
case 'mark_seen':
|
||||
$this->type = self::ACTION_SEEN;
|
||||
if (!empty($param)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'move_to':
|
||||
$this->type = self::ACTION_MOVE;
|
||||
if (empty($param)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'delete':
|
||||
$this->type = self::ACTION_DELETE;
|
||||
if (!empty($param)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
$this->param = $param;
|
||||
$this->valid = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The getter
|
||||
*
|
||||
* @param string $name Property name. Must be one of the following: 'type', 'param'
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get(string $name)
|
||||
{
|
||||
if (in_array($name, [ 'type', 'param' ])) {
|
||||
return $this->$name;
|
||||
}
|
||||
throw new LogicException('Undefined property: ' . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a setting, flags, and returns an array of SourceAction instances
|
||||
*
|
||||
* @param string|array $setting Setting from the conf.php
|
||||
* @param int $flags Flags of extra checking the result
|
||||
* @param string $default Action to add if the result array is empty
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function fromSetting($setting, int $flags, string $default): array
|
||||
{
|
||||
if (gettype($setting) !== 'array') {
|
||||
$setting = [ $setting ];
|
||||
}
|
||||
$tmap = [];
|
||||
$list = [];
|
||||
foreach ($setting as $it) {
|
||||
if (gettype($it) === 'string') {
|
||||
$sa = new self($it);
|
||||
if ($sa->valid && !isset($tmap[$sa->type])) {
|
||||
if (($flags & self::FLAG_BASENAME) && !self::checkBasename($sa)) {
|
||||
continue;
|
||||
}
|
||||
$list[] = $sa;
|
||||
$tmap[$sa->type] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count($list) === 0) {
|
||||
$sa = new self($default);
|
||||
if ($sa->valid) {
|
||||
$list[] = $sa;
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the param is just a directory name without a path
|
||||
*
|
||||
* @param self $sa
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function checkBasename($sa): bool
|
||||
{
|
||||
return ($sa->type !== self::ACTION_MOVE || basename($sa->param) === $sa->param);
|
||||
}
|
||||
}
|
113
root/opt/dmarc-srg/classes/Sources/UploadedFilesSource.php
Normal file
113
root/opt/dmarc-srg/classes/Sources/UploadedFilesSource.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class UploadedFilesSource
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg\Sources;
|
||||
|
||||
use Liuch\DmarcSrg\ReportFile\ReportFile;
|
||||
use Liuch\DmarcSrg\Exception\SoftException;
|
||||
|
||||
/**
|
||||
* This class is designed to process report files from uploaded files.
|
||||
*/
|
||||
class UploadedFilesSource extends Source
|
||||
{
|
||||
private $index = 0;
|
||||
|
||||
/**
|
||||
* Returns an instance of the ReportFile class for the current file.
|
||||
*
|
||||
* @return ReportFile
|
||||
*/
|
||||
public function current(): object
|
||||
{
|
||||
if ($this->data['error'][$this->index] !== UPLOAD_ERR_OK) {
|
||||
throw new SoftException('Failed to upload the report file');
|
||||
}
|
||||
|
||||
$realfname = $this->data['name'][$this->index];
|
||||
$tempfname = $this->data['tmp_name'][$this->index];
|
||||
if (!is_uploaded_file($tempfname)) {
|
||||
throw new SoftException('Possible file upload attack');
|
||||
}
|
||||
|
||||
return ReportFile::fromFile($tempfname, $realfname, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the currect file.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function key(): int
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves forward to the next file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
++$this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewinds the position to the first file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->index = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current postion is valid
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return isset($this->data['name'][$this->index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns type of the source.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function type(): int
|
||||
{
|
||||
return Source::SOURCE_UPLOADED_FILE;
|
||||
}
|
||||
}
|
169
root/opt/dmarc-srg/classes/Statistics.php
Normal file
169
root/opt/dmarc-srg/classes/Statistics.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - a php parser, viewer and summary report generator for incoming dmarc reports.
|
||||
* copyright (c) 2020 aleksey andreev (liuch)
|
||||
*
|
||||
* available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the license.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* you should have received a copy of the gnu general public license along with
|
||||
* this program. if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class Statistics
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg;
|
||||
|
||||
use Liuch\DmarcSrg\DateTime;
|
||||
|
||||
/**
|
||||
* This class is designed to get statistics on DMARC reports of a specified period
|
||||
*/
|
||||
class Statistics
|
||||
{
|
||||
private $db = null;
|
||||
private $domain = null;
|
||||
private $range = [
|
||||
'date1' => null,
|
||||
'date2' => null
|
||||
];
|
||||
|
||||
/**
|
||||
* The constructor of the class, it only uses in static methods of this class
|
||||
*
|
||||
* @param Domain|null $domain The domain for which you need to get statistics, null for all the domains.
|
||||
* @param DatabaseController $db The database controller
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function __construct($domain, $db)
|
||||
{
|
||||
$this->domain = $domain;
|
||||
$this->db = $db ?? Core::instance()->database();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the class for the period from $date1 to $date2
|
||||
*
|
||||
* @param Domain|null $domain See the constructor for the details
|
||||
* @param DateTime $date1 The date you need statistics from
|
||||
* @param DateTime $date2 The date you need statistics to (not included)
|
||||
* @param DatabaseController $db The database controller
|
||||
*
|
||||
* @return Statistics Instance of the class
|
||||
*/
|
||||
public static function fromTo($domain, $date1, $date2, $db = null)
|
||||
{
|
||||
$r = new Statistics($domain, $db);
|
||||
$r->range['date1'] = $date1;
|
||||
$r->range['date2'] = $date2;
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the class for the last week
|
||||
*
|
||||
* @param Domain|null $domain See the constructor for the details
|
||||
* @param DatabaseController $db The database controller
|
||||
*
|
||||
* @return Statistics Instance of the class
|
||||
*/
|
||||
public static function lastWeek($domain, $db = null)
|
||||
{
|
||||
$r = new Statistics($domain, $db);
|
||||
$r->range['date1'] = new DateTime('monday last week');
|
||||
$r->range['date2'] = (clone $r->range['date1'])->add(new \DateInterval('P7D'));
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the class for the last month
|
||||
*
|
||||
* @param Domain|null $domain See the construct for the details
|
||||
* @param DatabaseController $db The database controller
|
||||
*
|
||||
* @return Statistics Instance of the class
|
||||
*/
|
||||
public static function lastMonth($domain, $db = null)
|
||||
{
|
||||
$r = new Statistics($domain, $db);
|
||||
$r->range['date1'] = new DateTime('midnight first day of last month');
|
||||
$r->range['date2'] = new DateTime('midnight first day of this month');
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of the class for the last N days
|
||||
*
|
||||
* @param Domain|null $domain See the construct for the details
|
||||
* @param int $ndays Number of days
|
||||
* @param DatabaseController $db The database controller
|
||||
*
|
||||
* @return Statistics Instance of the class
|
||||
*/
|
||||
public static function lastNDays($domain, int $ndays, $db = null)
|
||||
{
|
||||
$r = new Statistics($domain, $db);
|
||||
$r->range['date2'] = new DateTime('midnight');
|
||||
$r->range['date1'] = (clone $r->range['date2'])->sub(new \DateInterval("P{$ndays}D"));
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date from and the date to in an array
|
||||
*
|
||||
* @return array - The range of the statistics
|
||||
*/
|
||||
public function range(): array
|
||||
{
|
||||
return [ (clone $this->range['date1']), (clone $this->range['date2'])->sub(new \DateInterval('PT1S')) ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns summary information for e-mail messages as an array
|
||||
*
|
||||
* @return array Array with summary information
|
||||
*/
|
||||
public function summary(): array
|
||||
{
|
||||
return $this->db->getMapper('statistics')->summary($this->domain, $this->range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of ip-addresses from which the e-mail messages were received, with some statistics for each one
|
||||
*
|
||||
* @return array A list of ip-addresses with fields
|
||||
* `ip`, `emails`, `dkim_aligned`, `spf_aligned`
|
||||
*/
|
||||
public function ips(): array
|
||||
{
|
||||
return $this->db->getMapper('statistics')->ips($this->domain, $this->range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of organizations that sent the reports with some statistics for each one
|
||||
*
|
||||
* @return array List of organizations with fields `name`, `reports`, `emails`
|
||||
*/
|
||||
public function organizations(): array
|
||||
{
|
||||
return $this->db->getMapper('statistics')->organizations($this->domain, $this->range);
|
||||
}
|
||||
}
|
109
root/opt/dmarc-srg/classes/Status.php
Normal file
109
root/opt/dmarc-srg/classes/Status.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
|
||||
* Copyright (C) 2020 Aleksey Andreev (liuch)
|
||||
*
|
||||
* Available at:
|
||||
* https://github.com/liuch/dmarc-srg
|
||||
*
|
||||
* 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 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* =========================
|
||||
*
|
||||
* This file contains the class Status
|
||||
*
|
||||
* @category API
|
||||
* @package DmarcSrg
|
||||
* @author Aleksey Andreev (liuch)
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
|
||||
*/
|
||||
|
||||
namespace Liuch\DmarcSrg;
|
||||
|
||||
use Liuch\DmarcSrg\Settings\SettingsList;
|
||||
|
||||
/**
|
||||
* This class is designed to get the general state of DmarcSrg
|
||||
*/
|
||||
class Status
|
||||
{
|
||||
private $core = null;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
* @param Core $core
|
||||
*/
|
||||
public function __construct(object $core)
|
||||
{
|
||||
$this->core = $core;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns general state of DmarcSrg
|
||||
*
|
||||
* This method returns an array with general state of the modules Admin, Auth
|
||||
* and statistics for the last N days.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
$adm_res = $this->core->admin()->state();
|
||||
$res = [
|
||||
'state' => $adm_res['state']
|
||||
];
|
||||
|
||||
if (isset($adm_res['error_code'])) {
|
||||
$res['error_code'] = $adm_res['error_code'];
|
||||
if (isset($adm_res['message'])) {
|
||||
$res['message'] = $adm_res['message'];
|
||||
}
|
||||
if (isset($adm_res['debug_info'])) {
|
||||
$res['debug_info'] = $adm_res['debug_info'];
|
||||
}
|
||||
} elseif (isset($adm_res['database']['error_code'])) {
|
||||
$res['error_code'] = $adm_res['database']['error_code'];
|
||||
if (isset($adm_res['database']['message'])) {
|
||||
$res['message'] = $adm_res['database']['message'];
|
||||
}
|
||||
if (isset($adm_res['database']['debug_info'])) {
|
||||
$res['debug_info'] = $adm_res['database']['debug_info'];
|
||||
}
|
||||
} elseif (isset($adm_res['message'])) {
|
||||
$res['message'] = $adm_res['message'];
|
||||
} elseif (isset($adm_res['database']['message'])) {
|
||||
$res['message'] = $adm_res['database']['message'];
|
||||
}
|
||||
|
||||
if (!isset($res['error_code']) || $res['error_code'] === 0) {
|
||||
$days = SettingsList::getSettingByName('status.emails-for-last-n-days')->value();
|
||||
$stat = Statistics::lastNDays(null, $days);
|
||||
$res['emails'] = $stat->summary()['emails'];
|
||||
$res['emails']['days'] = $days;
|
||||
}
|
||||
|
||||
$auth = null;
|
||||
if ($this->core->auth()->isEnabled()) {
|
||||
$auth = $this->core->userId() !== false ? 'yes' : 'no';
|
||||
} else {
|
||||
$auth = 'disabled';
|
||||
}
|
||||
$res['authenticated'] = $auth;
|
||||
$res['version'] = Core::APP_VERSION;
|
||||
$res['php_version'] = phpversion();
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user