about summary refs log tree commit diff
path: root/main.go
blob: ec0f55f429eedc67caf013c7f845a3cc70e44ad7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package main

import (
	"bufio"
	"encoding/xml"
	"fmt"
	"golang.org/x/crypto/ssh/terminal"
	"net/http"
	"os"
	"strings"
	"syscall"
)

// 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) != 1 {
		fmt.Fprintln(os.Stderr, "Usage: watchblob <vpn-host>")
		os.Exit(1)
	}

	host := args[0]

	username, password, err := readCredentials()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Could not read credentials: %v\n", err)
	}

	fmt.Println("Requesting challenge from %s as user %s\n", host, username)
	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.Printf("Login succeeded, you may now (quickly) authenticate OpenVPN with %d as your password\n", token)
}

func readCredentials() (string, string, error) {
	fmt.Printf("Username: ")
	reader := bufio.NewReader(os.Stdin)
	username, err := reader.ReadString('\n')

	fmt.Printf("Password: ")
	password, err := terminal.ReadPassword(syscall.Stdin)

	// If an error occured, I don't care about which one it is.
	return strings.TrimSpace(username), strings.TrimSpace(string(password)), err
}

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) {
	resp, err := http.Get(url)
	if err != nil {
		return
	}

	defer resp.Body.Close()
	decoder := xml.NewDecoder(resp.Body)

	err = decoder.Decode(&r)
	return
}