Skip to content
Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,9 @@ python3 telnet_opener.py 10.0.0.10 -t
```

Default telnet credentials on Xiongmai stock firmware are `root` / `xmhdipc`.
Firmware built from 2021 on (e.g. `HI3516EV300_85H50AI` `000529B2` build
2021-03-03, and a 2024 `GK7201V200` build) uses `root` / `Hp#!8CocD_g` instead;
the tool tries both.

`-b/--backup` is self-contained and exits 0 / non-0 for use in scripts and
agentic automation. It checks for telnet on port 23 — if closed, it runs
Expand Down
64 changes: 49 additions & 15 deletions telnet_opener.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3

from dvrip import DVRIPCam
from dvrip import DVRIPCam, SomethingIsWrongWithCamera
import argparse
import datetime
import json
Expand All @@ -19,6 +19,15 @@
# (`telnetctrl 1`); 50119 was observed on GK7205V300 + 000699H7.
DEFAULT_TELNET_PORTS = (23, 4321, 50119)

# Root passwords of the stock telnet, tried in order. The password changed
# between firmware builds: xmhdipc is the classic one; builds from 2021 on
# (seen on HI3516EV300_85H50AI 000529B2 build 2021-03-03 and on a 2024
# GK7201V200 build) use Hp#!8CocD_g.
TELNET_CREDENTIALS = (
("root", "xmhdipc"),
("root", "Hp#!8CocD_g"),
)

# InstallDesc that bypasses Hardware/Vendor/Flash validation ("SkipCheck"
# magic) and flips `telnetctrl=1` via both env tool variants. Works on
# post-2020-05-07 XM firmware where a hardware-specific InstallDesc has
Expand Down Expand Up @@ -171,25 +180,52 @@ def enable_telnet_via_dvrip(host_ip, user="admin", password="",
# `armbenv -s telnetctrl 1` only writes the env var; the telnet
# daemon is launched by an init script on next boot.
print("Rebooting camera to apply telnetctrl=1...")
cam = DVRIPCam(host_ip, user=user, password=password)
if cam.login():
cam.reboot()
cam.close()
else:
print("Could not log back in to reboot; camera may reboot itself.")
# The updater usually reboots the camera by itself once the upload is
# done, so this login can meet a dropped or silent connection -- which
# dvrip.py surfaces as an exception, not as a False return.
try:
cam = DVRIPCam(host_ip, user=user, password=password)
if cam.login():
cam.reboot()
cam.close()
else:
print("Could not log back in to reboot; camera may reboot itself.")
except (OSError, SomethingIsWrongWithCamera, TypeError, KeyError, ValueError):
# SomethingIsWrongWithCamera is what DVRIPCam.connect() raises when the
# socket connect fails, which is exactly what a mid-reboot camera does.
print("Camera is not answering DVRIP; it is most likely rebooting already.")

print(f"Waiting for telnet, probing {list(ports)}...")
deadline = time.monotonic() + wait_timeout
while time.monotonic() < deadline:
for p in ports:
if check_port(host_ip, p):
print(f"Telnet open on {host_ip}:{p} (login: root / xmhdipc)")
logins = " or ".join(f"{u} / {pw}" for u, pw in TELNET_CREDENTIALS)
print(f"Telnet open on {host_ip}:{p} (login: {logins})")
return p
time.sleep(4)
print(f"Timed out after {wait_timeout}s; probe ports manually.")
return None


def telnet_login(host_ip, port, credentials=TELNET_CREDENTIALS):
"""Open a telnet shell, trying each known root credential. Returns a
connected socket sitting at the shell prompt, or None if none work."""
for user, pw in credentials:
s = socket.create_connection((host_ip, port), timeout=10)
_read_until(s, b"login:", 5)
s.sendall(user.encode() + b"\n")
_read_until(s, b"assword:", 5)
s.sendall(pw.encode() + b"\n")
# The shell prompt means we are in; "incorrect" means try the next.
reply = _read_until(s, b"# ", 5)
if b"# " in reply and b"incorrect" not in reply:
print(f"Logged in as {user} / {pw}")
return s
s.close()
return None


def do_backup_via_telnet(host_ip, nfs_share, mount_point="/utils",
user="admin", password="",
telnet_ports=DEFAULT_TELNET_PORTS):
Expand All @@ -201,13 +237,11 @@ def do_backup_via_telnet(host_ip, nfs_share, mount_point="/utils",
print(f"Could not enable telnet on {host_ip}")
return False

print(f"Connecting to {host_ip}:{telnet_port} as root/xmhdipc")
s = socket.create_connection((host_ip, telnet_port), timeout=10)
_read_until(s, b"login:", 5)
s.sendall(b"root\n")
_read_until(s, b"assword:", 5)
s.sendall(b"xmhdipc\n")
_read_until(s, b"# ", 5)
print(f"Connecting to {host_ip}:{telnet_port}")
s = telnet_login(host_ip, telnet_port)
if s is None:
print(f"No known root password works on {host_ip}:{telnet_port}")
return False

s.sendall(f"mkdir -p {mount_point}\n".encode())
_read_until(s, b"# ", 5)
Expand Down
Loading