2025-01-15 16:38:23 +01:00
|
|
|
package websocketserver
|
|
|
|
|
|
|
|
|
|
import (
|
2025-01-19 22:13:52 +01:00
|
|
|
"encoding/json"
|
2025-01-15 16:38:23 +01:00
|
|
|
"log"
|
2025-01-19 22:13:52 +01:00
|
|
|
"net/http"
|
2025-01-15 16:38:23 +01:00
|
|
|
"sync"
|
2025-01-22 17:02:44 +01:00
|
|
|
"time"
|
|
|
|
|
|
2025-01-19 22:13:52 +01:00
|
|
|
"github.com/gorilla/websocket"
|
2025-01-15 16:38:23 +01:00
|
|
|
)
|
|
|
|
|
|
2025-01-22 17:02:44 +01:00
|
|
|
var responseChannels sync.Map // Key: agentName, Value: chan string
|
|
|
|
|
|
2025-01-15 16:38:23 +01:00
|
|
|
type webSocketHandler struct {
|
|
|
|
|
upgrader websocket.Upgrader
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-19 22:13:52 +01:00
|
|
|
var agentSockets = make(map[string]*websocket.Conn)
|
|
|
|
|
var agentSocketsMutex sync.Mutex
|
|
|
|
|
|
|
|
|
|
var getAgentNames http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
agentSocketsMutex.Lock()
|
|
|
|
|
agentNames := make([]string, 0, len(agentSockets))
|
|
|
|
|
for agentName := range agentSockets {
|
|
|
|
|
agentNames = append(agentNames, agentName)
|
|
|
|
|
}
|
|
|
|
|
agentSocketsMutex.Unlock()
|
|
|
|
|
|
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
|
json.NewEncoder(w).Encode(agentNames)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (wsh webSocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
|
2025-01-15 16:38:23 +01:00
|
|
|
c, err := wsh.upgrader.Upgrade(w, r, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Error %s when upgrading connection to websocket", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-01-19 22:13:52 +01:00
|
|
|
|
|
|
|
|
agentName := r.URL.Query().Get("agentName")
|
|
|
|
|
agentIP := r.URL.Query().Get("IPv4Address")
|
|
|
|
|
if agentName == "" || agentIP == "" {
|
|
|
|
|
log.Printf("Missing agentName or IPv4Address in query parameters")
|
|
|
|
|
c.Close()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Printf("Agent connected: %s (%s)", agentName, agentIP)
|
|
|
|
|
|
|
|
|
|
agentSocketsMutex.Lock()
|
|
|
|
|
agentSockets[agentName] = c
|
|
|
|
|
agentSocketsMutex.Unlock()
|
|
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
|
agentSocketsMutex.Lock()
|
|
|
|
|
delete(agentSockets, agentName)
|
|
|
|
|
agentSocketsMutex.Unlock()
|
|
|
|
|
c.Close()
|
|
|
|
|
log.Printf("Agent disconnected: %s (%s)", agentName, agentIP)
|
|
|
|
|
}()
|
2025-01-15 16:38:23 +01:00
|
|
|
|
|
|
|
|
for {
|
2025-01-22 17:02:44 +01:00
|
|
|
_, message, err := c.ReadMessage()
|
2025-01-15 16:38:23 +01:00
|
|
|
if err != nil {
|
2025-01-19 22:13:52 +01:00
|
|
|
log.Printf("Error reading from agent %s: %v", agentName, err)
|
|
|
|
|
break
|
2025-01-15 16:38:23 +01:00
|
|
|
}
|
2025-01-19 22:13:52 +01:00
|
|
|
log.Printf("Message from agent %s: %s", agentName, message)
|
2025-01-15 16:38:23 +01:00
|
|
|
|
2025-01-22 17:02:44 +01:00
|
|
|
|
|
|
|
|
if ch, ok := responseChannels.Load(agentName); ok {
|
|
|
|
|
responseChan := ch.(chan string)
|
|
|
|
|
responseChan <- string(message)
|
2025-01-15 16:38:23 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-19 22:13:52 +01:00
|
|
|
type Message struct {
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
Payload string `json:"payload"`
|
|
|
|
|
}
|
2025-01-15 16:38:23 +01:00
|
|
|
|
|
|
|
|
var executeCommand http.HandlerFunc = func(w http.ResponseWriter, r *http.Request){
|
|
|
|
|
err := r.ParseForm()
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Invalid form data", http.StatusBadRequest)
|
2025-01-19 22:13:52 +01:00
|
|
|
return
|
2025-01-15 16:38:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
agentName := r.FormValue("agentName")
|
|
|
|
|
command := r.FormValue("command")
|
|
|
|
|
|
|
|
|
|
agentSocketsMutex.Lock()
|
|
|
|
|
conn, ok := agentSockets[agentName]
|
|
|
|
|
agentSocketsMutex.Unlock()
|
|
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
|
http.Error(w, "Agent not connected", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 17:02:44 +01:00
|
|
|
responseChan := make(chan string, 1)
|
|
|
|
|
responseChannels.Store(agentName, responseChan)
|
|
|
|
|
defer responseChannels.Delete(agentName)
|
|
|
|
|
|
2025-01-19 22:13:52 +01:00
|
|
|
message := Message {
|
|
|
|
|
Type: "command",
|
|
|
|
|
Payload: command,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
messageBytes, _ := json.Marshal(message)
|
|
|
|
|
|
|
|
|
|
err = conn.WriteMessage(websocket.TextMessage, messageBytes)
|
2025-01-15 16:38:23 +01:00
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Failed to send command to the agent", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-22 17:02:44 +01:00
|
|
|
select {
|
|
|
|
|
case response := <-responseChan:
|
|
|
|
|
var parsedResponse map[string]string
|
|
|
|
|
if err := json.Unmarshal([]byte(response), &parsedResponse); err != nil {
|
|
|
|
|
http.Error(w, "Failed to parse response", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
payload, ok := parsedResponse["payload"]
|
|
|
|
|
if !ok {
|
|
|
|
|
http.Error(w, "Invalid response structure", http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
|
|
|
w.Write([]byte(payload))
|
|
|
|
|
case <- time.After(10 * time.Second):
|
|
|
|
|
http.Error(w, "Agent repsonse timed out", http.StatusGatewayTimeout)
|
|
|
|
|
}
|
2025-01-15 16:38:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Server() (*http.Server) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webSocketHandler := webSocketHandler {
|
2025-01-19 22:13:52 +01:00
|
|
|
upgrader: websocket.Upgrader{
|
|
|
|
|
CheckOrigin: func(r *http.Request) bool {
|
|
|
|
|
return true
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-01-15 16:38:23 +01:00
|
|
|
}
|
2025-01-19 22:13:52 +01:00
|
|
|
|
2025-01-20 16:32:13 +01:00
|
|
|
corsMiddleware := func(next http.Handler) http.Handler {
|
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Access-Control-Allow-Origin", "*") // Allow the WebUI origin
|
|
|
|
|
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
|
|
|
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, HX-Current-URL, HX-Request, HX-Target, HX-Trigger, HX-Trigger-Name")
|
|
|
|
|
if r.Method == "OPTIONS" {
|
|
|
|
|
// Handle preflight requests
|
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-15 16:38:23 +01:00
|
|
|
webSocketMux := http.NewServeMux()
|
|
|
|
|
webSocketMux.Handle("/data", webSocketHandler)
|
2025-01-20 16:32:13 +01:00
|
|
|
webSocketMux.Handle("/executeCommand", corsMiddleware(http.HandlerFunc(executeCommand)))
|
|
|
|
|
webSocketMux.Handle("/agentNames", corsMiddleware(http.HandlerFunc(getAgentNames)))
|
2025-01-15 16:38:23 +01:00
|
|
|
websocketServer := &http.Server{
|
|
|
|
|
Addr: ":5555",
|
|
|
|
|
Handler: webSocketMux,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return websocketServer
|
|
|
|
|
}
|