about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <tazjin@gmail.com>2017-02-11T11·27+0100
committerVincent Ambo <tazjin@gmail.com>2017-02-11T11·27+0100
commit98e81c2c0edd3d9bb483000d598e07e6dd9da6b0 (patch)
tree030c74862a1ca6990d3a97d6a8ebe85d8b31fb7e
parent33174cbb80a3147fb7bca9b1ac7f462350e7e0c7 (diff)
feat: Initial working implementation
-rw-r--r--main.go90
-rw-r--r--main_test.go96
-rw-r--r--urls.go19
3 files changed, 205 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 000000000000..11f27260e3ac
--- /dev/null
+++ b/main.go
@@ -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
+}
diff --git a/main_test.go b/main_test.go
new file mode 100644
index 000000000000..5c171c4041c4
--- /dev/null
+++ b/main_test.go
@@ -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()
+	}
+}
diff --git a/urls.go b/urls.go
new file mode 100644
index 000000000000..a1fd825f578c
--- /dev/null
+++ b/urls.go
@@ -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)
+}