package main import ( "bufio" "embed" "encoding/json" "fmt" "io/fs" "log" "net/http" "os" "os/exec" "regexp" "sort" "strings" "sync" "time" "github.com/godbus/dbus/v5" "github.com/gorilla/mux" "github.com/gorilla/websocket" ) //go:embed all:static var _assets embed.FS // Structures de données type WiFiNetwork struct { SSID string `json:"ssid"` Signal int `json:"signal"` Security string `json:"security"` Channel int `json:"channel"` BSSID string `json:"bssid"` } type ConnectedDevice struct { Name string `json:"name"` Type string `json:"type"` MAC string `json:"mac"` IP string `json:"ip"` } type HotspotConfig struct { SSID string `json:"ssid"` Password string `json:"password"` Channel int `json:"channel"` } type SystemStatus struct { Connected bool `json:"connected"` ConnectedSSID string `json:"connectedSSID"` HotspotEnabled bool `json:"hotspotEnabled"` ConnectedCount int `json:"connectedCount"` DataUsage float64 `json:"dataUsage"` Uptime int64 `json:"uptime"` ConnectedDevices []ConnectedDevice `json:"connectedDevices"` } type WiFiConnectRequest struct { SSID string `json:"ssid"` Password string `json:"password"` } type LogEntry struct { Timestamp time.Time `json:"timestamp"` Source string `json:"source"` Message string `json:"message"` } // Variables globales var ( currentStatus SystemStatus statusMutex sync.RWMutex logEntries []LogEntry logMutex sync.RWMutex websocketClients = make(map[*websocket.Conn]bool) clientsMutex sync.RWMutex upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } startTime = time.Now() dbusConn *dbus.Conn wpaSupplicant dbus.BusObject ) const ( WLAN_INTERFACE = "wlan0" AP_INTERFACE = "wlan1" HOSTAPD_CONF = "/etc/hostapd/hostapd.conf" WPA_CONF = "/etc/wpa_supplicant/wpa_supplicant.conf" // D-Bus constantes pour wpa_supplicant WPA_SUPPLICANT_SERVICE = "fi.w1.wpa_supplicant1" WPA_SUPPLICANT_PATH = "/fi/w1/wpa_supplicant1" WPA_SUPPLICANT_IFACE = "fi.w1.wpa_supplicant1" WPA_INTERFACE_IFACE = "fi.w1.wpa_supplicant1.Interface" WPA_BSS_IFACE = "fi.w1.wpa_supplicant1.BSS" WPA_NETWORK_IFACE = "fi.w1.wpa_supplicant1.Network" ) func main() { // Initialiser D-Bus var err error dbusConn, err = dbus.SystemBus() if err != nil { log.Fatalf("Erreur de connexion D-Bus: %v", err) } defer dbusConn.Close() wpaSupplicant = dbusConn.Object(WPA_SUPPLICANT_SERVICE, dbus.ObjectPath(WPA_SUPPLICANT_PATH)) // Initialiser le statut système initializeStatus() // Démarrer les tâches périodiques go periodicStatusUpdate() go periodicDeviceUpdate() // Configuration du routeur r := mux.NewRouter() // Routes API api := r.PathPrefix("/api").Subrouter() api.HandleFunc("/wifi/scan", scanWiFiHandler).Methods("GET") api.HandleFunc("/wifi/connect", connectWiFiHandler).Methods("POST") api.HandleFunc("/wifi/disconnect", disconnectWiFiHandler).Methods("POST") api.HandleFunc("/hotspot/config", configureHotspotHandler).Methods("POST") api.HandleFunc("/hotspot/toggle", toggleHotspotHandler).Methods("POST") api.HandleFunc("/devices", getDevicesHandler).Methods("GET") api.HandleFunc("/status", getStatusHandler).Methods("GET") api.HandleFunc("/logs", getLogsHandler).Methods("GET") api.HandleFunc("/logs", clearLogsHandler).Methods("DELETE") // WebSocket pour les logs en temps réel r.HandleFunc("/ws/logs", websocketHandler) // Servir les fichiers statiques sub, err := fs.Sub(_assets, "static") if err != nil { log.Fatal("Unable to cd to static/ directory:", err) } Assets := http.FS(sub) r.PathPrefix("/").Handler(http.FileServer(Assets)) addLog("Système", "Serveur API démarré sur le port 8080") log.Fatal(http.ListenAndServe(":8080", r)) } func initializeStatus() { statusMutex.Lock() defer statusMutex.Unlock() currentStatus = SystemStatus{ Connected: false, ConnectedSSID: "", HotspotEnabled: true, ConnectedCount: 0, DataUsage: 0.0, Uptime: 0, } } // Handlers API func scanWiFiHandler(w http.ResponseWriter, r *http.Request) { networks, err := scanWiFiNetworks() if err != nil { addLog("WiFi", fmt.Sprintf("Erreur lors du scan: %v", err)) http.Error(w, "Erreur lors du scan WiFi", http.StatusInternalServerError) return } addLog("WiFi", fmt.Sprintf("Scan terminé - %d réseaux trouvés", len(networks))) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(networks) } func connectWiFiHandler(w http.ResponseWriter, r *http.Request) { var req WiFiConnectRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Données invalides", http.StatusBadRequest) return } addLog("WiFi", fmt.Sprintf("Tentative de connexion à %s", req.SSID)) err := connectToWiFiDBus(req.SSID, req.Password) if err != nil { addLog("WiFi", fmt.Sprintf("Échec de connexion: %v", err)) http.Error(w, fmt.Sprintf("Échec de connexion: %v", err), http.StatusInternalServerError) return } statusMutex.Lock() currentStatus.Connected = true currentStatus.ConnectedSSID = req.SSID statusMutex.Unlock() addLog("WiFi", fmt.Sprintf("Connexion réussie à %s", req.SSID)) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "success"}) } func disconnectWiFiHandler(w http.ResponseWriter, r *http.Request) { addLog("WiFi", "Tentative de déconnexion") err := disconnectWiFiDBus() if err != nil { addLog("WiFi", fmt.Sprintf("Erreur de déconnexion: %v", err)) http.Error(w, fmt.Sprintf("Erreur de déconnexion: %v", err), http.StatusInternalServerError) return } statusMutex.Lock() currentStatus.Connected = false currentStatus.ConnectedSSID = "" statusMutex.Unlock() addLog("WiFi", "Déconnexion réussie") w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "success"}) } func configureHotspotHandler(w http.ResponseWriter, r *http.Request) { var config HotspotConfig if err := json.NewDecoder(r.Body).Decode(&config); err != nil { http.Error(w, "Données invalides", http.StatusBadRequest) return } err := configureHotspot(config) if err != nil { addLog("Hotspot", fmt.Sprintf("Erreur de configuration: %v", err)) http.Error(w, fmt.Sprintf("Erreur de configuration: %v", err), http.StatusInternalServerError) return } addLog("Hotspot", fmt.Sprintf("Configuration mise à jour: %s (Canal %d)", config.SSID, config.Channel)) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "success"}) } func toggleHotspotHandler(w http.ResponseWriter, r *http.Request) { statusMutex.Lock() currentStatus.HotspotEnabled = !currentStatus.HotspotEnabled enabled := currentStatus.HotspotEnabled statusMutex.Unlock() var err error if enabled { err = startHotspot() addLog("Hotspot", "Hotspot activé") } else { err = stopHotspot() addLog("Hotspot", "Hotspot désactivé") } if err != nil { addLog("Hotspot", fmt.Sprintf("Erreur: %v", err)) http.Error(w, fmt.Sprintf("Erreur: %v", err), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]bool{"enabled": enabled}) } func getDevicesHandler(w http.ResponseWriter, r *http.Request) { devices, err := getConnectedDevices() if err != nil { addLog("Système", fmt.Sprintf("Erreur lors de la récupération des appareils: %v", err)) http.Error(w, "Erreur lors de la récupération des appareils", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(devices) } func getStatusHandler(w http.ResponseWriter, r *http.Request) { statusMutex.RLock() status := currentStatus status.Uptime = int64(time.Since(startTime).Seconds()) statusMutex.RUnlock() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(status) } func getLogsHandler(w http.ResponseWriter, r *http.Request) { logMutex.RLock() logs := make([]LogEntry, len(logEntries)) copy(logs, logEntries) logMutex.RUnlock() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(logs) } func clearLogsHandler(w http.ResponseWriter, r *http.Request) { logMutex.Lock() logEntries = []LogEntry{} logMutex.Unlock() addLog("Système", "Logs effacés") w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "success"}) } // Fonctions WiFi avec D-Bus func scanWiFiNetworks() ([]WiFiNetwork, error) { interfacePath, err := getWiFiInterfacePath() if err != nil { return nil, fmt.Errorf("impossible d'obtenir l'interface WiFi: %v", err) } // Déclencher un scan wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath) call := wifiInterface.Call(WPA_INTERFACE_IFACE+".Scan", 0, map[string]dbus.Variant{"Type": dbus.MakeVariant("active")}) if call.Err != nil { return nil, fmt.Errorf("erreur lors du scan: %v", call.Err) } // Attendre un peu pour que le scan se termine time.Sleep(2 * time.Second) // Récupérer la liste des BSS bssePaths, err := wifiInterface.GetProperty(WPA_INTERFACE_IFACE + ".BSSs") if err != nil { return nil, fmt.Errorf("erreur lors de la récupération des BSS: %v", err) } var networks []WiFiNetwork seenSSIDs := make(map[string]bool) for _, bssPath := range bssePaths.Value().([]dbus.ObjectPath) { bss := dbusConn.Object(WPA_SUPPLICANT_SERVICE, bssPath) // Récupérer les propriétés du BSS var props map[string]dbus.Variant err = bss.Call("org.freedesktop.DBus.Properties.GetAll", 0, WPA_BSS_IFACE).Store(&props) if err != nil { continue } network := WiFiNetwork{} // Extraire SSID if ssidBytes, ok := props["SSID"].Value().([]byte); ok { network.SSID = string(ssidBytes) } // Éviter les doublons if network.SSID == "" || seenSSIDs[network.SSID] { continue } seenSSIDs[network.SSID] = true // Extraire BSSID if bssidBytes, ok := props["BSSID"].Value().([]byte); ok { network.BSSID = fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", bssidBytes[0], bssidBytes[1], bssidBytes[2], bssidBytes[3], bssidBytes[4], bssidBytes[5]) } // Extraire la force du signal if signal, ok := props["Signal"].Value().(int16); ok { network.Signal = signalToStrength(int(signal)) } // Extraire la fréquence et calculer le canal if frequency, ok := props["Frequency"].Value().(uint16); ok { network.Channel = frequencyToChannel(int(frequency)) } // Déterminer la sécurité if privacyVal, ok := props["Privacy"].Value().(bool); ok && privacyVal { if wpaProps, ok := props["WPA"].Value().(map[string]dbus.Variant); ok && len(wpaProps) > 0 { network.Security = "WPA" } else if rsnProps, ok := props["RSN"].Value().(map[string]dbus.Variant); ok && len(rsnProps) > 0 { network.Security = "WPA2" } else { network.Security = "WEP" } } else { network.Security = "Open" } networks = append(networks, network) } // Trier par force du signal sort.Slice(networks, func(i, j int) bool { return networks[i].Signal > networks[j].Signal }) return networks, nil } func connectToWiFiDBus(ssid, password string) error { interfacePath, err := getWiFiInterfacePath() if err != nil { return fmt.Errorf("impossible d'obtenir l'interface WiFi: %v", err) } wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath) // Créer un nouveau réseau networkConfig := map[string]dbus.Variant{ "ssid": dbus.MakeVariant(ssid), } if password != "" { networkConfig["psk"] = dbus.MakeVariant(password) } var networkPath dbus.ObjectPath err = wifiInterface.Call(WPA_INTERFACE_IFACE+".AddNetwork", 0, networkConfig).Store(&networkPath) if err != nil { return fmt.Errorf("erreur lors de l'ajout du réseau: %v", err) } // Sélectionner le réseau err = wifiInterface.Call(WPA_INTERFACE_IFACE+".SelectNetwork", 0, networkPath).Err if err != nil { return fmt.Errorf("erreur lors de la sélection du réseau: %v", err) } // Attendre la connexion for i := 0; i < 20; i++ { time.Sleep(500 * time.Millisecond) if isConnectedDBus() { return nil } } return fmt.Errorf("timeout lors de la connexion") } func disconnectWiFiDBus() error { interfacePath, err := getWiFiInterfacePath() if err != nil { return fmt.Errorf("impossible d'obtenir l'interface WiFi: %v", err) } wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath) // Déconnecter err = wifiInterface.Call(WPA_INTERFACE_IFACE+".Disconnect", 0).Err if err != nil { return fmt.Errorf("erreur lors de la déconnexion: %v", err) } // Supprimer tous les réseaux var networks []dbus.ObjectPath err = wifiInterface.Call(WPA_INTERFACE_IFACE+".Get", 0, WPA_INTERFACE_IFACE, "Networks").Store(&networks) if err == nil { for _, networkPath := range networks { wifiInterface.Call(WPA_INTERFACE_IFACE+".RemoveNetwork", 0, networkPath) } } return nil } func getWiFiInterfacePath() (dbus.ObjectPath, error) { var interfacePath dbus.ObjectPath err := wpaSupplicant.Call(WPA_SUPPLICANT_IFACE+".GetInterface", 0, WLAN_INTERFACE).Store(&interfacePath) if err != nil { return "", fmt.Errorf("erreur lors de la récupération des interfaces: %v", err) } return interfacePath, nil } func isConnectedDBus() bool { interfacePath, err := getWiFiInterfacePath() if err != nil { return false } wifiInterface := dbusConn.Object(WPA_SUPPLICANT_SERVICE, interfacePath) var state string err = wifiInterface.Call(WPA_INTERFACE_IFACE+".Get", 0, WPA_INTERFACE_IFACE, "State").Store(&state) if err != nil { return false } return state == "completed" } func frequencyToChannel(frequency int) int { if frequency >= 2412 && frequency <= 2484 { if frequency == 2484 { return 14 } return (frequency-2412)/5 + 1 } else if frequency >= 5170 && frequency <= 5825 { return (frequency - 5000) / 5 } return 0 } func signalToStrength(level int) int { if level >= -30 { return 5 } else if level >= -50 { return 4 } else if level >= -60 { return 3 } else if level >= -70 { return 2 } else { return 1 } } func connectToWiFi(ssid, password string) error { // Créer la configuration wpa_supplicant config := fmt.Sprintf(`ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 country=FR network={ ssid="%s" psk="%s" } `, ssid, password) err := os.WriteFile(WPA_CONF, []byte(config), 0600) if err != nil { return err } // Redémarrer wpa_supplicant cmd := exec.Command("systemctl", "restart", "wpa_supplicant") if err := cmd.Run(); err != nil { return err } // Attendre la connexion for i := 0; i < 10; i++ { time.Sleep(1 * time.Second) if isConnected() { return nil } } return fmt.Errorf("timeout lors de la connexion") } func isConnected() bool { cmd := exec.Command("iwconfig", WLAN_INTERFACE) output, err := cmd.Output() if err != nil { return false } return strings.Contains(string(output), "Access Point:") } // Fonctions Hotspot func configureHotspot(config HotspotConfig) error { hostapdConfig := fmt.Sprintf(`interface=%s driver=nl80211 ssid=%s hw_mode=g channel=%d wmm_enabled=0 macaddr_acl=0 auth_algs=1 ignore_broadcast_ssid=0 wpa=2 wpa_passphrase=%s wpa_key_mgmt=WPA-PSK wpa_pairwise=TKIP rsn_pairwise=CCMP `, AP_INTERFACE, config.SSID, config.Channel, config.Password) return os.WriteFile(HOSTAPD_CONF, []byte(hostapdConfig), 0644) } func startHotspot() error { cmd := exec.Command("systemctl", "start", "hostapd") return cmd.Run() } func stopHotspot() error { cmd := exec.Command("systemctl", "stop", "hostapd") return cmd.Run() } // Fonctions pour les appareils connectés func getConnectedDevices() ([]ConnectedDevice, error) { var devices []ConnectedDevice // Lire les baux DHCP leases, err := parseDHCPLeases() if err != nil { return devices, err } // Obtenir les informations ARP arpInfo, err := getARPInfo() if err != nil { return devices, err } for _, lease := range leases { device := ConnectedDevice{ Name: lease.Hostname, MAC: lease.MAC, IP: lease.IP, Type: guessDeviceType(lease.Hostname, lease.MAC), } // Vérifier si l'appareil est toujours connecté via ARP if _, exists := arpInfo[lease.IP]; exists { devices = append(devices, device) } } return devices, nil } type DHCPLease struct { IP string MAC string Hostname string } func parseDHCPLeases() ([]DHCPLease, error) { var leases []DHCPLease file, err := os.Open("/var/lib/dhcp/dhcpd.leases") if err != nil { return leases, err } defer file.Close() scanner := bufio.NewScanner(file) var currentLease DHCPLease for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if strings.HasPrefix(line, "lease ") { ip := strings.Fields(line)[1] currentLease = DHCPLease{IP: ip} } else if strings.Contains(line, "hardware ethernet") { mac := strings.Fields(line)[2] mac = strings.TrimSuffix(mac, ";") currentLease.MAC = mac } else if strings.Contains(line, "client-hostname") { hostname := strings.Fields(line)[1] hostname = strings.Trim(hostname, `";`) currentLease.Hostname = hostname } else if line == "}" { if currentLease.IP != "" && currentLease.MAC != "" { leases = append(leases, currentLease) } currentLease = DHCPLease{} } } return leases, nil } func getARPInfo() (map[string]string, error) { arpInfo := make(map[string]string) cmd := exec.Command("arp", "-a") output, err := cmd.Output() if err != nil { return arpInfo, err } lines := strings.Split(string(output), "\n") for _, line := range lines { if matches := regexp.MustCompile(`\(([^)]+)\) at ([0-9a-fA-F:]{17})`).FindStringSubmatch(line); len(matches) > 2 { ip := matches[1] mac := matches[2] arpInfo[ip] = mac } } return arpInfo, nil } func guessDeviceType(hostname, mac string) string { hostname = strings.ToLower(hostname) if strings.Contains(hostname, "iphone") || strings.Contains(hostname, "android") { return "mobile" } else if strings.Contains(hostname, "ipad") || strings.Contains(hostname, "tablet") { return "tablet" } else if strings.Contains(hostname, "macbook") || strings.Contains(hostname, "laptop") { return "laptop" } // Deviner par préfixe MAC (OUI) macPrefix := strings.ToUpper(mac[:8]) switch macPrefix { case "00:50:56", "00:0C:29", "00:05:69": // VMware return "laptop" case "08:00:27": // VirtualBox return "laptop" default: return "mobile" } } // Fonctions de logging func addLog(source, message string) { logMutex.Lock() entry := LogEntry{ Timestamp: time.Now(), Source: source, Message: message, } logEntries = append(logEntries, entry) // Garder seulement les 100 derniers logs if len(logEntries) > 100 { logEntries = logEntries[len(logEntries)-100:] } logMutex.Unlock() // Envoyer aux clients WebSocket broadcastToWebSockets(entry) // Log vers la console log.Printf("[%s] %s", source, message) } // WebSocket pour les logs en temps réel func websocketHandler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Printf("Erreur WebSocket: %v", err) return } defer conn.Close() clientsMutex.Lock() websocketClients[conn] = true clientsMutex.Unlock() defer func() { clientsMutex.Lock() delete(websocketClients, conn) clientsMutex.Unlock() }() // Envoyer les logs existants logMutex.RLock() for _, entry := range logEntries { conn.WriteJSON(entry) } logMutex.RUnlock() // Maintenir la connexion for { _, _, err := conn.ReadMessage() if err != nil { break } } } func broadcastToWebSockets(entry LogEntry) { clientsMutex.RLock() defer clientsMutex.RUnlock() for client := range websocketClients { err := client.WriteJSON(entry) if err != nil { client.Close() delete(websocketClients, client) } } } // Tâches périodiques func periodicStatusUpdate() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for range ticker.C { statusMutex.Lock() currentStatus.Connected = isConnected() if !currentStatus.Connected { currentStatus.ConnectedSSID = "" } statusMutex.Unlock() } } func periodicDeviceUpdate() { ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() for range ticker.C { devices, err := getConnectedDevices() if err != nil { continue } statusMutex.Lock() currentStatus.ConnectedDevices = devices currentStatus.ConnectedCount = len(devices) statusMutex.Unlock() } }