Skip to content
This repository has been archived by the owner on Feb 7, 2024. It is now read-only.

Compatibility with 6.43 #7

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions tikapy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,23 @@ def _connect(self):
self._connect_socket()
self._sock = self._base_sock

def login(self, user, password):
def login(self, user, password, allow_insecure_auth_without_tls=False):
"""
Connects to the API and tries to login the user.

:param user: Username for API connections
:param password: Password for API connections
:param allow_insecure_auth_without_tls: Allow plaintext authentication
against RouterOS 6.43+. This is false by default to avoid accidental
downgrade in security when the router is upgraded.
:raises: ClientError - if login failed
"""
self._connect()
self._api = ApiRos(self._sock)
try:
self._api.login(user, password)
socket_is_tls = hasattr(self._sock, "getpeercert")
send_plain_password = (socket_is_tls or allow_insecure_auth_without_tls)
self._api.login(user, password, send_plain_password)
except (ApiError, ApiUnrecoverableError) as exc:
raise ClientError('could not login') from exc

Expand Down
50 changes: 33 additions & 17 deletions tikapy/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,32 +57,48 @@ def __init__(self, sock):
self.sock = sock
self.currenttag = 0

def login(self, username, password):
def login(self, username, password, send_plain_password=True):
"""
Perform API login

Args:
username - Username used to login
password - Password used to login
send_plain_password - Whether to send plaintext password (new-style)
without requiring MD5 CRAM
"""

# RouterOS <= 6.43rc17 uses MD5 challenge-response:
# --> /login{}
# <-- {ret}
# --> /login{name, response}
# <-- {}
#
# RouterOS >= 6.43rc19 uses plaintext authentication:
# --> /login{name, password}
# <-- {}

# request login
# Mikrotik answers with a challenge in the 'ret' attribute
# 'ret' attribute accessible as attrs['ret']
_, attrs = self.talk(["/login"])[0]

# Prepare response for challenge-response login
# response is MD5 of 0-char + plaintext-password + challange
response = hashlib.md5()
response.update(b'\x00')
response.update(password.encode('UTF-8'))
response.update(binascii.unhexlify((attrs['ret']).encode('UTF-8')))
response = "00" + binascii.hexlify(response.digest()).decode('UTF-8')

# send response & login request
self.talk(["/login",
"=name=%s" % username,
"=response=%s" % response])
if send_plain_password:
_, attrs = self.talk(["/login",
"=name=%s" % username,
"=password=%s" % password])[0]
else:
_, attrs = self.talk(["/login"])[0]

if "ret" in attrs:
# Prepare response for challenge-response login
# response is MD5 of 0-char + plaintext-password + challange
response = hashlib.md5()
response.update(b'\x00')
response.update(password.encode('UTF-8'))
response.update(binascii.unhexlify((attrs['ret']).encode('UTF-8')))
response = "00" + binascii.hexlify(response.digest()).decode('UTF-8')

# send response & login request
self.talk(["/login",
"=name=%s" % username,
"=response=%s" % response])

def talk(self, words):
"""
Expand Down