From a0a3a9ca522863fe9546084355067a0b7ad5f051 Mon Sep 17 00:00:00 2001 From: billz Date: Thu, 27 Nov 2025 18:45:12 +0100 Subject: [PATCH] Refactor: encapsulate WiFi client operations in WiFiManager class --- includes/configure_client.php | 161 ++++---------- src/RaspAP/Networking/Hotspot/WiFiManager.php | 208 +++++++++++++++++- 2 files changed, 252 insertions(+), 117 deletions(-) mode change 100755 => 100644 includes/configure_client.php diff --git a/includes/configure_client.php b/includes/configure_client.php old mode 100755 new mode 100644 index 02360e8f..4825e4cc --- a/includes/configure_client.php +++ b/includes/configure_client.php @@ -20,15 +20,12 @@ function DisplayWPAConfig() if (isset($_POST['connect'])) { $netid = intval($_POST['connect']); - $cmd = "sudo wpa_cli -i $iface select_network $netid"; - $return = shell_exec($cmd); - sleep(3); - $cmd = "sudo wpa_cli -i $iface reassociate"; - $return = shell_exec($cmd); - if (trim($return) == "FAIL") { - $status->addMessage('WPA command line client returned failure. Check your adapter.', 'danger'); - } else { + $clientInterface = $_SESSION['wifi_client_interface']; + + if ($wifi->connectToNetwork($clientInterface, $netid)) { $status->addMessage('New network selected', 'success'); + } else { + $status->addMessage('WPA command line client returned failure. Check your adapter.', 'danger'); } } elseif (isset($_POST['wpa_reinit'])) { $status->addMessage('Attempting to reinitialize wpa_supplicant', 'warning'); @@ -36,125 +33,57 @@ function DisplayWPAConfig() $result = $wifi->reinitializeWPA($force_remove); } elseif (isset($_POST['client_settings'])) { $tmp_networks = $networks; - if ($wpa_file = fopen('/tmp/wifidata', 'w')) { - fwrite($wpa_file, 'ctrl_interface=DIR=' . RASPI_WPA_CTRL_INTERFACE . ' GROUP=netdev' . PHP_EOL); - fwrite($wpa_file, 'update_config=1' . PHP_EOL); - foreach (array_keys($_POST) as $post) { + foreach (array_keys($_POST) as $post) { - if (preg_match('/delete(\d+)/', $post, $post_match)) { - $network = $tmp_networks[$_POST['ssid' . $post_match[1]]]; - $netid = $network['index']; - exec('sudo wpa_cli -i ' . $iface . ' disconnect ' . $netid); - exec('sudo wpa_cli -i ' . $iface . ' remove_network ' . $netid); - unset($tmp_networks[$_POST['ssid' . $post_match[1]]]); - } elseif (preg_match('/disconnect(\d+)/', $post, $post_match)) { - $network = $tmp_networks[$_POST['ssid' . $post_match[1]]]; - $netid = $network['index']; - exec('sudo wpa_cli -i ' . $iface . ' disconnect ' . $netid); - exec('sudo wpa_cli -i ' . $iface . ' remove_network ' . $netid); - sleep(2); - } elseif (preg_match('/update(\d+)/', $post, $post_match)) { - // NB, multiple protocols are separated with a forward slash ('/') - $tmp_networks[$_POST['ssid' . $post_match[1]]] = array( - 'protocol' => ( $_POST['protocol' . $post_match[1]] === $wifi::SECURITY_OPEN ? $wifi::SECURITY_OPEN : 'WPA' ), - 'passphrase' => $_POST['passphrase' . $post_match[1]], - 'configured' => true - ); - if (array_key_exists('priority' . $post_match[1], $_POST)) { - $tmp_networks[$_POST['ssid' . $post_match[1]]]['priority'] = $_POST['priority' . $post_match[1]]; - } - $network = $tmp_networks[$_POST['ssid' . $post_match[1]]]; - $ssid = escapeshellarg('"'.$_POST['ssid' . $post_match[1]].'"'); - $psk = escapeshellarg('"'.$_POST['passphrase' . $post_match[1]].'"'); - $netid = trim(shell_exec("sudo wpa_cli -i $iface add_network")); - if (isset($netid)) { - $commands = [ - "sudo wpa_cli -i $iface set_network $netid ssid $ssid", - "sudo wpa_cli -i $iface set_network $netid psk $psk", - "sudo wpa_cli -i $iface enable_network $netid" - ]; - foreach ($commands as $cmd) { - exec($cmd); - } - } else { - $status->addMessage('Unable to add network with WPA command line client', 'warning'); - } + if (preg_match('/delete(\d+)/', $post, $post_match)) { + $network = $tmp_networks[$_POST['ssid' . $post_match[1]]]; + $netid = $network['index']; + $clientInterface = $_SESSION['wifi_client_interface']; + $wifi->deleteNetwork($clientInterface, $netid); + unset($tmp_networks[$_POST['ssid' . $post_match[1]]]); + } elseif (preg_match('/disconnect(\d+)/', $post, $post_match)) { + $network = $tmp_networks[$_POST['ssid' . $post_match[1]]]; + $netid = $network['index']; + $clientInterface = $_SESSION['wifi_client_interface']; + $wifi->disconnectNetwork($clientInterface, $netid); + } elseif (preg_match('/update(\d+)/', $post, $post_match)) { + // NB, multiple protocols are separated with a forward slash ('/') + $protocol = $_POST['protocol' . $post_match[1]] === $wifi::SECURITY_OPEN ? $wifi::SECURITY_OPEN : 'WPA'; + $tmp_networks[$_POST['ssid' . $post_match[1]]] = array( + 'protocol' => $protocol, + 'passphrase' => $_POST['passphrase' . $post_match[1]] ?? '', + 'configured' => true + ); + if (array_key_exists('priority' . $post_match[1], $_POST)) { + $tmp_networks[$_POST['ssid' . $post_match[1]]]['priority'] = $_POST['priority' . $post_match[1]]; + } + $network = $tmp_networks[$_POST['ssid' . $post_match[1]]]; + + $clientInterface = $_SESSION['wifi_client_interface']; + $ssid = $_POST['ssid' . $post_match[1]]; + $passphrase = $_POST['passphrase' . $post_match[1]] ?? ''; + + $netid = $wifi->updateNetwork($clientInterface, $ssid, $passphrase, $protocol); + if ($netid === null) { + $status->addMessage('Unable to add network with WPA command line client', 'warning'); } } + } - $ok = true; - foreach ($tmp_networks as $ssid => $network) { - if ($network['protocol'] === $wifi::SECURITY_OPEN) { - fwrite($wpa_file, "network={".PHP_EOL); - fwrite($wpa_file, "\tssid=\"".$ssid."\"".PHP_EOL); - fwrite($wpa_file, "\tkey_mgmt=NONE".PHP_EOL); - fwrite($wpa_file, "\tscan_ssid=1".PHP_EOL); - if (array_key_exists('priority', $network)) { - fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL); - } - fwrite($wpa_file, "}".PHP_EOL); - } else { - if (strlen($network['passphrase']) >=8 && strlen($network['passphrase']) <= 63) { - unset($wpa_passphrase); - unset($line); - exec('wpa_passphrase '. $wifi->ssid2utf8( escapeshellarg($ssid) ) . ' ' . escapeshellarg($network['passphrase']), $wpa_passphrase); - foreach ($wpa_passphrase as $line) { - if (preg_match('/^\s*}\s*$/', $line)) { - if (array_key_exists('priority', $network)) { - fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL); - } - fwrite($wpa_file, $line.PHP_EOL); - } else { - if ( preg_match('/\\\\x[0-9A-Fa-f]{2}/',$ssid) && strpos($line, "ssid=\"") !== false ) { - fwrite($wpa_file, "\tssid=P\"".$ssid."\"".PHP_EOL); - } else { - fwrite($wpa_file, $line.PHP_EOL); - } - } - } - } elseif (strlen($network['passphrase']) == 0 && strlen($network['passkey']) == 64) { - $line = "\tpsk=" . $network['passkey']; - fwrite($wpa_file, "network={".PHP_EOL); - fwrite($wpa_file, "\tssid=\"".$ssid."\"".PHP_EOL); - fwrite($wpa_file, $line.PHP_EOL); - if (array_key_exists('priority', $network)) { - fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL); - } - fwrite($wpa_file, "}".PHP_EOL); - } else { - $status->addMessage('WPA passphrase must be between 8 and 63 characters', 'danger'); - $ok = false; - } - } - } + $clientInterface = $_SESSION['wifi_client_interface']; + $result = $wifi->writeWpaSupplicant($tmp_networks, $clientInterface); - if ($ok) { - system('sudo cp /tmp/wifidata ' . RASPI_WPA_SUPPLICANT_CONFIG, $returnval); - if ($returnval == 0) { - exec('sudo wpa_cli -i ' . $_SESSION['wifi_client_interface'] . ' reconfigure', $reconfigure_out, $reconfigure_return); - if ($reconfigure_return == 0) { - $status->addMessage('Wifi settings updated successfully', 'success'); - $networks = $tmp_networks; - } else { - $status->addMessage('Wifi settings updated but cannot restart (cannot execute "wpa_cli reconfigure")', 'danger'); - } - } else { - $status->addMessage('Wifi settings failed to be updated', 'danger'); - } - } + if ($result['success']) { + $status->addMessage($result['message'], 'success'); + $networks = $tmp_networks; } else { - $status->addMessage('Failed to update wifi settings', 'danger'); + $status->addMessage($result['message'], 'danger'); } } $clientInterface = $_SESSION['wifi_client_interface']; - - exec('ip a show '.$clientInterface, $stdoutIp); - $stdoutIpAllLinesGlued = implode(" ", $stdoutIp); - $stdoutIpWRepeatedSpaces = preg_replace('/\s\s+/', ' ', $stdoutIpAllLinesGlued); - preg_match('/state (UP|DOWN)/i', $stdoutIpWRepeatedSpaces, $matchesState) || $matchesState[1] = 'unknown'; - $ifaceStatus = strtolower($matchesState[1]) ? "up" : "down"; + $ifaceStatus = $wifi->getInterfaceStatus($clientInterface); echo renderTemplate("configure_client", compact("status", "clientInterface", "ifaceStatus")); } diff --git a/src/RaspAP/Networking/Hotspot/WiFiManager.php b/src/RaspAP/Networking/Hotspot/WiFiManager.php index fc0e29a2..814276ab 100644 --- a/src/RaspAP/Networking/Hotspot/WiFiManager.php +++ b/src/RaspAP/Networking/Hotspot/WiFiManager.php @@ -552,5 +552,211 @@ CONF; } } -} + /** + * Gets the operational status of a network interface + * + * @param string $interface The network interface name + * @return string Returns 'up', 'down', or 'unknown' + */ + public function getInterfaceStatus(string $interface): string + { + exec('ip a show ' . escapeshellarg($interface), $output); + $outputGlued = implode(" ", $output); + $outputNormalized = preg_replace('/\s\s+/', ' ', $outputGlued); + + if (preg_match('/state (UP|DOWN)/i', $outputNormalized, $matches)) { + return strtolower($matches[1]); + } + + return 'unknown'; + } + + /** + * Connects to a network using wpa_cli + * + * @param string $interface network interface name + * @param int $netid network ID to connect to + * @return bool true on success, false on failure + */ + public function connectToNetwork(string $interface, int $netid): bool + { + $iface = escapeshellarg($interface); + + $cmd = "sudo wpa_cli -i $iface select_network $netid"; + $selectResult = shell_exec($cmd); + + if ($selectResult === null || trim($selectResult) === "FAIL") { + return false; + } + sleep(3); + + $cmd = "sudo wpa_cli -i $iface reassociate"; + $reassociateResult = shell_exec($cmd); + + if ($reassociateResult !== null) { + $trimmed = trim($reassociateResult); + if ($trimmed === "FAIL") { + return false; + } + } + + return true; + } + + /** + * Deletes a network from wpa_cli + * + * @param string $interface network interface name + * @param int $netid network ID to delete + * @return void + */ + public function deleteNetwork(string $interface, int $netid): void + { + $iface = escapeshellarg($interface); + + exec("sudo wpa_cli -i $iface disconnect $netid"); + exec("sudo wpa_cli -i $iface remove_network $netid"); + } + + /** + * Disconnects from a network using wpa_cli + * + * @param string $interface network interface name + * @param int $netid network ID to disconnect from + * @return void + */ + public function disconnectNetwork(string $interface, int $netid): void + { + $iface = escapeshellarg($interface); + + exec("sudo wpa_cli -i $iface disconnect $netid"); + exec("sudo wpa_cli -i $iface remove_network $netid"); + sleep(2); + } + + /** + * Updates/adds a network via wpa_cli + * + * @param string $interface network interface name + * @param string $ssid network SSID + * @param string $passphrase network passphrase + * @param string $protocol security protocol (OPEN or WPA) + * @return int|null network ID on success, null on failure + */ + public function updateNetwork(string $interface, string $ssid, string $passphrase, string $protocol = 'WPA'): ?int + { + $iface = escapeshellarg($interface); + $escapedSsid = escapeshellarg('"' . $ssid . '"'); + + $netid = shell_exec("sudo wpa_cli -i $iface add_network"); + + if ($netid === null || !is_numeric(trim($netid))) { + return null; + } + + $netid = trim($netid); + $commands = [ + "sudo wpa_cli -i $iface set_network $netid ssid $escapedSsid" + ]; + + if ($protocol === self::SECURITY_OPEN) { + $commands[] = "sudo wpa_cli -i $iface set_network $netid key_mgmt NONE"; + } else { + $escapedPsk = escapeshellarg('"' . $passphrase . '"'); + $commands[] = "sudo wpa_cli -i $iface set_network $netid psk $escapedPsk"; + } + + $commands[] = "sudo wpa_cli -i $iface enable_network $netid"; + + foreach ($commands as $cmd) { + exec($cmd); + } + + return (int)$netid; + } + + /** + * Writes a wpa_supplicant configuration and applies it + * + * @param array $networks array of network configurations + * @param string $interface the network interface name + * @return array Array with 'success' (bool) and 'message' (string) + */ + public function writeWpaSupplicant(array $networks, string $interface): array + { + $wpa_file = fopen('/tmp/wifidata', 'w'); + if (!$wpa_file) { + return ['success' => false, 'message' => 'Failed to update wifi settings']; + } + + fwrite($wpa_file, 'ctrl_interface=DIR=' . RASPI_WPA_CTRL_INTERFACE . ' GROUP=netdev' . PHP_EOL); + fwrite($wpa_file, 'update_config=1' . PHP_EOL); + + $ok = true; + foreach ($networks as $ssid => $network) { + if ($network['protocol'] === self::SECURITY_OPEN) { + fwrite($wpa_file, "network={".PHP_EOL); + fwrite($wpa_file, "\tssid=\"".$ssid."\"".PHP_EOL); + fwrite($wpa_file, "\tkey_mgmt=NONE".PHP_EOL); + fwrite($wpa_file, "\tscan_ssid=1".PHP_EOL); + if (array_key_exists('priority', $network)) { + fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL); + } + fwrite($wpa_file, "}".PHP_EOL); + } else { + if (strlen($network['passphrase']) >= 8 && strlen($network['passphrase']) <= 63) { + unset($wpa_passphrase); + unset($line); + exec('wpa_passphrase '. $this->ssid2utf8(escapeshellarg($ssid)) . ' ' . escapeshellarg($network['passphrase']), $wpa_passphrase); + foreach ($wpa_passphrase as $line) { + if (preg_match('/^\s*}\s*$/', $line)) { + if (array_key_exists('priority', $network)) { + fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL); + } + fwrite($wpa_file, $line.PHP_EOL); + } else { + if (preg_match('/\\\\x[0-9A-Fa-f]{2}/', $ssid) && strpos($line, "ssid=\"") !== false) { + fwrite($wpa_file, "\tssid=P\"".$ssid."\"".PHP_EOL); + } else { + fwrite($wpa_file, $line.PHP_EOL); + } + } + } + } elseif (strlen($network['passphrase']) == 0 && strlen($network['passkey']) == 64) { + $line = "\tpsk=" . $network['passkey']; + fwrite($wpa_file, "network={".PHP_EOL); + fwrite($wpa_file, "\tssid=\"".$ssid."\"".PHP_EOL); + fwrite($wpa_file, $line.PHP_EOL); + if (array_key_exists('priority', $network)) { + fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL); + } + fwrite($wpa_file, "}".PHP_EOL); + } else { + $ok = false; + fclose($wpa_file); + return ['success' => false, 'message' => 'WPA passphrase must be between 8 and 63 characters']; + } + } + } + + fclose($wpa_file); + + if ($ok) { + system('sudo cp /tmp/wifidata ' . RASPI_WPA_SUPPLICANT_CONFIG, $returnval); + if ($returnval == 0) { + exec('sudo wpa_cli -i ' . escapeshellarg($interface) . ' reconfigure', $reconfigure_out, $reconfigure_return); + if ($reconfigure_return == 0) { + return ['success' => true, 'message' => 'Wifi settings updated successfully']; + } else { + return ['success' => false, 'message' => 'Wifi settings updated but cannot restart (cannot execute "wpa_cli reconfigure")']; + } + } else { + return ['success' => false, 'message' => 'Wifi settings failed to be updated']; + } + } + + return ['success' => false, 'message' => 'Unknown error']; + } + +}