2024-06-03 09:24:18 +02:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
2024-06-03 10:44:16 +02:00
|
|
|
"embed"
|
2024-06-11 10:45:31 +02:00
|
|
|
"encoding/json"
|
2024-06-05 09:47:16 +02:00
|
|
|
"fmt"
|
2024-06-11 10:45:31 +02:00
|
|
|
"log"
|
2024-06-03 09:24:18 +02:00
|
|
|
"net/http"
|
2024-06-11 10:45:31 +02:00
|
|
|
"os"
|
2024-06-05 09:47:16 +02:00
|
|
|
"strconv"
|
2024-06-06 09:32:35 +02:00
|
|
|
"strings"
|
2024-06-07 11:07:35 +02:00
|
|
|
"time"
|
2024-06-03 09:24:18 +02:00
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
WebPort string = "8080"
|
|
|
|
)
|
|
|
|
|
2024-06-03 10:44:16 +02:00
|
|
|
//go:embed templates/*
|
|
|
|
var templatesFolder embed.FS
|
|
|
|
|
2024-06-03 09:24:18 +02:00
|
|
|
func WebServer() {
|
|
|
|
p := viper.GetString("Server.WebPort")
|
|
|
|
if p != "" {
|
|
|
|
WebPort = p
|
|
|
|
}
|
|
|
|
gin.SetMode(gin.ReleaseMode)
|
|
|
|
r := gin.Default()
|
2024-06-03 10:44:16 +02:00
|
|
|
LoadHTMLFromEmbedFS(r, templatesFolder, "templates/*.html")
|
|
|
|
r.StaticFileFS("/style.css", "./templates/style.css", http.FS(templatesFolder))
|
2024-06-04 12:41:09 +02:00
|
|
|
r.StaticFileFS("/htmx.js", "./templates/htmx.js", http.FS(templatesFolder))
|
2024-06-10 11:33:31 +02:00
|
|
|
setFavicons(r)
|
|
|
|
|
2024-06-03 09:24:18 +02:00
|
|
|
r.GET("/", getRoot)
|
2024-06-05 09:47:16 +02:00
|
|
|
r.GET("/command/:clientid", getCommands)
|
2024-06-17 10:04:40 +02:00
|
|
|
r.GET("/fs/:clientid", getFilesystem)
|
|
|
|
r.POST("/ls/:clientid", listFiles)
|
2024-06-05 09:47:16 +02:00
|
|
|
r.POST("/command/:clientid", execCMD)
|
2024-06-07 09:59:14 +02:00
|
|
|
r.POST("/kill/:clientid", sendKillswitch)
|
2024-06-07 11:07:35 +02:00
|
|
|
r.GET("/dump", dumpClients)
|
2024-06-11 10:45:31 +02:00
|
|
|
r.POST("/save", saveClients)
|
2024-06-11 12:27:43 +02:00
|
|
|
r.POST("/remove/:clientid", removeClient)
|
2024-06-13 09:24:20 +02:00
|
|
|
r.POST("/bulk-kill", bulkKill)
|
|
|
|
r.POST("/bulk-remove/", bulkRemove)
|
|
|
|
|
|
|
|
// Start
|
2024-06-03 09:24:18 +02:00
|
|
|
r.Run(":" + WebPort)
|
|
|
|
}
|
|
|
|
|
2024-06-17 10:04:40 +02:00
|
|
|
func listFiles(c *gin.Context) {
|
|
|
|
id := c.Param("clientid")
|
|
|
|
idInt, err := strconv.Atoi(id)
|
|
|
|
if err != nil {
|
|
|
|
c.String(http.StatusInternalServerError, "Error happened, please make this a proper error later")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
client, _, err := returnClient(idInt)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if client.IsOnline == false {
|
|
|
|
c.String(http.StatusOK, "Client is currently offline!")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
path, _ := c.GetPostForm("cmd")
|
|
|
|
|
|
|
|
resp, err := requestFiles(*client, path)
|
|
|
|
if err != nil {
|
|
|
|
e := fmt.Sprintf("Error happened executing command: %v\n", err)
|
|
|
|
c.String(http.StatusOK, e)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var list string
|
|
|
|
for _, v := range resp.FileList.File {
|
|
|
|
isDir := ""
|
|
|
|
if v.IsFolder == true {
|
|
|
|
isDir = "/"
|
|
|
|
}
|
2024-06-17 10:58:21 +02:00
|
|
|
entry := fmt.Sprintf("<p><a hx-post=\"/ls/%v\" hx-target=\"#files\" hx-vals='{\"cmd\": \"%v\"}' id=\"pointer\">%v%v</a></p>",
|
2024-06-17 10:04:40 +02:00
|
|
|
client.ClientID, v.FullPath, v.FullPath, isDir)
|
|
|
|
list += entry
|
|
|
|
}
|
|
|
|
c.String(http.StatusOK, list)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2024-06-13 09:24:20 +02:00
|
|
|
func bulkKill(c *gin.Context) {
|
|
|
|
list := Bulk{}
|
|
|
|
c.ShouldBind(&list)
|
|
|
|
if len(list.ClientIDs) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, v := range list.ClientIDs {
|
|
|
|
genericKillswitch(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func bulkRemove(c *gin.Context) {
|
|
|
|
list := Bulk{}
|
|
|
|
c.ShouldBind(&list)
|
|
|
|
if len(list.ClientIDs) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, v := range list.ClientIDs {
|
|
|
|
genericRemoveClient(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-07 11:07:35 +02:00
|
|
|
func dumpClients(c *gin.Context) {
|
2024-06-11 10:45:31 +02:00
|
|
|
jsonClients := marshalClients()
|
|
|
|
c.IndentedJSON(http.StatusOK, jsonClients)
|
|
|
|
}
|
|
|
|
|
|
|
|
func saveClients(c *gin.Context) {
|
|
|
|
log.Println("Saving clients...")
|
|
|
|
jsonClients := marshalClients()
|
|
|
|
fileToSaveTo, err := setClientPath()
|
|
|
|
if err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err := os.Create(fileToSaveTo)
|
|
|
|
if err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
as_json, _ := json.MarshalIndent(jsonClients, "", "\t")
|
|
|
|
f.Write(as_json)
|
2024-06-12 09:49:37 +02:00
|
|
|
log.Println("Success!")
|
2024-06-11 10:45:31 +02:00
|
|
|
}
|
|
|
|
|
2024-06-11 12:27:43 +02:00
|
|
|
func removeClient(c *gin.Context) {
|
|
|
|
clientID := c.Param("clientid")
|
|
|
|
intClientID, err := strconv.Atoi(clientID)
|
|
|
|
if err != nil {
|
|
|
|
c.String(http.StatusInternalServerError, "Error happened fetching client: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2024-06-13 09:24:20 +02:00
|
|
|
genericRemoveClient(intClientID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func genericRemoveClient(id int) {
|
|
|
|
client, id, err := returnClient(id)
|
2024-06-11 13:41:49 +02:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-06-12 08:10:06 +02:00
|
|
|
|
|
|
|
if client.IsOnline == true {
|
|
|
|
client.Instruct(Instructions{
|
|
|
|
IsKillswitch: true,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-13 09:24:20 +02:00
|
|
|
log.Printf("Removing client %v\n", id)
|
2024-06-11 12:27:43 +02:00
|
|
|
if len(clientList) != 1 {
|
2024-06-12 09:49:37 +02:00
|
|
|
clientList = append(clientList[:id], clientList[id+1:]...)
|
2024-06-11 12:27:43 +02:00
|
|
|
} else {
|
|
|
|
clientList = nil
|
|
|
|
}
|
|
|
|
log.Printf("Success!")
|
|
|
|
}
|
|
|
|
|
2024-06-11 10:45:31 +02:00
|
|
|
func marshalClients() ClientJSON {
|
2024-06-07 11:07:35 +02:00
|
|
|
jsonClients := ClientJSON{Date: time.Now()}
|
|
|
|
for _, v := range clientList {
|
|
|
|
jsonClients.List = append(jsonClients.List, v.ClientBasicInfo)
|
|
|
|
}
|
2024-06-11 10:45:31 +02:00
|
|
|
return jsonClients
|
2024-06-07 11:07:35 +02:00
|
|
|
}
|
|
|
|
|
2024-06-03 09:24:18 +02:00
|
|
|
func getRoot(c *gin.Context) {
|
2024-06-03 10:44:16 +02:00
|
|
|
c.HTML(http.StatusOK, "templates/index.html", gin.H{
|
2024-06-07 09:59:14 +02:00
|
|
|
"Clients": clientList,
|
2024-06-03 10:44:16 +02:00
|
|
|
})
|
2024-06-03 09:24:18 +02:00
|
|
|
}
|
2024-06-05 09:47:16 +02:00
|
|
|
|
|
|
|
func getCommands(c *gin.Context) {
|
|
|
|
clientID := c.Param("clientid")
|
|
|
|
intClientID, err := strconv.Atoi(clientID)
|
|
|
|
if err != nil {
|
|
|
|
c.String(http.StatusInternalServerError, "Error happened fetching client: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2024-06-12 09:49:37 +02:00
|
|
|
client, _, err := returnClient(intClientID)
|
2024-06-05 09:47:16 +02:00
|
|
|
if err != nil {
|
|
|
|
c.String(http.StatusNotFound, "Client not found")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.HTML(http.StatusOK, "templates/command.html", gin.H{
|
2024-06-07 09:59:14 +02:00
|
|
|
"Client": client,
|
2024-06-17 10:04:40 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFilesystem(c *gin.Context) {
|
|
|
|
clientID := c.Param("clientid")
|
|
|
|
intClientID, err := strconv.Atoi(clientID)
|
|
|
|
if err != nil {
|
|
|
|
c.String(http.StatusInternalServerError, "Error happened fetching client: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
client, _, err := returnClient(intClientID)
|
|
|
|
if err != nil {
|
|
|
|
c.String(http.StatusNotFound, "Client not found")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
c.HTML(http.StatusOK, "templates/fs.html", gin.H{
|
|
|
|
"Client": client,
|
2024-06-05 09:47:16 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-07 09:59:14 +02:00
|
|
|
func sendKillswitch(c *gin.Context) {
|
|
|
|
clientID := c.Param("clientid")
|
|
|
|
intClientID, err := strconv.Atoi(clientID)
|
|
|
|
if err != nil {
|
|
|
|
c.String(http.StatusInternalServerError, "Error happened fetching client: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2024-06-13 09:24:20 +02:00
|
|
|
genericKillswitch(intClientID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func genericKillswitch(id int) {
|
|
|
|
_, id, err := returnClient(id)
|
2024-06-12 09:49:37 +02:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-06-07 12:39:07 +02:00
|
|
|
|
2024-06-12 09:49:37 +02:00
|
|
|
if clientList[id].IsOnline == false {
|
2024-06-07 12:39:07 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-06-07 09:59:14 +02:00
|
|
|
inst := Instructions{
|
|
|
|
IsKillswitch: true,
|
|
|
|
}
|
2024-06-12 09:49:37 +02:00
|
|
|
clientList[id].Instruct(inst)
|
|
|
|
clientList[id].IsOnline = false
|
2024-06-07 09:59:14 +02:00
|
|
|
}
|
|
|
|
|
2024-06-05 09:47:16 +02:00
|
|
|
func execCMD(c *gin.Context) {
|
2024-06-06 09:32:35 +02:00
|
|
|
id := c.Param("clientid")
|
|
|
|
idInt, err := strconv.Atoi(id)
|
|
|
|
if err != nil {
|
|
|
|
c.String(http.StatusInternalServerError, "Error happened, please make this a proper error later")
|
|
|
|
return
|
|
|
|
}
|
2024-06-07 12:39:07 +02:00
|
|
|
|
2024-06-12 09:49:37 +02:00
|
|
|
client, _, err := returnClient(idInt)
|
2024-06-11 13:41:49 +02:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if client.IsOnline == false {
|
2024-06-07 12:39:07 +02:00
|
|
|
c.String(http.StatusOK, "Client is currently offline!")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-06-06 09:32:35 +02:00
|
|
|
command, _ := c.GetPostForm("cmd")
|
|
|
|
|
2024-06-12 09:49:37 +02:00
|
|
|
out, err := sendCommand(*client, command)
|
2024-06-06 09:32:35 +02:00
|
|
|
if err != nil {
|
|
|
|
e := fmt.Sprintf("Error happened executing command: %v\n", err)
|
|
|
|
c.String(http.StatusOK, e)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
prettyOut := strings.Replace(out, "\n", "<br>", -1)
|
2024-06-07 08:31:52 +02:00
|
|
|
c.String(http.StatusOK, "$ "+command+"<br>"+prettyOut)
|
2024-06-05 09:47:16 +02:00
|
|
|
}
|
2024-06-10 11:33:31 +02:00
|
|
|
|
|
|
|
func setFavicons(r *gin.Engine) {
|
|
|
|
r.StaticFileFS("/favicon.ico", "./templates/assets/favicon.ico", http.FS(templatesFolder))
|
|
|
|
r.StaticFileFS("/favicon-32x32.png", "./templates/assets/favicon-32x32.png", http.FS(templatesFolder))
|
|
|
|
r.StaticFileFS("/favicon-16x16.png", "./templates/assets/favicon-16x16.png", http.FS(templatesFolder))
|
|
|
|
r.StaticFileFS("/apple-touch-icon.png", "./templates/assets/apple-touch-icon.png", http.FS(templatesFolder))
|
|
|
|
r.StaticFileFS("/android-chrome-512x512.png", "./templates/assets/android-chrome-512x512.png", http.FS(templatesFolder))
|
|
|
|
r.StaticFileFS("/android-chrome-192x192.png", "./templates/assets/android-chrome-192x192.png", http.FS(templatesFolder))
|
|
|
|
r.StaticFileFS("/site.webmanifest", "./templates/assets/site.webmanifest", http.FS(templatesFolder))
|
|
|
|
}
|