@@ -27,7 +27,7 @@ import (
2727type Client struct {
2828 core.Listener
2929
30- url string
30+ url * url. URL
3131
3232 medias []* core.Media
3333 receivers []* core.Receiver
@@ -52,17 +52,15 @@ type cbcMode interface {
5252 SetIV ([]byte )
5353}
5454
55- func Dial (url string ) (* Client , error ) {
56- var err error
57- c := & Client {url : url }
58- if c .conn1 , err = c .newConn (); err != nil {
59- return nil , err
60- }
61- return c , nil
62- }
63-
64- func (c * Client ) newConn () (net.Conn , error ) {
65- u , err := url .Parse (c .url )
55+ // Dial support different urls:
56+ // - tapo://{cloud-password}@192.168.1.123 - auth to Tapo cameras
57+ // with cloud password (autodetect hash method)
58+ // - tapo://admin:{hashed-cloud-password}@192.168.1.123 - auth to Tapo cameras
59+ // with pre-hashed cloud password
60+ // - vigi://admin:{password}@192.168.1.123 - auth to Vigi cameras with password
61+ // for admin account (other not supported)
62+ func Dial (rawURL string ) (* Client , error ) {
63+ u , err := url .Parse (rawURL )
6664 if err != nil {
6765 return nil , err
6866 }
@@ -71,21 +69,31 @@ func (c *Client) newConn() (net.Conn, error) {
7169 u .Host += ":8800"
7270 }
7371
74- req , err := http .NewRequest ("POST" , "http://" + u .Host + "/stream" , nil )
72+ c := & Client {url : u }
73+ if c .conn1 , err = c .newConn (); err != nil {
74+ return nil , err
75+ }
76+ return c , nil
77+ }
78+
79+ func (c * Client ) newConn () (net.Conn , error ) {
80+ req , err := http .NewRequest ("POST" , "http://" + c .url .Host + "/stream" , nil )
7581 if err != nil {
7682 return nil , err
7783 }
7884
79- query := u .Query ()
85+ query := c . url .Query ()
8086
8187 if deviceId := query .Get ("deviceId" ); deviceId != "" {
8288 req .URL .RawQuery = "deviceId=" + deviceId
8389 }
8490
85- req .URL .User = u .User
8691 req .Header .Set ("Content-Type" , "multipart/mixed; boundary=--client-stream-boundary--" )
8792
88- conn , res , err := dial (req )
93+ username := c .url .User .Username ()
94+ password , _ := c .url .User .Password ()
95+
96+ conn , res , err := dial (req , c .url .Scheme , username , password )
8997 if err != nil {
9098 return nil , err
9199 }
@@ -95,7 +103,7 @@ func (c *Client) newConn() (net.Conn, error) {
95103 }
96104
97105 if c .decrypt == nil {
98- c .newDectypter (res )
106+ c .newDectypter (res , c . url . Scheme , username , password )
99107 }
100108
101109 channel := query .Get ("channel" )
@@ -119,14 +127,18 @@ func (c *Client) newConn() (net.Conn, error) {
119127 return conn , nil
120128}
121129
122- func (c * Client ) newDectypter (res * http.Response ) {
123- username := res .Request . URL . User . Username ( )
124- password , _ := res . Request . URL . User . Password ( )
130+ func (c * Client ) newDectypter (res * http.Response , brand , username , password string ) {
131+ exchange := res .Header . Get ( "Key-Exchange" )
132+ nonce := core . Between ( exchange , `nonce="` , `"` )
125133
126- // extract nonce from response
127- // cipher="AES_128_CBC" username="admin" padding="PKCS7_16" algorithm="MD5" nonce="***"
128- nonce := res .Header .Get ("Key-Exchange" )
129- nonce = core .Between (nonce , `nonce="` , `"` )
134+ if brand == "tapo" && password == "" {
135+ if strings .Contains (exchange , `encrypt_type="3"` ) {
136+ password = fmt .Sprintf ("%32X" , sha256 .Sum256 ([]byte (username )))
137+ } else {
138+ password = fmt .Sprintf ("%16X" , md5 .Sum ([]byte (username )))
139+ }
140+ username = "admin"
141+ }
130142
131143 key := md5 .Sum ([]byte (nonce + ":" + password ))
132144 iv := md5 .Sum ([]byte (username + ":" + nonce ))
@@ -263,16 +275,12 @@ func (c *Client) Request(conn net.Conn, body []byte) (string, error) {
263275 }
264276}
265277
266- func dial (req * http.Request ) (net.Conn , * http.Response , error ) {
278+ func dial (req * http.Request , brand , username , password string ) (net.Conn , * http.Response , error ) {
267279 conn , err := net .DialTimeout ("tcp" , req .URL .Host , core .ConnDialTimeout )
268280 if err != nil {
269281 return nil , nil , err
270282 }
271283
272- username := req .URL .User .Username ()
273- password , _ := req .URL .User .Password ()
274- req .URL .User = nil
275-
276284 if err = req .Write (conn ); err != nil {
277285 return nil , nil , err
278286 }
@@ -291,14 +299,16 @@ func dial(req *http.Request) (net.Conn, *http.Response, error) {
291299 return nil , nil , fmt .Errorf ("Expected StatusCode to be %d, received %d" , http .StatusUnauthorized , res .StatusCode )
292300 }
293301
294- if password == "" {
302+ if brand == "tapo" && password == "" {
295303 // support cloud password in place of username
296304 if strings .Contains (auth , `encrypt_type="3"` ) {
297305 password = fmt .Sprintf ("%32X" , sha256 .Sum256 ([]byte (username )))
298306 } else {
299307 password = fmt .Sprintf ("%16X" , md5 .Sum ([]byte (username )))
300308 }
301309 username = "admin"
310+ } else if brand == "vigi" && username == "admin" {
311+ password = securityEncode (password )
302312 }
303313
304314 realm := tcp .Between (auth , `realm="` , `"` )
@@ -331,7 +341,39 @@ func dial(req *http.Request) (net.Conn, *http.Response, error) {
331341 return nil , nil , err
332342 }
333343
334- req .URL .User = url .UserPassword (username , password )
335-
336344 return conn , res , nil
337345}
346+
347+ const (
348+ keyShort = "RDpbLfCPsJZ7fiv"
349+ keyLong = "yLwVl0zKqws7LgKPRQ84Mdt708T1qQ3Ha7xv3H7NyU84p21BriUWBU43odz3iP4rBL3cD02KZciXTysVXiV8ngg6vL48rPJyAUw0HurW20xqxv9aYb4M9wK1Ae0wlro510qXeU07kV57fQMc8L6aLgMLwygtc0F10a0Dg70TOoouyFhdysuRMO51yY5ZlOZZLEal1h0t9YQW0Ko7oBwmCAHoic4HYbUyVeU3sfQ1xtXcPcf1aT303wAQhv66qzW"
350+ )
351+
352+ func securityEncode (s string ) string {
353+ size := len (s )
354+
355+ var n int // max
356+ if size > len (keyShort ) {
357+ n = size
358+ } else {
359+ n = len (keyShort )
360+ }
361+
362+ b := make ([]byte , n )
363+
364+ for i := 0 ; i < n ; i ++ {
365+ c1 := 187
366+ c2 := 187
367+ if i >= size {
368+ c1 = int (keyShort [i ])
369+ } else if i >= len (keyShort ) {
370+ c2 = int (s [i ])
371+ } else {
372+ c1 = int (keyShort [i ])
373+ c2 = int (s [i ])
374+ }
375+ b [i ] = keyLong [(c1 ^ c2 )% len (keyLong )]
376+ }
377+
378+ return string (b )
379+ }
0 commit comments