diff --git a/Dockerfile b/Dockerfile index 5129d17..444b5d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,18 @@ FROM debian LABEL maintainer="loblab" -#ARG TZ=Asia/Shanghai -#ARG APT_MIRROR=mirrors.163.com ARG DEBIAN_FRONTED=noninteractive ARG PYTHON=python3 -#RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -#RUN sed -i "s/deb.debian.org/$APT_MIRROR/" /etc/apt/sources.list +ENV CONTAINER=1 + RUN apt-get update && apt-get -y upgrade RUN apt-get -y install chromium-chromedriver || \ apt-get -y install chromium-driver || \ apt-get -y install chromedriver RUN apt-get -y install ${PYTHON}-pip RUN apt-get -y install ${PYTHON}-selenium +RUN apt-get -y install ${PYTHON}-pyotp RUN apt-get -y install curl wget RUN mkdir -p /home/loblab && \ @@ -22,4 +21,4 @@ RUN mkdir -p /home/loblab && \ USER loblab WORKDIR /home/loblab COPY /noip-renew.py /home/loblab/ -ENTRYPOINT ["python3", "/home/loblab/noip-renew.py"] +ENTRYPOINT ["python3", "/home/loblab/noip-renew.py"] \ No newline at end of file diff --git a/README.md b/README.md index 1d15734..b7810da 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,16 @@ grep -h Confirmed *.log | grep -v ": 0" | sort ``` ## Usage with Docker -For docker users, run the following: +For docker users you need to define the following ENV variables: + +NOIP_USERNAME = '' +NOIP_PASSWORD = '' +NOIP_2FA_SECRET_KEY = '' +NOIP_DEBUG = + +so you can run the following: ```sh -my_username='add username here' -my_password='add base64 encoded password here' -debug_lvl=2 -echo -e "$(crontab -l)"$'\n'"12 3 * * 1,3,5 docker run --rm --network host moebiuss/noip-renew ${my_username} ${my_password} ${debug_lvl}" | crontab - +echo -e "$(crontab -l)"$'\n'"12 3 * * 1,3,5 docker run --rm --network host -e NOIP_USERNAME='' -e NOIP_PASSWORD='' -e NOIP_2FA_SECRET_KEY='' -e NOIP_DEBUG=2 moebiuss/noip-renew" | crontab - ``` NOTE: with newer versions of ChromeDriver (>v99) you might need to increase the shm size of the container otherwise ChromeDriver will crash and throw an exception. To do it, you can just add the "--shm-size="512m" flag to the docker run command. diff --git a/noip-renew.py b/noip-renew.py index 7f98edf..a2577fc 100755 --- a/noip-renew.py +++ b/noip-renew.py @@ -21,6 +21,7 @@ from selenium.webdriver.support import expected_conditions as EC from datetime import date from datetime import timedelta +from pyotp import * import time import sys import os @@ -28,6 +29,9 @@ import base64 import subprocess +VERSION = "2.0.0" +DOCKER = False + class Logger: def __init__(self, level): self.level = 0 if level is None else level @@ -45,10 +49,12 @@ class Robot: LOGIN_URL = "https://www.noip.com/login" HOST_URL = "https://my.noip.com/dynamic-dns" - def __init__(self, username, password, debug): + def __init__(self, username, password, totp_secret, debug, docker): self.debug = debug + self.docker = docker self.username = username self.password = password + self.totp_secret = totp_secret self.browser = self.init_browser() self.logger = Logger(debug) @@ -86,9 +92,29 @@ def login(self): ele_pwd = elem.find_element(By.NAME, "password") ele_usr.send_keys(self.username) - ele_pwd.send_keys(base64.b64decode(self.password).decode('utf-8')) + + # If running on docker, password is not base64 encoded + if self.docker: + ele_pwd.send_keys(self.password) + else: + ele_pwd.send_keys(base64.b64decode(self.password).decode('utf-8')) ele_pwd.send_keys(Keys.ENTER) + try: + elem = WebDriverWait(self.browser, 10).until( EC.presence_of_element_located((By.ID, "verificationCode"))) + except: + raise Exception("2FA verify page could not load") + + if self.debug > 1: + self.browser.save_screenshot("debug-otp.png") + + self.logger.log("Sending OTP...") + + ele_challenge = elem.find_element(By.NAME, "challenge_code") + + ele_challenge.send_keys(TOTP(self.totp_secret).now()) + ele_challenge.send_keys(Keys.ENTER) + # After Loggin browser loads my.noip.com page - give him some time to load # 'noip-cart' element is near the end of html, so html have been loaded try: @@ -129,10 +155,11 @@ def update_hosts(self): today = date.today() + timedelta(days=nr) day = str(today.day) month = str(today.month) - try: - subprocess.call(['/usr/local/bin/noip-renew-skd.sh', day, month, "True"]) - except (FileNotFoundError,PermissionError): - self.logger.log(f"noip-renew-skd.sh missing or not executable, skipping crontab configuration") + if not self.docker: + try: + subprocess.call(['/usr/local/bin/noip-renew-skd.sh', day, month, "True"]) + except (FileNotFoundError,PermissionError): + self.logger.log(f"noip-renew-skd.sh missing or not executable, skipping crontab configuration") return True def open_hosts_page(self): @@ -190,8 +217,7 @@ def get_hosts(self): def run(self): rc = 0 - version = "1.7.1" - self.logger.log(f"No-IP renew script version {version}") + self.logger.log(f"No-IP renew script version {VERSION}") self.logger.log(f"Debug level: {self.debug}") try: self.login() @@ -200,10 +226,11 @@ def run(self): except Exception as e: self.logger.log(str(e)) self.browser.save_screenshot("exception.png") - try: - subprocess.call(['/usr/local/bin/noip-renew-skd.sh', "*", "*", "False"]) - except (FileNotFoundError,PermissionError): - self.logger.log(f"noip-renew-skd.sh missing or not executable, skipping crontab configuration") + if not self.docker: + try: + subprocess.call(['/usr/local/bin/noip-renew-skd.sh', "*", "*", "False"]) + except (FileNotFoundError,PermissionError): + self.logger.log(f"noip-renew-skd.sh missing or not executable, skipping crontab configuration") rc = 2 finally: self.browser.quit() @@ -211,23 +238,33 @@ def run(self): def main(argv=None): - noip_username, noip_password, debug, = get_args_values(argv) - return (Robot(noip_username, noip_password, debug)).run() + # check if we're running on docker + DOCKER = os.environ.get("CONTAINER", "").lower() in ("yes", "y", "on", "true", "1") + if DOCKER: + print("Running inside docker container") + noip_username = os.environ.get('NOIP_USERNAME') + noip_password = os.environ.get('NOIP_PASSWORD') + noip_totp = os.environ.get('NOIP_2FA_SECRET_KEY') + debug = int(os.environ.get('NOIP_DEBUG', 1)) + else: + noip_username, noip_password, noip_totp, debug = get_args_values(argv) + return (Robot(noip_username, noip_password, noip_totp, debug, DOCKER)).run() def get_args_values(argv): if argv is None: argv = sys.argv - if len(argv) < 3: - print(f"Usage: {argv[0]} [] ") + if len(argv) < 4: + print(f"Usage: {argv[0]} <2FA_secret_key> [] ") sys.exit(1) noip_username = argv[1] noip_password = argv[2] + noip_totp = argv[3] debug = 1 - if len(argv) > 3: - debug = int(argv[3]) - return noip_username, noip_password, debug + if len(argv) > 4: + debug = int(argv[4]) + return noip_username, noip_password, noip_totp, debug if __name__ == "__main__": diff --git a/noip-renew.sh b/noip-renew.sh index d0001b9..ce9b9d9 100755 --- a/noip-renew.sh +++ b/noip-renew.sh @@ -2,13 +2,14 @@ USERNAME="" PASSWORD="" +TOTP_SECRET="" LOGDIR=$1 PROGDIR=$(dirname -- $0) if [ -z "$LOGDIR" ]; then - $PROGDIR/noip-renew.py "$USERNAME" "$PASSWORD" 2 + $PROGDIR/noip-renew.py "$USERNAME" "$PASSWORD" "$TOTP_SECRET" 2 else cd $LOGDIR - $PROGDIR/noip-renew.py "$USERNAME" "$PASSWORD" 0 >> $USERNAME.log + $PROGDIR/noip-renew.py "$USERNAME" "$PASSWORD" "$TOTP_SECRET" 0 >> $USERNAME.log fi diff --git a/setup.sh b/setup.sh index f8fa294..975ae37 100755 --- a/setup.sh +++ b/setup.sh @@ -35,9 +35,7 @@ function install() { install_debian ;; esac - # Debian9 package 'python-selenium' does not work with chromedriver, - # Install from pip, which is newer - $SUDO $PYTHON -m pip install selenium + if [ "$PYTHON35" = true ]; then $SUDO $PYTHON -m pip install future-fstrings fi @@ -47,7 +45,9 @@ function install_arch(){ $SUDO pacman -Qi cronie > /dev/null || $SUDO pacman -S cronie $SUDO pacman -Qi python > /dev/null || $SUDO pacman -S python $SUDO pacman -Qi python-pip > /dev/null || $SUDO pacman -S python-pip + $SUDO pacman -Qi python-pyotp > /dev/null || $SUDO pacman -S python-pyotp $SUDO pacman -Qi chromium > /dev/null || $SUDO pacman -S chromium + $SUDO $PYTHON -m pip install selenium } function install_debian(){ @@ -88,6 +88,13 @@ function install_debian(){ $SUDO apt -y install chromium # Update Chromium Browser or script won't work. $SUDO apt -y install $PYTHON-pip + $SUDO apt -y install $PYTHON-pyotp + + if [[ "$PYV" -gt "36" ]]; then + $SUDO apt -y install $PYTHON-selenium + else + $SUDO $PYTHON -m pip install selenium + fi } function deploy() { @@ -121,15 +128,18 @@ function deploy() { } function noip() { - echo "Enter your No-IP Account details..." + echo "Enter your No-IP Account details...make sure you enabled 2fa authentication and saved the 2fa secret key" read -p 'Username: ' uservar read -sp 'Password: ' passvar + echo + read -sp '2FA Secret Key: ' totpsecret passvar=`echo -n $passvar | base64` echo $SUDO sed -i 's/USERNAME=".*"/USERNAME="'$uservar'"/1' $INSTEXE $SUDO sed -i 's/PASSWORD=".*"/PASSWORD="'$passvar'"/1' $INSTEXE + $SUDO sed -i 's/TOTP_SECRET=".*"/TOTP_SECRET="'$totpsecret'"/1' $INSTEXE read -p 'Do you want randomized cronjob? (y/n): ' rcron if [ "${rcron^^}" = "Y" ]