Skip to content

Commit

Permalink
Evolution for #18, and new HTTP query parameter ?format=JSON or ?form…
Browse files Browse the repository at this point in the history
…at=XML
  • Loading branch information
SR-G committed Dec 20, 2017
1 parent 8aac537 commit 7182b32
Show file tree
Hide file tree
Showing 15 changed files with 164 additions and 22 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ build:
clean:
rm -rf bin

conf:
cp /go/src/main/resources/sol.json /go/bin/

run:
bin/sol

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ Authed REST may still be triggered from a remote host, if needed, through :
curl http://myusername:mypassword@<IP>/sleep/
```

Default output from REST command is `XML` but may be switched from a configuration point of view (by adding a `HTTPOutput : 'JSON'`) or on a per-request basis (by adding a `?format=JSON` to the request, one would retrieve a JSON result).

**LogLevel** defines the log level to use. Available values are NONE|OFF, DEBUG, INFO, WARN|WARNING, ERROR. Logs are just written to the stderr/stdout outputs.

**BroadcastIP** defines the broadcast IP used by the /wol service. By default the IP used is 192.168.255.255 (local network range).
Expand Down
1 change: 1 addition & 0 deletions src/github.com/Microsoft/go-winio
Submodule go-winio added at 784399
1 change: 1 addition & 0 deletions src/github.com/mdlayher/arp
Submodule arp added at 7140e8
1 change: 1 addition & 0 deletions src/github.com/mdlayher/ethernet
Submodule ethernet added at e72cf8
1 change: 1 addition & 0 deletions src/github.com/mdlayher/raw
Submodule raw added at 9df8b4
1 change: 1 addition & 0 deletions src/github.com/sparrc/go-ping
Submodule go-ping added at 416e72
1 change: 1 addition & 0 deletions src/golang.org/x/net
Submodule net added at d866cf
1 change: 1 addition & 0 deletions src/golang.org/x/sys
Submodule sys added at 0dd5e1
1 change: 1 addition & 0 deletions src/golang.org/x/text
Submodule text added at eb2267
2 changes: 2 additions & 0 deletions src/main/go/sol/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Configuration struct {
BroadcastIP string
Commands []CommandConfiguration // the various defined commands. Will be enhanded with default operation if empty from configuration
Auth AuthConfiguration // optional
HTTPOutput string

listenersConfiguration []ListenerConfiguration // converted once parsed from Listeners
}
Expand Down Expand Up @@ -49,6 +50,7 @@ func (conf *Configuration) InitDefaultConfiguration() {
conf.Listeners = []string{"UDP:9", "HTTP:8009"}
conf.LogLevel = "INFO"
conf.BroadcastIP = "192.168.255.255"
conf.HTTPOutput = "XML"
// default commands are registered on Parse() method, depending on the current operating system
}

Expand Down
118 changes: 101 additions & 17 deletions src/main/go/sol/listener_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"sort"
"strconv"
"time"
// "encoding/json"
// "io/ioutil"
"os"
Expand All @@ -13,46 +14,49 @@ import (
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
// "github.com/labstack/echo/engine/standard"

"github.com/sparrc/go-ping" // for ping
// "github.com/mdlayher/arp" // for mac > ip conversion
)

type RestResultHost struct {
XMLName xml.Name `xml:"host"`
XMLName xml.Name `xml:"host" json:"-"`
Ip string `xml:"ip,attr"`
MacAddress string `xml:"mac,attr"`
}

type RestResultHosts struct {
XMLName xml.Name `xml:"hosts"`
XMLName xml.Name `xml:"hosts" json:"-"`
Hosts []RestResultHost
}

type RestResultCommands struct {
XMLName xml.Name `xml:"commands"`
XMLName xml.Name `xml:"commands" json:"-"`
Commands []RestResultCommandConfiguration
}

type RestResultCommandConfiguration struct {
XMLName xml.Name `xml:"command"`
XMLName xml.Name `xml:"command" json:"-"`
Operation string `xml:"operation,attr"`
Command string `xml:"command,attr"`
IsDefault bool `xml:"default,attr"`
Type string `xml:"type,attr"`
}

type RestResultListeners struct {
XMLName xml.Name `xml:"listeners"`
XMLName xml.Name `xml:"listeners" json:"-"`
Listeners []RestResultListenerConfiguration
}

type RestResultListenerConfiguration struct {
XMLName xml.Name `xml:"listener"`
XMLName xml.Name `xml:"listener" json:"-"`
Type string `xml:"type,attr"`
Port int `xml:"port,attr"`
Active bool `xml:"active,attr"`
}

type RestResult struct {
XMLName xml.Name `xml:"result"`
XMLName xml.Name `xml:"result" json:"-"`
Application string `xml:"application"`
Version string `xml:"version"`
CompilationTimestamp string `xml:"compilation"`
Expand All @@ -62,16 +66,65 @@ type RestResult struct {
}

type RestOperationResult struct {
XMLName xml.Name `xml:"result"`
XMLName xml.Name `xml:"result" json:"-"`
Operation string `xml:"operation"`
Result bool `xml:"successful"`
}

const (
HOST_STATE_ONLINE = "online"
HOST_STATE_OFFLINE = "offline"
HOST_STATE_UNKNOWN = "unknown"
)

type RestStateResult struct {
XMLName xml.Name `xml:"result" json:"-"`
State string `xml:"state"`
Host string `xml:"host"`
}

func dumpRoute(route string) {
Info.Println("Registering route [/" + route + "]")
}

func ListenHTTP(port int, commands []CommandConfiguration, auth AuthConfiguration) {
// func retrieveIpFromMac(mac strinc) string {
// requires defined interface ...
// }

func renderResult(c echo.Context, status int, result interface{}) error {
format := c.QueryParam("format")
if strings.EqualFold(configuration.HTTPOutput, "JSON") || strings.EqualFold(format, "JSON") {
return c.JSONPretty(status, result, " ")
} else {
return c.XMLPretty(status, result, " ")
}
}

func pingIp(ip string) *RestStateResult {
Info.Println("Checking state of remote host with IP [" + ip + "]")
result := &RestStateResult{
Host: ip,
State: HOST_STATE_ONLINE,
}
pinger, err := ping.NewPinger(ip)
if err != nil {
Info.Println("Can't retrieve PING results (rights problems when executing sol, maybe ?)", err)
result.State = HOST_STATE_UNKNOWN
}
pinger.Count = 3
// pinger.Interval = // default is 1s, which is fine
pinger.Timeout = time.Second * 5
pinger.SetPrivileged(true)
pinger.Run() // blocks until finished
stats := pinger.Statistics() // get send/receive/rtt stats
Info.Println("Ping results for [" + stats.Addr + "], [" + strconv.Itoa(stats.PacketsSent) + "] packets transmitted, [" + strconv.Itoa(stats.PacketsRecv) + "] packets received, [" + strconv.FormatFloat(stats.PacketLoss, 'f', 2, 64) + "] packet loss") // , round-trip min/avg/max/stddev = " + stats.MinRtt + "/" + stats.AvgRtt + "/" + stats.MaxRtt + "/" + stats.StdDevRtt + "")
if (stats.PacketsRecv == 0) {
result.State = HOST_STATE_OFFLINE
}
return result
}

func ListenHTTP(port int) {
// externalIp, _ := ExternalIP()
// baseExternalUrl := "http://" + externalIp + ":" + strconv.Itoa(port)
// Info.Println("Now listening HTTP on port [" + strconv.Itoa(port) + "], urls will be : ")
Expand All @@ -88,12 +141,12 @@ func ListenHTTP(port int, commands []CommandConfiguration, auth AuthConfiguratio
// e.Static("/", "public")
// e.Use(middleware.Gzip())

if auth.isEmpty() {
if configuration.Auth.isEmpty() {
Info.Println("HTTP starting on port [" + strconv.Itoa(port) + "], without auth")
} else {
Info.Println("HTTP starting on port [" + strconv.Itoa(port) + "], with auth activated : login [" + auth.Login + "], password [" + strings.Repeat("*", len(auth.Password)) + "]")
Info.Println("HTTP starting on port [" + strconv.Itoa(port) + "], with auth activated : login [" + configuration.Auth.Login + "], password [" + strings.Repeat("*", len(configuration.Auth.Password)) + "]")
e.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
if username == auth.Login && password == auth.Password {
if username == configuration.Auth.Login && password == configuration.Auth.Password {
return true, nil
}
return false, nil
Expand Down Expand Up @@ -129,11 +182,11 @@ func ListenHTTP(port int, commands []CommandConfiguration, auth AuthConfiguratio
result.Commands.Commands = append(result.Commands.Commands, RestResultCommandConfiguration{Type: commandConfiguration.CommandType, Operation: commandConfiguration.Operation, Command: commandConfiguration.Command, IsDefault: commandConfiguration.IsDefault})
}

return c.XMLPretty(http.StatusOK, result, " ")
return renderResult(c, http.StatusOK, result)
})

// N.B.: sleep operation is now registred through commands below
for _, command := range commands {
for _, command := range configuration.Commands {
dumpRoute(command.Operation)
e.GET("/" + command.Operation, func(c echo.Context) error {

Expand All @@ -151,8 +204,8 @@ func ListenHTTP(port int, commands []CommandConfiguration, auth AuthConfiguratio
ExecuteCommand(availableCommand)
break
}
}
return c.XMLPretty(http.StatusOK, result, " ")
}
return renderResult(c, http.StatusOK, result)
})
}

Expand All @@ -173,6 +226,37 @@ func ListenHTTP(port int, commands []CommandConfiguration, auth AuthConfiguratio
// return c.XMLPretty(http.StatusOK, result, " ")
})

dumpRoute("state/local/online")
e.GET("/state/local/online", func(c echo.Context) error {
return c.String(http.StatusOK, "true")
})

dumpRoute("state/local")
e.GET("/state/local", func(c echo.Context) error {
result := &RestStateResult{
Host: "localhost",
State: HOST_STATE_ONLINE,
}
return renderResult(c, http.StatusOK, result)
})

dumpRoute("state/ip/:ip")
e.GET("/state/ip/:ip", func(c echo.Context) error {
ip := c.Param("ip")
result := pingIp(ip)
return renderResult(c, http.StatusOK, result)
})

/*
dumpRoute("state/mac/:mac")
e.GET("/state/ip/:ip", func(c echo.Context) error {
mac := c.Param("mac")
ip := retrieveIpFromMac(mac)
result := pingIp(ip)
return c.XMLPretty(http.StatusOK, result, " ")
})
*/

dumpRoute("wol/:mac")
e.GET("/wol/:mac", func(c echo.Context) error {
result := &RestOperationResult{
Expand All @@ -188,7 +272,7 @@ func ListenHTTP(port int, commands []CommandConfiguration, auth AuthConfiguratio
} else {
magicPacket.Wake(configuration.BroadcastIP)
}
return c.XMLPretty(http.StatusOK, result, " ")
return renderResult(c, http.StatusOK, result)
})

// localIp := "0.0.0.0"
Expand Down
3 changes: 2 additions & 1 deletion src/main/go/sol/listener_udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ func leftPad2Len(s string, padStr string, overallLen int) string {
}

func doAction() {
for _, Command := range configuration.Commands {
for idx, _ := range configuration.Commands {
Command := configuration.Commands[idx]
if Command.IsDefault {
ExecuteCommand(Command)
break
Expand Down
48 changes: 45 additions & 3 deletions src/main/go/sol/sleeper_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,31 @@ package main

import (
"syscall"
"unsafe"
"fmt"

winio "github.com/Microsoft/go-winio"
)

const (
DEFAULT_COMMAND_SLEEP = "sleep"
DEFAULT_COMMAND_SHUTDOWN = "shutdown"
)

func RegisterDefaultCommand() {
defaultCommand := CommandConfiguration{Operation: "sleep", CommandType: COMMAND_TYPE_INTERNAL_DLL, IsDefault: true}
configuration.Commands = []CommandConfiguration{defaultCommand}
defaultSleepCommand := CommandConfiguration{Operation: DEFAULT_COMMAND_SLEEP, CommandType: COMMAND_TYPE_INTERNAL_DLL, IsDefault: true}
defaultShutdownCommand := CommandConfiguration{Operation: DEFAULT_COMMAND_SHUTDOWN, CommandType: COMMAND_TYPE_INTERNAL_DLL, IsDefault: false}
configuration.Commands = []CommandConfiguration{defaultSleepCommand, defaultShutdownCommand}
}

func ExecuteCommand(Command CommandConfiguration) {
if Command.CommandType == COMMAND_TYPE_INTERNAL_DLL {
Info.Println("Executing operation [" + Command.Operation + "], type[" + Command.CommandType + "]")
sleepDLLImplementation()
if Command.Operation == DEFAULT_COMMAND_SLEEP {
sleepDLLImplementation()
} else if Command.Operation == DEFAULT_COMMAND_SHUTDOWN {
shutdownDLLImplementation()
}
} else if Command.CommandType == COMMAND_TYPE_EXTERNAL {
Info.Println("Executing operation [" + Command.Operation + "], type[" + Command.CommandType + "], command [" + Command.Command + "]")
sleepCommandLineImplementation(Command.Command)
Expand Down Expand Up @@ -45,5 +58,34 @@ func sleepDLLImplementation() {
uintptr(0), // hibernate
uintptr(1), // forceCritical
uintptr(1)) // disableWakeEvent

Info.Printf("Command executed, result code [" + fmt.Sprint(ret) + "]")
}

func shutdownDLLImplementation() {
// SeShutdownPrivilege
err := winio.RunWithPrivilege("SeShutdownPrivilege", func() error {
var mod = syscall.NewLazyDLL("Advapi32.dll")
var proc = mod.NewProc("InitiateSystemShutdownW")

// DLL API : public static extern bool InitiateSystemShutdown(string lpMachineName, string lpMessage, int dwTimeout, bool bForceAppsClosed, bool bRebootAfterShutdown);
// ex. : uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Done Title"))),

// var a [1]byte
// a[0] = byte(0)
// addrPtr := unsafe.Pointer(&a)
ret, _, _ := proc.Call(0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(""))), // lpMachineName
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(""))), // lpMessage
uintptr(0), // dwTimeout
uintptr(1), // bForceAppsClosed
uintptr(0)) // bRebootAfterShutdown

// ret 0 = false, ret 1 = true = success
Info.Printf("Command executed, result code [" + fmt.Sprint(ret) + "]")
return nil
})
if err != nil {
Error.Printf("Can't execute command")
}
}
2 changes: 1 addition & 1 deletion src/main/go/sol/sol.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func main() {
if strings.EqualFold(listenerConfiguration.nature, "UDP") {
go ListenUDP(listenerConfiguration.port)
} else if strings.EqualFold(listenerConfiguration.nature, "HTTP") {
go ListenHTTP(listenerConfiguration.port, configuration.Commands, configuration.Auth)
go ListenHTTP(listenerConfiguration.port) // , configuration.Commands, configuration.Auth, configuration.HTTPOutput)
}
}
}
Expand Down

0 comments on commit 7182b32

Please sign in to comment.