Example #1
0
def _update_openvpn_conf(force):
    printer = Printer()
    try:
        resources.get_zip_dir(create=False)
        initial_run = False
    except resources.ResourceNotFoundError:
        initial_run = True

    if force or initial_run:
        get()
        unzip()
    else:
        update_archives = False
        for archive in __ARCHIVES.values():
            zip_file = resources.get_zip_path(archive)
            # if one archive needs an update all archives are updated
            if update_needed(zip_file):
                update_archives = True
                break
        else:
            raise UpdateError(
                "Just one update per hour allowed. Use --force to ignore "
                "the timeout."
            )

        if update_archives:
            get()
            for archive in __ARCHIVES.values():
                zip_file = resources.get_zip_path(archive)
                orig_file = resources.get_zip_path(archive + ".orig")
                if not file_equals(orig_file, zip_file):
                    unzip()
                    break
            else:
                printer.info("Configurations are up-to-date.")
Example #2
0
def ping(server):
    """
    Ping a server
    :param dict server: A server as dictionary
    :returns: copy of server with additional key 'ping'
    """

    server_copy = server.copy()
    pattern = re.compile(r"rtt .* = ([\d\.]+)/([\d\.]+)/([\d\.]+)/.* ms")
    ip_address = server["ip_address"]
    with subprocess.Popen(
        ["ping", "-q", "-n", "-c", "1", "-l", "1", "-W", "1", ip_address],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    ) as ping_:

        out, _ = ping_.communicate()
        mat = pattern.search(out.decode())
        if mat:
            server_copy["ping"] = float(mat.group(2))
        else:
            server_copy["ping"] = float("inf")

        printer = Printer()
        printer.info(
            "{:6}: ping: {}".format(server_copy["domain"], server_copy["ping"])
        )
        return server_copy
Example #3
0
def print_version():
    """Prints the version of connord along with the copyright."""
    printer = Printer()
    print("connord {}".format(__version__), file=printer)
    print(__copyright__, file=printer)
    printer.info("\nConfiguration directory: '{}'".format(
        resources.get_config_dir()))
Example #4
0
    def run(self):
        """High-level command to run openvpn with the assembled command-line.
        Shuts down openvpn after a timeout. Waits this time for openvpn to startup
        and writes the environment files from the scripts. As soon as they are
        present iptables rules are applied. If something goes wrong call the panic
        method

        :returns: True if everything went fine or running in daemon mode.
        """

        config_dict = resources.get_config()["openvpn"]
        self.cleanup()

        printer = Printer()
        printer.info("Running openvpn with '{}'".format(self.cmd))
        with subprocess.Popen(self.cmd) as ovpn:
            # give openvpn a maximum of 60 seconds to startup. A lower value is bad if
            # asked for username/password.
            # pylint: disable=unused-variable
            for i in range(300):
                try:
                    if self.is_running(ovpn):
                        # delay initialization of iptables until resource files are
                        # created. If none are created the delay still applies as normal
                        # timeout
                        time.sleep(0.2)
                        for script in config_dict["scripts"]:
                            stage = script["stage"]
                            if stage in ("up", "always"):
                                resources.get_stats_file(
                                    stats_name=script["creates"], create=False
                                )

                    else:
                        self.panic(ovpn, "Openvpn process stopped unexpected.")

                    if iptables.apply_config_dir(self.server, self.protocol):
                        resources.write_stats(self.server, stats_name="server")

                        stats_dict = resources.get_stats()
                        stats_dict["last_server"] = {}
                        stats_dict["last_server"]["domain"] = self.domain
                        stats_dict["last_server"]["protocol"] = self.protocol
                        resources.write_stats(stats_dict)
                    else:
                        self.panic(ovpn, "Applying iptables failed.")

                    break
                except resources.ResourceNotFoundError:
                    pass
            ### for
            else:
                self.panic(ovpn, "Timeout reached.")

            if self.is_running(ovpn):
                ovpn.wait()

        return True
Example #5
0
def test_print_map_good(capsys):
    from connord.printer import Printer

    printer = Printer()
    printer.print_map("50.116667", "8.683333")

    captured = capsys.readouterr()

    map_frankfurt = get_stub("de_frankfurt.map")
    assert captured.out == map_frankfurt
Example #6
0
def test_print_map_location_does_not_exist(capsys):
    from connord.printer import Printer

    printer = Printer()

    printer.print_map("0", "0")

    captured = capsys.readouterr()
    assert not captured.out
    assert not captured.err
Example #7
0
def update_database():
    """Updates the location database with least possible online queries."""
    connection = sqlite.create_connection()
    with connection:
        init_database(connection)

    servers_ = servers.get_servers()
    printer = Printer()
    progress_bar = printer.incremental_bar("Updating location database",
                                           max=len(servers_))

    for server in servers_:
        longitude = str(server["location"]["long"])
        latitude = str(server["location"]["lat"])

        connection = sqlite.create_connection()
        with connection:
            if sqlite.location_exists(connection, latitude, longitude):
                progress_bar.next()
                continue
            else:
                location_d = query_location(latitude, longitude)

        display_name = location_d["display_name"]
        city_keys = ["city", "town", "village", "residential", "state"]
        for key in city_keys:
            try:
                city = location_d["address"][key]
                break
            except KeyError:
                continue
        else:
            city = "Unknown"

        country = server["country"]
        country_code = server["flag"].lower()
        location = (
            latitude,
            longitude,
            display_name,
            city,
            country,
            country_code,
            None,  # map
        )

        connection = sqlite.create_connection()
        with connection:
            sqlite.create_location(connection, location)

        # if not printer.quiet:
        progress_bar.next()

    progress_bar.finish()
Example #8
0
def flush_tables(ipv6=False):
    """Flush all tables and apply the default policy ACCEPT to standard tables"""
    printer = Printer()
    with printer.Do("Flushing all tables: ipv6={!r}".format(ipv6)):
        iptc.easy.flush_all(ipv6=ipv6)

    with printer.Do("Setting ACCEPT policy in all chains"):
        policy = iptc.Policy("ACCEPT")
        for table_s in iptc.easy.get_tables(ipv6):
            for chain_s in iptc.easy.get_chains(table_s, ipv6):
                iptc.easy.set_policy(table_s,
                                     chain_s,
                                     policy=policy,
                                     ipv6=ipv6)
Example #9
0
def get():
    """Get the zip file"""

    for category, archive in __ARCHIVES.items():
        printer = Printer()
        zip_path = resources.get_zip_path(archive)
        update_orig(archive)
        spinner = printer.spinner("Downloading {} configurations".format(category))
        url = "{}/{}".format(__URL, archive)
        with requests.get(url, stream=True, timeout=60) as response, open(
            zip_path, "wb"
        ) as zip_fd:
            chunk_size = 512
            for chunk in response.iter_content(chunk_size=chunk_size):
                spinner.next()
                zip_fd.write(chunk)

        spinner.finish()

    return True
Example #10
0
def connect_to_specific_server(domain, openvpn, daemon, protocol):
    """Connect to a specific server

    :param domain: list of domains which holds one value like gb111
    :param openvpn: string of options to pass-through to openvpn
    :param daemon: True if the openvpn shall be run in daemon mode
    :param protocol: may be one of 'udp' or 'tcp'
    :returns: True if openvpn was run successfully
    """
    server = servers.get_server_by_domain(domain[0])
    printer = Printer()
    if categories.has_category(server, "Obfuscated Servers"):
        if not printer.yes_no(
            (
                "WARNING: {} is an obfuscated server.\n"
                "This may fail if not configured properly.\n"
                "Are you sure you want to continue?"
            ).format(server["domain"])
        ):
            return False
    return run_openvpn(server, openvpn, daemon, protocol)
Example #11
0
def unzip():
    """Unzip the configuration files"""

    printer = Printer()
    zip_dir = resources.get_zip_dir(create=True)

    with printer.Do("Deleting old configuration files"):
        for ovpn_dir in ("ovpn_udp", "ovpn_tcp"):
            remove_dir = "{}/{}".format(zip_dir, ovpn_dir)
            if os.path.exists(remove_dir):
                rmtree(remove_dir, ignore_errors=True)

    for key, archive in __ARCHIVES.items():
        zip_file = resources.get_zip_file(archive)
        with ZipFile(zip_file, "r") as zip_stream:
            name_list = zip_stream.namelist()

            with printer.incremental_bar(
                "Unzipping {} configurations".format(key), max=len(name_list)
            ) as incremental_bar:
                for file_name in name_list:
                    zip_stream.extract(file_name, zip_dir)
                    incremental_bar.next()
Example #12
0
def apply_config(config_file, server=None, protocol=None):
    """Apply a configuration to ip[6]tables (depends on the file name)

    :raises: IptablesError if an invalid rule is present. This leaves the
             table intact with rules applied so far.
    """
    table = init_table_from_file_name(config_file)
    table_s = table.name
    is_ipv6 = is_table_v6(table)

    printer = Printer()
    with printer.Do("Flushing table '{}', ipv6={!r}".format(table_s, is_ipv6)):
        iptc.easy.flush_table(table_s, ipv6=is_ipv6)
        policy = iptc.Policy("ACCEPT")
        for chain_s in iptc.easy.get_chains(table_s, ipv6=is_ipv6):
            iptc.easy.set_policy(table_s, chain_s, policy=policy, ipv6=is_ipv6)

    config_base = os.path.basename(config_file)
    with printer.Do("Applying '{}'".format(config_base)):
        config_d = read_config(config_file, server, protocol)
        _apply_config(table_s, config_d, ipv6=is_ipv6)

    return True
Example #13
0
def main():  # noqa: C901
    """Entry Point for the program. A first level argument processing method.
    All Exceptions that lead to an exit of the program are catched here.

    :raises: SystemExit either 0 or 1
    """

    if not sys.argv[1:]:
        sys.argv.extend(["-h"])

    args = parse_args(sys.argv[1:])
    # TODO: recognize configuration in config.yml
    printer = Printer(verbose=args.verbose, quiet=args.quiet)

    try:
        if args.command == "update":
            update.update(force=args.force)
        elif args.command == "version":
            version.print_version()
        elif args.command == "list":
            process_list_cmd(args)
        elif args.command == "connect":
            process_connect_cmd(args)
        elif args.command == "kill":
            process_kill_cmd(args)
        elif args.command == "iptables":
            process_iptables_cmd(args)
        else:
            # This should only happen when someone tampers with sys.argv
            raise NotImplementedError(
                "Could not process command-line arguments.")
        sys.exit(0)
    except PermissionError:
        printer.error(
            'Permission Denied: You need to run "connord {}" as root'.format(
                args.command))
    except resources.ResourceNotFoundError as error:
        printer.error(str(error))
    except resources.MalformedResourceError as error:
        printer.error(str(error))
    except areas.AreaError as error:
        printer.error(str(error))
    except iptables.IptablesError as error:
        printer.error(str(error))
    except countries.CountryError as error:
        printer.error(str(error))
    except FeatureError as error:
        printer.error(str(error))
    except servers.DomainNotFoundError as error:
        printer.error(str(error))
    except servers.MalformedDomainError as error:
        printer.error(str(error))
    except connect.ConnectError as error:
        printer.error(str(error))
    except update.UpdateError as error:
        printer.error(str(error))
    except RequestException as error:
        printer.error(str(error))
    except sqlite.SqliteError as error:
        printer.error(str(error))
    except IOError as error:
        # Don't handle broken pipe
        if error.errno != errno.EPIPE:
            raise
    except KeyboardInterrupt:
        time.sleep(0.5)
        sys.exit(0)

    sys.exit(1)
Example #14
0
    def init_printer():
        from connord.printer import Printer

        Printer(verbose=True, quiet=True)
Example #15
0
    def test_printer():
        from connord.printer import Printer

        printer = Printer()
        assert printer.verbose
        assert printer.quiet
Example #16
0
 def get_stream_file(self, stream=False):
     """Return self as stream if False else stdout"""
     return Printer() if stream else self
Example #17
0
def connect(
    domain,
    countries_,
    areas_,
    features_,
    categories_,
    netflix,
    load_,
    match,
    daemon,
    openvpn,
    protocol,
):
    """High-level function to connect to a openvpn server. Filters servers and
    tries to connect to an openvpn server 'max_retries' times or else raise an
    exception.

    :param domain: list of domains. defaults to one value 'best'.
    :param countries_: list of countries
    :param areas_: list of areas
    :param features_: list of features
    :param categories_: list of categories
    :param netflix: True to filter netflix optimized servers
    :param load_: depending on match, filter by max, min or equal load servers
    :param match: may be 'max', 'min' or 'equal'
    :param daemon: True if openvpn shall run in daemon mode
    :param config_: the path to the openvpn configuration file
    :param openvpn: options to pass-through to openvpn as string
    :param protocol: may be 'udp' or 'tcp'
    :returns: True if running openvpn was successful
    :raises: ConnectError
    """

    if "best" not in domain:
        return connect_to_specific_server(domain, openvpn, daemon, protocol)

    servers_ = servers.get_servers()
    servers_ = filter_servers(
        servers_, netflix, countries_, areas_, features_, categories_, load_, match
    )

    best_servers = filter_best_servers(servers_)
    max_retries = 3
    printer = Printer()
    for i, server in enumerate(best_servers):
        if i == max_retries:
            raise ConnectError("Maximum retries reached.")

        if categories.has_category(server, "Obfuscated Servers"):
            if not printer.yes_no(
                (
                    "WARNING: {} is an obfuscated server.\n"
                    "This may fail if not configured properly.\n"
                    "Are you sure you want to continue?"
                ).format(server["domain"])
            ):
                continue

        if server["ping"] != inf:
            printer.info(
                "Trying to connect to {}: {} ms".format(
                    server["domain"], server["ping"]
                )
            )
            printer.print_map(server["location"]["lat"], server["location"]["long"])
            if run_openvpn(server, openvpn, daemon, protocol):
                return True

            # else give the next server a try
        else:
            raise ConnectError("No server left with a valid ping.")

    raise ConnectError("No server found to establish a connection.")