def show_hostnames(hostnames: list) -> None: pos("Fetched hostnames:") for hostname in hostnames: pos(f" * Hostname targeting \"{hostname['target']}\": \"{hostname['hostname']}\", " + f"expiring soon: {hostname['is_expiring_soon']}: " + f"{hostname['days_remaining']} days, {hostname['hours_remaining']} hours remaining." )
def update_hostname(session: Session, hostnames: list, target_hostname_name: str, new_ip: str) -> None: target_hostnames = [ hostname for hostname in hostnames if hostname["hostname"] == target_hostname_name ] if not target_hostnames: neg(f"No hostname called \"{target_hostname_name}\"!") return pos(f"Updating hostname \"{target_hostname_name}\" to point to \"{new_ip}\"." ) target_hostname = target_hostnames[0] target_hostname["target"] = new_ip url = HOSTNAME_PUT_URL % target_hostname["id"] response = session.put(url, json.dumps(target_hostname), headers={"Content-Type": "application/json"}) if response.status_code == 200: pos("Success.") else: neg("Failure.")
def ddns_set_hostnames(session: Session, hostname_names: list, ip: str) -> None: pos(f"({datetime.now()}) Updating hostnames {hostname_names} to point to \"{ip}\"." ) url = URL_DDNS_UPDATE % (",".join(hostname_names), ip) if (rsp := session.get(url)).status_code != 200: neg(f"Error {rsp.status_code}")
def simulate_ddns_router(session: Session, hostname_names: list, hostnames: list) -> None: verify_hostnames_exist(hostname_names, hostnames) get_ip = IPGetter() old_ip = None # Break the update sometimes del session.headers["X-Requested-With"] del session.headers["X-CSRF-TOKEN"] del session.headers["Referer"] del session.headers["Origin"] pos("Entering DDNS loop.") while True: time.sleep(random.randint(20, 80)) if (new_ip := get_ip()) != old_ip: old_ip = new_ip ddns_set_hostnames(session, hostname_names, new_ip)
def renew_hostnames(session: Session, hostnames: list) -> None: hostnames_to_renew = [ name for name in hostnames if name["is_expiring_soon"] ] if not hostnames_to_renew: pos("No hostnames to renew!") return pos("Renewing hostnames:") for hostname in hostnames_to_renew: pos(f" * Renewing: \"{hostname['hostname']}\"!") session.get(HOSTNAME_TOUCH_URL % hostname["id"])
def login(username: str, password: str) -> Session: session = Session() # Obtain a CSRF token token_response = session.get(URL_LOGIN_PAGE) token = EXTRACTOR_LOGIN_TOKEN.search(token_response.text).group(1) # FIXME: I think this broke because they now require a captcha pos(f"Extracted login token \"{token}\".") pos(f"Logging in as \"{username}\"...") login_response = session.post(URL_LOGIN_PAGE, data={ "username": username, "password": password, "submit_login_page": "1", "_token": token, }) if TOKEN_RATE_LIMITED in login_response.text: neg("Rate limited.") sys.exit(-1) if TOKEN_LOGIN_SUCCESS not in login_response.text: neg("Login failed.") sys.exit(-1) main_page_response = session.get(URL_DYNAMIC_DNS_PAGE) csrf_token = EXTRACTOR_CSRF_TOKEN.search(main_page_response.text).group(1) # Set up some disguise headers. # We might get an "403 - Unauthorized" without this. session.headers["X-Requested-With"] = "XMLHttpRequest" session.headers["X-CSRF-TOKEN"] = csrf_token session.headers["Referer"] = HEADER_REFERER session.headers["Origin"] = HEADER_ORIGIN session.headers["User-Agent"] = USER_AGENT pos(f"Extracted CSRF token \"{csrf_token}\".") pos("Login succeeded.") return session
def main() -> None: parser = argparse.ArgumentParser( description="HASS - NoIP Hostname Automation System Script", epilog="Developed 2020 - 2021 by Flesh-Network developers.") parser.add_argument("username", type=str, help="the username of the NoIP account") parser.add_argument("password", type=str, help="the password of the NoIP account") action_group = parser.add_mutually_exclusive_group() action_group.add_argument( "-r", "--renew", action="store_true", help="simulate website access to renew all hostnames") action_group.add_argument( "-u", "--update", type=str, help="simulate website access to update hostname") action_group.add_argument("-l", "--loop", type=str, nargs="+", help="simulate a ddns router for all hostnames") parser.add_argument("-j", "--jitter", action="store_true", help="add a random delay to avoid detection") parser.add_argument("-a", "--address", type=str, help="the ip to update the hostname with", required="-u" in sys.argv or "--update" in sys.argv) parsed_args = parser.parse_args() if parsed_args.jitter: jitter_minutes = random.randint(1, 60) pos(f" Jittering for {jitter_minutes} minutes...") time.sleep(jitter_minutes * 60) pos("Starting No-IP scraper...") with login(parsed_args.username, parsed_args.password) as session: hostnames = get_hostnames(session) show_hostnames(hostnames) if parsed_args.renew: renew_hostnames(session, hostnames) elif parsed_args.update: update_hostname(session, hostnames, parsed_args.update, parsed_args.address) elif parsed_args.loop: simulate_ddns_router(session, parsed_args.loop, hostnames) pos("Closing session, all done!")
def get_hostnames(session: Session) -> list: pos("Requesting hostnames...") hostname_response = session.get(HOSTNAME_LIST_URL) return hostname_response.json()["hosts"]