$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); }