From 35dfc8f2db3aa566c86e1f43ebd2cf7031f6ec93 Mon Sep 17 00:00:00 2001 From: Vapourisation Date: Sun, 6 Oct 2024 15:42:06 +0100 Subject: [PATCH 1/7] [FEAT] Add Initial OpenVPN Implementation Currently only the ProtonVPN CLI is supported, this adds support for general OpenVPN configs. It utilises the openvpn CLI which most common VPN providers support and have documentation for. It checks for some specific config setups where the authentication is split into individual files (Mullvad does it this way). Checked against Proton VPN, Mullvad, Nord VPN and generic OpenVPN config files. Resolves: #10 --- src/main.py | 9 +- src/modules/ovpn.py | 224 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 src/modules/ovpn.py diff --git a/src/main.py b/src/main.py index fca0cdd..0573581 100755 --- a/src/main.py +++ b/src/main.py @@ -25,6 +25,7 @@ import src.modules.cryptotrace as cryptotrace import src.modules.vt as vt import src.modules.mactrace as mactrace +import src.modules.ovpn as ovpn import src.modules.pvpn as pvpn import src.modules.flightinfo as flightinfo import src.modules.wigle as wigle @@ -70,7 +71,7 @@ def section(text): "Torshell | Drop into a Tor sub-shell, or connect to Tor.") command(Fore.GREEN, "Pvpn | Connect to a random Proton vpn.") - command(Fore.RED, + command(Fore.YELLOW, "Ovpn | Connect to a specified open vpn.") section("ENUMERATION") ################# command(Fore.YELLOW, @@ -136,7 +137,7 @@ def section(text): "Falcon | Packet analysis; sniff for your own in the terminal or use a capture file!") print(f"\n{notice} Remember; run `apicon` command to configure the API database.") - option = input(f"{prompt}") + option = input(f"{prompt} ") # SECURITY. # ENUMERATION. # OSINT. @@ -160,6 +161,10 @@ def section(text): vt.vt() os._exit(0) + if option.lower() == "ovpn": + ovpn.ovpn() + os._exit(0) + if option.lower() == "pvpn": pvpn.pvpn() os._exit(0) diff --git a/src/modules/ovpn.py b/src/modules/ovpn.py new file mode 100644 index 0000000..fb140db --- /dev/null +++ b/src/modules/ovpn.py @@ -0,0 +1,224 @@ +# Imports. +import glob +import multiprocessing +import os +import shutil +import signal +import subprocess +import sys +import time + +from colorama import Fore # For text colour. +from pathlib import Path + +# Config (Prints). +text = (f"{Fore.WHITE}") # Change the colour of text output in the client side +# Changes the [], | and : in the client side +dividers = (f"{Fore.LIGHTRED_EX}") +# Success output. +success = (f"\n{Fore.WHITE}[{Fore.GREEN}SUCCESS{ + Fore.WHITE}] Program executed sucessfully.") +response = (f"{Fore.WHITE}[{Fore.GREEN}+{Fore.WHITE}]") +# Successfully output. +successfully = (f"{Fore.WHITE}[{Fore.GREEN}SUCCESSFULLY{Fore.WHITE}]") +# Failed output. +failed = (f"{Fore.WHITE}[{Fore.LIGHTRED_EX}FAILED{Fore.WHITE}]") +prompt = (f"{Fore.WHITE}[{Fore.YELLOW}»{Fore.WHITE}]") # Prompt output. +notice = (f"{Fore.WHITE}[{Fore.YELLOW}!{Fore.WHITE}]") # Notice output. +question = (f"{Fore.WHITE}[{Fore.YELLOW}?{Fore.WHITE}]") # Alert output. +alert = (f"{Fore.WHITE}[{Fore.LIGHTRED_EX}!{Fore.WHITE}]") # Alert output. +# Execited output. +exited = (f"{Fore.WHITE}[{Fore.LIGHTRED_EX}EXITED{Fore.WHITE}]") +# Disconnected output. +disconnected = (f"{Fore.WHITE}[{Fore.LIGHTRED_EX}DISCONNECTED{Fore.WHITE}]") +# Always asks for a command on a new line. +command = (f"\n[{Fore.YELLOW}>_{Fore.WHITE}]: ") + +# Pre-run. +os.system("clear") + +# Hide tracebacks - change to 1 for dev mode. +sys.tracebacklimit = 0 + +# Program. + +PROC_ID = multiprocessing.Value('i', 0) +PLATFORM = sys.platform + +# -------------------------------- +# Helper functions +# -------------------------------- +def has_dependencies_installed(): + exec_path = shutil.which("openvpn") + return exec_path and os.access(exec_path, os.X_OK) + +def get_links_by_platform(): + global PLATFORM + + openvpn = '' + proton = '' + nord = '' + mullvad = '' + + if PLATFORM == "linux": + openvpn = "https://openvpn.net/openvpn-client-for-linux/" + nord = "https://support.nordvpn.com/hc/en-us/articles/20164827795345-Connect-to-NordVPN-using-Linux-Terminal" + proton = "https://protonvpn.com/support/linux-openvpn/" + mullvad = "https://mullvad.net/en/help/linux-openvpn-installation" + elif PLATFORM == "windows": + openvpn = "https://openvpn.net/connect-docs/installation-guide-windows.html" + nord = "https://support.nordvpn.com/hc/en-us/articles/19749554331793-How-to-set-up-a-manual-connection-on-Windows-using-OpenVPN" + proton = "https://protonvpn.com/support/openvpn-windows-setup/" + mullvad = "https://mullvad.net/en/help/windows-openvpn-installation" + elif PLATFORM == "macos": + openvpn = "https://openvpn.net/connect-docs/connect-for-macos.html" + proton = "https://protonvpn.com/support/mac-vpn-setup/" + nord = "https://support.nordvpn.com/hc/en-us/articles/19924903986961-Manual-connection-setup-with-Tunnelblick-on-macOS" + mullvad = "https://mullvad.net/en/help/tunnelblick-mac" + else: + raise Exception("Unsupported platform") + + return openvpn, proton, nord, mullvad + +def has_files_in_dir(directory: str, pattern: str) -> list[str]: + """ + Checks if a directory contains any file that matches the given pattern + """ + files = glob.glob(os.path.join(directory, pattern)) + return files and len(files) > 0 + +# -------------------------------- + +def connect(config_path: str, move = False): + """ + Call the underlying openvpn command to connect to the VPN server. + + Args: + config_path (str): Path to the OpenVPN configuration file. + move (bool, optional): Move into the directory of the config file before connecting. Defaults to False. + + Returns: + None + """ + global PROC_ID + global PLATFORM + + if move: + os.chdir(os.path.dirname(config_path)) + + try: + proc = subprocess.Popen( + ["sudo", "openvpn", "--config", config_path], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE + ) + + while True: + line = proc.stdout.readline() + if 'Initialization Sequence Completed' in line.decode(): + print(f"{response} OpenVPN initialized") + PROC_ID.value = proc.pid + break + + except subprocess.CalledProcessError as e: + print(f"{alert} Error executing command: {e}") + except Exception as e: + print(f"{alert} An error occurred: {e}") + + +def process(config_path: str, move = False): + """ + This function is the main entry point of the program. It takes a config file path and an optional 'move' flag. + + The 'move' flag is determined by whether there are "authentication" files in the same folder as the config file. Some + providers include the authentication and certs in the config file itself, others include them in separate files and + then refer to those in the config file using relative paths. + + Args: + config_path (str): The path to the OpenVPN configuration file. + move (bool): If True, the program will change directory to the same as the config file before execution. + + Returns: + None + """ + + global PROC_ID + proc_id = None + running = True + + while running: + try: + if not PROC_ID.value or PROC_ID.value == 0: + print(f"{alert} openvpn requires admin permissions. You might be asked to enter your password") + proc = multiprocessing.Process(target=connect, args=(config_path, move,)) + proc.start() + + while not PROC_ID.value or PROC_ID.value == 0: + time.sleep(1) + + if not proc: + print(f"{alert} Error connecting to VPN") + running = False + + proc_id = PROC_ID.value + print(f"{response} VPN is connected using process {proc_id}") + break + except KeyboardInterrupt: + proc.terminate() + running = False + + if PROC_ID and PROC_ID.value != 0: + os.kill(PROC_ID.value, signal.SIGTERM) + PROC_ID.value = 0 + + if proc_id: + print(f"{notice} VPN process is running as process ID {proc_id}. If you wish to stop it, run: sudo kill -9 {proc_id} or restarting the computer will end it.") + + sys.exit(0) + + return proc, proc_id + + +def ovpn(): + global PLATFORM + + openvpn, proton, nord, mullvad = get_links_by_platform() + + if not has_dependencies_installed(): + print(f"{alert} OpenVPN is required but is not installed.") + print(f"Please install it using your package manager or by following the instructions here: {openvpn}") + + while True: + # Prompt for option. + print(f"\n{prompt} What would you like to do? [Connect, Config]") + option = input(f"{command}").lower() + + if option == "connect": + path = input( + f"{prompt} Enter the FULLY QUALIFIED filepath to your OpenVPN connection profile (*.ovpn file): " + ) + move = input(f"{alert}{prompt} Are there any auth files in the same folder as your ovpn profile? Such as a *.crt and *_userpass.txt [y/n/unsure] ").lower() + + if move == 'unsure': + if ( + has_files_in_dir(Path(path).resolve(),"*.crt") + or has_files_in_dir(Path(path).resolve(),"*_userpass.txt") + ): + move = "y" + + if Path(path).is_file(): + proc, proc_id = process(Path(path).resolve(), move == 'y') + return proc + else: + print(f"{alert} Supplied config file does not exist, try again.") + elif option == "config": + print(f"{alert} We can't set up the OpenVPN config file for any particular provider, but here are some helpful links for how to get started:") + print(f"\tProton VPN: {proton}") + print(f"\tNord VPN: {nord}") + print(f"\tMullvad: {mullvad}") + else: + print(f"{alert} Invalid option. Exiting.") + return None + + +if __name__ == '__main__': + ovpn() From d1016d60ab22aa0c3d828d42c68c80a58c05dfa4 Mon Sep 17 00:00:00 2001 From: Vapourisation Date: Mon, 7 Oct 2024 10:37:56 +0100 Subject: [PATCH 2/7] fix: platform string checks --- src/modules/ovpn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/ovpn.py b/src/modules/ovpn.py index fb140db..189e882 100644 --- a/src/modules/ovpn.py +++ b/src/modules/ovpn.py @@ -60,12 +60,12 @@ def get_links_by_platform(): nord = '' mullvad = '' - if PLATFORM == "linux": + if PLATFORM.startswith("linux"): openvpn = "https://openvpn.net/openvpn-client-for-linux/" nord = "https://support.nordvpn.com/hc/en-us/articles/20164827795345-Connect-to-NordVPN-using-Linux-Terminal" proton = "https://protonvpn.com/support/linux-openvpn/" mullvad = "https://mullvad.net/en/help/linux-openvpn-installation" - elif PLATFORM == "windows": + elif PLATFORM == "win32": openvpn = "https://openvpn.net/connect-docs/installation-guide-windows.html" nord = "https://support.nordvpn.com/hc/en-us/articles/19749554331793-How-to-set-up-a-manual-connection-on-Windows-using-OpenVPN" proton = "https://protonvpn.com/support/openvpn-windows-setup/" From 28fad6455c153f0219cc6a1fff1ed01e4b99a412 Mon Sep 17 00:00:00 2001 From: Vapourisation Date: Mon, 7 Oct 2024 21:33:38 +0100 Subject: [PATCH 3/7] [DOCS]: Update Documentation And horus.py For ovpn --- README.md | 1 + horus.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cae4214..b7b1c16 100755 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Once it is installed, follow these steps: 4. In the 'horus' directory, run ```python3 horus.py``` on Linux/MacOS, or ```py horus.py``` on Windows *Note: protonvpn-cli is a requirement for the 'pvpn' command* +*Note: openvpn CLI is a requirement for the 'ovpn' command* ## ⚙️ API Configuration To configure the APIs necessary for usage of certain commands, you can either manually enter them, or use the 'apicon' command diff --git a/horus.py b/horus.py index 1f2d1ab..1c9b591 100755 --- a/horus.py +++ b/horus.py @@ -10,6 +10,7 @@ # Modules import src.apicon as apicon # # SECURITY. +import src.modules.ovpn as ovpn import src.modules.pvpn as pvpn # ENUMERATION. import src.modules.recpull as recpull @@ -45,7 +46,7 @@ # SECURITY. #ap.add_argument('-Torshell', help='\n', action="store_true") ap.add_argument('-pvpn', help='\n', action="store_true") -#ap.add_argument('-Ovpn', help='\n', action="store_true") +ap.add_argument('-ovpn', help='Connect to an OpenVPN server using a config/profile file.\n', action="store_true") # ENUMERATION. #ap.add_argument('-Fallenflare', help='\n', action="store_true") ap.add_argument('-recpull', help='\n', action="store_true") @@ -115,6 +116,14 @@ def __exit__(self, *args): print(f">_ {Fore.RED}FAILURE{Fore.WHITE}: {error}\n") os._exit(0) +if args['ovpn']: + while True: + try: + ovpn.ovpn() + os._exit(0) + except Exception as error: + print(f">_ {Fore.RED}FAILURE{Fore.WHITE}: {error}\n") + if args['shodan']: # Runs the shodan program. while True: try: From 656eb7e1c1999f9657c19d8ec79a20a19a701865 Mon Sep 17 00:00:00 2001 From: Vapourisation Date: Tue, 8 Oct 2024 07:29:31 +0100 Subject: [PATCH 4/7] [FIX]: Platform Matches and Correct Return Types --- src/modules/ovpn.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/modules/ovpn.py b/src/modules/ovpn.py index 189e882..3a71dd7 100644 --- a/src/modules/ovpn.py +++ b/src/modules/ovpn.py @@ -48,11 +48,11 @@ # -------------------------------- # Helper functions # -------------------------------- -def has_dependencies_installed(): +def has_dependencies_installed() -> bool: exec_path = shutil.which("openvpn") return exec_path and os.access(exec_path, os.X_OK) -def get_links_by_platform(): +def get_links_by_platform() -> (str, str, str, str): global PLATFORM openvpn = '' @@ -70,7 +70,7 @@ def get_links_by_platform(): nord = "https://support.nordvpn.com/hc/en-us/articles/19749554331793-How-to-set-up-a-manual-connection-on-Windows-using-OpenVPN" proton = "https://protonvpn.com/support/openvpn-windows-setup/" mullvad = "https://mullvad.net/en/help/windows-openvpn-installation" - elif PLATFORM == "macos": + elif PLATFORM == "darwin": openvpn = "https://openvpn.net/connect-docs/connect-for-macos.html" proton = "https://protonvpn.com/support/mac-vpn-setup/" nord = "https://support.nordvpn.com/hc/en-us/articles/19924903986961-Manual-connection-setup-with-Tunnelblick-on-macOS" @@ -80,7 +80,7 @@ def get_links_by_platform(): return openvpn, proton, nord, mullvad -def has_files_in_dir(directory: str, pattern: str) -> list[str]: +def has_files_in_dir(directory: str, pattern: str) -> bool: """ Checks if a directory contains any file that matches the given pattern """ @@ -125,7 +125,7 @@ def connect(config_path: str, move = False): print(f"{alert} An error occurred: {e}") -def process(config_path: str, move = False): +def process(config_path: str, move = False) -> (multiprocessing.Process, int): """ This function is the main entry point of the program. It takes a config file path and an optional 'move' flag. @@ -173,8 +173,6 @@ def process(config_path: str, move = False): if proc_id: print(f"{notice} VPN process is running as process ID {proc_id}. If you wish to stop it, run: sudo kill -9 {proc_id} or restarting the computer will end it.") - sys.exit(0) - return proc, proc_id From 2f65f844fb3b8c1355101ca863b7f18a02def4d1 Mon Sep 17 00:00:00 2001 From: Thomas Pegler <144659344+tpegl@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:46:59 +0100 Subject: [PATCH 5/7] [FIX]: Ensures better Windows Support - Checks for Windows more thoroughly and handles that - Checks whether running in Administrator Mode in Windows and exists if not as this is required - Move the config file to the OpenVPN config directory making it easier to run --- src/modules/ovpn.py | 92 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 16 deletions(-) diff --git a/src/modules/ovpn.py b/src/modules/ovpn.py index 3a71dd7..8dd5312 100644 --- a/src/modules/ovpn.py +++ b/src/modules/ovpn.py @@ -1,4 +1,5 @@ # Imports. +import ctypes import glob import multiprocessing import os @@ -87,9 +88,23 @@ def has_files_in_dir(directory: str, pattern: str) -> bool: files = glob.glob(os.path.join(directory, pattern)) return files and len(files) > 0 +def list_files_in_dir(directory: str): + """ + Lists all the files in a directory + """ + files = glob.glob(os.path.join(directory, "*")) + [print(file) for file in files] + +def is_admin(): + try: + admin = os.getuid() == 0 + except AttributeError: + admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 + + return admin # -------------------------------- -def connect(config_path: str, move = False): +def connect(config_file_path: str, move = False): """ Call the underlying openvpn command to connect to the VPN server. @@ -103,21 +118,50 @@ def connect(config_path: str, move = False): global PROC_ID global PLATFORM + windows_path = "C:\\Program Files\\OpenVPN\\bin\\openvpn-gui.exe" + command = [] + + if PLATFORM == "win32": + config_path = "C:\\Program Files\\OpenVPN\\config\\" + seperator = "\\" + else: + config_path = "/etc/openvpn/" + seperator = "/" + + config_file_name = config_file_path.split(seperator)[-1] + if move: os.chdir(os.path.dirname(config_path)) + if not has_files_in_dir(Path(config_path).resolve(), config_file_name): + print(f"{notice} Moving config file to {config_path}") + shutil.move(config_file_path, config_path + config_file_name) + else: + print(f"{notice} Config file already exists in {config_path}. Executing...") + + if PLATFORM == "win32": + command = [windows_path, "--connect", config_file_name] + else: + command = ["sudo", "openvpn", "--config", config_file_name] + try: proc = subprocess.Popen( - ["sudo", "openvpn", "--config", config_path], + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE ) - while True: - line = proc.stdout.readline() - if 'Initialization Sequence Completed' in line.decode(): - print(f"{response} OpenVPN initialized") - PROC_ID.value = proc.pid - break + # On Windows we're using the GUI as the CLI isn't compiled with support for compression and this breaks many + # config files. The GUI doesn't return any data to our process so we can't read the output _but_ as the GUI + # opens up, it should be obvious if it starts or are any issues. + if PLATFORM != "win32": + while True: + line = proc.stdout.readline() + if 'Initialization Sequence Completed' in line.decode(): + print(f"{response} OpenVPN initialized") + PROC_ID.value = proc.pid + break + else: + print(f"{response} OpenVPN initialized. Check the GUI for any errors or the taskbar for the OpenVPN GUI icon.") except subprocess.CalledProcessError as e: print(f"{alert} Error executing command: {e}") @@ -141,16 +185,25 @@ def process(config_path: str, move = False) -> (multiprocessing.Process, int): None """ + global PLATFORM global PROC_ID + proc_id = None running = True while running: try: if not PROC_ID.value or PROC_ID.value == 0: - print(f"{alert} openvpn requires admin permissions. You might be asked to enter your password") - proc = multiprocessing.Process(target=connect, args=(config_path, move,)) - proc.start() + + # On Windows, multiprocessing imports the main script and executes it, causing all of the intro text to display + # again. It doesn't break anything but it looks bad and confusing. + if PLATFORM != "win32": + print(f"{alert} openvpn requires admin permissions. You might be asked to enter your password") + proc = multiprocessing.Process(target=connect, args=(config_path, move,)) + proc.start() + else: + connect(config_path, move) + return None, None while not PROC_ID.value or PROC_ID.value == 0: time.sleep(1) @@ -170,7 +223,7 @@ def process(config_path: str, move = False) -> (multiprocessing.Process, int): os.kill(PROC_ID.value, signal.SIGTERM) PROC_ID.value = 0 - if proc_id: + if proc_id and proc_id != 0: print(f"{notice} VPN process is running as process ID {proc_id}. If you wish to stop it, run: sudo kill -9 {proc_id} or restarting the computer will end it.") return proc, proc_id @@ -191,10 +244,17 @@ def ovpn(): option = input(f"{command}").lower() if option == "connect": + if PLATFORM == "win32" and not is_admin(): + print(f"{alert} This program does support Windows but you will need to run in Administrator Mode.") + return + path = input( - f"{prompt} Enter the FULLY QUALIFIED filepath to your OpenVPN connection profile (*.ovpn file): " + f"{prompt} Enter the filepath to your OpenVPN connection profile (*.ovpn file): " ) - move = input(f"{alert}{prompt} Are there any auth files in the same folder as your ovpn profile? Such as a *.crt and *_userpass.txt [y/n/unsure] ").lower() + move = input(f"{prompt} Are there any auth files in the same folder as your ovpn profile? Such as a *.crt and *_userpass.txt [y/n/unsure] ").lower() + + if not path.startswith("\\") and not path.startswith("C:\\"): + path = os.path.abspath(path) if move == 'unsure': if ( @@ -204,10 +264,10 @@ def ovpn(): move = "y" if Path(path).is_file(): - proc, proc_id = process(Path(path).resolve(), move == 'y') + proc, proc_id = process(path, move == 'y') return proc else: - print(f"{alert} Supplied config file does not exist, try again.") + print(f"{alert} Supplied config file does not exist, is not a file or is not accessible, please try again.") elif option == "config": print(f"{alert} We can't set up the OpenVPN config file for any particular provider, but here are some helpful links for how to get started:") print(f"\tProton VPN: {proton}") From 9fdc34b2537b4c84718810d507c5eea22fb021ed Mon Sep 17 00:00:00 2001 From: Vapourisation Date: Tue, 8 Oct 2024 19:44:10 +0100 Subject: [PATCH 6/7] [FIX]: Organise Code Better --- src/modules/ovpn.py | 77 +++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/src/modules/ovpn.py b/src/modules/ovpn.py index 8dd5312..688c303 100644 --- a/src/modules/ovpn.py +++ b/src/modules/ovpn.py @@ -104,9 +104,43 @@ def is_admin(): return admin # -------------------------------- -def connect(config_file_path: str, move = False): +def get_windows_command_and_path(config_file_path: str) -> (list, str): + """ + Handle Windows specific config and setup. + + The Windows method requires that the *.ovpn config file is placed inside the config directory. + This function checks for its existence there and moves it if it does not exist. + + Args: + config_file_path (str): The path to the OpenVPN configuration file. + + Returns: + list: The command to execute. + str: The path to the config directory. + """ + command = [] + windows_path = "C:\\Program Files\\OpenVPN\\bin\\openvpn-gui.exe" + config_path = "C:\\Program Files\\OpenVPN\\config\\" + config_file_name = config_file_path.split("\\")[-1] + + command = [windows_path, "--connect", config_file_name] + if not has_files_in_dir(Path(config_path).resolve(), config_file_name): + print(f"{notice} Moving config file to {config_path}") + moved = subprocess.run(f"copy {config_file_path} {config_path + config_file_name}", shell=True, check=True) + print(f"{response} Config file moved to {config_path}: {moved.returncode == 0}") + else: + print(f"{notice} Config file already exists in {config_path}. Executing...") + + return command, config_path + + +def connect(config_file_path: str, move = False) -> subprocess.Popen: """ Call the underlying openvpn command to connect to the VPN server. + + If run on Windows, move the config file to the OpenVPN config directory to make running the program easier. + + Linux and MacOS do not need the config file to be moved. Args: config_path (str): Path to the OpenVPN configuration file. @@ -118,32 +152,17 @@ def connect(config_file_path: str, move = False): global PROC_ID global PLATFORM - windows_path = "C:\\Program Files\\OpenVPN\\bin\\openvpn-gui.exe" command = [] if PLATFORM == "win32": - config_path = "C:\\Program Files\\OpenVPN\\config\\" - seperator = "\\" + command, config_path = get_windows_command_and_path(config_file_path) else: config_path = "/etc/openvpn/" - seperator = "/" - - config_file_name = config_file_path.split(seperator)[-1] + command = ["sudo", "openvpn", "--config", config_file_path] if move: os.chdir(os.path.dirname(config_path)) - if not has_files_in_dir(Path(config_path).resolve(), config_file_name): - print(f"{notice} Moving config file to {config_path}") - shutil.move(config_file_path, config_path + config_file_name) - else: - print(f"{notice} Config file already exists in {config_path}. Executing...") - - if PLATFORM == "win32": - command = [windows_path, "--connect", config_file_name] - else: - command = ["sudo", "openvpn", "--config", config_file_name] - try: proc = subprocess.Popen( command, @@ -153,16 +172,20 @@ def connect(config_file_path: str, move = False): # On Windows we're using the GUI as the CLI isn't compiled with support for compression and this breaks many # config files. The GUI doesn't return any data to our process so we can't read the output _but_ as the GUI # opens up, it should be obvious if it starts or are any issues. - if PLATFORM != "win32": - while True: - line = proc.stdout.readline() - if 'Initialization Sequence Completed' in line.decode(): - print(f"{response} OpenVPN initialized") - PROC_ID.value = proc.pid - break - else: + if PLATFORM == "win32": print(f"{response} OpenVPN initialized. Check the GUI for any errors or the taskbar for the OpenVPN GUI icon.") - + return proc + + while True: + line = proc.stdout.readline() + if 'Initialization Sequence Completed' in line.decode(): + print(f"{response} OpenVPN initialized") + PROC_ID.value = proc.pid + break + else: + print(line.decode().strip()) + + return proc except subprocess.CalledProcessError as e: print(f"{alert} Error executing command: {e}") except Exception as e: From 8d1c9aa164ac671e5888fabbf1627b58858172eb Mon Sep 17 00:00:00 2001 From: Vapourisation Date: Tue, 8 Oct 2024 21:35:31 +0100 Subject: [PATCH 7/7] [FIX]: Flake8 Fixes --- src/modules/ovpn.py | 47 +++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/modules/ovpn.py b/src/modules/ovpn.py index 688c303..bffa283 100644 --- a/src/modules/ovpn.py +++ b/src/modules/ovpn.py @@ -17,8 +17,7 @@ # Changes the [], | and : in the client side dividers = (f"{Fore.LIGHTRED_EX}") # Success output. -success = (f"\n{Fore.WHITE}[{Fore.GREEN}SUCCESS{ - Fore.WHITE}] Program executed sucessfully.") +success = (f"\n{Fore.WHITE}[{Fore.GREEN}SUCCESS{Fore.WHITE}] Program executed successfully.") response = (f"{Fore.WHITE}[{Fore.GREEN}+{Fore.WHITE}]") # Successfully output. successfully = (f"{Fore.WHITE}[{Fore.GREEN}SUCCESSFULLY{Fore.WHITE}]") @@ -36,7 +35,7 @@ command = (f"\n[{Fore.YELLOW}>_{Fore.WHITE}]: ") # Pre-run. -os.system("clear") +os.system("clear") # Hide tracebacks - change to 1 for dev mode. sys.tracebacklimit = 0 @@ -46,6 +45,7 @@ PROC_ID = multiprocessing.Value('i', 0) PLATFORM = sys.platform + # -------------------------------- # Helper functions # -------------------------------- @@ -53,6 +53,7 @@ def has_dependencies_installed() -> bool: exec_path = shutil.which("openvpn") return exec_path and os.access(exec_path, os.X_OK) + def get_links_by_platform() -> (str, str, str, str): global PLATFORM @@ -78,9 +79,10 @@ def get_links_by_platform() -> (str, str, str, str): mullvad = "https://mullvad.net/en/help/tunnelblick-mac" else: raise Exception("Unsupported platform") - + return openvpn, proton, nord, mullvad + def has_files_in_dir(directory: str, pattern: str) -> bool: """ Checks if a directory contains any file that matches the given pattern @@ -88,6 +90,7 @@ def has_files_in_dir(directory: str, pattern: str) -> bool: files = glob.glob(os.path.join(directory, pattern)) return files and len(files) > 0 + def list_files_in_dir(directory: str): """ Lists all the files in a directory @@ -95,6 +98,7 @@ def list_files_in_dir(directory: str): files = glob.glob(os.path.join(directory, "*")) [print(file) for file in files] + def is_admin(): try: admin = os.getuid() == 0 @@ -104,6 +108,7 @@ def is_admin(): return admin # -------------------------------- + def get_windows_command_and_path(config_file_path: str) -> (list, str): """ Handle Windows specific config and setup. @@ -120,7 +125,7 @@ def get_windows_command_and_path(config_file_path: str) -> (list, str): """ command = [] windows_path = "C:\\Program Files\\OpenVPN\\bin\\openvpn-gui.exe" - config_path = "C:\\Program Files\\OpenVPN\\config\\" + config_path = "C:\\Program Files\\OpenVPN\\config\\" config_file_name = config_file_path.split("\\")[-1] command = [windows_path, "--connect", config_file_name] @@ -132,12 +137,12 @@ def get_windows_command_and_path(config_file_path: str) -> (list, str): print(f"{notice} Config file already exists in {config_path}. Executing...") return command, config_path - -def connect(config_file_path: str, move = False) -> subprocess.Popen: + +def connect(config_file_path: str, move=False) -> subprocess.Popen: """ Call the underlying openvpn command to connect to the VPN server. - + If run on Windows, move the config file to the OpenVPN config directory to make running the program easier. Linux and MacOS do not need the config file to be moved. @@ -184,22 +189,22 @@ def connect(config_file_path: str, move = False) -> subprocess.Popen: break else: print(line.decode().strip()) - + return proc except subprocess.CalledProcessError as e: - print(f"{alert} Error executing command: {e}") + print(f"{alert} Error executing command: {e}") except Exception as e: print(f"{alert} An error occurred: {e}") -def process(config_path: str, move = False) -> (multiprocessing.Process, int): +def process(config_path: str, move=False) -> (multiprocessing.Process, int): """ This function is the main entry point of the program. It takes a config file path and an optional 'move' flag. The 'move' flag is determined by whether there are "authentication" files in the same folder as the config file. Some - providers include the authentication and certs in the config file itself, others include them in separate files and + providers include the authentication and certs in the config file itself, others include them in separate files and then refer to those in the config file using relative paths. - + Args: config_path (str): The path to the OpenVPN configuration file. move (bool): If True, the program will change directory to the same as the config file before execution. @@ -207,7 +212,7 @@ def process(config_path: str, move = False) -> (multiprocessing.Process, int): Returns: None """ - + global PLATFORM global PROC_ID @@ -217,8 +222,8 @@ def process(config_path: str, move = False) -> (multiprocessing.Process, int): while running: try: if not PROC_ID.value or PROC_ID.value == 0: - - # On Windows, multiprocessing imports the main script and executes it, causing all of the intro text to display + + # On Windows, multiprocessing imports the main script and executes it, causing all of the intro text to display # again. It doesn't break anything but it looks bad and confusing. if PLATFORM != "win32": print(f"{alert} openvpn requires admin permissions. You might be asked to enter your password") @@ -233,7 +238,7 @@ def process(config_path: str, move = False) -> (multiprocessing.Process, int): if not proc: print(f"{alert} Error connecting to VPN") - running = False + running = False proc_id = PROC_ID.value print(f"{response} VPN is connected using process {proc_id}") @@ -244,7 +249,7 @@ def process(config_path: str, move = False) -> (multiprocessing.Process, int): if PROC_ID and PROC_ID.value != 0: os.kill(PROC_ID.value, signal.SIGTERM) - PROC_ID.value = 0 + PROC_ID.value = 0 if proc_id and proc_id != 0: print(f"{notice} VPN process is running as process ID {proc_id}. If you wish to stop it, run: sudo kill -9 {proc_id} or restarting the computer will end it.") @@ -270,7 +275,7 @@ def ovpn(): if PLATFORM == "win32" and not is_admin(): print(f"{alert} This program does support Windows but you will need to run in Administrator Mode.") return - + path = input( f"{prompt} Enter the filepath to your OpenVPN connection profile (*.ovpn file): " ) @@ -281,8 +286,8 @@ def ovpn(): if move == 'unsure': if ( - has_files_in_dir(Path(path).resolve(),"*.crt") - or has_files_in_dir(Path(path).resolve(),"*_userpass.txt") + has_files_in_dir(Path(path).resolve(), "*.crt") + or has_files_in_dir(Path(path).resolve(), "*_userpass.txt") ): move = "y"