Fix support Tapo C210 firmware v1.3.9 #733
This commit is contained in:
+78
-18
@@ -1,10 +1,12 @@
|
||||
package tapo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -14,6 +16,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/mpegts"
|
||||
@@ -62,33 +65,24 @@ func (c *Client) newConn() (net.Conn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// support raw username/password
|
||||
username := u.User.Username()
|
||||
password, _ := u.User.Password()
|
||||
|
||||
// or cloud password in place of username
|
||||
if password == "" {
|
||||
password = fmt.Sprintf("%16X", md5.Sum([]byte(username)))
|
||||
username = "admin"
|
||||
u.User = url.UserPassword(username, password)
|
||||
// support raw username/password or cloud password in place of username
|
||||
if _, ok := u.User.Password(); !ok {
|
||||
u.User = url.UserPassword("admin", u.User.Username())
|
||||
}
|
||||
|
||||
u.Scheme = "http"
|
||||
u.Path = "/stream"
|
||||
if u.Port() == "" {
|
||||
u.Host += ":8800"
|
||||
}
|
||||
|
||||
// TODO: fix closing connection
|
||||
ctx, pconn := tcp.WithConn()
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", u.String(), nil)
|
||||
req, err := http.NewRequest("POST", "http://"+u.Host+"/stream", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.URL.User = u.User
|
||||
req.Header.Set("Content-Type", "multipart/mixed; boundary=--client-stream-boundary--")
|
||||
|
||||
res, err := tcp.Do(req)
|
||||
conn, res, err := dial(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -98,13 +92,16 @@ func (c *Client) newConn() (net.Conn, error) {
|
||||
}
|
||||
|
||||
if c.decrypt == nil {
|
||||
c.newDectypter(res, username, password)
|
||||
c.newDectypter(res)
|
||||
}
|
||||
|
||||
return *pconn, nil
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) newDectypter(res *http.Response, username, password string) {
|
||||
func (c *Client) newDectypter(res *http.Response) {
|
||||
username := res.Request.URL.User.Username()
|
||||
password, _ := res.Request.URL.User.Password()
|
||||
|
||||
// extract nonce from response
|
||||
// cipher="AES_128_CBC" username="admin" padding="PKCS7_16" algorithm="MD5" nonce="***"
|
||||
nonce := res.Header.Get("Key-Exchange")
|
||||
@@ -244,3 +241,66 @@ func (c *Client) Request(conn net.Conn, body []byte) (string, error) {
|
||||
return v.Params.SessionID, nil
|
||||
}
|
||||
}
|
||||
|
||||
func dial(req *http.Request) (net.Conn, *http.Response, error) {
|
||||
conn, err := net.DialTimeout("tcp", req.URL.Host, core.ConnDialTimeout)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
username := req.URL.User.Username()
|
||||
password, _ := req.URL.User.Password()
|
||||
req.URL.User = nil
|
||||
|
||||
if err = req.Write(conn); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
r := bufio.NewReader(conn)
|
||||
|
||||
res, err := http.ReadResponse(r, req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
auth := res.Header.Get("WWW-Authenticate")
|
||||
|
||||
if res.StatusCode != http.StatusUnauthorized || !strings.HasPrefix(auth, "Digest") {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if strings.Contains(auth, `encrypt_type="3"`) {
|
||||
password = fmt.Sprintf("%32X", sha256.Sum256([]byte(password)))
|
||||
} else {
|
||||
password = fmt.Sprintf("%16X", md5.Sum([]byte(password)))
|
||||
}
|
||||
|
||||
realm := tcp.Between(auth, `realm="`, `"`)
|
||||
nonce := tcp.Between(auth, `nonce="`, `"`)
|
||||
qop := tcp.Between(auth, `qop="`, `"`)
|
||||
uri := req.URL.RequestURI()
|
||||
ha1 := tcp.HexMD5(username, realm, password)
|
||||
ha2 := tcp.HexMD5(req.Method, uri)
|
||||
nc := "00000001"
|
||||
cnonce := "00000001"
|
||||
response := tcp.HexMD5(ha1, nonce, nc, cnonce, qop, ha2)
|
||||
|
||||
header := fmt.Sprintf(
|
||||
`Digest username="%s", realm="%s", nonce="%s", uri="%s", qop=%s, nc=%s, cnonce="%s", response="%s"`,
|
||||
username, realm, nonce, uri, qop, nc, cnonce, response,
|
||||
)
|
||||
|
||||
req.Header.Set("Authorization", header)
|
||||
|
||||
if err = req.Write(conn); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if res, err = http.ReadResponse(r, req); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
req.URL.User = url.UserPassword(username, password)
|
||||
|
||||
return conn, res, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user