Skip to content

Commit

Permalink
fix for C5400X
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexandrErohin committed Nov 6, 2024
1 parent 3423027 commit 03efbfb
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 92 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ from tplinkrouterc6u import (
TplinkRouterProvider,
TplinkRouter,
TplinkC1200Router,
TplinkC5400XRouter,
TPLinkMRClient,
TPLinkDecoClient,
Connection
Expand All @@ -31,6 +32,10 @@ router = TplinkRouterProvider.get_client('http://192.168.0.1', 'password')
# router = TplinkRouter('http://192.168.0.1', 'password')
# You may also pass username if it is different and a logger to log errors as
# router = TplinkRouter('http://192.168.0.1','password','admin2', Logger('test'))
# If you have the TP-link C5400X or similar, you can use the TplinkC5400XRouter class instead of the TplinkRouter class.
# Remember that the password for this router is different, here you need to use the web encrypted password.
# To get web encrypted password, read Web Encrypted Password section
# router = TplinkC5400XRouter('http://192.168.0.1','WebEncryptedPassword', Logger('test'))

try:
router.authorize() # authorizing
Expand Down Expand Up @@ -60,6 +65,16 @@ finally:
The TP-Link Web Interface only supports upto 1 user logged in at a time (for security reasons, apparently).
So before action you need to authorize and after logout

### <a id="encrypted_pass">Web Encrypted Password</a>
If you got exception - `use web encrypted password instead. Check the documentation!`
or you have TP-link C5400X or similar router you need to get web encrypted password by these actions:
1. Go to the login page of your router. (default: 192.168.0.1).
2. Type in the password you use to login into the password field.
3. Click somewhere else on the page so that the password field is not selected anymore.
4. Open the JavaScript console of your browser (usually by pressing F12 and then clicking on "Console").
5. Type `document.getElementById("login-password").value;`
6. Copy the returned value as password and use it.

## Functions
| Function | Args | Description | Return |
|---|---|---|---|
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="tplinkrouterc6u",
version="5.0.0",
version="5.0.1",
author="Alex Erohin",
author_email="[email protected]",
description="TP-Link Router API",
Expand Down
1 change: 1 addition & 0 deletions tplinkrouterc6u/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
TplinkRouter,
TplinkRouterProvider,
TplinkC1200Router,
TplinkC5400XRouter,
TPLinkMRClient,
AbstractRouter,
TPLinkDecoClient,
Expand Down
221 changes: 130 additions & 91 deletions tplinkrouterc6u/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from tplinkrouterc6u.encryption import EncryptionWrapper, EncryptionWrapperMR
from tplinkrouterc6u.package_enum import Connection
from tplinkrouterc6u.dataclass import Firmware, Status, Device, IPv4Reservation, IPv4DHCPLease, IPv4Status
from tplinkrouterc6u.exception import ClientException, ClientError
from tplinkrouterc6u.exception import ClientException, ClientError, AuthorizeError
from abc import ABC, abstractmethod


Expand Down Expand Up @@ -108,7 +108,7 @@ def request(self, path: str, data: str, ignore_response: bool = False, ignore_er
error = ('TplinkRouter - {} - Response with error; Request {} - Response {}'
.format(self.__class__.__name__, path, data)) if not error else error
if self._logger:
self._logger.error(error)
self._logger.debug(error)
raise ClientError(error)

def _is_valid_response(self, data: dict) -> bool:
Expand Down Expand Up @@ -178,7 +178,7 @@ def authorize(self) -> None:
error = ("TplinkRouter - {} - Cannot authorize! Error - {}; Response - {}"
.format(self.__class__.__name__, e, data))
if self._logger:
self._logger.error(error)
self._logger.debug(error)
raise ClientException(error)

def _request_pwd(self) -> None:
Expand All @@ -203,7 +203,7 @@ def _request_pwd(self) -> None:
error = ('TplinkRouter - {} - Unknown error for pwd! Error - {}; Response - {}'
.format(self.__class__.__name__, e, response.text))
if self._logger:
self._logger.error(error)
self._logger.debug(error)
raise ClientException(error)

def _request_seq(self) -> None:
Expand All @@ -230,7 +230,7 @@ def _request_seq(self) -> None:
error = ('TplinkRouter - {} - Unknown error for seq! Error - {}; Response - {}'
.format(self.__class__.__name__, e, response.text))
if self._logger:
self._logger.error(error)
self._logger.debug(error)
raise ClientException(error)

def _try_login(self) -> Response:
Expand Down Expand Up @@ -658,7 +658,7 @@ def supports(self) -> bool:
return False
if response.status_code == 401 and response.text.startswith('00'):
raise ClientException(('Your router is not supported. Please add your router support to '
'https://github.com/AlexandrErohin/TP-Link-Archer-C6U'
'https://github.com/AlexandrErohin/TP-Link-Archer-C6U '
'by implementing methods for TplinkC6V4Router class'
))
return False
Expand All @@ -682,7 +682,111 @@ def set_wifi(self, wifi: Connection, enable: bool) -> None:
raise ClientException('Not Implemented')


class TplinkC1200Router(TplinkBaseRouter):
class TplinkC5400XRouter(TplinkBaseRouter):
def supports(self) -> bool:
return len(self.password) >= 200

def authorize(self) -> None:
if len(self.password) < 200:
raise Exception('You need to use web encrypted password instead. Check the documentation!')

url = '{}/cgi-bin/luci/;stok=/login?form=login'.format(self.host)

response = post(
url,
params={'operation': 'login', 'username': self.username, 'password': self.password},
headers=self._headers_login,
timeout=self.timeout,
verify=self._verify_ssl,
)

try:
self._stok = response.json().get('data').get('stok')
regex_result = search('sysauth=(.*);', response.headers['set-cookie'])
self._sysauth = regex_result.group(1)
self._logged = True
self._smart_network = False

except Exception as e:
error = "TplinkRouter - C5400X - Cannot authorize! Error - {}; Response - {}".format(e, response.text)
if self._logger:
self._logger.debug(error)
raise ClientException(error)

def set_led(self, enable: bool) -> None:
current_state = (self.request('admin/ledgeneral?form=setting&operation=read', 'operation=read')
.get('enable', 'off') == 'on')
if current_state != enable:
self.request('admin/ledgeneral?form=setting&operation=write', 'operation=write')

def get_led(self) -> bool:

data = self.request('admin/ledgeneral?form=setting&operation=read', 'operation=read')
led_status = data.get('enable') if 'enable' in data else None
if led_status == 'on':
return True
elif led_status == 'off':
return False
else:
return None

def set_wifi(self, wifi: Connection, enable: bool = None, ssid: str = None, hidden: str = None,
encryption: str = None, psk_version: str = None, psk_cipher: str = None, psk_key: str = None,
hwmode: str = None, htmode: str = None, channel: int = None, txpower: str = None,
disabled_all: str = None) -> None:
values = {
Connection.HOST_2G: 'wireless_2g',
Connection.HOST_5G: 'wireless_5g',
Connection.HOST_6G: 'wireless_6g',
Connection.GUEST_2G: 'guest_2g',
Connection.GUEST_5G: 'guest_5g',
Connection.GUEST_6G: 'guest_6g',
Connection.IOT_2G: 'iot_2g',
Connection.IOT_5G: 'iot_5g',
Connection.IOT_6G: 'iot_6g',
}

value = values.get(wifi)
if not value:
raise ValueError(f"Invalid Wi-Fi connection type: {wifi}")

if all(v is None for v in [enable, ssid, hidden, encryption, psk_version, psk_cipher, psk_key, hwmode,
htmode, channel, txpower, disabled_all]):
raise ValueError("At least one wireless setting must be provided")

data = "operation=write"

if enable is not None:
data += f"&enable={'on' if enable else 'off'}"
if ssid is not None:
data += f"&ssid={ssid}"
if hidden is not None:
data += f"&hidden={hidden}"
if encryption is not None:
data += f"&encryption={encryption}"
if psk_version is not None:
data += f"&psk_version={psk_version}"
if psk_cipher is not None:
data += f"&psk_cipher={psk_cipher}"
if psk_key is not None:
data += f"&psk_key={psk_key}"
if hwmode is not None:
data += f"&hwmode={hwmode}"
if htmode is not None:
data += f"&htmode={htmode}"
if channel is not None:
data += f"&channel={channel}"
if txpower is not None:
data += f"&txpower={txpower}"
if disabled_all is not None:
data += f"&disabled_all={disabled_all}"

path = f"admin/wireless?form={value}&{data}"

self.request(path, data)


class TplinkC1200Router(TplinkC5400XRouter):
username = ''
password = ''
_pwdNN = ''
Expand Down Expand Up @@ -729,10 +833,11 @@ def authorize(self) -> None:
self._logged = True

except Exception as e:
error = ("TplinkRouter - C1200 - {} - Cannot authorize! Error - {}; Response - {}"
.format(self.__class__.__name__, e, data))
error = ("TplinkRouter - C1200 - Cannot authorize! Error - {}; Response - {}".format(e, data))
if self._logger:
self._logger.error(error)
self._logger.debug(error)
if 'data' in vars() and data.get('errorcode') == 'login failed':
raise AuthorizeError(error)
raise ClientException(error)

def _request_pwd(self) -> None:
Expand All @@ -755,7 +860,7 @@ def _request_pwd(self) -> None:
error = ('TplinkRouter - C1200 - {} - Unknown error for pwd! Error - {}; Response - {}'
.format(self.__class__.__name__, e, response.text))
if self._logger:
self._logger.error(error)
self._logger.debug(error)
raise ClientException(error)

def _try_login(self) -> Response:
Expand All @@ -777,78 +882,6 @@ def _try_login(self) -> Response:
def _get_login_data(crypted_pwd: str) -> str:
return 'operation=login&password={}'.format(crypted_pwd)

def set_led(self, enable: bool) -> None:
current_state = (self.request('admin/ledgeneral?form=setting&operation=read', 'operation=read')
.get('enable', 'off') == 'on')
if current_state != enable:
self.request('admin/ledgeneral?form=setting&operation=write', 'operation=write')

def get_led(self) -> bool:

data = self.request('admin/ledgeneral?form=setting&operation=read', 'operation=read')
led_status = data.get('enable') if 'enable' in data else None
if led_status == 'on':
return True
elif led_status == 'off':
return False
else:
return None

def set_wifi(self, wifi: Connection, enable: bool = None, ssid: str = None, hidden: str = None,
encryption: str = None, psk_version: str = None, psk_cipher: str = None, psk_key: str = None,
hwmode: str = None, htmode: str = None, channel: int = None, txpower: str = None,
disabled_all: str = None) -> None:
values = {
Connection.HOST_2G: 'wireless_2g',
Connection.HOST_5G: 'wireless_5g',
Connection.HOST_6G: 'wireless_6g',
Connection.GUEST_2G: 'guest_2g',
Connection.GUEST_5G: 'guest_5g',
Connection.GUEST_6G: 'guest_6g',
Connection.IOT_2G: 'iot_2g',
Connection.IOT_5G: 'iot_5g',
Connection.IOT_6G: 'iot_6g',
}

value = values.get(wifi)
if not value:
raise ValueError(f"Invalid Wi-Fi connection type: {wifi}")

if all(v is None for v in [enable, ssid, hidden, encryption, psk_version, psk_cipher, psk_key, hwmode,
htmode, channel, txpower, disabled_all]):
raise ValueError("At least one wireless setting must be provided")

data = "operation=write"

if enable is not None:
data += f"&enable={'on' if enable else 'off'}"
if ssid is not None:
data += f"&ssid={ssid}"
if hidden is not None:
data += f"&hidden={hidden}"
if encryption is not None:
data += f"&encryption={encryption}"
if psk_version is not None:
data += f"&psk_version={psk_version}"
if psk_cipher is not None:
data += f"&psk_cipher={psk_cipher}"
if psk_key is not None:
data += f"&psk_key={psk_key}"
if hwmode is not None:
data += f"&hwmode={hwmode}"
if htmode is not None:
data += f"&htmode={htmode}"
if channel is not None:
data += f"&channel={channel}"
if txpower is not None:
data += f"&txpower={txpower}"
if disabled_all is not None:
data += f"&disabled_all={disabled_all}"

path = f"admin/wireless?form={value}&{data}"

self.request(path, data)


class TPLinkMRClient(AbstractRouter):
REQUEST_RETRIES = 3
Expand Down Expand Up @@ -1168,7 +1201,7 @@ def req_act(self, acts: list):
if code != 200:
error = 'TplinkRouter - MR - Response with error; Request {} - Response {}'.format(data, response)
if self._logger:
self._logger.error(error)
self._logger.debug(error)
raise ClientError(error)

result = self._merge_response(response)
Expand Down Expand Up @@ -1319,7 +1352,7 @@ def _req_login(self) -> None:

if error:
if self._logger:
self._logger.error(error)
self._logger.debug(error)
raise ClientException(error)

def _request(self, url, method='POST', data_str=None, encrypt=False):
Expand Down Expand Up @@ -1398,13 +1431,19 @@ def _prepare_data(self, data: str, is_login: bool) -> tuple[str, str]:
class TplinkRouterProvider:
@staticmethod
def get_client(host: str, password: str, username: str = 'admin', logger: Logger = None,
verify_ssl: bool = True, timeout: int = 30) -> AbstractRouter | None:
for client in [TPLinkMRClient, TplinkC6V4Router, TPLinkDecoClient, TplinkRouter, TplinkC1200Router]:
verify_ssl: bool = True, timeout: int = 30) -> AbstractRouter:
for client in [TplinkC5400XRouter, TPLinkMRClient, TplinkC6V4Router, TPLinkDecoClient, TplinkRouter]:
router = client(host, password, username, logger, verify_ssl, timeout)
if router.supports():
return router

raise ClientException(('Your router is not supported. Please add your router support to '
'https://github.com/AlexandrErohin/TP-Link-Archer-C6U'
'by implementing methods for AbstractRouter class'
))
router = TplinkC1200Router(host, password, username, logger, verify_ssl, timeout)
try:
router.authorize()
except AuthorizeError as e:
logger.error(e.__str__())
raise ClientException(('Login failed! Please check if your router local password is correct or '
'try to use web encrypted password instead. Check the documentation!'
))

raise ClientException('You need to use web encrypted password instead. Check the documentation!')
4 changes: 4 additions & 0 deletions tplinkrouterc6u/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ class ClientException(Exception):

class ClientError(ClientException):
pass


class AuthorizeError(ClientException):
pass

0 comments on commit 03efbfb

Please sign in to comment.