ip2nginx/index.php
2025-05-21 13:35:18 +02:00

280 lines
8.1 KiB
PHP

<?php
/**
* Initializes core constants used across the ip2nginx project.
*
* This script is **only** intended to be included by other scripts.
* It prevents direct execution and ensures all key configuration constants are defined.
*/
// If the script is included and the FILE_IS_INCLUDED flag is defined,
// proceed to define the required constants.
if (defined('FILE_IS_INCLUDED')) {
// === Core data paths ===
// Define DATA_DIR if not already defined (used to store data files)
if (!defined('DATA_DIR')) {
define('DATA_DIR', __DIR__ . '/data'); // Directory where data files are stored
}
// Define METAFILE if not already defined (path to meta.json file)
if (!defined('METAFILE')) {
define('METAFILE', DATA_DIR . '/meta.json'); // Central file for domain metadata
}
// === Nginx configuration settings ===
// Define VHOST_BASE if not already defined
if (!defined('VHOST_BASE')) {
define('VHOST_BASE', '/var/www/vhosts/system'); // Base path to vhost configs (Plesk)
}
// Define NGINX_CONF_NAME if not already defined
if (!defined('NGINX_CONF_NAME')) {
define('NGINX_CONF_NAME', 'nginx.conf'); // Target file to modify inside each vhost
}
// === Additional configuration ===
// Define blocklist file
if (!defined('BLOCKLIST_FILE')) {
define('BLOCKLIST_FILE', DATA_DIR . '/blocklist.json'); // File storing blocked IPs
}
// Define authentication failure tracking file
if (!defined('FAILURESFILE')) {
define('FAILURESFILE', DATA_DIR . '/failures.json'); // Tracks failed login attempts
}
// Define general log file
if (!defined('LOGFILE')) {
define('LOGFILE', DATA_DIR . '/log.json'); // Change log
}
// Define API token file
if (!defined('TOKENFILE')) {
define('TOKENFILE', DATA_DIR . '/token.json'); // Token registry
}
// Define default location block used if none is set
if (!defined('DEF_LOCATION')) {
define('DEF_LOCATION', '/'); // Default nginx location path
}
// Define default example block for meta.json
if (!defined('DEF_EXAMPLE')) {
define('DEF_EXAMPLE', ["example.com" => "SECRET_EXAMPLE"]);
}
// === Security settings ===
// Block duration in seconds (48 hours by default)
if (!defined('BLOCK_DURATION_SECONDS')) {
define('BLOCK_DURATION_SECONDS', 48 * 3600); // Duration of IP block after failures
}
} else {
// If the file was accessed directly, deny access
error_deny();
}
/**
* Sends a standard 403 Forbidden HTTP response with an HTML message
* and terminates the script.
*/
function error_deny() : void {
// Set HTTP response status code to 403
http_response_code(403);
// Explicitly send HTTP headers for 403 Forbidden and HTML content type
header('HTTP/1.1 403 Forbidden');
header('Content-Type: text/html; charset=utf-8');
// Output a simple HTML page explaining the forbidden access
echo "<HTML>\n";
echo "<HEAD>\n";
echo "<TITLE>403 Forbidden</TITLE>\n";
echo "</HEAD>\n";
echo "<BODY>\n";
echo "<H1>Forbidden</H1>\n";
echo "You do not have permission to access this document.\n";
echo "<P>\n";
echo "<HR>\n";
echo "<ADDRESS>\n";
echo "Web Server at " . $_SERVER['SERVER_NAME'] . "\n";
echo "</ADDRESS>\n";
echo "</BODY>\n";
echo "</HTML>\n";
// Exit the script with status code 403
exit(403);
}
/**
* Retrieves a request parameter from POST or GET with fallback to default.
*
* @param string $name The name of the parameter to retrieve.
* @param mixed $def The default value if the parameter is not found or empty.
* @return mixed The parameter value or the default.
*/
function getQuery(string $name, mixed $def): mixed {
// Check POST first, then GET, fallback to default
$val = $_POST[$name] ?? $_GET[$name] ?? $def;
// Return default if value is empty (including trimmed empty strings)
return isEmpty($val) ? $def : $val;
}
/**
* Retrieves multiple request parameters from POST or GET with fallback to default.
* Key matching is case-insensitive.
*
* @param array $params Array of parameters in the format:
* [
* 'KeyName' => defaultValue,
* ...
* ]
* @return array Associative array:
* [
* 'KeyName' => [
* 'value' => mixed,
* 'source' => 'post' | 'get' | 'default'
* ],
* ...
* ]
*/
function getQueryMulti(array $params): array {
$result = [];
// Lowercase copies of GET and POST for case-insensitive matching
$post = array_change_key_case($_POST, CASE_LOWER);
$get = array_change_key_case($_GET, CASE_LOWER);
foreach ($params as $key => $default) {
$keyLower = strtolower($key);
$value = $default;
$source = 'default';
if (isset($post[$keyLower]) && !isEmpty($post[$keyLower])) {
$value = $post[$keyLower];
$source = 'post';
} elseif (isset($get[$keyLower]) && !isEmpty($get[$keyLower])) {
$value = $get[$keyLower];
$source = 'get';
}
$result[$key] = [
'value' => $value,
'source' => $source
];
}
return $result;
}
/**
* Checks if a given value is considered "empty".
* - Returns true for null, empty strings, empty arrays, 0, "0", etc. (like PHP's `empty()`)
* - Additionally, for strings, also trims whitespace before checking emptiness
*
* @param mixed $val The value to evaluate
* @return bool True if the value is empty or whitespace-only, false otherwise
*/
function isEmpty(mixed $val): bool {
// Handle typical empty values (null, "", 0, [], false, etc.)
if (empty($val)) {
return true;
}
// If it's a string, trim and check again
if (is_string($val)) {
if (trim($val) === '') {
return true;
}
}
return false;
}
/**
* Loads and parses a JSON file into an associative array.
*
* Checks if the file exists and is readable before attempting to decode it.
* If the JSON is invalid, an error message is printed.
* If decoding fails or the result is not an array, an empty array is returned.
*
* @param string $filename Path to the JSON file
* @return array Parsed associative array or empty array on error
*/
function loadJson(string $filename): array {
$result = [];
// Attempt to read and decode the file if it exists and is readable
if (file_exists($filename) && is_readable($filename)) {
$result = json_decode(file_get_contents($filename), true);
// Log an error if JSON is invalid
if (json_last_error() !== JSON_ERROR_NONE) {
echo "Invalid JSON in $filename: " . json_last_error_msg() . "\n";
}
}
// Ensure the result is an array
if (!is_array($result)) {
$result = [];
}
return $result;
}
/**
* Saves a PHP array to a JSON file in pretty format.
*
* - Ensures the target file is writable if it already exists.
* - Automatically replaces null or empty input with an empty array.
* - Returns true on success, false on any failure.
*
* @param string $filename The path to the JSON file to write
* @param array|null $val The data to save (null or empty → [])
* @return bool True if saved successfully, false otherwise
*/
function saveJson(string $filename, ?array $val): bool {
// Default to empty array if null or empty
if (isEmpty($val)) {
$val = [];
}
// Encode to JSON
$encoded = json_encode($val, JSON_PRETTY_PRINT);
if ($encoded === false) {
echo "Failed to encode $filename: " . json_last_error_msg() . "\n";
return false;
}
// If file exists, check for write permissions
if (file_exists($filename)) {
if (!is_writable($filename)) {
echo "No access to write in $filename\n";
return false;
}
}
-+
// Try writing up to 10 times with 250ms delay
$success = false;
$attempt = 0;
for ($attempt = 1; $attempt <= 10; $attempt++) {
if (@file_put_contents($filename, $encoded) !== false) {
$success = true;
break;
}
usleep(250 * 1000); // 250 ms delay
}
if (!$success) {
echo "Failed to write to $filename after $attempt attempts\n";
}
return $success;
}