def get_transferred_data():
    """Reads and returns the amount of data transferred during a session
    from the /sys/ directory"""
    def convert_size(size_bytes):
        """Converts byte amounts into human readable formats"""
        if size_bytes == 0:
            return "0B"
        size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")

        i = int(math.floor(math.log(size_bytes, 1000)))
        p = math.pow(1000, i)
        s = round(size_bytes / p, 2)
        return "{0} {1}".format(s, size_name[i])

    base_path = "/sys/class/net/{0}/statistics/{1}"

    if os.path.isfile(base_path.format('proton0', 'rx_bytes')):
        adapter_name = 'proton0'
    elif os.path.isfile(base_path.format('tun0', 'rx_bytes')):
        adapter_name = 'tun0'
    else:
        gui_logger.debug("No usage stats for VPN interface available")
        return '-', '-'

    # Get transmitted and received bytes from /sys/ directory
    with open(base_path.format(adapter_name, 'tx_bytes'), "r") as f:
        tx_bytes = int(f.read())

    with open(base_path.format(adapter_name, 'rx_bytes'), "r") as f:
        rx_bytes = int(f.read())

    return convert_size(tx_bytes), convert_size(rx_bytes)
def country_f(country_code, protocol=None):
    """Connect to the fastest server in a specific country."""
    gui_logger.debug("Starting fastest country connect")

    if not protocol:
        protocol = get_config_value("USER", "default_protocol")

    country_code = country_code.strip().upper()

    disconnect(passed=True)
    pull_server_data(force=True)

    servers = get_servers()

    # ProtonVPN Features: 1: SECURE-CORE, 2: TOR, 4: P2P
    excluded_features = [1, 2]

    # Filter out excluded features and countries
    server_pool = []
    for server in servers:
        if server["Features"] not in excluded_features \
         and server["ExitCountry"] == country_code:
            server_pool.append(server)

    if len(server_pool) == 0:
        print("[!] No Server in country {0} found\n".format(country_code) +
              "[!] Please choose a valid country")
        gui_logger.debug("No server in country {0}".format(country_code))
        sys.exit(1)

    fastest_server = get_fastest_server(server_pool)
    openvpn_connect(fastest_server, protocol)
def is_connected():
    """Check if a VPN connection already exists."""
    ovpn_processes = subprocess.run(["pgrep", "--exact", "openvpn"],
                                    stdout=subprocess.PIPE)
    ovpn_processes = ovpn_processes.stdout.decode("utf-8").split()

    gui_logger.debug(
        "Checking connection Status. OpenVPN processes: {0}".format(
            len(ovpn_processes)))
    return True if ovpn_processes != [] else False
def set_config_value(group, key, value):
    """Write a specific value to CONFIG_FILE"""

    config = configparser.ConfigParser()
    config.read(CONFIG_FILE)
    config[group][key] = str(value)

    gui_logger.debug("Writing {0} to [{1}] in config file".format(key, group))

    with open(CONFIG_FILE, "w+") as f:
        config.write(f)
def call_api(endpoint,
             json_format=True,
             handle_errors=True,
             gui_enabled=False):
    """Call to the ProtonVPN API."""

    api_domain = "https://api.protonvpn.ch"
    url = api_domain + endpoint

    headers = {
        "x-pm-appversion": "Other",
        "x-pm-apiversion": "3",
        "Accept": "application/vnd.protonmail.v1+json"
    }

    gui_logger.debug("Initiating API Call: {0}".format(url))

    # For manual error handling, such as in wait_for_network()
    if not handle_errors:
        response = requests.get(url, headers=headers)
        return response

    try:
        # response = requests.get(url, headers=headers, timeout=8)
        response = requests.get(url, headers=headers)
    except (requests.exceptions.ConnectionError,
            requests.exceptions.ConnectTimeout):
        # requests.exceptions.ReadTimeout):
        if not gui_enabled:
            print("[!] There was an error connecting to the ProtonVPN API.\n"
                  "[!] Please make sure your connection is working properly!")
        gui_logger.debug("Error connecting to ProtonVPN API")
        if not gui_enabled:
            sys.exit(1)
        return False
    try:
        response.raise_for_status()
    except requests.exceptions.HTTPError:
        print("[!] There was an error with accessing the ProtonVPN API.\n"
              "[!] Please make sure your connection is working properly!\n"
              "[!] HTTP Error Code: {0}".format(response.status_code))
        gui_logger.debug("Bad Return Code: {0}".format(response.status_code))
        if not gui_enabled:
            sys.exit(1)
        return False

    if json_format:
        gui_logger.debug("Successful json response")
        return response.json()
    else:
        gui_logger.debug("Successful non-json response")
        return response
def get_fastest_server(server_pool):
    """Return the fastest server from a list of servers"""

    # Sort servers by "speed" and select top n according to pool_size
    fastest_pool = sorted(server_pool, key=lambda server: server["Score"])
    if len(fastest_pool) >= 50:
        pool_size = 4
    else:
        pool_size = 1
    gui_logger.debug(
        "Returning fastest server with pool size {0}".format(pool_size))
    fastest_server = random.choice(fastest_pool[:pool_size])["Name"]
    return fastest_server
    def show_dialog(headline, choices, stop=False):
        """Show the dialog and process response."""
        d = Dialog(dialog="dialog")

        gui_logger.debug("Showing Dialog: {0}".format(headline))

        code, tag = d.menu(headline, title="ProtonVPN-CLI", choices=choices)
        if code == "ok":
            return tag
        else:
            os.system("clear")
            print("Canceled.")
            sys.exit(1)
def get_ip_info(gui_enabled=False):
    """Return the current public IP Address"""
    gui_logger.debug("Getting IP Information")
    ip_info = call_api("/vpn/location", gui_enabled=gui_enabled)

    if ip_info == None or ip_info == False:
        return False

    ip = ip_info["IP"]
    isp = ip_info["ISP"]

    if gui_enabled == True:
        return ip, isp, ip_info["Country"]
    return ip, isp
def change_file_owner(path):
    """Change the owner of specific files to the sudo user."""
    uid = int(
        subprocess.run(["id", "-u", USER], stdout=subprocess.PIPE).stdout)
    gid = int(
        subprocess.run(["id", "-u", USER], stdout=subprocess.PIPE).stdout)

    current_owner = subprocess.run(
        ["id", "-nu", str(os.stat(path).st_uid)],
        stdout=subprocess.PIPE).stdout
    current_owner = current_owner.decode().rstrip("\n")

    # Only change file owner if it wasn't owned by current running user.
    if current_owner != USER:
        os.chown(path, uid, gid)
        gui_logger.debug("Changed owner of {0} to {1}".format(path, USER))
def reconnect(gui_enabled=False):
    """Reconnect to the last VPN Server."""

    gui_logger.debug("Starting reconnect")

    try:
        servername = get_config_value("metadata", "connected_server")
        protocol = get_config_value("metadata", "connected_proto")
    except KeyError:
        gui_logger.debug("No previous connection found")
        print("[!] Couldn't find a previous connection\n"
              "[!] Please connect normally first")
        if not gui_enabled:
            sys.exit(1)
        return "Couldn't find a previous connection.\nPlease connect normally first."

    if gui_enabled:
        return openvpn_connect(servername, protocol, gui_enabled)
    openvpn_connect(servername, protocol)
def random_c(protocol=None, gui_enabled=False):
    """Connect to a random ProtonVPN Server."""

    gui_logger.debug("Starting random connect")

    if not protocol:
        protocol = get_config_value("USER", "default_protocol")

    servers = get_servers()

    servername = random.choice(servers)["Name"]

    if gui_enabled:
        return openvpn_connect(servername,
                               protocol,
                               gui_enabled,
                               has_disconnected=False,
                               servers=servers)
    openvpn_connect(servername, protocol)
示例#12
0
def check_root():
    """Check if the program was executed as root and prompt the user."""
    if os.geteuid() != 0:
        print("[!] The program was not executed as root.\n"
              "[!] Please run as root.")
        gui_logger.debug("Program wasn't executed as root")
        sys.exit(1)
    else:
        # Check for dependencies
        dependencies = ["openvpn", "ip", "sysctl", "pgrep", "pkill"]
        for program in dependencies:
            check = subprocess.run(["which", program],
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            if not check.returncode == 0:
                gui_logger.debug("{0} not found".format(program))
                print("'{0}' not found. \n".format(program) +
                      "Please install {0}.".format(program))
                sys.exit(1)
def fastest(protocol=None, gui_enabled=False):
    """Connect to the fastest server available."""

    gui_logger.debug("Starting fastest connect")

    if not protocol:
        protocol = get_config_value("USER", "default_protocol")

    disconnect(passed=True)

    tries = 2

    # while tries > 0:
    #     try:
    pull_server_data(force=True)
    # except:
    #     tries -= 1
    #     time.sleep(1)

    servers = get_servers()

    # ProtonVPN Features: 1: SECURE-CORE, 2: TOR, 4: P2P
    excluded_features = [1, 2]

    # Filter out excluded features
    server_pool = []
    for server in servers:
        if server["Features"] not in excluded_features:
            server_pool.append(server)

    fastest_server = get_fastest_server(server_pool)
    if gui_enabled:
        return openvpn_connect(fastest_server,
                               protocol,
                               gui_enabled,
                               has_disconnected=True,
                               servers=servers)
    openvpn_connect(fastest_server,
                    protocol,
                    has_disconnected=True,
                    servers=servers)
示例#14
0
    def init_config_file():
        """"Initialize configuration file."""
        config = configparser.ConfigParser()
        config["USER"] = {
            "username": "******",
            "tier": "None",
            "default_protocol": "None",
            "initialized": "0",
            "dns_leak_protection": "1",
            "custom_dns": "None",
            "check_update_interval": "3",
        }
        config["metadata"] = {
            "last_api_pull": "0",
            "last_update_check": str(int(time.time())),
        }

        with open(CONFIG_FILE, "w") as f:
            config.write(f)
        change_file_owner(CONFIG_FILE)
        gui_logger.debug("pvpn-cli.cfg initialized")
def disconnect(passed=False, gui_enabled=False):
    """Disconnect VPN if a connection is present."""

    gui_logger.debug("Initiating disconnect")
    return_message = 'Error occured with this message'
    if is_connected():
        if passed:
            print("There is already a VPN connection running.")
            print("Terminating previous connection...")
        subprocess.run(["pkill", "openvpn"])

        time.sleep(0.5)
        timer_start = time.time()

        while True:
            if is_connected():
                if time.time() - timer_start <= 5:
                    subprocess.run(["pkill", "openvpn"])
                    time.sleep(0.2)
                else:
                    subprocess.run(["pkill", "-9", "openvpn"])
                    gui_logger.debug("SIGKILL sent")
                    break
            else:
                break

        if is_connected():
            print("[!] Could not terminate OpenVPN process.")
            if not gui_enabled:
                sys.exit(1)
            return_message = "Could not terminate OpenVPN process."
        else:
            manage_dns("restore")
            manage_ipv6("restore")
            manage_killswitch("restore")
            gui_logger.debug("Disconnected")
            if not passed:
                print("Disconnected.")
                return_message = "Disconnected from VPN server."
    else:
        if not passed:
            print("No connection found.")
        manage_dns("restore")
        manage_ipv6("restore")
        manage_killswitch("restore")
        gui_logger.debug("No connection found")
        return_message = "No active connection was found."

    if gui_enabled:
        return return_message
示例#16
0
def set_username_password(write=False, gui_enabled=False, user_data=False):
    """Set the ProtonVPN Username and Password."""
    
    return_message = 'Something went wrong.'
    print()
    if gui_enabled:
        ovpn_username, ovpn_password1 = user_data
    else:
        ovpn_username = input("Enter your ProtonVPN OpenVPN username: "******"Enter your ProtonVPN OpenVPN password: "******"Confirm your ProtonVPN OpenVPN password: "******"[!] The passwords do not match. Please try again.")
            else:
                break

    if write:
        set_config_value("USER", "username", ovpn_username)

        with open(PASSFILE, "w") as f:
            f.write("{0}\n{1}".format(ovpn_username, ovpn_password1))
            gui_logger.debug("Passfile updated")
            os.chmod(PASSFILE, 0o600)

        print("Username and Password has been updated!")
        return_message = "Username and Password has been updated!"

    if gui_enabled:
        return return_message
    return ovpn_username, ovpn_password1
def feature_f(feature, protocol=None):
    """Connect to the fastest server in a specific country."""
    gui_logger.debug(
        "Starting fastest feature connect with feature {0}".format(feature))

    if not protocol:
        protocol = get_config_value("USER", "default_protocol")

    disconnect(passed=True)
    pull_server_data(force=True)

    servers = get_servers()

    server_pool = [s for s in servers if s["Features"] == feature]

    if len(server_pool) == 0:
        gui_logger.debug("No servers found with users selection. Exiting.")
        print("[!] No servers found with your selection.")
        sys.exit(1)

    fastest_server = get_fastest_server(server_pool)
    openvpn_connect(fastest_server, protocol)
示例#18
0
def get_servers():
    """Return a list of all servers for the users Tier."""

    server_data = {}
    timer_start = time.time()

    while True:
        if time.time() - timer_start > 5:
            break

        with open(SERVER_INFO_FILE, "r") as f:
            gui_logger.debug("Reading servers from file")
            try:
                data = json.load(f)
                if not data == None and not len(data) == 0:
                    server_data = data
                    break
            except:
                pull_server_data(force=True)
                time.sleep(2)
                pass

        # time.sleep(2)

    if len(server_data) == 0:
        return False

    servers = server_data["LogicalServers"]

    user_tier = int(get_config_value("USER", "tier"))

    # Sort server IDs by Tier
    return [
        server for server in servers
        if server["Tier"] <= user_tier and server["Status"] == 1
    ]  # noqa
def direct(user_input, protocol=None):
    """Connect to a single given server directly"""

    gui_logger.debug("Starting direct connect with {0}".format(user_input))
    pull_server_data()

    if not protocol:
        protocol = get_config_value("USER", "default_protocol")

    # For short format (UK-03/HK#5-Tor | Normal Servers/Tor Servers)
    re_short = re.compile(r"^((\w\w)(-|#)?(\d{1,3})-?(TOR)?)$")
    # For long format (IS-DE-01 | Secure-Core/Free/US Servers)
    re_long = re.compile(
        r"^(((\w\w)(-|#)?([A-Z]{2}|FREE))(-|#)?(\d{1,3})-?(TOR)?)$")

    user_input = user_input.upper()

    if re_short.search(user_input):
        user_server = re_short.search(user_input)

        country_code = user_server.group(2)
        number = user_server.group(4).lstrip("0")
        tor = user_server.group(5)
        servername = "{0}#{1}".format(country_code, number) +\
                     "{0}".format('-' + tor if tor is not None else '')
    elif re_long.search(user_input):
        user_server = re_long.search(user_input)
        country_code = user_server.group(3)
        country_code2 = user_server.group(5)
        number = user_server.group(7).lstrip("0")
        tor = user_server.group(8)
        servername = "{0}-{1}#{2}".format(country_code,
                                          country_code2, number) + \
                     "{0}".format('-' + tor if tor is not None else '')
    else:
        print("[!] '{0}' is not a valid servername\n".format(user_input) +
              "[!] Please enter a valid servername")
        gui_logger.debug("'{0}' is not a valid servername'".format(user_input))
        sys.exit(1)

    servers = get_servers()

    if servername not in [server["Name"] for server in servers]:
        print("[!] {0} doesn't exist, ".format(servername) +
              "is under maintenance, or inaccessible with your plan.\n"
              "[!] Please enter a different, valid servername.")
        gui_logger.debug("{0} doesn't exist".format(servername))
        sys.exit(1)

    openvpn_connect(servername, protocol)
示例#20
0
    def get_latest_version():
        """Return the latest version from pypi"""
        gui_logger.debug("Calling pypi API")
        try:
            r = requests.get("https://pypi.org/pypi/protonvpn-cli/json")
        except (requests.exceptions.ConnectionError,
                requests.exceptions.ConnectTimeout):
            gui_logger.debug("Couldn't connect to pypi API")
            return False
        try:
            r.raise_for_status()
        except requests.exceptions.HTTPError:
            gui_logger.debug("HTTP Error with pypi API: {0}".format(
                r.status_code))
            return False

        release = r.json()["info"]["version"]

        return release
示例#21
0
def check_init():
    """Check if a profile has been initialized, quit otherwise."""

    try:
        if not int(get_config_value("USER", "initialized")):
            print("[!] There has been no profile initialized yet. "
                  "Please run 'protonvpn init'.")
            gui_logger.debug("Initialized Profile not found")
            sys.exit(1)
        else:
            # Check if required configuration values are set
            # If this isn't the case it will set a default value

            default_conf = {
                "USER": {
                    "username": "******",
                    "tier": "0",
                    "default_protocol": "udp",
                    "dns_leak_protection": "1",
                    "custom_dns": "None",
                    "check_update_interval": "3",
                    "killswitch": "0",
                    "split_tunnel": "0",
                },
            }

            for section in default_conf:
                for config_key in default_conf[section]:
                    try:
                        get_config_value(section, config_key)
                    except KeyError:
                        gui_logger.debug(
                            "Config {0}/{1} not found, default set".format(
                                section, config_key))
                        set_config_value(section, config_key,
                                         default_conf[section][config_key])

    except KeyError:
        print("[!] There has been no profile initialized yet. "
              "Please run 'protonvpn init'.")
        gui_logger.debug("Initialized Profile not found")
        sys.exit(1)
示例#22
0
def wait_for_network(wait_time):
    """Check if internet access is working"""

    print("Waiting for connection...")
    start = time.time()

    while True:
        if time.time() - start > wait_time:
            gui_logger.debug("Max waiting time reached.")
            print("Max waiting time reached.")
            sys.exit(1)
        gui_logger.debug(
            "Waiting for {0}s for connection...".format(wait_time))
        try:
            call_api("/test/ping", handle_errors=False)
            time.sleep(2)
            print("Connection working!")
            gui_logger.debug("Connection working!")
            break
        except (requests.exceptions.ConnectionError,
                requests.exceptions.ConnectTimeout):
            time.sleep(2)
示例#23
0
def pull_server_data(force=False):
    """Pull current server data from the ProtonVPN API."""
    config = configparser.ConfigParser()
    config.read(CONFIG_FILE)

    if not force:
        # Check if last server pull happened within the last 15 min (900 sec)
        if int(time.time()) - int(config["metadata"]["last_api_pull"]) <= 900:
            gui_logger.debug("Last server pull within 15mins")
            return

    data = call_api("/vpn/logicals")

    with open(SERVER_INFO_FILE, "w") as f:
        json.dump(data, f)
        gui_logger.debug("SERVER_INFO_FILE written")

    change_file_owner(SERVER_INFO_FILE)
    config["metadata"]["last_api_pull"] = str(int(time.time()))

    with open(CONFIG_FILE, "w+") as f:
        config.write(f)
        gui_logger.debug("last_api_call updated")
def manage_ipv6(mode):
    """
    Disable and Enable IPv6 to circumvent IPv6 leaks.

    Has 2 modes (string): disable / restore.
    disable: Disables IPv6 for the default interface.
    restore: Revert changes and restore original configuration.
    """

    ipv6_backupfile = os.path.join(CONFIG_DIR, "ipv6.backup")
    ip6tables_backupfile = os.path.join(CONFIG_DIR, "ip6tables.backup")

    if mode == "disable":

        gui_logger.debug("Disabling IPv6")
        # Needs to be removed eventually. I'll leave it in for now
        # so it still properly restores the IPv6 address the old way
        if os.path.isfile(ipv6_backupfile):
            manage_ipv6("legacy_restore")

        if os.path.isfile(ip6tables_backupfile):
            gui_logger.debug("IPv6 backup exists")
            manage_ipv6("restore")

        # Backing up ip6ables rules
        gui_logger.debug("Backing up ip6tables rules")
        ip6tables_rules = subprocess.run(["ip6tables-save"],
                                         stdout=subprocess.PIPE)

        if "COMMIT" in ip6tables_rules.stdout.decode():
            with open(ip6tables_backupfile, "wb") as f:
                f.write(ip6tables_rules.stdout)
        else:
            with open(ip6tables_backupfile, "w") as f:
                f.write("*filter\n")
                f.write(":INPUT ACCEPT\n")
                f.write(":FORWARD ACCEPT\n")
                f.write(":OUTPUT ACCEPT\n")
                f.write("COMMIT\n")

        # Get the default nic from ip route show output
        default_nic = get_default_nic()

        ip6tables_commands = [
            "ip6tables -A INPUT -i {0} -j DROP".format(default_nic),
            "ip6tables -A OUTPUT -o {0} -j DROP".format(default_nic),
        ]
        for command in ip6tables_commands:
            command = command.split()
            subprocess.run(command)
        gui_logger.debug("IPv6 disabled successfully")

    elif mode == "restore":
        gui_logger.debug("Restoring ip6tables")
        # Same as above, remove eventually
        if os.path.isfile(ipv6_backupfile):
            gui_logger.debug("legacy ipv6 backup found")
            manage_ipv6("legacy_restore")
        if os.path.isfile(ip6tables_backupfile):
            subprocess.run(
                "ip6tables-restore < {0}".format(ip6tables_backupfile),
                shell=True,
                stdout=subprocess.PIPE)
            gui_logger.debug("ip6tables restored")
            os.remove(ip6tables_backupfile)
            gui_logger.debug("ip6tables.backup removed")
        else:
            gui_logger.debug("No Backupfile found")
        return

    elif mode == "legacy_restore":
        gui_logger.debug("Restoring IPv6")
        if not os.path.isfile(ipv6_backupfile):
            gui_logger.debug("No Backupfile found")
            return

        with open(ipv6_backupfile, "r") as f:
            lines = f.readlines()
            default_nic = lines[0].strip()
            ipv6_addr = lines[1].strip()

        ipv6_info = subprocess.run(
            "ip addr show dev {0} | grep '\<inet6.*global\>'".format(
                default_nic),  # noqa
            shell=True,
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE)

        has_ipv6 = True if ipv6_info.returncode == 0 else False

        if has_ipv6:
            gui_logger.debug("IPv6 address present")
            os.remove(ipv6_backupfile)
            return

        ipv6_enable = subprocess.run(
            "sysctl -w net.ipv6.conf.{0}.disable_ipv6=0".format(default_nic),
            shell=True,
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE)

        if not ipv6_enable.returncode == 0:
            print(
                "[!] There was an error with restoring the IPv6 configuration")
            gui_logger.debug("IPv6 restoration error: sysctl")
            gui_logger.debug("stdout: {0}".format(ipv6_enable.stdout))
            gui_logger.debug("stderr: {0}".format(ipv6_enable.stderr))
            return

        ipv6_restore_address = subprocess.run("ip addr add {0} dev {1}".format(
            ipv6_addr, default_nic),
                                              shell=True,
                                              stderr=subprocess.PIPE,
                                              stdout=subprocess.PIPE)

        if not ipv6_restore_address.returncode == 0:
            print(
                "[!] There was an error with restoring the IPv6 configuration")
            gui_logger.debug("IPv6 restoration error: ip")
            gui_logger.debug("stdout: {0}".format(ipv6_restore_address.stdout))
            gui_logger.debug("stderr: {0}".format(ipv6_restore_address.stderr))
            return

        gui_logger.debug("Removing IPv6 backup file")
        os.remove(ipv6_backupfile)
        gui_logger.debug("IPv6 restored")

    else:
        raise Exception("Invalid argument provided. "
                        "Mode must be 'disable' or 'restore'")
示例#25
0
def init_cli(gui_enabled=False, gui_user_input=False):
    """Initialize the CLI."""

    def init_config_file():
        """"Initialize configuration file."""
        config = configparser.ConfigParser()
        config["USER"] = {
            "username": "******",
            "tier": "None",
            "default_protocol": "None",
            "initialized": "0",
            "dns_leak_protection": "1",
            "custom_dns": "None",
            "check_update_interval": "3",
        }
        config["metadata"] = {
            "last_api_pull": "0",
            "last_update_check": str(int(time.time())),
        }

        with open(CONFIG_FILE, "w") as f:
            config.write(f)
        change_file_owner(CONFIG_FILE)
        gui_logger.debug("pvpn-cli.cfg initialized")

    check_root()

    if not os.path.isdir(CONFIG_DIR):
        os.mkdir(CONFIG_DIR)
        gui_logger.debug("Config Directory created")
    change_file_owner(CONFIG_DIR)

    # Warn user about reinitialization
    try:
        if int(get_config_value("USER", "initialized")):
            print("An initialized profile has been found.")
            overwrite = input(
                "Are you sure you want to overwrite that profile? [y/N]: "
            )
            if overwrite.strip().lower() != "y":
                print("Quitting...")
                sys.exit(1)
            # Disconnect, so every setting (Kill Switch, IPv6, ...)
            # will be reverted (See #62)
            connection.disconnect(passed=True)
    except KeyError:
        pass

    term_width = shutil.get_terminal_size()[0]
    print("[ -- PROTONVPN-CLI INIT -- ]\n".center(term_width))

    init_msg = (
        "ProtonVPN uses two different sets of credentials, one for the "
        "website and official apps where the username is most likely your "
        "e-mail, and one for connecting to the VPN servers.\n\n"
        "You can find the OpenVPN credentials at "
        "https://account.protonvpn.com/account.\n\n"
        "--- Please make sure to use the OpenVPN credentials ---\n"
    ).splitlines()

    for line in init_msg:
        print(textwrap.fill(line, width=term_width))

    # Check if GUI was enabled and get the data from GUI
    if gui_enabled == True:
        ovpn_username = gui_user_input['username']
        ovpn_password = gui_user_input['password']
        user_tier = gui_user_input['protonvpn_plan']
        user_protocol = gui_user_input['openvpn_protocol']
    else:
        # Set ProtonVPN Username and Password
        ovpn_username, ovpn_password = set_username_password(write=False)
        # Set the ProtonVPN Plan
        user_tier = set_protonvpn_tier(write=False)
        # Set default Protocol
        user_protocol = set_default_protocol(write=False)

    protonvpn_plans = {1: "Free", 2: "Basic", 3: "Plus", 4: "Visionary"}

    print()
    print(
        "You entered the following information:\n" +
        "Username: {0}\n".format(ovpn_username) +
        "Password: {0}\n".format("*" * len(ovpn_password)) +
        "Tier: {0}\n".format(protonvpn_plans[user_tier]) +
        "Default protocol: {0}".format(user_protocol.upper())
    )
    print()

    if not gui_enabled: 
        user_confirmation = input(
            "Is this information correct? [Y/n]: "
        ).strip().lower()

    if gui_enabled or (user_confirmation == "y" or user_confirmation == ""):
        print("Writing configuration to disk...")
        init_config_file()

        pull_server_data()
        make_ovpn_template()

        # Change user tier to correct value
        if user_tier == 4:
            user_tier = 3
        user_tier -= 1

        set_config_value("USER", "username", ovpn_username)
        set_config_value("USER", "tier", user_tier)
        set_config_value("USER", "default_protocol", user_protocol)
        set_config_value("USER", "dns_leak_protection", 1)
        set_config_value("USER", "custom_dns", None)
        set_config_value("USER", "killswitch", 0)

        with open(PASSFILE, "w") as f:
            f.write("{0}\n{1}".format(ovpn_username, ovpn_password))
            gui_logger.debug("Passfile created")
            os.chmod(PASSFILE, 0o600)

        set_config_value("USER", "initialized", 1)

        print()
        print("Done! Your account has been successfully initialized.")
        gui_logger.debug("Initialization completed.")
        if gui_enabled:
            return True
    else:
        print()
        print("Please restart the initialization process.")
        sys.exit(1)
def manage_killswitch(mode, proto=None, port=None):
    """
    Disable and enable the VPN Kill Switch.

    The Kill Switch creates IPTables rules that only allow connections to go
    through the OpenVPN device. If the OpenVPN process stops for some unknown
    reason this will completely block access to the internet.
    """

    backupfile = os.path.join(CONFIG_DIR, "iptables.backup")

    if mode == "restore":
        gui_logger.debug("Restoring iptables")
        if os.path.isfile(backupfile):
            gui_logger.debug("Restoring IPTables rules")
            subprocess.run("iptables-restore < {0}".format(backupfile),
                           shell=True,
                           stdout=subprocess.PIPE)
            gui_logger.debug("iptables restored")
            os.remove(backupfile)
            gui_logger.debug("iptables.backup removed")
        else:
            gui_logger.debug("No Backupfile found")
        return

    # Stop if Kill Switch is disabled
    if not int(get_config_value("USER", "killswitch")):
        return

    if mode == "enable":
        if os.path.isfile(backupfile):
            gui_logger.debug("Kill Switch backup exists")
            manage_killswitch("restore")

        with open(os.path.join(CONFIG_DIR, "ovpn.log"), "r") as f:
            content = f.read()
            device = re.search(r"(TUN\/TAP device) (.+) opened", content)
            if not device:
                print("[!] Kill Switch activation failed."
                      "Device couldn't be determined.")
                gui_logger.debug(
                    "Kill Switch activation failed. No device in logfile")
            device = device.group(2)

        # Backing up IPTables rules
        gui_logger.debug("Backing up iptables rules")
        iptables_rules = subprocess.run(["iptables-save"],
                                        stdout=subprocess.PIPE)

        if "COMMIT" in iptables_rules.stdout.decode():
            with open(backupfile, "wb") as f:
                f.write(iptables_rules.stdout)
        else:
            with open(backupfile, "w") as f:
                f.write("*filter\n")
                f.write(":INPUT ACCEPT\n")
                f.write(":FORWARD ACCEPT\n")
                f.write(":OUTPUT ACCEPT\n")
                f.write("COMMIT\n")

        # Creating Kill Switch rules
        iptables_commands = [
            "iptables -F",
            "iptables -P INPUT DROP",
            "iptables -P OUTPUT DROP",
            "iptables -P FORWARD DROP",
            "iptables -A OUTPUT -o lo -j ACCEPT",
            "iptables -A INPUT -i lo -j ACCEPT",
            "iptables -A OUTPUT -o {0} -j ACCEPT".format(device),
            "iptables -A INPUT -i {0} -j ACCEPT".format(device),
            "iptables -A OUTPUT -o {0} -m state --state ESTABLISHED,RELATED -j ACCEPT"
            .format(device),  # noqa
            "iptables -A INPUT -i {0} -m state --state ESTABLISHED,RELATED -j ACCEPT"
            .format(device),  # noqa
            "iptables -A OUTPUT -p {0} -m {1} --dport {2} -j ACCEPT".format(
                proto.lower(), proto.lower(), port),  # noqa
            "iptables -A INPUT -p {0} -m {1} --sport {2} -j ACCEPT".format(
                proto.lower(), proto.lower(), port),  # noqa
        ]

        if int(get_config_value("USER", "killswitch")) == 2:
            # Getting local network information
            default_nic = get_default_nic()
            local_network = subprocess.run(
                "ip addr show {0} | grep inet".format(default_nic),
                stdout=subprocess.PIPE,
                shell=True)
            local_network = local_network.stdout.decode().strip().split()[1]

            exclude_lan_commands = [
                "iptables -A OUTPUT -o {0} -d {1} -j ACCEPT".format(
                    default_nic, local_network),  # noqa
                "iptables -A INPUT -i {0} -s {1} -j ACCEPT".format(
                    default_nic, local_network),  # noqa
            ]

            for lan_command in exclude_lan_commands:
                iptables_commands.append(lan_command)

        for command in iptables_commands:
            command = command.split()
            subprocess.run(command)
        gui_logger.debug("Kill Switch enabled")
示例#27
0
def set_split_tunnel(gui_enabled=False, user_data=False):
    """Enable or disable split tunneling"""

    result_message = ''

    print()

    if gui_enabled == True:
        if len(user_data) == 0:
            set_config_value("USER", "split_tunnel", 0)
            if os.path.isfile(SPLIT_TUNNEL_FILE):
                os.remove(SPLIT_TUNNEL_FILE)
                result_message = "Split tunneling disabled.\n\n"
        else:
            if int(get_config_value("USER", "killswitch")):
                set_config_value("USER", "killswitch", 0)
                print()
                print(
                    "[!] Split Tunneling can't be used with Kill Switch.\n" +
                    "[!] Kill Switch has been disabled.\n"
                )
                result_message = "Split Tunneling can't be used with Kill Switch.\nKill Switch has been disabled.\n\n"
                time.sleep(1)

            set_config_value("USER", "split_tunnel", 1)

            with open(SPLIT_TUNNEL_FILE, "w") as f:
                for ip in user_data:
                    f.write("\n{0}".format(ip))

            if os.path.isfile(SPLIT_TUNNEL_FILE):
                change_file_owner(SPLIT_TUNNEL_FILE)
            else:
                # If no no config file exists,
                # split tunneling should be disabled again
                gui_logger.debug("No split tunneling file existing.")
                set_config_value("USER", "split_tunnel", 0)
                result_message = result_message + "No split tunneling file, split tunneling will be disabled.\n\n"
    else:
        user_choice = input("Enable split tunneling? [y/N]: ")

        if user_choice.strip().lower() == "y":
            if int(get_config_value("USER", "killswitch")):
                set_config_value("USER", "killswitch", 0)
                print()
                print(
                    "[!] Split Tunneling can't be used with Kill Switch.\n" +
                    "[!] Kill Switch has been disabled.\n"
                )
                time.sleep(1)

            set_config_value("USER", "split_tunnel", 1)


            while True:
                ip = input(
                    "Please enter an IP or CIDR to exclude from VPN.\n"
                    "Or leave empty to stop: "
                ).strip()

                if ip == "":
                    break

                if not is_valid_ip(ip):
                    print("[!] Invalid IP")
                    print()
                    continue

                with open(SPLIT_TUNNEL_FILE, "a") as f:
                    f.write("\n{0}".format(ip))

            if os.path.isfile(SPLIT_TUNNEL_FILE):
                change_file_owner(SPLIT_TUNNEL_FILE)
            else:
                # If no no config file exists,
                # split tunneling should be disabled again
                gui_logger.debug("No split tunneling file existing.")
                set_config_value("USER", "split_tunnel", 0)

        else:
            set_config_value("USER", "split_tunnel", 0)

            if os.path.isfile(SPLIT_TUNNEL_FILE):
                clear_config = input("Remove split tunnel configuration? [y/N]: ")

                if clear_config.strip().lower() == "y":
                    os.remove(SPLIT_TUNNEL_FILE)

    print()
    print("Split tunneling configuration updated.")
    result_message = result_message + "Split tunneling configuration updated."
    make_ovpn_template()

    if gui_enabled:
        return result_message
示例#28
0
def make_ovpn_template():
    """Create OpenVPN template file."""
    pull_server_data()

    with open(SERVER_INFO_FILE, "r") as f:
        server_data = json.load(f)

    # Get the ID of the first server from the API
    server_id = server_data["LogicalServers"][0]["ID"]

    config_file_response = call_api(
        "/vpn/config?Platform=linux&LogicalID={0}&Protocol=tcp".format(
            server_id),  # noqa
        json_format=False)

    with open(TEMPLATE_FILE, "wb") as f:
        for chunk in config_file_response.iter_content(100000):
            f.write(chunk)
            gui_logger.debug("OpenVPN config file downloaded")

    # Write split tunneling config to OpenVPN Template
    try:
        if get_config_value("USER", "split_tunnel") == "1":
            split = True
        else:
            split = False
    except KeyError:
        split = False
    if split:
        gui_logger.debug("Writing Split Tunnel config")
        with open(SPLIT_TUNNEL_FILE, "r") as f:
            content = f.readlines()

        with open(TEMPLATE_FILE, "a") as f:
            for line in content:
                line = line.rstrip("\n")
                netmask = None

                if not is_valid_ip(line):
                    gui_logger.debug(
                        "[!] '{0}' is invalid. Skipped.".format(line))
                    continue

                if "/" in line:
                    ip, cidr = line.split("/")
                    netmask = cidr_to_netmask(int(cidr))
                else:
                    ip = line

                if netmask is None:
                    netmask = "255.255.255.255"

                if is_valid_ip(ip):
                    f.write("\nroute {0} {1} net_gateway".format(ip, netmask))

                else:
                    gui_logger.debug(
                        "[!] '{0}' is invalid. Skipped.".format(line))

        gui_logger.debug("Split Tunneling Written")

    # Remove all remote, proto, up, down and script-security lines
    # from template file
    remove_regex = re.compile(r"^(remote|proto|up|down|script-security) .*$")

    for line in fileinput.input(TEMPLATE_FILE, inplace=True):
        if not remove_regex.search(line):
            print(line, end="")

    gui_logger.debug("remote and proto lines removed")

    change_file_owner(TEMPLATE_FILE)
示例#29
0
def cli():
    """Run user's input command."""

    # Initial log values
    change_file_owner(os.path.join(CONFIG_DIR, "pvpn-cli.log"))
    gui_logger.debug("###########################")
    gui_logger.debug("### NEW PROCESS STARTED ###")
    gui_logger.debug("###########################")
    gui_logger.debug(sys.argv)
    gui_logger.debug("USER: {0}".format(USER))
    gui_logger.debug("CONFIG_DIR: {0}".format(CONFIG_DIR))

    args = docopt(__doc__, version="ProtonVPN-CLI v{0}".format(VERSION))
    gui_logger.debug("Arguments\n{0}".format(str(args).replace("\n", "")))

    # Parse arguments
    if args.get("init"):
        init_cli()
    elif args.get("c") or args.get("connect"):
        check_root()
        check_init()

        # Wait until a connection to the ProtonVPN API can be made
        # As this is mainly for automatically connecting on boot, it only
        # activates when the environment variable PVPN_WAIT is 1
        # Otherwise it wouldn't connect when a VPN process without
        # internet access exists or the Kill Switch is active
        if int(os.environ.get("PVPN_WAIT", 0)) > 0:
            wait_for_network(int(os.environ["PVPN_WAIT"]))

        protocol = args.get("-p")
        if protocol is not None and protocol.lower().strip() in ["tcp", "udp"]:
            protocol = protocol.lower().strip()

        if args.get("--random"):
            connection.random_c(protocol)
        elif args.get("--fastest"):
            connection.fastest(protocol)
        elif args.get("<servername>"):
            connection.direct(args.get("<servername>"), protocol)
        elif args.get("--cc") is not None:
            connection.country_f(args.get("--cc"), protocol)
        # Features: 1: Secure-Core, 2: Tor, 4: P2P
        elif args.get("--p2p"):
            connection.feature_f(4, protocol)
        elif args.get("--sc"):
            connection.feature_f(1, protocol)
        elif args.get("--tor"):
            connection.feature_f(2, protocol)
        else:
            connection.dialog()
    elif args.get("r") or args.get("reconnect"):
        check_root()
        check_init()
        connection.reconnect()
    elif args.get("d") or args.get("disconnect"):
        check_root()
        check_init()
        connection.disconnect()
    elif args.get("s") or args.get("status"):
        connection.status()
    elif args.get("configure"):
        check_root()
        check_init()
        configure_cli()
    elif args.get("refresh"):
        pull_server_data(force=True)
        make_ovpn_template()
    elif args.get("examples"):
        print_examples()
示例#30
0
def check_update():
    """Return the download URL if an Update is available, False if otherwise"""
    def get_latest_version():
        """Return the latest version from pypi"""
        gui_logger.debug("Calling pypi API")
        try:
            r = requests.get("https://pypi.org/pypi/protonvpn-cli/json")
        except (requests.exceptions.ConnectionError,
                requests.exceptions.ConnectTimeout):
            gui_logger.debug("Couldn't connect to pypi API")
            return False
        try:
            r.raise_for_status()
        except requests.exceptions.HTTPError:
            gui_logger.debug("HTTP Error with pypi API: {0}".format(
                r.status_code))
            return False

        release = r.json()["info"]["version"]

        return release

    # Determine if an update check should be run
    check_interval = int(get_config_value("USER", "check_update_interval"))
    check_interval = check_interval * 24 * 3600
    last_check = int(get_config_value("metadata", "last_update_check"))

    if (last_check + check_interval) >= time.time():
        # Don't check for update
        return

    gui_logger.debug("Checking for new update")
    current_version = list(VERSION.split("."))
    current_version = [int(i) for i in current_version]
    gui_logger.debug("Current: {0}".format(current_version))

    latest_version = get_latest_version()
    if not latest_version:
        # Skip if get_latest_version() ran into errors
        return
    latest_version = latest_version.split(".")
    latest_version = [int(i) for i in latest_version]
    gui_logger.debug("Latest: {0}".format(latest_version))

    for idx, i in enumerate(latest_version):
        if i > current_version[idx]:
            gui_logger.debug("Update found")
            update_available = True
            break
        elif i < current_version[idx]:
            gui_logger.debug("No update")
            update_available = False
            break
    else:
        gui_logger.debug("No update")
        update_available = False

    set_config_value("metadata", "last_update_check", int(time.time()))

    if update_available:
        print()
        print(
            "A new Update for ProtonVPN-CLI (v{0}) ".format('.'.join(
                [str(x) for x in latest_version])) + "is available.\n" +
            "Follow the Update instructions on\n" +
            "https://github.com/ProtonVPN/protonvpn-cli-ng/blob/master/USAGE.md#updating-protonvpn-cli\n"
            +  # noqa
            "\n"
            "To see what's new, check out the changelog:\n" +
            "https://github.com/ProtonVPN/protonvpn-cli-ng/blob/master/CHANGELOG.md"  # noqa
        )