diff --git a/go.mod b/go.mod index 059c5ce..8a5530d 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,5 @@ require ( github.com/mdlayher/lldp v0.0.0-20150915211757-afd9f83164c5 github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 github.com/vishvananda/netlink v1.1.0 + gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 442989a..61eddde 100644 --- a/go.sum +++ b/go.sum @@ -21,3 +21,6 @@ golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444 h1:/d2cWp6PSamH4jDPFLyO150psQdqvtoNX8Zjg3AQ31g= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index f034913..09bde0a 100644 --- a/main.go +++ b/main.go @@ -31,11 +31,23 @@ func main() { } log.Printf("UniFi API URL: %s", api) + /* upstream, err := getUpstream(link.Attrs().Name) if err != nil { log.Fatal(err) } log.Printf("Connected to %s [%s] on %s", upstream.device, upstream.addr, upstream.port) + */ + + unifi, err := NewClient(api) + if err != nil { + log.Fatal(err) + } + + err = unifi.Login() + if err != nil { + log.Fatal(err) + } } type upstream struct { @@ -137,7 +149,7 @@ func findApis(start_ip, end_ip uint32) ([]string, error) { defer cancel() url := fmt.Sprintf( - "https://%d.%d.%d.%d/", + "https://%d.%d.%d.%d", ip >> 24 & 0xff, ip >> 16 & 0xff, ip >> 8 & 0xff, diff --git a/unifi.go b/unifi.go new file mode 100644 index 0000000..1d950d9 --- /dev/null +++ b/unifi.go @@ -0,0 +1,164 @@ +package main + +import ( + "bufio" + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "log" + "net/http" + "net/http/cookiejar" + "os" + "path" + "strings" + "time" + + "gopkg.in/yaml.v2" +) + +type Client struct { + http_client *http.Client + base_url string +} + +func NewClient(base_url string) (*Client, error) { + jar, err := cookiejar.New(nil) + if err != nil { + return nil, err + } + + return &Client{ + http_client: &http.Client{ + Timeout: 5 * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + Jar: jar, + }, + base_url: base_url, + }, nil +} + +type Creds struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type ErrorResp struct { + Errors []string +} + +func (c *Client) Login() error { + needSave := false + + creds, err := loadCreds() + if err != nil { + creds, err = promptCreds() + if err != nil { + return err + } + needSave = true + } + + body, err := json.Marshal(creds) + if err != nil { + return err + } + + req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/auth/login", c.base_url), bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") + + resp, err := c.http_client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + dec := json.NewDecoder(resp.Body) + error_resp := ErrorResp{} + err = dec.Decode(&error_resp) + if err != nil { + return err + } + return fmt.Errorf("Failed to log into UniFi with saved credentials (\"%s\"). Delete %s to prompt again.", error_resp.Errors[0], credPath()) + } + + log.Printf("Logged into UniFi as %s", creds.Username) + + if needSave { + err = creds.Save() + if err != nil { + return err + } + } + + return nil +} + +func loadCreds() (*Creds, error) { + fh, err := os.OpenFile(credPath(), os.O_RDONLY, 0) + if err != nil { + return nil, err + } + defer fh.Close() + + dec := yaml.NewDecoder(fh) + + ret := &Creds{} + + err = dec.Decode(ret) + if err != nil { + return nil, err + } + + return ret, nil +} + +func promptCreds() (*Creds, error) { + reader := bufio.NewReader(os.Stdin) + + fmt.Print("UniFi username: ") + username, err := reader.ReadString('\n') + if err != nil { + return nil, err + } + + fmt.Print("UniFi password: ") + password, err := reader.ReadString('\n') + if err != nil { + return nil, err + } + + return &Creds{ + Username: strings.TrimSuffix(username, "\n"), + Password: strings.TrimSuffix(password, "\n"), + }, nil +} + +func (c *Creds) Save() error { + fh, err := os.OpenFile(credPath(), os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return err + } + defer fh.Close() + + enc := yaml.NewEncoder(fh) + defer enc.Close() + + err = enc.Encode(c) + if err != nil { + return err + } + + log.Printf("Saved credentials to %s", credPath()) + + return nil +} + +func credPath() string { + return path.Join(os.Getenv("HOME"), ".netperfect.creds") +}