diff --git a/main.go b/main.go new file mode 100644 index 000000000..11f27260e --- /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 \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 000000000..5c171c404 --- /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 = ` + + + sslvpn_logon + 4 + + + RADIUS + + + 441 + Enter Your 6 Digit Passcode +` + + 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 = ` + + + sslvpn_logon + 2 + + + RADIUS + + + 501 +` + + 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 = ` + + + sslvpn_logon + 1 + + + RADIUS + + + +` + 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 000000000..a1fd825f5 --- /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) +}