Browse Source

Merge pull request #5 from OpenIPC/ipctool-backup-one-shot

telnet_opener: make --backup self-contained for one-shot/agentic use
telnet-opener-skipcheck-installdesc
Dmitry Ilyin 1 month ago
committed by GitHub
parent
commit
2332f73fe2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 26
      README.md
  2. 65
      telnet_opener.py

26
README.md

@ -625,24 +625,28 @@ cam.upgrade("General_HZXM_IPC_HI3516CV300_50H20L_AE_S38_V4.03.R12.Nat.OnvifS.HIK
Xiongmai cameras to run shell commands as root, with no firmware change.
```sh
# 1. Enable telnet on port 23 (camera reboots once).
# Make a full hardware backup with ipctool, written to your NFS share.
# Auto-enables telnet first if needed (camera reboots once on first run).
python3 telnet_opener.py 10.0.0.10 -b --nfs 10.0.0.1:/srv/ipctool
# Manual: just enable telnet (camera reboots once).
python3 telnet_opener.py 10.0.0.10
# 2. Open a non-persistent shell on port 4321 (no reboot).
# Manual: open a non-persistent shell on port 4321 (no reboot).
python3 telnet_opener.py 10.0.0.10 -t
# 3. Make a full hardware backup with ipctool, written to your NFS share.
python3 telnet_opener.py 10.0.0.10 -b --nfs 10.0.0.1:/srv/ipctool
```
Default telnet credentials on Xiongmai stock firmware are `root` / `xmhdipc`.
`-b/--backup` requires that telnet is already enabled (step 1) and that you
export an NFS share containing the [`ipctool`][ipctool] ARM32 binary. The
script telnets in, mounts your share at `/utils`, runs
`ipctool backup /utils/backup-<MAC>`, and unmounts. The resulting
`backup-<MAC>` file lives on your NFS server — nothing is uploaded to a
third party.
`-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
the InstallDesc exploit to enable it (this reboots the camera once) and
waits up to 3 minutes for the camera to come back. Then it telnets in,
mounts your NFS share at `/utils`, runs `ipctool backup
/utils/backup-<MAC>`, and unmounts. The resulting `backup-<MAC>` file
lives on your NFS server — nothing is uploaded to a third party.
The NFS share must contain the [`ipctool`][ipctool] ARM32 binary.
Minimal NFS server setup on the host machine:

65
telnet_opener.py

@ -125,12 +125,50 @@ def _read_until(sock, token, timeout):
return bytes(buf)
def do_backup_via_telnet(host_ip, nfs_share, mount_point="/utils"):
def enable_telnet_via_dvrip(host_ip, user="admin", password=""):
cam = DVRIPCam(host_ip, user=user, password=password)
if not cam.login():
print(f"DVRIP login failed for {host_ip}")
return False
upinfo = cam.get_upgrade_info()
sysinfo = cam.get_system_info()
if not isinstance(upinfo, dict) or "Hardware" not in upinfo:
print(f"Camera {host_ip} did not return upgrade info: {upinfo}")
cam.close()
return False
swver = extract_gen(sysinfo["SoftWareVersion"])
desc = {
"Hardware": upinfo["Hardware"],
"DevID": f"{swver}1001000000000000",
"CompatibleVersion": 2,
"Vendor": "General",
"CRC": "1ce6242100007636",
"UpgradeCommand": [cmd_armebenv(swver)],
}
add_flashes(desc, swver)
zipfname = "upgrade.bin"
make_zip(zipfname, json.dumps(desc, indent=2))
cam.upgrade(zipfname)
cam.close()
os.remove(zipfname)
print(f"Camera {host_ip} rebooting, waiting for telnet...")
for _ in range(45):
time.sleep(4)
if check_port(host_ip, 23):
return True
return False
def do_backup_via_telnet(host_ip, nfs_share, mount_point="/utils",
user="admin", password=""):
if not check_port(host_ip, 23):
print(f"Telnet (port 23) is not open on {host_ip}.")
print("Enable it first by running: "
f"python3 telnet_opener.py {host_ip}")
return
print(f"Telnet (port 23) closed on {host_ip}, enabling...")
if not enable_telnet_via_dvrip(host_ip, user, password):
print(f"Could not enable telnet on {host_ip}")
return False
print(f"Connecting to {host_ip}:23 as root/xmhdipc")
s = socket.create_connection((host_ip, 23), timeout=10)
@ -163,6 +201,7 @@ def do_backup_via_telnet(host_ip, nfs_share, mount_point="/utils"):
s.close()
print(f"Done. Backup file is at {nfs_share.rstrip('/')}/backup-{mac} "
"on your NFS server.")
return True
def downgrade_old_version(cam, buildtime, swver):
@ -209,9 +248,9 @@ def open_telnet(host_ip, port, **kwargs):
if not nfs_share:
print("--backup requires --nfs HOST:/exported/path "
"(NFS share with ipctool, where the backup will be written)")
return
do_backup_via_telnet(host_ip, nfs_share)
return
return False
return do_backup_via_telnet(host_ip, nfs_share,
user=user, password=password)
cam = DVRIPCam(host_ip, user=user, password=password)
if not cam.login():
@ -277,8 +316,9 @@ def main():
"-b",
"--backup",
action="store_true",
help="Telnet in (root/xmhdipc), mount NFS, run ipctool backup "
"(requires --nfs and telnet already enabled on the camera)",
help="Mount NFS, run ipctool backup via telnet. Enables telnet "
"first if it's not already on (camera will reboot once). "
"Requires --nfs.",
)
parser.add_argument(
"-t",
@ -292,8 +332,9 @@ def main():
"Must contain the ipctool binary; backup-<MAC> is written here.",
)
args = parser.parse_args()
open_telnet(args.hostname, TELNET_PORT, **vars(args))
return open_telnet(args.hostname, TELNET_PORT, **vars(args))
if __name__ == "__main__":
main()
import sys
sys.exit(0 if main() is not False else 1)

Loading…
Cancel
Save