tplinkrouterc6u 5.11.0__tar.gz → 5.12.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/PKG-INFO +19 -5
  2. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/README.md +17 -4
  3. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/setup.py +2 -1
  4. tplinkrouterc6u-5.12.1/test/test_client_mr_200.py +43 -0
  5. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/__init__.py +2 -2
  6. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/c80.py +7 -28
  7. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/ex.py +38 -1
  8. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/mr.py +76 -5
  9. tplinkrouterc6u-5.12.1/tplinkrouterc6u/client/mr200.py +165 -0
  10. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/re330.py +6 -36
  11. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/xdr.py +2 -0
  12. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/common/dataclass.py +1 -0
  13. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/common/encryption.py +113 -0
  14. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/common/package_enum.py +1 -0
  15. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/provider.py +8 -5
  16. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u.egg-info/PKG-INFO +19 -5
  17. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u.egg-info/SOURCES.txt +1 -0
  18. tplinkrouterc6u-5.11.0/tplinkrouterc6u/client/mr200.py +0 -412
  19. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/LICENSE +0 -0
  20. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/setup.cfg +0 -0
  21. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/test/__init__.py +0 -0
  22. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/test/test_client_c1200.py +0 -0
  23. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/test/test_client_c6u.py +0 -0
  24. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/test/test_client_c80.py +0 -0
  25. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/test/test_client_deco.py +0 -0
  26. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/test/test_client_ex.py +0 -0
  27. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/test/test_client_mr.py +0 -0
  28. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/test/test_client_re330.py +0 -0
  29. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/test/test_client_wdr.py +0 -0
  30. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/test/test_client_xdr.py +0 -0
  31. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/__init__.py +0 -0
  32. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/c1200.py +0 -0
  33. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/c5400x.py +0 -0
  34. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/c6u.py +0 -0
  35. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/deco.py +0 -0
  36. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/vr.py +0 -0
  37. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client/wdr.py +0 -0
  38. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/client_abstract.py +0 -0
  39. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/common/__init__.py +0 -0
  40. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/common/exception.py +0 -0
  41. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u/common/helper.py +0 -0
  42. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u.egg-info/dependency_links.txt +0 -0
  43. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u.egg-info/requires.txt +0 -0
  44. {tplinkrouterc6u-5.11.0 → tplinkrouterc6u-5.12.1}/tplinkrouterc6u.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tplinkrouterc6u
3
- Version: 5.11.0
3
+ Version: 5.12.1
4
4
  Summary: TP-Link Router API (supports also Mercusys Router)
5
5
  Home-page: https://github.com/AlexandrErohin/TP-Link-Archer-C6U
6
6
  Author: Alex Erohin
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
15
16
  Classifier: Programming Language :: Python :: Implementation :: PyPy
16
17
  Requires-Python: >=3.10
17
18
  Description-Content-Type: text/markdown
@@ -48,7 +49,7 @@ Python package for API access and management for TP-Link and Mercusys Routers. S
48
49
  - [pycryptodome](https://pypi.org/project/pycryptodome/)
49
50
 
50
51
  ## Usage
51
- Enter the host & credentials used to log in to your router management page. Username is admin by default. But you may pass username as third parameter
52
+ Enter the host & credentials used to log in to your router management page. Username is `admin` by default. But you may pass username as third parameter. Some routers have default username - `user`
52
53
 
53
54
  ```python
54
55
  from tplinkrouterc6u import (
@@ -56,13 +57,17 @@ from tplinkrouterc6u import (
56
57
  TplinkRouter,
57
58
  TplinkC1200Router,
58
59
  TplinkC5400XRouter,
59
- TPLinkMRClient,
60
+ TPLinkMRClient, # Class for MR series routers which supports old firmwares with AES cipher CBC mode
61
+ TPLinkMRClientGCM, # Class for MR series routers which supports AES cipher GCM mode
62
+ TPLinkMR200Client,
60
63
  TPLinkVRClient,
61
- TPLinkEXClient,
64
+ TPLinkEXClient, # Class for EX series routers which supports old firmwares with AES cipher CBC mode
65
+ TPLinkEXClientGCM, # Class for EX series routers which supports AES cipher GCM mode
62
66
  TPLinkXDRClient,
63
67
  TPLinkDecoClient,
64
68
  TplinkC80Router,
65
69
  TplinkWDRRouter,
70
+ TplinkRE330Router,
66
71
  Connection
67
72
  )
68
73
  from logging import Logger
@@ -243,6 +248,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
243
248
  | --- |---|---|
244
249
  | openvpn_enable | OpenVPN is enabled | bool |
245
250
  | pptpvpn_enable | PPTPVPN is enabled | bool |
251
+ | ipsecvpn_enable | IPSEC is enabled | bool |
246
252
  | openvpn_clients_total | OpenVPN clients connected | int |
247
253
  | pptpvpn_clients_total | PPTPVPN clients connected | int |
248
254
 
@@ -292,6 +298,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
292
298
  ### <a id="vpn">VPN</a>
293
299
  - VPN.OPEN_VPN
294
300
  - VPN.PPTP_VPN
301
+ - VPN.IPSEC
295
302
 
296
303
  ## <a id="supports">Supported routers</a>
297
304
  - [TP-LINK routers](#tplink)
@@ -316,6 +323,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
316
323
  - Archer AX73 (V1, V2.0)
317
324
  - Archer AX75 V1
318
325
  - Archer AX90 V1.20
326
+ - Archer AX95 v1.0
319
327
  - Archer AXE75 V1
320
328
  - Archer AXE5400 v1.0
321
329
  - Archer AXE16000
@@ -323,6 +331,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
323
331
  - Archer AX3000 V1
324
332
  - Archer AX6000 V1
325
333
  - Archer AX11000 V1
334
+ - Archer BE220 v1.0
326
335
  - Archer BE230 v1.0
327
336
  - Archer BE400 v1.0
328
337
  - Archer BE550 v1.0
@@ -340,7 +349,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
340
349
  - Archer C80 (1.0, 2.20)
341
350
  - Archer C5400X V1
342
351
  - Archer GX90 v1.0
343
- - Archer MR200 (v5, v5.3, v6.0)
352
+ - Archer MR200 (v2, v5, v5.3, v6.0)
344
353
  - Archer MR550 v1
345
354
  - Archer MR600 (v1, v2, v3)
346
355
  - Archer NX200 v2.0
@@ -349,6 +358,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
349
358
  - Archer VR900v
350
359
  - Archer VR1200v v1
351
360
  - Archer VR2100v v1
361
+ - Archer VX231v v1.0
352
362
  - Archer VX1800v v1.0
353
363
  - BE11000 2.0
354
364
  - Deco M4 2.0
@@ -379,11 +389,15 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
379
389
  - TL-WA3001 v1.0
380
390
  - TL-XDR3010 V2
381
391
  - TL-WDR3600 V1
392
+ - TL-XDR6088 v1.0.30
382
393
  - VX420-G2h v1.1
383
394
  - VX800v v1
384
395
  - XC220-G3v v2.30
396
+ - RE305 4.0
397
+ - RE315 1.0
385
398
  - RE330 v1
386
399
  ### <a id="mercusys">MERCUSYS routers</a>
400
+ - AC10 1.20
387
401
  - MR47BE v1.0
388
402
  - MR50G 1.0
389
403
  - H60XR 1.0
@@ -16,7 +16,7 @@ Python package for API access and management for TP-Link and Mercusys Routers. S
16
16
  - [pycryptodome](https://pypi.org/project/pycryptodome/)
17
17
 
18
18
  ## Usage
19
- Enter the host & credentials used to log in to your router management page. Username is admin by default. But you may pass username as third parameter
19
+ Enter the host & credentials used to log in to your router management page. Username is `admin` by default. But you may pass username as third parameter. Some routers have default username - `user`
20
20
 
21
21
  ```python
22
22
  from tplinkrouterc6u import (
@@ -24,13 +24,17 @@ from tplinkrouterc6u import (
24
24
  TplinkRouter,
25
25
  TplinkC1200Router,
26
26
  TplinkC5400XRouter,
27
- TPLinkMRClient,
27
+ TPLinkMRClient, # Class for MR series routers which supports old firmwares with AES cipher CBC mode
28
+ TPLinkMRClientGCM, # Class for MR series routers which supports AES cipher GCM mode
29
+ TPLinkMR200Client,
28
30
  TPLinkVRClient,
29
- TPLinkEXClient,
31
+ TPLinkEXClient, # Class for EX series routers which supports old firmwares with AES cipher CBC mode
32
+ TPLinkEXClientGCM, # Class for EX series routers which supports AES cipher GCM mode
30
33
  TPLinkXDRClient,
31
34
  TPLinkDecoClient,
32
35
  TplinkC80Router,
33
36
  TplinkWDRRouter,
37
+ TplinkRE330Router,
34
38
  Connection
35
39
  )
36
40
  from logging import Logger
@@ -211,6 +215,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
211
215
  | --- |---|---|
212
216
  | openvpn_enable | OpenVPN is enabled | bool |
213
217
  | pptpvpn_enable | PPTPVPN is enabled | bool |
218
+ | ipsecvpn_enable | IPSEC is enabled | bool |
214
219
  | openvpn_clients_total | OpenVPN clients connected | int |
215
220
  | pptpvpn_clients_total | PPTPVPN clients connected | int |
216
221
 
@@ -260,6 +265,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
260
265
  ### <a id="vpn">VPN</a>
261
266
  - VPN.OPEN_VPN
262
267
  - VPN.PPTP_VPN
268
+ - VPN.IPSEC
263
269
 
264
270
  ## <a id="supports">Supported routers</a>
265
271
  - [TP-LINK routers](#tplink)
@@ -284,6 +290,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
284
290
  - Archer AX73 (V1, V2.0)
285
291
  - Archer AX75 V1
286
292
  - Archer AX90 V1.20
293
+ - Archer AX95 v1.0
287
294
  - Archer AXE75 V1
288
295
  - Archer AXE5400 v1.0
289
296
  - Archer AXE16000
@@ -291,6 +298,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
291
298
  - Archer AX3000 V1
292
299
  - Archer AX6000 V1
293
300
  - Archer AX11000 V1
301
+ - Archer BE220 v1.0
294
302
  - Archer BE230 v1.0
295
303
  - Archer BE400 v1.0
296
304
  - Archer BE550 v1.0
@@ -308,7 +316,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
308
316
  - Archer C80 (1.0, 2.20)
309
317
  - Archer C5400X V1
310
318
  - Archer GX90 v1.0
311
- - Archer MR200 (v5, v5.3, v6.0)
319
+ - Archer MR200 (v2, v5, v5.3, v6.0)
312
320
  - Archer MR550 v1
313
321
  - Archer MR600 (v1, v2, v3)
314
322
  - Archer NX200 v2.0
@@ -317,6 +325,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
317
325
  - Archer VR900v
318
326
  - Archer VR1200v v1
319
327
  - Archer VR2100v v1
328
+ - Archer VX231v v1.0
320
329
  - Archer VX1800v v1.0
321
330
  - BE11000 2.0
322
331
  - Deco M4 2.0
@@ -347,11 +356,15 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
347
356
  - TL-WA3001 v1.0
348
357
  - TL-XDR3010 V2
349
358
  - TL-WDR3600 V1
359
+ - TL-XDR6088 v1.0.30
350
360
  - VX420-G2h v1.1
351
361
  - VX800v v1
352
362
  - XC220-G3v v2.30
363
+ - RE305 4.0
364
+ - RE315 1.0
353
365
  - RE330 v1
354
366
  ### <a id="mercusys">MERCUSYS routers</a>
367
+ - AC10 1.20
355
368
  - MR47BE v1.0
356
369
  - MR50G 1.0
357
370
  - H60XR 1.0
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="tplinkrouterc6u",
8
- version="5.11.0",
8
+ version="5.12.1",
9
9
  author="Alex Erohin",
10
10
  author_email="alexanderErohin@yandex.ru",
11
11
  description="TP-Link Router API (supports also Mercusys Router)",
@@ -21,6 +21,7 @@ setuptools.setup(
21
21
  "Programming Language :: Python :: 3.11",
22
22
  "Programming Language :: Python :: 3.12",
23
23
  "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
24
25
  "Programming Language :: Python :: Implementation :: PyPy",
25
26
  ],
26
27
  install_requires=['requests', 'pycryptodome', 'macaddress'],
@@ -0,0 +1,43 @@
1
+ from unittest import main, TestCase
2
+ from unittest.mock import patch, MagicMock
3
+ from requests import Session
4
+ from tplinkrouterc6u import TPLinkMR200Client
5
+
6
+
7
+ class TestTPLinkMR200Client(TestCase):
8
+ def setUp(self):
9
+ self.obj = TPLinkMR200Client('', '')
10
+
11
+ def test_supports_false(self):
12
+ responses = [
13
+ 'var param1="0x1A"\nvar param2="0x2B"\nignored line\n',
14
+ '404',
15
+ 'var nn="dfgdfg"\nvar ee="0x2B"\n'
16
+ ]
17
+
18
+ fake_responses = []
19
+ for text in responses:
20
+ r = MagicMock()
21
+ r.text = text
22
+ fake_responses.append(r)
23
+
24
+ with patch.object(Session, "get", side_effect=fake_responses):
25
+ for _ in range(len(fake_responses)):
26
+ result = self.obj.supports()
27
+ self.assertFalse(result)
28
+
29
+ def test_supports_true(self):
30
+ fake_response = MagicMock()
31
+ fake_response.text = (
32
+ 'var nn="0x1A"\n'
33
+ 'var ee="0x2B"\n'
34
+ )
35
+
36
+ with patch.object(Session, "get", return_value=fake_response):
37
+ result = self.obj.supports()
38
+
39
+ self.assertEqual(result, True)
40
+
41
+
42
+ if __name__ == '__main__':
43
+ main()
@@ -1,9 +1,9 @@
1
1
  from tplinkrouterc6u.client.c6u import TplinkRouter
2
2
  from tplinkrouterc6u.client.deco import TPLinkDecoClient
3
3
  from tplinkrouterc6u.client_abstract import AbstractRouter
4
- from tplinkrouterc6u.client.mr import TPLinkMRClient
4
+ from tplinkrouterc6u.client.mr import TPLinkMRClient, TPLinkMRClientGCM
5
5
  from tplinkrouterc6u.client.mr200 import TPLinkMR200Client
6
- from tplinkrouterc6u.client.ex import TPLinkEXClient
6
+ from tplinkrouterc6u.client.ex import TPLinkEXClient, TPLinkEXClientGCM
7
7
  from tplinkrouterc6u.client.vr import TPLinkVRClient
8
8
  from tplinkrouterc6u.client.c80 import TplinkC80Router
9
9
  from tplinkrouterc6u.client.c5400x import TplinkC5400XRouter
@@ -1,12 +1,9 @@
1
1
  from dataclasses import dataclass
2
2
  from logging import Logger
3
3
  from urllib import parse
4
- from base64 import b64encode, b64decode
5
4
  from collections import defaultdict
6
5
  from ipaddress import IPv4Address
7
6
  import re
8
- from Crypto.Cipher import AES
9
- from Crypto.Util.Padding import pad, unpad
10
7
  from macaddress import EUI48
11
8
  import requests
12
9
  from requests import Session
@@ -21,7 +18,6 @@ from tplinkrouterc6u.client_abstract import AbstractRouter
21
18
  class RouterConstants:
22
19
  AUTH_TOKEN_INDEX1 = 3
23
20
  AUTH_TOKEN_INDEX2 = 4
24
- DEFAULT_AES_VALUE = "0000000000000000"
25
21
 
26
22
  HOST_WIFI_2G_REQUEST = '33|1,1,0'
27
23
  HOST_WIFI_5G_REQUEST = '33|2,1,0'
@@ -64,9 +60,7 @@ class EncryptionState:
64
60
  self.nn_rsa = ''
65
61
  self.ee_rsa = ''
66
62
  self.seq = ''
67
- self.key_aes = ''
68
- self.iv_aes = ''
69
- self.aes_string = ''
63
+ self.aes = EncryptionWrapper()
70
64
  self.token = ''
71
65
 
72
66
 
@@ -105,14 +99,11 @@ class TplinkC80Router(AbstractRouter):
105
99
  self._encryption.nn_rsa = responseText[2]
106
100
  self._encryption.seq = responseText[3]
107
101
 
108
- # Generate key and initialization vector
109
- self._encryption.key_aes = RouterConstants.DEFAULT_AES_VALUE
110
- self._encryption.iv_aes = RouterConstants.DEFAULT_AES_VALUE
111
- self._encryption.aes_string = f'k={self._encryption.key_aes}&i={self._encryption.iv_aes}'
112
-
113
102
  # Encrypt AES string
114
- aes_string_encrypted = EncryptionWrapper.rsa_encrypt(self._encryption.aes_string, self._encryption.nn_rsa,
103
+ aes_string_encrypted = EncryptionWrapper.rsa_encrypt(self._encryption.aes._get_aes_string(),
104
+ self._encryption.nn_rsa,
115
105
  self._encryption.ee_rsa)
106
+
116
107
  # Register AES string for decryption on server side
117
108
  self.request(16, 0, True, data=f'set {aes_string_encrypted}')
118
109
  # Some auth request, might be redundant
@@ -368,7 +359,7 @@ class TplinkC80Router(AbstractRouter):
368
359
 
369
360
  def _get_signature(self, datalen: int) -> str:
370
361
  encryption = self._encryption
371
- r = f'{encryption.aes_string}&s={str(int(encryption.seq) + datalen)}'
362
+ r = f'{encryption.aes._get_aes_string()}&s={str(int(encryption.seq) + datalen)}'
372
363
  e = ''
373
364
  n = 0
374
365
  while n < len(r):
@@ -377,24 +368,12 @@ class TplinkC80Router(AbstractRouter):
377
368
  return e
378
369
 
379
370
  def _encrypt_body(self, text: str) -> str:
380
- encryption = self._encryption
381
-
382
- key_bytes = encryption.key_aes.encode("utf-8")
383
- iv_bytes = encryption.iv_aes.encode("utf-8")
384
-
385
- cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
386
- data = b64encode(cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))).decode()
387
-
371
+ data = self._encryption.aes.aes_encrypt(text)
388
372
  sign = self._get_signature(len(data))
389
373
  return f'sign={sign}\r\ndata={data}'
390
374
 
391
375
  def _decrypt_data(self, encrypted_text: str) -> str:
392
- key_bytes = self._encryption.key_aes.encode("utf-8")
393
- iv_bytes = self._encryption.iv_aes.encode("utf-8")
394
-
395
- cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
396
- decrypted_padded = cipher.decrypt(b64decode(encrypted_text))
397
- return unpad(decrypted_padded, AES.block_size).decode("utf-8")
376
+ return self._encryption.aes.aes_decrypt(encrypted_text)
398
377
 
399
378
  def _extract_value(self, response_list, prefix):
400
379
  return next((s.split(prefix, 1)[1] for s in response_list if s.startswith(prefix)), None)
@@ -15,9 +15,10 @@ from tplinkrouterc6u.common.dataclass import (
15
15
  IPv4Status,
16
16
  VPNStatus)
17
17
  from tplinkrouterc6u.common.exception import ClientException, ClientError
18
- from tplinkrouterc6u.client.mr import TPLinkMRClientBase
18
+ from tplinkrouterc6u.client.mr import TPLinkMRClientBase, TPLinkMRClientBaseGCM
19
19
 
20
20
 
21
+ # Class for EX series routers which supports old firmwares with AES cipher CBC mode
21
22
  class TPLinkEXClient(TPLinkMRClientBase):
22
23
  WIFI_SET = {
23
24
  Connection.HOST_2G: '1,0,0,0,0,0',
@@ -331,3 +332,39 @@ class TPLinkEXClient(TPLinkMRClientBase):
331
332
  ]
332
333
 
333
334
  self.req_act(acts)
335
+
336
+
337
+ # Class for EX series routers which supports AES cipher GCM mode
338
+ class TPLinkEXClientGCM(TPLinkMRClientBaseGCM, TPLinkEXClient):
339
+
340
+ def _req_login(self) -> None:
341
+ login_data = ('{"data":{"UserName":"%s","Passwd":"%s","Action": "1","stack":"0,0,0,0,0,0",'
342
+ '"pstack":"0,0,0,0,0,0"},"operation":"cgi","oid":"/cgi/login"}') % (
343
+ b64encode(bytes(self.username, "utf-8")).decode("utf-8"),
344
+ b64encode(bytes(self.password, "utf-8")).decode("utf-8")
345
+ )
346
+
347
+ sign, data, tag = self._prepare_data(login_data, True)
348
+ assert len(sign) == 256
349
+
350
+ request_data = f"sign={sign}\r\ndata={data}\r\ntag={tag}\r\n"
351
+
352
+ url = f"{self.host}/cgi_gdpr?9"
353
+ (code, response) = self._request(url, data_str=request_data)
354
+ response = self._encryption.aes_decrypt(response)
355
+
356
+ # parse and match return code
357
+ ret_code = self._parse_ret_val(response)
358
+ error = ''
359
+ if ret_code == self.HTTP_ERR_USER_PWD_NOT_CORRECT:
360
+ error = ('TplinkRouter - EX - Login failed, wrong user or password. '
361
+ 'Try to pass user instead of admin in username')
362
+ elif ret_code == self.HTTP_ERR_USER_BAD_REQUEST:
363
+ error = 'TplinkRouter - EX - Login failed. Generic error code: {}'.format(ret_code)
364
+ elif ret_code != self.HTTP_RET_OK:
365
+ error = 'TplinkRouter - EX - Login failed. Unknown error code: {}'.format(ret_code)
366
+
367
+ if error:
368
+ if self._logger:
369
+ self._logger.debug(error)
370
+ raise ClientException(error)
@@ -8,7 +8,7 @@ from macaddress import EUI48
8
8
  from ipaddress import IPv4Address
9
9
  from logging import Logger
10
10
  from tplinkrouterc6u.common.helper import get_ip, get_mac, get_value
11
- from tplinkrouterc6u.common.encryption import EncryptionWrapperMR
11
+ from tplinkrouterc6u.common.encryption import EncryptionWrapperMR, EncryptionWrapperMRGCM
12
12
  from tplinkrouterc6u.common.package_enum import Connection, VPN
13
13
  from tplinkrouterc6u.common.dataclass import (
14
14
  Firmware,
@@ -80,6 +80,7 @@ class TPLinkMRClientBase(AbstractRouter):
80
80
  if self._verify_ssl is False:
81
81
  self.req.verify = False
82
82
  self._token = None
83
+ self._authorized_at = None
83
84
  self._hash = md5(f"{self.username}{self.password}".encode()).hexdigest()
84
85
  self._nn = None
85
86
  self._ee = None
@@ -96,10 +97,9 @@ class TPLinkMRClientBase(AbstractRouter):
96
97
  return False
97
98
 
98
99
  def authorize(self) -> None:
99
- '''
100
- Establishes a login session to the host using provided credentials
101
- '''
102
- # hash the password
100
+ if self._token is not None and self._authorized_at >= (datetime.now() - timedelta(seconds=3)):
101
+ return
102
+ self._token = None
103
103
 
104
104
  # request the RSA public key from the host
105
105
  self._nn, self._ee, self._seq = self._req_rsa_key()
@@ -109,6 +109,7 @@ class TPLinkMRClientBase(AbstractRouter):
109
109
 
110
110
  # request TokenID
111
111
  self._token = self._req_token()
112
+ self._authorized_at = datetime.now()
112
113
 
113
114
  def reboot(self) -> None:
114
115
  acts = [
@@ -587,6 +588,71 @@ class TPLinkMRClientBase(AbstractRouter):
587
588
  return signature, encrypted_data
588
589
 
589
590
 
591
+ class TPLinkMRClientBaseGCM(TPLinkMRClientBase):
592
+ def __init__(self, host: str, password: str, username: str = 'admin', logger: Logger = None,
593
+ verify_ssl: bool = True, timeout: int = 30) -> None:
594
+ super().__init__(host, password, username, logger, verify_ssl, timeout)
595
+
596
+ self._encryption = EncryptionWrapperMRGCM()
597
+
598
+ def supports(self) -> bool:
599
+ try:
600
+ self.authorize()
601
+ return True
602
+ except Exception:
603
+ pass
604
+
605
+ return False
606
+
607
+ def _request(self, url, method='POST', data_str=None, encrypt=False, is_login=False):
608
+ headers = self.HEADERS
609
+ headers['Referer'] = self.host
610
+
611
+ if self._token is not None:
612
+ headers['TokenID'] = self._token
613
+
614
+ if encrypt:
615
+ sign, data, tag = self._prepare_data(data_str, is_login)
616
+ data = 'sign={}\r\ndata={}\r\ntag={}\r\n'.format(sign, data, tag)
617
+ else:
618
+ data = data_str
619
+
620
+ retry = 0
621
+ while retry < self.REQUEST_RETRIES:
622
+ # send the request
623
+ if method == 'POST':
624
+ r = self.req.post(url, data=data, headers=headers, timeout=self.timeout, verify=self._verify_ssl)
625
+ elif method == 'GET':
626
+ r = self.req.get(url, data=data, headers=headers, timeout=self.timeout, verify=self._verify_ssl)
627
+ else:
628
+ raise Exception('Unsupported method ' + str(method))
629
+
630
+ # sometimes we get 500 here, not sure why... just retry the request
631
+ if (r.status_code not in [500, 406]
632
+ and '<title>500 Internal Server Error</title>' not in r.text
633
+ and '<title>406 Not Acceptable</title>' not in r.text):
634
+ break
635
+
636
+ sleep(0.1)
637
+ retry += 1
638
+
639
+ # decrypt the response, if needed
640
+ if encrypt and (r.status_code == 200) and (r.text != ''):
641
+ return r.status_code, self._encryption.aes_decrypt(r.text)
642
+ else:
643
+ return r.status_code, r.text
644
+
645
+ def _prepare_data(self, data: str, is_login: bool) -> tuple[str, str, str]:
646
+ encrypted_data, tag = self._encryption.aes_encrypt(data)
647
+ data_len = len(encrypted_data)
648
+ # get encrypted signature
649
+ signature = self._encryption.get_signature(int(self._seq) + data_len, is_login, self._hash, self._nn, self._ee)
650
+
651
+ # format expected raw request data
652
+ return signature, encrypted_data, tag
653
+
654
+
655
+ # Class for MR series routers which supports old firmwares with AES cipher CBC mode
590
656
  class TPLinkMRClient(TPLinkMRClientBase):
591
657
 
592
658
  def logout(self) -> None:
@@ -716,3 +782,8 @@ class TPLinkMRClient(TPLinkMRClientBase):
716
782
  status.isp_name = values['3']['ispName']
717
783
 
718
784
  return status
785
+
786
+
787
+ # Class for MR series routers which supports AES cipher GCM mode
788
+ class TPLinkMRClientGCM(TPLinkMRClientBaseGCM, TPLinkMRClient):
789
+ pass