// ============================================ // ENHANCED SECURITY FUNCTIONS // ============================================ /** * Secure password hashing (recommended replacement for md5) */ function secure_hash_password(string $password): string { // Use password_hash for new passwords return password_hash($password, PASSWORD_DEFAULT); } /** * Verify password against hash */ function verify_password(string $password, string $hash): bool { // First check if it's an old md5 hash if (strlen($hash) === 32 && ctype_xdigit($hash)) { // Old md5 hash - verify and upgrade if (md5($password) === $hash) { // Password is correct, but stored as md5 // You can upgrade it here if needed return true; } return false; } // New password_hash verification return password_verify($password, $hash); } /** * Generate secure random token */ function generate_token(int $length = 32): string { return bin2hex(random_bytes($length)); } /** * Validate username format */ function validate_username(string $username): bool { return preg_match("/^[a-zA-Z0-9_]{3,20}$/", $username) === 1; } /** * Validate password strength */ function validate_password_strength(string $password): array { $errors = []; if (strlen($password) < 8) { $errors[] = "Password must be at least 8 characters"; } if (!preg_match("/[A-Z]/", $password)) { $errors[] = "Password must contain at least one uppercase letter"; } if (!preg_match("/[a-z]/", $password)) { $errors[] = "Password must contain at least one lowercase letter"; } if (!preg_match("/[0-9]/", $password)) { $errors[] = "Password must contain at least one number"; } if (!preg_match("/[!@#$%^&*()\-_=+{};:,<.>]/", $password)) { $errors[] = "Password must contain at least one special character"; } return $errors; } /** * Rate limiting for registration attempts */ function check_rate_limit(string $ip, int $limit = 5, int $timeout = 3600): bool { $key = 'reg_attempts_' . md5($ip); if (!isset($_SESSION[$key])) { $_SESSION[$key] = [ 'count' => 1, 'first_attempt' => time() ]; return true; } $attempts = $_SESSION[$key]; // Reset if timeout passed if (time() - $attempts['first_attempt'] > $timeout) { $_SESSION[$key] = [ 'count' => 1, 'first_attempt' => time() ]; return true; } // Check if limit exceeded if ($attempts['count'] >= $limit) { return false; } // Increment count $_SESSION[$key]['count']++; return true; } /** * Get client IP address securely */ function get_client_ip(): string { $ip_keys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR']; foreach ($ip_keys as $key) { if (isset($_SERVER[$key]) && filter_var($_SERVER[$key], FILTER_VALIDATE_IP)) { return $_SERVER[$key]; } } return '0.0.0.0'; } /** * Log security event */ function log_security_event(string $event, array $data = []): void { global $config; $log_entry = date('Y-m-d H:i:s') . " - " . $event; if (!empty($data)) { $log_entry .= " - " . json_encode($data); } $log_entry .= "\n"; // Log to file $log_file = __DIR__ . '/../logs/security_' . date('Y-m-d') . '.log'; file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX); // Optionally log to database if (isset($config['log_to_db']) && $config['log_to_db'] == '1') { try { $db = Database::getConnection(); $sql = "INSERT INTO security_logs (event, data, ip_address, user_agent, created_at) VALUES (:event, :data, :ip, :ua, :created)"; $stmt = $db->prepare($sql); $stmt->execute([ ':event' => $event, ':data' => json_encode($data), ':ip' => get_client_ip(), ':ua' => $_SERVER['HTTP_USER_AGENT'] ?? '', ':created' => time() ]); } catch (Exception $e) { // Silently fail logging to DB } } } // ============================================ // BACKWARD COMPATIBILITY (Remove after updating all files) // ============================================ /** * Temporary backward compatibility * REMOVE THIS AFTER UPDATING ALL FILES TO PDO */ if (!function_exists('db_connection')) { function db_connection() { return Database::getConnection(); } } if (!function_exists('db_close')) { function db_close() { // PDO handles connection closing automatically return true; } }