mirror of
https://github.com/seriousm4x/UpSnap.git
synced 2026-04-05 08:53:55 -04:00
306 lines
8.0 KiB
Go
306 lines
8.0 KiB
Go
package pb
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pocketbase/pocketbase/apis"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
"github.com/robfig/cron/v3"
|
|
"github.com/seriousm4x/upsnap/logger"
|
|
"github.com/seriousm4x/upsnap/networking"
|
|
)
|
|
|
|
func HandlerWake(e *core.RequestEvent) error {
|
|
record, err := e.App.FindFirstRecordByData("devices", "id", e.Request.PathValue("id"))
|
|
if err != nil {
|
|
return apis.NewNotFoundError("The device does not exist.", err)
|
|
}
|
|
|
|
record.Set("status", "pending")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
|
|
if err := networking.WakeDevice(record); err != nil {
|
|
logger.Error.Println(err)
|
|
record.Set("status", "offline")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
return apis.NewBadRequestError(err.Error(), nil)
|
|
}
|
|
|
|
record.Set("status", "online")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
|
|
return e.JSON(http.StatusOK, record)
|
|
}
|
|
|
|
func HandlerSleep(e *core.RequestEvent) error {
|
|
record, err := e.App.FindFirstRecordByData("devices", "id", e.Request.PathValue("id"))
|
|
if err != nil {
|
|
return apis.NewNotFoundError("The device does not exist.", err)
|
|
}
|
|
|
|
record.Set("status", "pending")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
|
|
resp, err := networking.SleepDevice(record)
|
|
if err != nil {
|
|
logger.Error.Println(err)
|
|
record.Set("status", "online")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
return apis.NewBadRequestError(resp.Message, nil)
|
|
}
|
|
|
|
record.Set("status", "offline")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
|
|
return e.JSON(http.StatusOK, nil)
|
|
}
|
|
|
|
func HandlerReboot(e *core.RequestEvent) error {
|
|
record, err := e.App.FindFirstRecordByData("devices", "id", e.Request.PathValue("id"))
|
|
if err != nil {
|
|
return apis.NewNotFoundError("The device does not exist.", err)
|
|
}
|
|
|
|
record.Set("status", "pending")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
|
|
if err := networking.ShutdownDevice(record); err != nil {
|
|
logger.Error.Println(err)
|
|
record.Set("status", "online")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
return apis.NewBadRequestError(err.Error(), nil)
|
|
}
|
|
|
|
// if this code is reached, the device is shutting down and not responding to pings anymore.
|
|
// this does not mean it is ready to boot again, it might still be in the process of shutting down.
|
|
// so we wait a little to make sure the device has shut down completely and is ready to receive wake requests.
|
|
time.Sleep(15 * time.Second)
|
|
|
|
if err := networking.WakeDevice(record); err != nil {
|
|
logger.Error.Println(err)
|
|
record.Set("status", "offline")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
return apis.NewBadRequestError(err.Error(), nil)
|
|
}
|
|
|
|
record.Set("status", "online")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
return e.JSON(http.StatusOK, record)
|
|
}
|
|
|
|
func HandlerShutdown(e *core.RequestEvent) error {
|
|
record, err := e.App.FindFirstRecordByData("devices", "id", e.Request.PathValue("id"))
|
|
if err != nil {
|
|
return apis.NewNotFoundError("The device does not exist.", err)
|
|
}
|
|
|
|
record.Set("status", "pending")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
|
|
if err := networking.ShutdownDevice(record); err != nil {
|
|
logger.Error.Println(err)
|
|
record.Set("status", "online")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
return apis.NewBadRequestError(err.Error(), nil)
|
|
}
|
|
|
|
record.Set("status", "offline")
|
|
if err := e.App.Save(record); err != nil {
|
|
logger.Error.Println("Failed to save record:", err)
|
|
}
|
|
|
|
return e.JSON(http.StatusOK, record)
|
|
}
|
|
|
|
type Nmaprun struct {
|
|
Host []struct {
|
|
Address []struct {
|
|
Addr string `xml:"addr,attr" binding:"required"`
|
|
Addrtype string `xml:"addrtype,attr" binding:"required"`
|
|
Vendor string `xml:"vendor,attr"`
|
|
} `xml:"address"`
|
|
} `xml:"host"`
|
|
}
|
|
|
|
func HandlerScan(e *core.RequestEvent) error {
|
|
// check if nmap installed
|
|
nmap, err := exec.LookPath("nmap")
|
|
if err != nil {
|
|
return apis.NewBadRequestError(err.Error(), nil)
|
|
}
|
|
|
|
// check if scan range is valid
|
|
allPrivateSettings, err := e.App.FindAllRecords("settings_private")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
settingsPrivate := allPrivateSettings[0]
|
|
scanRange := settingsPrivate.GetString("scan_range")
|
|
_, ipNet, err := net.ParseCIDR(scanRange)
|
|
if err != nil {
|
|
return apis.NewBadRequestError(err.Error(), nil)
|
|
}
|
|
|
|
// run nmap
|
|
timeout := os.Getenv("UPSNAP_SCAN_TIMEOUT")
|
|
if timeout == "" {
|
|
timeout = "500ms"
|
|
}
|
|
cmd := exec.Command(nmap, "-sn", "-oX", "-", scanRange, "--host-timeout", timeout)
|
|
cmdOutput, err := cmd.Output()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// unmarshal xml
|
|
nmapOutput := Nmaprun{}
|
|
if err := xml.Unmarshal(cmdOutput, &nmapOutput); err != nil {
|
|
return err
|
|
}
|
|
|
|
type Device struct {
|
|
Name string `json:"name"`
|
|
IP string `json:"ip"`
|
|
MAC string `json:"mac"`
|
|
MACVendor string `json:"mac_vendor"`
|
|
}
|
|
|
|
// extract info from struct into data
|
|
type Response struct {
|
|
Netmask string `json:"netmask"`
|
|
Devices []Device `json:"devices"`
|
|
}
|
|
res := Response{}
|
|
var nm []string
|
|
for _, octet := range ipNet.Mask {
|
|
nm = append(nm, strconv.Itoa(int(octet)))
|
|
}
|
|
res.Netmask = strings.Join(nm, ".")
|
|
|
|
for _, host := range nmapOutput.Host {
|
|
dev := Device{}
|
|
for _, addr := range host.Address {
|
|
if addr.Addrtype == "ipv4" {
|
|
dev.IP = addr.Addr
|
|
} else if addr.Addrtype == "mac" {
|
|
dev.MAC = addr.Addr
|
|
}
|
|
if addr.Vendor != "" {
|
|
dev.MACVendor = addr.Vendor
|
|
} else {
|
|
dev.MACVendor = "Unknown"
|
|
}
|
|
}
|
|
|
|
if dev.IP == "" || dev.MAC == "" {
|
|
continue
|
|
}
|
|
|
|
names, err := net.LookupAddr(dev.IP)
|
|
if err != nil || len(names) == 0 {
|
|
dev.Name = dev.MACVendor
|
|
} else {
|
|
dev.Name = strings.TrimSuffix(names[0], ".")
|
|
}
|
|
|
|
if dev.Name == "" && dev.MACVendor == "" {
|
|
continue
|
|
}
|
|
|
|
res.Devices = append(res.Devices, dev)
|
|
}
|
|
|
|
return e.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
func HandlerInitSuperuser(e *core.RequestEvent) error {
|
|
superusersCollection, err := e.App.FindCollectionByNameOrId(core.CollectionNameSuperusers)
|
|
if err != nil {
|
|
return e.NotFoundError("Failed to retrieve superusers collection", err)
|
|
}
|
|
|
|
totalSuperusers, err := e.App.CountRecords(superusersCollection)
|
|
if err != nil {
|
|
return e.InternalServerError("Failed to retrieve superusers count", err)
|
|
}
|
|
|
|
if totalSuperusers > 0 {
|
|
return e.BadRequestError("An initial superuser already exists", nil)
|
|
}
|
|
|
|
data := struct {
|
|
Email string `json:"email" form:"email"`
|
|
Password string `json:"password" form:"password"`
|
|
PasswordConfirm string `json:"password_confirm" form:"password-confirm"`
|
|
}{}
|
|
err = e.BindBody(&data)
|
|
if err != nil {
|
|
return e.BadRequestError("Failed to read request data", err)
|
|
}
|
|
|
|
if data.Password != data.PasswordConfirm {
|
|
return e.BadRequestError("Password don't match", err)
|
|
}
|
|
|
|
record := core.NewRecord(superusersCollection)
|
|
record.SetEmail(data.Email)
|
|
record.SetPassword(data.Password)
|
|
err = e.App.Save(record)
|
|
if err != nil {
|
|
return e.BadRequestError("Failed to create initial superuser", err)
|
|
}
|
|
|
|
return apis.RecordAuthResponse(e, record, "", nil)
|
|
}
|
|
|
|
type ValidateCronRequest struct {
|
|
Cron string `json:"cron"`
|
|
}
|
|
|
|
func HandlerValidateCron(e *core.RequestEvent) error {
|
|
var body ValidateCronRequest
|
|
|
|
if err := e.BindBody(&body); err != nil {
|
|
logger.Error.Println(err)
|
|
return e.BadRequestError("invalid request", err)
|
|
}
|
|
|
|
p := cron.NewParser(cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
|
|
if _, err := p.Parse(body.Cron); err != nil {
|
|
return e.BadRequestError("failed to parse cron expression", err)
|
|
}
|
|
|
|
return e.JSON(200, "valid")
|
|
}
|