232 lines
7.1 KiB
PHP
232 lines
7.1 KiB
PHP
<?php
|
|
// Define a constant to indicate index.php is included, not accessed directly
|
|
define('FILE_IS_INCLUDED', true);
|
|
|
|
// Include the index.php file, which contains shared configuration and logic
|
|
include('index.php');
|
|
|
|
/**
|
|
* Main logic handler
|
|
*/
|
|
main();
|
|
|
|
function main(): void {
|
|
// Ensure the data directory exists and is writable
|
|
if (!ensureDirectoryExists(DATA_DIR)) {
|
|
error_deny();
|
|
}
|
|
|
|
// Get client IP address
|
|
$clientIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
|
|
|
// Deny access if the client IP is currently blocked
|
|
if (isBlockedIP($clientIp)) {
|
|
error_deny();
|
|
}
|
|
|
|
// Retrieve parameters from GET or POST
|
|
$name = getQuery('name', null);
|
|
$domain = getQuery('domain', null);
|
|
$token = getQuery('token', null);
|
|
$ip = getQuery('ip', $clientIp);
|
|
$port = getQuery('port', 80);
|
|
$protocol = getQuery('protocol', 'http');
|
|
|
|
// Load the authorized tokens
|
|
$tokens = loadTokens();
|
|
|
|
// Validate required input and IP address format
|
|
if (isEmpty($name) || isEmpty($token) || !filter_var($ip, FILTER_VALIDATE_IP)) {
|
|
error_deny();
|
|
}
|
|
|
|
// Check if token is valid for the given name
|
|
if (!array_key_exists($name, $tokens) || $tokens[$name] !== $token) {
|
|
recordFailure($clientIp);
|
|
error_deny();
|
|
}
|
|
|
|
// Ensure the protocol is either http or https
|
|
if (!in_array($protocol, ['http', 'https'])) {
|
|
error_deny();
|
|
}
|
|
|
|
// If port is not set, use default based on protocol
|
|
if ($port === null || $port === '') {
|
|
$port = ($protocol === 'https') ? '443' : '80';
|
|
} elseif (!preg_match('/^\d{2,5}$/', $port)) {
|
|
error_deny();
|
|
}
|
|
|
|
if ($domain === null || $domain === '') {
|
|
$domain = $ip;
|
|
}
|
|
|
|
// Update metadata and log the change
|
|
updateMeta($name, $domain, $ip, $port, $protocol);
|
|
}
|
|
|
|
/**
|
|
* Loads token list from TOKENFILE or creates an example if file does not exist
|
|
*/
|
|
function loadTokens(): array {
|
|
if (!file_exists(TOKENFILE)) {
|
|
saveJson(TOKENFILE, DEF_EXAMPLE);
|
|
echo "Token file created at " . TOKENFILE . ". Please edit it and rerun.\n";
|
|
exit (1);
|
|
}
|
|
return loadJson(TOKENFILE);
|
|
}
|
|
|
|
/**
|
|
* Updates the meta.json file with new connection data for a given name (domain),
|
|
* and logs the update if changes are detected.
|
|
*
|
|
* If any field (IP, port, protocol, domain) differs from the stored data,
|
|
* the entry is updated and flagged with "changed": 1. The location field is
|
|
* preserved if it already exists and is not empty.
|
|
*
|
|
* @param string $name The identifier name (usually domain alias)
|
|
* @param string $domain The target domain for proxy_pass
|
|
* @param string $ip The updated IP address
|
|
* @param int $port The proxy port to use
|
|
* @param string $protocol Protocol to use ('http' or 'https')
|
|
* @return void
|
|
*/
|
|
function updateMeta(string $name, string $domain, string $ip, int $port, string $protocol): void {
|
|
$meta = loadJson(METAFILE);
|
|
$resp = "No changes for $name\n";
|
|
|
|
// New data coming in from client
|
|
$incoming = [
|
|
'domain' => $domain,
|
|
'ip' => $ip,
|
|
'port' => $port,
|
|
'protocol' => $protocol,
|
|
];
|
|
|
|
// Existing data for this entry, if any
|
|
$existing = $meta[$name] ?? [];
|
|
|
|
// If any changes detected — update
|
|
if (array_diff_assoc($incoming, $existing)) {
|
|
$incoming['location'] = DEF_LOCATION; // Default location
|
|
$incoming['time'] = date('c'); // Timestamp
|
|
$incoming['changed'] = 1; // Flag as changed
|
|
|
|
// Preserve existing location if defined and not empty
|
|
if (array_key_exists('location', $existing)) {
|
|
if (!isEmpty($existing['location'])) {
|
|
$incoming['location'] = $existing['location'];
|
|
}
|
|
}
|
|
|
|
// Save updated meta
|
|
$meta[$name] = $incoming;
|
|
|
|
saveJson (METAFILE, $meta);
|
|
|
|
// Log the change
|
|
logChange ($name, $incoming);
|
|
$resp = "Updated: [$protocol://$ip:$port] for $name\n";
|
|
}
|
|
|
|
// Always respond with 200 OK and a message
|
|
http_response_code(200);
|
|
echo $resp;
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks whether a given IP address is currently blocked,
|
|
* based on a JSON file containing expiration timestamps.
|
|
*
|
|
* @param string $ip IP address to check
|
|
* @return bool True if the IP is currently blocked, false otherwise
|
|
*/
|
|
function isBlockedIP(string $ip): bool {
|
|
// Load blocklist from file, or use an empty list if file is missing
|
|
$blocklist = loadJson(BLOCKLIST_FILE);
|
|
|
|
// Return true if IP exists in blocklist and its expiration time is in the future
|
|
return isset($blocklist[$ip]) && time() < $blocklist[$ip];
|
|
}
|
|
|
|
|
|
/**
|
|
* Ensures that the specified directory (or its parent) exists.
|
|
*
|
|
* If the provided path is a file, the function checks the directory it would belong to.
|
|
* If the directory does not exist and $create is true, it attempts to create it.
|
|
*
|
|
* @param string $path The path to check (can be a file or a directory)
|
|
* @param bool $create Whether to create the directory if it doesn't exist (default: true)
|
|
* @return bool True if the directory exists or was successfully created, false otherwise
|
|
*/
|
|
function ensureDirectoryExists(string $path, bool $create = true): bool {
|
|
// Determine the directory: if it's not already a dir, get its parent
|
|
$dir = is_dir($path) ? $path : dirname($path);
|
|
|
|
// If the directory doesn't exist and we're allowed to create it
|
|
if (!is_dir($dir) && $create) {
|
|
return mkdir($dir, 0755, true);
|
|
}
|
|
|
|
// Directory exists or was not created intentionally
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Appends a structured log entry to the log file.
|
|
*
|
|
* Each log entry is an associative array containing the provided data,
|
|
* merged with a `name` identifier. The function preserves existing log
|
|
* entries by appending to the decoded array before writing back to the file.
|
|
*
|
|
* @param string $name Identifier for the log entry (e.g. domain or config name)
|
|
* @param array $data Additional data to include in the log entry
|
|
* @return void
|
|
*/
|
|
function logChange(string $name, array $data): void {
|
|
// Load existing log data or start with an empty array
|
|
$log = loadJson(LOGFILE);
|
|
|
|
// Merge the name into the entry and append
|
|
$log[] = array_merge(['name' => $name], $data);
|
|
|
|
// Write updated log back to file
|
|
saveJson(LOGFILE, $log);
|
|
}
|
|
|
|
/**
|
|
* Records a failed authentication attempt for the given IP address.
|
|
*
|
|
* Tracks the number of failures in `failures.json`. If an IP reaches
|
|
* the failure threshold (e.g., 3), it is added to the blocklist with
|
|
* an expiration timestamp defined by `BLOCK_DURATION_SECONDS`.
|
|
*
|
|
* @param string $ip The IP address that failed authentication
|
|
* @return void
|
|
*/
|
|
function recordFailure(string $ip): void {
|
|
// Load the current failure count from the failures file
|
|
$failures = loadJson(FAILURESFILE);
|
|
|
|
// Increment failure count for the IP
|
|
$failures[$ip] = ($failures[$ip] ?? 0) + 1;
|
|
|
|
// If threshold exceeded, add IP to blocklist and reset failure count
|
|
if ($failures[$ip] >= 3) {
|
|
$blocklist = loadJson(BLOCKLIST_FILE);
|
|
$blocklist[$ip] = time() + BLOCK_DURATION_SECONDS;
|
|
|
|
saveJson (BLOCKLIST_FILE, $blocklist);
|
|
unset ($failures[$ip]); // Reset after blocking
|
|
}
|
|
|
|
// Save updated failure log
|
|
saveJson (FAILURESFILE, $failures);
|
|
}
|
|
|