diff --git a/nord-checker.py b/nord-checker.py index 94140bf..40ec7ef 100644 --- a/nord-checker.py +++ b/nord-checker.py @@ -5,13 +5,16 @@ import time import datetime import re import sys +from psql_utils import connect_to_database, should_skip_file, update_or_insert_ip +from ovpn_downloader import download_and_extract_ovpn_configs +from openvpn_manager import establish_vpn_connection, is_vpn_active, disconnect_vpn, get_external_ip + # Variables CREDENTIAL_FILE = "nord.creds" -OVPN_DIR = "/root/nordvpn/ovpn_configs/ovpn_tcp" +OVPN_DIR = "/root/nordvpn/ovpn_configs/ovpn_tcp" LOG_FILE = "/root/nordvpn/openvpn.log" RESULT_FILE = "vpnlist.txt" -OVPN_ZIP_URL = "https://downloads.nordcdn.com/configs/archives/servers/ovpn.zip" # Check for debug flag DEBUG_MODE = False @@ -29,30 +32,25 @@ print(f"{datetime.datetime.now()} [Main Script]: Current IP before VPN connectio # Terminate any existing OpenVPN connections try: result = subprocess.run(["pgrep", "openvpn"], check=True, capture_output=True) - pids = result.stdout.decode().strip().split('\n') + pids = result.stdout.decode().strip().split('\n') # Get PIDs as a list for pid in pids: subprocess.run(["kill", pid]) print(f"{datetime.datetime.now()} [Main Script]: Existing OpenVPN connections terminated.") except subprocess.CalledProcessError: pass # No openvpn process found -# Download and extract OVPN configurations +# Download and extract OVPN configurations (using the function from ovpn_downloader.py) debug_print(f"{datetime.datetime.now()} [Main Script]: Downloading and extracting OVPN configurations...") -if not os.path.exists(OVPN_DIR): - os.makedirs(OVPN_DIR) - -subprocess.run(["curl", "-s", "-L", "-o", f"{OVPN_DIR}/ovpn.zip", OVPN_ZIP_URL]) -subprocess.run(["unzip", "-q", "-o", f"{OVPN_DIR}/ovpn.zip", "-d", OVPN_DIR]) -os.remove(f"{OVPN_DIR}/ovpn.zip") - -# Find all OVPN files -ovpn_files = [f for f in os.listdir(OVPN_DIR) if f.endswith(".ovpn")] +ovpn_files = download_and_extract_ovpn_configs() # The function now returns the shuffled list total_files = len(ovpn_files) debug_print(f"{datetime.datetime.now()} [Main Script]: Found {total_files} OVPN files to process.") debug_print(ovpn_files) # Print the list of OVPN files +# Connect to PostgreSQL database +conn, cursor = connect_to_database() + # Process each OVPN file FILE_NUM = 1 for OVPN_FILE in ovpn_files: @@ -60,12 +58,21 @@ for OVPN_FILE in ovpn_files: debug_print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Processing OVPN file...") + # Check if file exists in database and if last check is older than 3 days + skip_file = should_skip_file(cursor, OVPN_FILENAME) + debug_print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: should_skip_file returned: {skip_file}") + + if skip_file: + print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Skipping, last check was less than 3 days ago.") + FILE_NUM += 1 + continue + # Read credentials with open(CREDENTIAL_FILE, 'r') as f: username = f.readline().strip() password = f.readline().strip() - debug_print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Read credentials from {CREDENTIAL_FILE}") + debug_print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Read credentials: username={username}") # Establish VPN connection (capture stdout and stderr) openvpn_command = ["openvpn", "--config", f"{OVPN_DIR}/{OVPN_FILE}", @@ -97,33 +104,24 @@ for OVPN_FILE in ovpn_files: print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: VPN connection established.") # Check external IP (with validation and retries) - external_ip = subprocess.check_output(["curl", "-s", "ifconfig.me"]).decode().strip() + external_ip = get_external_ip() - if not re.match(r"^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$", external_ip): - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Invalid response from ifconfig.me, retrying with ifconfig.co") - external_ip = subprocess.check_output(["curl", "-s", "ifconfig.co"]).decode().strip() - - if not re.match(r"^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$", external_ip): - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Invalid response from ifconfig.co, retrying with ipinfo.io/ip") - external_ip = subprocess.check_output(["curl", "-s", "ipinfo.io/ip"]).decode().strip() - - if not re.match(r"^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$", external_ip): - print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Unable to get external IP. Skipping this OVPN file.") - continue + if external_ip is None: + print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Unable to get external IP. Skipping this OVPN file.") + continue print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: External IP via VPN: {external_ip}") - # Append result to file - with open(RESULT_FILE, 'a') as f: - f.write(f"{external_ip} # {OVPN_FILENAME}\n") + # Update or insert into database + update_or_insert_ip(cursor, conn, OVPN_FILENAME, external_ip) except subprocess.CalledProcessError: print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: VPN connection failed to establish.") - # Disconnect VPN (replace killall with pgrep and kill) + # Disconnect VPN try: result = subprocess.run(["pgrep", "openvpn"], check=True, capture_output=True) - pids = result.stdout.decode().strip().split('\n') # Get PIDs as a list + pids = result.stdout.decode().strip().split('\n') for pid in pids: subprocess.run(["kill", pid]) print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: VPN disconnected.") @@ -131,10 +129,14 @@ for OVPN_FILE in ovpn_files: print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: No openvpn process found.") # Wait for a random time between 8 and 30 seconds - sleep_time = random.randint(1, 8) + sleep_time = random.randint(6, 8) print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Waiting for {sleep_time} seconds before the next connection...") time.sleep(sleep_time) print(f"{datetime.datetime.now()} [{FILE_NUM}/{total_files}] [{OVPN_FILENAME}]: Resuming...") # Increment file number - FILE_NUM += 1 \ No newline at end of file + FILE_NUM += 1 + +# Close the database connection +cursor.close() +conn.close() \ No newline at end of file diff --git a/openvpn_manager.py b/openvpn_manager.py new file mode 100644 index 0000000..663c7e0 --- /dev/null +++ b/openvpn_manager.py @@ -0,0 +1,73 @@ +import subprocess +import time +import datetime +import re + +def establish_vpn_connection(ovpn_file, credential_file, log_file): + """ + Establishes a VPN connection using the provided OVPN file and credentials. + Returns True if the connection is successful, False otherwise. + """ + + # Read credentials + with open(credential_file, 'r') as f: + username = f.readline().strip() + password = f.readline().strip() + + # Establish VPN connection (capture stdout and stderr) + openvpn_command = ["openvpn", "--config", ovpn_file, + "--auth-user-pass", credential_file, "--daemon", + "--log-append", log_file, "--verb", "3"] + + process = subprocess.Popen(openvpn_command, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = process.communicate(input=password.encode()) + + # Check for errors + if process.returncode != 0: + return False, stderr.decode() + else: + return True, stdout.decode() + +def is_vpn_active(ovpn_filename): + """ + Checks if the VPN connection associated with the given OVPN filename is active. + Returns True if active, False otherwise. + """ + try: + subprocess.run(["pgrep", "-f", ovpn_filename], check=True, capture_output=True) + return True + except subprocess.CalledProcessError: + return False + +def disconnect_vpn(): + """ + Disconnects the active VPN connection. + """ + try: + result = subprocess.run(["pgrep", "openvpn"], check=True, capture_output=True) + pids = result.stdout.decode().strip().split('\n') + for pid in pids: + subprocess.run(["kill", pid]) + except subprocess.CalledProcessError: + pass # No openvpn process found + +def get_external_ip(): + """ + Gets the external IP address using various services, with retries. + Returns the external IP if successful, or None if unable to get it. + """ + + for service in ["ifconfig.me", "ifconfig.co", "ipinfo.io/ip"]: + try: + external_ip = subprocess.check_output(["curl", "-s", service]).decode().strip() + if re.match(r"^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$", external_ip): + return external_ip + else: + print(f"{datetime.datetime.now()} [Main Script]: Invalid response from {service}") + except subprocess.CalledProcessError: + print(f"{datetime.datetime.now()} [Main Script]: Error getting external IP from {service}") + + return None # Unable to get external IP \ No newline at end of file diff --git a/psql_utils.py b/psql_utils.py new file mode 100644 index 0000000..060ee25 --- /dev/null +++ b/psql_utils.py @@ -0,0 +1,69 @@ +import psycopg2 +import datetime +from zoneinfo import ZoneInfo + +def connect_to_database(): + """ + Connects to the PostgreSQL database using credentials from 'psql.creds'. + Returns a connection and cursor object. + """ + with open('psql.creds', 'r') as f: + config = {} + for line in f: + key, value = line.strip().split(' = ') + config[key] = value + + conn = psycopg2.connect( + dbname=config['db_name'], + user=config['db_user'], + password=config['db_password'], + host=config['db_host'], + port=config['db_port'] + ) + cursor = conn.cursor() + return conn, cursor + +def should_skip_file(cursor, filename): + """ + Checks if the given OVPN file should be skipped based on the last check time in the database. + Returns True if it should be skipped, False otherwise. + """ + try: + cursor.execute("SELECT last_exit_ip_check FROM ovpn_files WHERE file_name = %s", (filename,)) + row = cursor.fetchone() + + if row is None: + print(f"No record found for {filename} in the database.") + return False # No record found, so don't skip + + last_check = row[0] + + if last_check is None: # Handle NULL value + return False # If last_check is NULL, don't skip + + # Get current time in UTC timezone using zoneinfo + now = datetime.datetime.now(tz=ZoneInfo('UTC')) + + time_difference = now - last_check + if time_difference.days < 3: + return True + return False + + except psycopg2.Error as e: + print(f"Error executing database query: {e}") + return False # In case of an error, don't skip the file + + +def update_or_insert_ip(cursor, conn, filename, external_ip): + """ + Updates or inserts the external IP information for the given OVPN file in the database. + """ + cursor.execute(""" + INSERT INTO ovpn_files (file_name, last_observed, last_exit_ip_check, exit_ip) + VALUES (%s, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, %s) + ON CONFLICT (file_name) DO UPDATE + SET last_observed = CURRENT_TIMESTAMP, + last_exit_ip_check = CURRENT_TIMESTAMP, + exit_ip = %s + """, (filename, external_ip, external_ip)) + conn.commit() \ No newline at end of file