feat: Initial working implementation
This commit is contained in:
parent
33174cbb80
commit
98e81c2c0e
3 changed files with 205 additions and 0 deletions
90
main.go
Normal file
90
main.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The XML response returned by the WatchGuard server
|
||||
type Resp struct {
|
||||
Action string `xml:"action"`
|
||||
LogonStatus int `xml:"logon_status"`
|
||||
LogonId int `xml:"logon_id"`
|
||||
Error string `xml:"errStr"`
|
||||
Challenge string `xml:"chaStr"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
args := os.Args[1:]
|
||||
|
||||
if len(args) != 3 {
|
||||
fmt.Fprintf(os.Stderr, "Usage: watchblob <vpn-host> <username> <password>\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
host := args[0]
|
||||
username := args[1]
|
||||
password := args[2]
|
||||
|
||||
challenge, err := triggerChallengeResponse(&host, &username, &password)
|
||||
|
||||
if err != nil || challenge.LogonStatus != 4 {
|
||||
fmt.Fprintln(os.Stderr, "Did not receive challenge from server")
|
||||
fmt.Fprintf(os.Stderr, "Response: %v\nError: %v\n", challenge, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
token := getToken(&challenge)
|
||||
err = logon(&host, &challenge, &token)
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Logon failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Login succeeded, you may now (quickly) authenticate OpenVPN with %s as your password", token)
|
||||
}
|
||||
|
||||
func triggerChallengeResponse(host *string, username *string, password *string) (r Resp, err error) {
|
||||
return request(templateUrl(host, templateChallengeTriggerUri(username, password)))
|
||||
}
|
||||
|
||||
func getToken(challenge *Resp) string {
|
||||
fmt.Println(challenge.Challenge)
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
token, _ := reader.ReadString('\n')
|
||||
|
||||
return strings.TrimSpace(token)
|
||||
}
|
||||
|
||||
func logon(host *string, challenge *Resp, token *string) (err error) {
|
||||
resp, err := request(templateUrl(host, templateResponseUri(challenge.LogonId, token)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if resp.LogonStatus != 1 {
|
||||
err = fmt.Errorf("Challenge/response authentication failed: %v", resp)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func request(url string) (r Resp, err error) {
|
||||
fmt.Println(url)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
decoder := xml.NewDecoder(resp.Body)
|
||||
|
||||
err = decoder.Decode(&r)
|
||||
return
|
||||
}
|
96
main_test.go
Normal file
96
main_test.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnmarhshalChallengeRespones(t *testing.T) {
|
||||
var testXml string = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resp>
|
||||
<action>sslvpn_logon</action>
|
||||
<logon_status>4</logon_status>
|
||||
<auth-domain-list>
|
||||
<auth-domain>
|
||||
<name>RADIUS</name>
|
||||
</auth-domain>
|
||||
</auth-domain-list>
|
||||
<logon_id>441</logon_id>
|
||||
<chaStr>Enter Your 6 Digit Passcode </chaStr>
|
||||
</resp>`
|
||||
|
||||
var r Resp
|
||||
xml.Unmarshal([]byte(testXml), &r)
|
||||
|
||||
expected := Resp{
|
||||
Action: "sslvpn_logon",
|
||||
LogonStatus: 4,
|
||||
LogonId: 441,
|
||||
Challenge: "Enter Your 6 Digit Passcode ",
|
||||
}
|
||||
|
||||
assertEqual(t, expected, r)
|
||||
}
|
||||
|
||||
func TestUnmarshalLoginError(t *testing.T) {
|
||||
var testXml string = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resp>
|
||||
<action>sslvpn_logon</action>
|
||||
<logon_status>2</logon_status>
|
||||
<auth-domain-list>
|
||||
<auth-domain>
|
||||
<name>RADIUS</name>
|
||||
</auth-domain>
|
||||
</auth-domain-list>
|
||||
<errStr>501</errStr>
|
||||
</resp>`
|
||||
|
||||
var r Resp
|
||||
xml.Unmarshal([]byte(testXml), &r)
|
||||
|
||||
expected := Resp{
|
||||
Action: "sslvpn_logon",
|
||||
LogonStatus: 2,
|
||||
Error: "501",
|
||||
}
|
||||
|
||||
assertEqual(t, expected, r)
|
||||
}
|
||||
|
||||
func TestUnmarshalLoginSuccess(t *testing.T) {
|
||||
var testXml string = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resp>
|
||||
<action>sslvpn_logon</action>
|
||||
<logon_status>1</logon_status>
|
||||
<auth-domain-list>
|
||||
<auth-domain>
|
||||
<name>RADIUS</name>
|
||||
</auth-domain>
|
||||
</auth-domain-list>
|
||||
</resp>
|
||||
`
|
||||
var r Resp
|
||||
xml.Unmarshal([]byte(testXml), &r)
|
||||
|
||||
expected := Resp{
|
||||
Action: "sslvpn_logon",
|
||||
LogonStatus: 1,
|
||||
}
|
||||
|
||||
assertEqual(t, expected, r)
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, expected interface{}, result interface{}) {
|
||||
if !reflect.DeepEqual(expected, result) {
|
||||
t.Errorf(
|
||||
"Unmarshaled values did not match.\nExpected: %v\nResult: %v\n",
|
||||
expected, result,
|
||||
)
|
||||
|
||||
t.Fail()
|
||||
}
|
||||
}
|
19
urls.go
Normal file
19
urls.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
const urlFormat string = "https://%s%s"
|
||||
const triggerChallengeUri = "/?action=sslvpn_logon&fw_username=%s&fw_password=%s&style=fw_logon_progress.xsl&fw_logon_type=logon&fw_domain=Firebox-DB"
|
||||
const responseUri = "/?action=sslvpn_logon&style=fw_logon_progress.xsl&fw_logon_type=response&response=%s&fw_logon_id=%d"
|
||||
|
||||
func templateChallengeTriggerUri(username *string, password *string) string {
|
||||
return fmt.Sprintf(triggerChallengeUri, *username, *password)
|
||||
}
|
||||
|
||||
func templateResponseUri(logonId int, token *string) string {
|
||||
return fmt.Sprintf(responseUri, *token, logonId)
|
||||
}
|
||||
|
||||
func templateUrl(baseUrl *string, uri string) string {
|
||||
return fmt.Sprintf("https://%s%s", *baseUrl, uri)
|
||||
}
|
Loading…
Reference in a new issue