diff --git a/pkg/onvif/client.go b/pkg/onvif/client.go index 77bbe0ff..bad103c7 100644 --- a/pkg/onvif/client.go +++ b/pkg/onvif/client.go @@ -3,10 +3,9 @@ package onvif import ( "bytes" "errors" - "fmt" "html" "io" - "net" + "net/http" "net/url" "regexp" "strings" @@ -32,26 +31,18 @@ func NewClient(rawURL string) (*Client, error) { baseURL := "http://" + u.Host client := &Client{url: u} - if u.Path == "" { - client.deviceURL = baseURL + PathDevice - } else { - client.deviceURL = baseURL + u.Path - } + client.deviceURL = baseURL + GetPath(u.Path, PathDevice) b, err := client.DeviceRequest(DeviceGetCapabilities) if err != nil { return nil, err } - client.mediaURL = FindTagValue(b, "Media.+?XAddr") - if client.mediaURL == "" { - client.mediaURL = baseURL + "/onvif/media_service" - } + s := FindTagValue(b, "Media.+?XAddr") + client.mediaURL = baseURL + GetPath(s, "/onvif/media_service") - client.imaginURL = FindTagValue(b, "Imaging.+?XAddr") - if client.imaginURL == "" { - client.imaginURL = baseURL + "/onvif/imaging_service" - } + s = FindTagValue(b, "Imaging.+?XAddr") + client.imaginURL = baseURL + GetPath(s, "/onvif/imaging_service") return client, nil } @@ -183,62 +174,24 @@ func (c *Client) MediaRequest(operation string) ([]byte, error) { return c.Request(c.mediaURL, operation) } -func (c *Client) Request(rawUrl, body string) ([]byte, error) { - if rawUrl == "" { +func (c *Client) Request(url, body string) ([]byte, error) { + if url == "" { return nil, errors.New("onvif: unsupported service") } e := NewEnvelopeWithUser(c.url.User) e.Append(body) - u, err := url.Parse(rawUrl) + client := &http.Client{Timeout: time.Second * 5000} + res, err := client.Post(url, `application/soap+xml;charset=utf-8`, bytes.NewReader(e.Bytes())) if err != nil { return nil, err } + defer res.Body.Close() - host := u.Host - if u.Port() == "" { - host += ":80" + if res.StatusCode != http.StatusOK { + return nil, errors.New("onvif: wrong response " + res.Status) } - conn, err := net.DialTimeout("tcp", host, 5*time.Second) - if err != nil { - return nil, err - } - defer conn.Close() - - reqBody := e.Bytes() - rawReq := fmt.Appendf(nil, "POST %s HTTP/1.1\r\n"+ - "Host: %s\r\n"+ - "Content-Type: application/soap+xml;charset=utf-8\r\n"+ - "Content-Length: %d\r\n"+ - "Connection: close\r\n"+ - "\r\n", u.Path, u.Host, len(reqBody)) - rawReq = append(rawReq, reqBody...) - - if _, err = conn.Write(rawReq); err != nil { - return nil, err - } - - rawRes, err := io.ReadAll(conn) - if err != nil { - return nil, err - } - - // Look for XML in complete response - if i := bytes.Index(rawRes, []byte(" 0 { - return rawRes[i:], nil - } - - // No XML found - might be an error response - if i := bytes.Index(rawRes, []byte("\r\n\r\n")); i > 0 { - if bytes.Contains(rawRes[:i], []byte("chunked")) { - return nil, errors.New("onvif: TODO: support chunked encoding") - } - - // Return body after headers - return rawRes[i+4:], nil - } - - return rawRes, nil + return io.ReadAll(res.Body) } diff --git a/pkg/onvif/helpers.go b/pkg/onvif/helpers.go index f240f2ec..893beb00 100644 --- a/pkg/onvif/helpers.go +++ b/pkg/onvif/helpers.go @@ -3,6 +3,7 @@ package onvif import ( "fmt" "net" + "net/url" "regexp" "strconv" "strings" @@ -129,3 +130,14 @@ func GetPosixTZ(current time.Time) string { return prefix + fmt.Sprintf("%02d:%02d", offset/60, offset%60) } + +func GetPath(urlOrPath, defPath string) string { + if urlOrPath == "" || urlOrPath[0] == '/' { + return defPath + } + u, err := url.Parse(urlOrPath) + if err != nil { + return defPath + } + return GetPath(u.Path, defPath) +} diff --git a/pkg/tapo/client.go b/pkg/tapo/client.go index 5a9af501..be669eb5 100644 --- a/pkg/tapo/client.go +++ b/pkg/tapo/client.go @@ -140,6 +140,12 @@ func (c *Client) newDectypter(res *http.Response, brand, username, password stri username = "admin" } + if strings.Contains(exchange, `username="none"`) { + // https://nvd.nist.gov/vuln/detail/CVE-2022-37255 + username = "none" + password = "TPL075526460603" + } + key := md5.Sum([]byte(nonce + ":" + password)) iv := md5.Sum([]byte(username + ":" + nonce)) @@ -158,8 +164,9 @@ func (c *Client) newDectypter(res *http.Response, brand, username, password stri cbc.CryptBlocks(b, b) // unpad - padSize := int(b[len(b)-1]) - return b[:len(b)-padSize] + n := len(b) + padSize := int(b[n-1]) + return b[:n-padSize] } } @@ -297,7 +304,7 @@ func dial(req *http.Request, brand, username, password string) (net.Conn, *http. auth := res.Header.Get("WWW-Authenticate") if res.StatusCode != http.StatusUnauthorized || !strings.HasPrefix(auth, "Digest") { - return nil, nil, fmt.Errorf("Expected StatusCode to be %d, received %d", http.StatusUnauthorized, res.StatusCode) + return nil, nil, errors.New("tapo: wrond status: " + res.Status) } if brand == "tapo" && password == "" {