Skip to content

Instantly share code, notes, and snippets.

@troykelly
Created September 21, 2024 05:45
Show Gist options
  • Select an option

  • Save troykelly/f4ec21bfc92178389fc4a5c4f94a0f8b to your computer and use it in GitHub Desktop.

Select an option

Save troykelly/f4ec21bfc92178389fc4a5c4f94a0f8b to your computer and use it in GitHub Desktop.
Reboots cheap Aliexpress et. al. "box" cameras
#!/usr/bin/env bash
#
# reset_camera.sh
#
# Purpose:
# A Bash script to log in to and reset a web camera.
# To reset the camera, the script must first log in and then set appropriate
# cookies and send the reboot subject and XML payload.
#
# Author: Troy Kelly
# Contact: troy@troykelly.com
# Code History:
# - 2024-09-21: Initial version.
#
# Usage:
# ./reset_camera.sh --host <hostname> --username <username> --password <password> [--timeout <timeout>]
#
set -euo pipefail
# Catch signals and perform cleanup
cleanup() {
echo "An error occurred. Exiting." >&2
exit 1
}
trap cleanup SIGINT SIGTERM SIGHUP ERR
# Default values
CAMERA_HOSTNAME=""
CAMERA_USERNAME=""
CAMERA_PASSWORD=""
ALIVE_TIMEOUT=15
# Camera URLs and payloads
CAMERA_LOGIN="login.html"
CAMERA_DO_LOGIN="/goform/formLogin"
CAMERA_SET="/action/set"
CAMERA_GET="/action/get"
CAMERA_SUBJECT_REBOOT="maintain"
CAMERA_PAYLOAD_REBOOT='<?xml version="1.0" encoding="utf-8"?><request><maintain><type>0</type></maintain></request>'
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
--host|-h)
if [[ $# -gt 1 ]]; then
CAMERA_HOSTNAME="$2"
shift 2
else
echo "Error: --host requires a value" >&2
exit 1
fi
;;
--username|-u)
if [[ $# -gt 1 ]]; then
CAMERA_USERNAME="$2"
shift 2
else
echo "Error: --username requires a value" >&2
exit 1
fi
;;
--password|-p)
if [[ $# -gt 1 ]]; then
CAMERA_PASSWORD="$2"
shift 2
else
echo "Error: --password requires a value" >&2
exit 1
fi
;;
--timeout|-t)
if [[ $# -gt 1 ]]; then
ALIVE_TIMEOUT="$2"
shift 2
else
echo "Error: --timeout requires a value" >&2
exit 1
fi
;;
*)
echo "Unknown argument: $1" >&2
exit 1
;;
esac
done
# Check if the camera hostname is set
if [[ -z "$CAMERA_HOSTNAME" ]]; then
echo "Error: Camera hostname not set" >&2
exit 1
fi
# Check if the camera username is set
if [[ -z "$CAMERA_USERNAME" ]]; then
echo "Error: Camera username not set" >&2
exit 1
fi
# Check if the camera password is set
if [[ -z "$CAMERA_PASSWORD" ]]; then
echo "Error: Camera password not set" >&2
exit 1
fi
# Get the 'tid' value from the login URL
get_tid() {
echo "Retrieving 'tid' value from the camera..." >&2
local login_url="http://${CAMERA_HOSTNAME}/${CAMERA_LOGIN}"
# Use curl to follow redirects and capture the final URL
local final_url
final_url=$(curl -s -I -L "${login_url}" -o /dev/null -w "%{url_effective}")
if [[ -z "${final_url}" ]]; then
echo "Error: Failed to retrieve the final URL" >&2
exit 1
fi
# Extract 't' parameter from the final URL
local tid
tid=$(echo "${final_url}" | sed -n 's/.*[?&]t=\([^&]*\).*/\1/p')
if [[ -z "${tid}" ]]; then
echo "Error: Failed to extract 'tid' from the final URL" >&2
exit 1
else
echo "Retrieved 'tid' value: ${tid}" >&2
fi
# Output the tid value to STDOUT
echo "${tid}"
}
# Login to the camera
login() {
echo "Logging into the camera..." >&2
local tid="$1"
local do_login_url="http://${CAMERA_HOSTNAME}${CAMERA_DO_LOGIN}"
# Generate access value: lowercase MD5 of 'tid:password'
local access_input="${tid}:${CAMERA_PASSWORD}"
local access
access=$(echo -n "${access_input}" | md5 | awk '{print $NF}' | tr '[:upper:]' '[:lower:]')
# Prepare POST data
local login_payload="username=${CAMERA_USERNAME}&access=${access}&tid=${tid}"
# Perform POST request to Do Login URL
# Capture cookies from response
local cookie_file
cookie_file=$(mktemp)
local response_code
response_code=$(curl -s -o /dev/null -w "%{http_code}" -c "${cookie_file}" --data "${login_payload}" "${do_login_url}")
if [[ "${response_code}" != "200" ]]; then
echo "Error: Login failed with response code ${response_code}" >&2
rm -f "${cookie_file}"
exit 1
fi
# Extract 'wsid' cookie from cookie file
local wsid
wsid=$(grep 'wsid' "${cookie_file}" | awk '{print $7}')
if [[ -z "${wsid}" ]]; then
echo "Error: Failed to extract 'wsid' from cookies" >&2
rm -f "${cookie_file}"
exit 1
else
echo "Login successful. wsid: ${wsid}" >&2
fi
# Clean up cookie file
rm -f "${cookie_file}"
# Output the wsid value to STDOUT
echo "${wsid}"
}
# Reboot the camera
reboot_camera() {
echo "Initiating camera reboot..." >&2
local wsid="$1"
# Generate bvpassword: lowercase MD5 of password
local bvpassword
bvpassword=$(echo -n "${CAMERA_PASSWORD}" | md5 | awk '{print $NF}' | tr '[:upper:]' '[:lower:]')
# Prepare cookies
local cookies
cookies="bvusername=${CAMERA_USERNAME}; bvpassword=${bvpassword}; bvlanguage=English; wsid=${wsid}"
# Prepare referer
local nonce_value
nonce_value=$(( RANDOM % 653700 + 800 ))
local referer="http://${CAMERA_HOSTNAME}/nonce.html?slce=${nonce_value}"
# Prepare the URL
local reboot_url="http://${CAMERA_HOSTNAME}${CAMERA_SET}?subject=${CAMERA_SUBJECT_REBOOT}"
# Perform POST request with XML payload and subject querystring
local response_code
response_code=$(curl -s -o /dev/null -w "%{http_code}" \
--cookie "${cookies}" \
--header "Referer: ${referer}" \
--header "Content-Type: application/xml" \
--data "${CAMERA_PAYLOAD_REBOOT}" \
"${reboot_url}")
if [[ "${response_code}" != "200" ]]; then
echo "Error: Reboot failed with response code ${response_code}" >&2
exit 1
fi
echo "Camera rebooted successfully." >&2
}
# Main function
main() {
# Get the 'tid' value from the login URL
local tid
tid="$(get_tid)"
# Login to the camera and get wsid
local wsid
wsid="$(login "${tid}")"
# Reboot the camera
reboot_camera "${wsid}"
}
# Run main function
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment