package webserver import ( "fmt" "log" "net/http" serveractions "manage-servers/server-actions" "github.com/gorilla/websocket" "golang.org/x/crypto/ssh" ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { return true // Allowing all origins for local tool }, } func (ws *WebServer) handleSSH(w http.ResponseWriter, r *http.Request) { serverName := r.URL.Query().Get("name") if serverName == "" { http.Error(w, "Missing server name", http.StatusBadRequest) return } var targetServer *serveractions.Server for _, s := range ws.servers { if s.Name == serverName { targetServer = &s break } } if targetServer == nil { http.Error(w, "Server not found", http.StatusNotFound) return } if targetServer.SSHUser == "" || targetServer.SSHPass == "" { conn, err := upgrader.Upgrade(w, r, nil) if err == nil { conn.WriteMessage(websocket.TextMessage, []byte("\r\n[Error] SSH Credentials missing for this node.\r\nPlease edit the server entry and save with a Username and Password.\r\n")) conn.Close() } return } conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Printf("Websocket upgrade failed: %v", err) return } defer conn.Close() client, err := serveractions.GetSSHClient(*targetServer) if err != nil { conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("\r\n[Error] SSH Connection failed: %v\r\n", err))) return } defer client.Close() session, err := client.NewSession() if err != nil { conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("\r\n[Error] SSH Session failed: %v\r\n", err))) return } defer session.Close() // Request pseudo-terminal modes := ssh.TerminalModes{ ssh.ECHO: 1, ssh.TTY_OP_ISPEED: 14400, ssh.TTY_OP_OSPEED: 14400, } // Default to 24x80 if not provided rows := 24 cols := 80 if r.URL.Query().Get("rows") != "" { fmt.Sscanf(r.URL.Query().Get("rows"), "%d", &rows) } if r.URL.Query().Get("cols") != "" { fmt.Sscanf(r.URL.Query().Get("cols"), "%d", &cols) } // Sanity check for dimensions if rows <= 0 { rows = 24 } if cols <= 0 { cols = 80 } if err := session.RequestPty("xterm-256color", rows, cols, modes); err != nil { conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("\r\n[Error] Request for PTY failed: %v\r\n", err))) return } stdin, err := session.StdinPipe() if err != nil { return } stdout, err := session.StdoutPipe() if err != nil { return } stderr, err := session.StderrPipe() if err != nil { return } if err := session.Shell(); err != nil { conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("\r\n[Error] Shell start failed: %v\r\n", err))) return } // Proxy stdout/stderr to WebSocket go func() { buf := make([]byte, 1024) for { n, err := stdout.Read(buf) if n > 0 { if err := conn.WriteMessage(websocket.TextMessage, buf[:n]); err != nil { return } } if err != nil { return } } }() go func() { buf := make([]byte, 1024) for { n, err := stderr.Read(buf) if n > 0 { if err := conn.WriteMessage(websocket.TextMessage, buf[:n]); err != nil { return } } if err != nil { return } } }() // Proxy WebSocket to stdin for { _, message, err := conn.ReadMessage() if err != nil { break } if _, err := stdin.Write(message); err != nil { break } } }