diff options
Diffstat (limited to 'src/leap/services/eip')
-rw-r--r-- | src/leap/services/eip/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/services/eip/eipbootstrapper.py | 183 | ||||
-rw-r--r-- | src/leap/services/eip/eipconfig.py | 263 | ||||
-rw-r--r-- | src/leap/services/eip/eipspec.py | 85 | ||||
-rw-r--r-- | src/leap/services/eip/providerbootstrapper.py | 340 | ||||
-rw-r--r-- | src/leap/services/eip/tests/__init__.py | 0 | ||||
-rw-r--r-- | src/leap/services/eip/tests/test_eipbootstrapper.py | 347 | ||||
-rw-r--r-- | src/leap/services/eip/tests/test_eipconfig.py | 324 | ||||
-rw-r--r-- | src/leap/services/eip/tests/test_providerbootstrapper.py | 531 | ||||
-rw-r--r-- | src/leap/services/eip/tests/test_vpngatewayselector.py | 131 | ||||
-rw-r--r-- | src/leap/services/eip/tests/wrongcert.pem | 33 | ||||
-rw-r--r-- | src/leap/services/eip/udstelnet.py | 60 | ||||
-rw-r--r-- | src/leap/services/eip/vpnlaunchers.py | 927 | ||||
-rw-r--r-- | src/leap/services/eip/vpnprocess.py | 791 |
14 files changed, 0 insertions, 4015 deletions
diff --git a/src/leap/services/eip/__init__.py b/src/leap/services/eip/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/src/leap/services/eip/__init__.py +++ /dev/null diff --git a/src/leap/services/eip/eipbootstrapper.py b/src/leap/services/eip/eipbootstrapper.py deleted file mode 100644 index 1d7bc342..00000000 --- a/src/leap/services/eip/eipbootstrapper.py +++ /dev/null @@ -1,183 +0,0 @@ -# -*- coding: utf-8 -*- -# eipbootstrapper.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -""" -EIP bootstrapping -""" - -import logging -import os - -from PySide import QtCore - -from leap.common.check import leap_assert, leap_assert_type -from leap.common import certs -from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p -from leap.config.providerconfig import ProviderConfig -from leap.crypto.srpauth import SRPAuth -from leap.services.eip.eipconfig import EIPConfig -from leap.util.request_helpers import get_content -from leap.util.constants import REQUEST_TIMEOUT -from leap.services.abstractbootstrapper import AbstractBootstrapper - -logger = logging.getLogger(__name__) - - -class EIPBootstrapper(AbstractBootstrapper): - """ - Sets up EIP for a provider a series of checks and emits signals - after they are passed. - If a check fails, the subsequent checks are not executed - """ - - # All dicts returned are of the form - # {"passed": bool, "error": str} - download_config = QtCore.Signal(dict) - download_client_certificate = QtCore.Signal(dict) - - def __init__(self): - AbstractBootstrapper.__init__(self) - - self._provider_config = None - self._eip_config = None - self._download_if_needed = False - - def _download_config(self, *args): - """ - Downloads the EIP config for the given provider - """ - - leap_assert(self._provider_config, - "We need a provider configuration!") - - logger.debug("Downloading EIP config for %s" % - (self._provider_config.get_domain(),)) - - api_version = self._provider_config.get_api_version() - self._eip_config = EIPConfig() - self._eip_config.set_api_version(api_version) - - headers = {} - mtime = get_mtime(os.path.join(self._eip_config - .get_path_prefix(), - "leap", - "providers", - self._provider_config.get_domain(), - "eip-service.json")) - - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - # there is some confusion with this uri, - # it's in 1/config/eip, config/eip and config/1/eip... - config_uri = "%s/%s/config/eip-service.json" % ( - self._provider_config.get_api_uri(), - api_version) - logger.debug('Downloading eip config from: %s' % config_uri) - - res = self._session.get(config_uri, - verify=self._provider_config - .get_ca_cert_path(), - headers=headers, - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - - # Not modified - if res.status_code == 304: - logger.debug("EIP definition has not been modified") - else: - eip_definition, mtime = get_content(res) - - self._eip_config.load(data=eip_definition, mtime=mtime) - self._eip_config.save(["leap", - "providers", - self._provider_config.get_domain(), - "eip-service.json"]) - - def _download_client_certificates(self, *args): - """ - Downloads the EIP client certificate for the given provider - """ - leap_assert(self._provider_config, "We need a provider configuration!") - leap_assert(self._eip_config, "We need an eip configuration!") - - logger.debug("Downloading EIP client certificate for %s" % - (self._provider_config.get_domain(),)) - - client_cert_path = self._eip_config.\ - get_client_cert_path(self._provider_config, - about_to_download=True) - - # For re-download if something is wrong with the cert - self._download_if_needed = self._download_if_needed and \ - not certs.should_redownload(client_cert_path) - - if self._download_if_needed and \ - os.path.exists(client_cert_path): - check_and_fix_urw_only(client_cert_path) - return - - srp_auth = SRPAuth(self._provider_config) - session_id = srp_auth.get_session_id() - cookies = None - if session_id: - cookies = {"_session_id": session_id} - cert_uri = "%s/%s/cert" % ( - self._provider_config.get_api_uri(), - self._provider_config.get_api_version()) - logger.debug('getting cert from uri: %s' % cert_uri) - res = self._session.get(cert_uri, - verify=self._provider_config - .get_ca_cert_path(), - cookies=cookies, - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - client_cert = res.content - - if not certs.is_valid_pemfile(client_cert): - raise Exception(self.tr("The downloaded certificate is not a " - "valid PEM file")) - - mkdir_p(os.path.dirname(client_cert_path)) - - with open(client_cert_path, "w") as f: - f.write(client_cert) - - check_and_fix_urw_only(client_cert_path) - - def run_eip_setup_checks(self, - provider_config, - download_if_needed=False): - """ - Starts the checks needed for a new eip setup - - :param provider_config: Provider configuration - :type provider_config: ProviderConfig - """ - leap_assert(provider_config, "We need a provider config!") - leap_assert_type(provider_config, ProviderConfig) - - self._provider_config = provider_config - self._download_if_needed = download_if_needed - - cb_chain = [ - (self._download_config, self.download_config), - (self._download_client_certificates, - self.download_client_certificate) - ] - - return self.addCallbackChain(cb_chain) diff --git a/src/leap/services/eip/eipconfig.py b/src/leap/services/eip/eipconfig.py deleted file mode 100644 index d69e1fd8..00000000 --- a/src/leap/services/eip/eipconfig.py +++ /dev/null @@ -1,263 +0,0 @@ -# -*- coding: utf-8 -*- -# eipconfig.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -""" -Provider configuration -""" -import logging -import os -import re -import time - -import ipaddr - -from leap.common.check import leap_assert, leap_assert_type -from leap.common.config.baseconfig import BaseConfig -from leap.config.providerconfig import ProviderConfig -from leap.services.eip.eipspec import get_schema - -logger = logging.getLogger(__name__) - - -class VPNGatewaySelector(object): - """ - VPN Gateway selector. - """ - # http://www.timeanddate.com/time/map/ - equivalent_timezones = {13: -11, 14: -10} - - def __init__(self, eipconfig, tz_offset=None): - ''' - Constructor for VPNGatewaySelector. - - :param eipconfig: a valid EIP Configuration. - :type eipconfig: EIPConfig - :param tz_offset: use this offset as a local distance to GMT. - :type tz_offset: int - ''' - leap_assert_type(eipconfig, EIPConfig) - - self._local_offset = tz_offset - if tz_offset is None: - tz_offset = self._get_local_offset() - - if tz_offset in self.equivalent_timezones: - tz_offset = self.equivalent_timezones[tz_offset] - - self._local_offset = tz_offset - - self._eipconfig = eipconfig - - def get_gateways(self): - """ - Returns the 4 best gateways, sorted by timezone proximity. - - :rtype: list of IPv4Address or IPv6Address object. - """ - gateways_timezones = [] - locations = self._eipconfig.get_locations() - gateways = self._eipconfig.get_gateways() - - for idx, gateway in enumerate(gateways): - gateway_location = gateway.get('location') - gateway_distance = 99 # if hasn't location -> should go last - - if gateway_location is not None: - gw_offset = int(locations[gateway['location']]['timezone']) - if gw_offset in self.equivalent_timezones: - gw_offset = self.equivalent_timezones[gw_offset] - - gateway_distance = self._get_timezone_distance(gw_offset) - - ip = self._eipconfig.get_gateway_ip(idx) - gateways_timezones.append((ip, gateway_distance)) - - gateways_timezones = sorted(gateways_timezones, - key=lambda gw: gw[1])[:4] - - gateways = [ip for ip, dist in gateways_timezones] - return gateways - - def _get_timezone_distance(self, offset): - ''' - Returns the distance between the local timezone and - the one with offset 'offset'. - - :param offset: the distance of a timezone to GMT. - :type offset: int - :returns: distance between local offset and param offset. - :rtype: int - ''' - timezones = range(-11, 13) - tz1 = offset - tz2 = self._local_offset - distance = abs(timezones.index(tz1) - timezones.index(tz2)) - if distance > 12: - if tz1 < 0: - distance = timezones.index(tz1) + timezones[::-1].index(tz2) - else: - distance = timezones[::-1].index(tz1) + timezones.index(tz2) - - return distance - - def _get_local_offset(self): - ''' - Returns the distance between GMT and the local timezone. - - :rtype: int - ''' - local_offset = time.timezone - if time.daylight: - local_offset = time.altzone - - return local_offset / 3600 - - -class EIPConfig(BaseConfig): - """ - Provider configuration abstraction class - """ - OPENVPN_ALLOWED_KEYS = ("auth", "cipher", "tls-cipher") - OPENVPN_CIPHERS_REGEX = re.compile("[A-Z0-9\-]+") - - def __init__(self): - BaseConfig.__init__(self) - self._api_version = None - - def _get_schema(self): - """ - Returns the schema corresponding to the version given. - - :rtype: dict or None if the version is not supported. - """ - return get_schema(self._api_version) - - def get_clusters(self): - # TODO: create an abstraction for clusters - return self._safe_get_value("clusters") - - def get_gateways(self): - # TODO: create an abstraction for gateways - return self._safe_get_value("gateways") - - def get_locations(self): - ''' - Returns a list of locations - - :rtype: dict - ''' - return self._safe_get_value("locations") - - def get_openvpn_configuration(self): - """ - Returns a dictionary containing the openvpn configuration - parameters. - - These are sanitized with alphanumeric whitelist. - - :returns: openvpn configuration dict - :rtype: C{dict} - """ - ovpncfg = self._safe_get_value("openvpn_configuration") - config = {} - for key, value in ovpncfg.items(): - if key in self.OPENVPN_ALLOWED_KEYS and value is not None: - sanitized_val = self.OPENVPN_CIPHERS_REGEX.findall(value) - if len(sanitized_val) != 0: - _val = sanitized_val[0] - config[str(key)] = str(_val) - return config - - def get_serial(self): - return self._safe_get_value("serial") - - def get_version(self): - return self._safe_get_value("version") - - def get_gateway_ip(self, index=0): - """ - Returns the ip of the gateway. - - :rtype: An IPv4Address or IPv6Address object. - """ - gateways = self.get_gateways() - leap_assert(len(gateways) > 0, "We don't have any gateway!") - if index > len(gateways): - index = 0 - logger.warning("Provided an unknown gateway index %s, " + - "defaulting to 0") - ip_addr_str = gateways[index]["ip_address"] - - try: - ipaddr.IPAddress(ip_addr_str) - return ip_addr_str - except ValueError: - logger.error("Invalid ip address in config: %s" % (ip_addr_str,)) - return None - - def get_client_cert_path(self, - providerconfig=None, - about_to_download=False): - """ - Returns the path to the certificate used by openvpn - """ - - leap_assert(providerconfig, "We need a provider") - leap_assert_type(providerconfig, ProviderConfig) - - cert_path = os.path.join(self.get_path_prefix(), - "leap", - "providers", - providerconfig.get_domain(), - "keys", - "client", - "openvpn.pem") - - if not about_to_download: - leap_assert(os.path.exists(cert_path), - "You need to download the certificate first") - logger.debug("Using OpenVPN cert %s" % (cert_path,)) - - return cert_path - - -if __name__ == "__main__": - logger = logging.getLogger(name='leap') - logger.setLevel(logging.DEBUG) - console = logging.StreamHandler() - console.setLevel(logging.DEBUG) - formatter = logging.Formatter( - '%(asctime)s ' - '- %(name)s - %(levelname)s - %(message)s') - console.setFormatter(formatter) - logger.addHandler(console) - - eipconfig = EIPConfig('1') - - try: - eipconfig.get_clusters() - except Exception as e: - assert isinstance(e, AssertionError), "Expected an assert" - print "Safe value getting is working" - - if eipconfig.load("leap/providers/bitmask.net/eip-service.json"): - print eipconfig.get_clusters() - print eipconfig.get_gateways() - print eipconfig.get_locations() - print eipconfig.get_openvpn_configuration() - print eipconfig.get_serial() - print eipconfig.get_version() diff --git a/src/leap/services/eip/eipspec.py b/src/leap/services/eip/eipspec.py deleted file mode 100644 index 9cc56be3..00000000 --- a/src/leap/services/eip/eipspec.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -# eipspec.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - - -# Schemas dict -# To add a schema for a version you should follow the form: -# { '1': schema_v1, '2': schema_v2, ... etc } -# so for instance, to add the '2' version, you should do: -# eipservice_config_spec['2'] = schema_v2 -eipservice_config_spec = {} - -eipservice_config_spec['1'] = { - 'description': 'sample eip service config', - 'type': 'object', - 'properties': { - 'serial': { - 'type': int, - 'default': 1, - 'required': ["True"] - }, - 'version': { - 'type': int, - 'default': 1, - 'required': ["True"] - }, - 'clusters': { - 'type': list, - 'default': [ - {"label": { - "en": "Location Unknown"}, - "name": "location_unknown"}] - }, - 'gateways': { - 'type': list, - 'default': [ - {"capabilities": { - "adblock": True, - "filter_dns": True, - "ports": ["80", "53", "443", "1194"], - "protocols": ["udp", "tcp"], - "transport": ["openvpn"], - "user_ips": False}, - "cluster": "location_unknown", - "host": "location.example.org", - "ip_address": "127.0.0.1"}] - }, - 'locations': { - 'type': dict, - 'default': {} - }, - 'openvpn_configuration': { - 'type': dict, - 'default': { - "auth": None, - "cipher": None, - "tls-cipher": None} - } - } -} - - -def get_schema(version): - """ - Returns the schema corresponding to the version given. - - :param version: the version of the schema to get. - :type version: str - :rtype: dict or None if the version is not supported. - """ - schema = eipservice_config_spec.get(version, None) - return schema diff --git a/src/leap/services/eip/providerbootstrapper.py b/src/leap/services/eip/providerbootstrapper.py deleted file mode 100644 index bf5938dc..00000000 --- a/src/leap/services/eip/providerbootstrapper.py +++ /dev/null @@ -1,340 +0,0 @@ -# -*- coding: utf-8 -*- -# providerbootstrapper.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -""" -Provider bootstrapping -""" -import logging -import socket -import os - -import requests - -from PySide import QtCore - -from leap.common.certs import get_digest -from leap.common.files import check_and_fix_urw_only, get_mtime, mkdir_p -from leap.common.check import leap_assert, leap_assert_type, leap_check -from leap.config.providerconfig import ProviderConfig, MissingCACert -from leap.util.request_helpers import get_content -from leap.util.constants import REQUEST_TIMEOUT -from leap.services.abstractbootstrapper import AbstractBootstrapper -from leap.provider.supportedapis import SupportedAPIs - - -logger = logging.getLogger(__name__) - - -class UnsupportedProviderAPI(Exception): - """ - Raised when attempting to use a provider with an incompatible API. - """ - pass - - -class WrongFingerprint(Exception): - """ - Raised when a fingerprint comparison does not match. - """ - pass - - -class ProviderBootstrapper(AbstractBootstrapper): - """ - Given a provider URL performs a series of checks and emits signals - after they are passed. - If a check fails, the subsequent checks are not executed - """ - - # All dicts returned are of the form - # {"passed": bool, "error": str} - name_resolution = QtCore.Signal(dict) - https_connection = QtCore.Signal(dict) - download_provider_info = QtCore.Signal(dict) - - download_ca_cert = QtCore.Signal(dict) - check_ca_fingerprint = QtCore.Signal(dict) - check_api_certificate = QtCore.Signal(dict) - - def __init__(self, bypass_checks=False): - """ - Constructor for provider bootstrapper object - - :param bypass_checks: Set to true if the app should bypass - first round of checks for CA certificates at bootstrap - :type bypass_checks: bool - """ - AbstractBootstrapper.__init__(self, bypass_checks) - - self._domain = None - self._provider_config = None - self._download_if_needed = False - - def _check_name_resolution(self): - """ - Checks that the name resolution for the provider name works - """ - leap_assert(self._domain, "Cannot check DNS without a domain") - - logger.debug("Checking name resolution for %s" % (self._domain)) - - # We don't skip this check, since it's basic for the whole - # system to work - socket.gethostbyname(self._domain) - - def _check_https(self, *args): - """ - Checks that https is working and that the provided certificate - checks out - """ - - leap_assert(self._domain, "Cannot check HTTPS without a domain") - - logger.debug("Checking https for %s" % (self._domain)) - - # We don't skip this check, since it's basic for the whole - # system to work - - try: - res = self._session.get("https://%s" % (self._domain,), - verify=not self._bypass_checks, - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - except requests.exceptions.SSLError: - self._err_msg = self.tr("Provider certificate could " - "not be verified") - raise - except Exception: - self._err_msg = self.tr("Provider does not support HTTPS") - raise - - def _download_provider_info(self, *args): - """ - Downloads the provider.json defition - """ - leap_assert(self._domain, - "Cannot download provider info without a domain") - - logger.debug("Downloading provider info for %s" % (self._domain)) - - headers = {} - - provider_json = os.path.join( - ProviderConfig().get_path_prefix(), "leap", "providers", - self._domain, "provider.json") - mtime = get_mtime(provider_json) - - if self._download_if_needed and mtime: - headers['if-modified-since'] = mtime - - uri = "https://%s/%s" % (self._domain, "provider.json") - verify = not self._bypass_checks - - if mtime: # the provider.json exists - provider_config = ProviderConfig() - provider_config.load(provider_json) - try: - verify = provider_config.get_ca_cert_path() - uri = provider_config.get_api_uri() + '/provider.json' - except MissingCACert: - # get_ca_cert_path fails if the certificate does not exists. - pass - - logger.debug("Requesting for provider.json... " - "uri: {0}, verify: {1}, headers: {2}".format( - uri, verify, headers)) - res = self._session.get(uri, verify=verify, - headers=headers, timeout=REQUEST_TIMEOUT) - res.raise_for_status() - logger.debug("Request status code: {0}".format(res.status_code)) - - # Not modified - if res.status_code == 304: - logger.debug("Provider definition has not been modified") - else: - provider_definition, mtime = get_content(res) - - provider_config = ProviderConfig() - provider_config.load(data=provider_definition, mtime=mtime) - provider_config.save(["leap", - "providers", - self._domain, - "provider.json"]) - - api_version = provider_config.get_api_version() - if SupportedAPIs.supports(api_version): - logger.debug("Provider definition has been modified") - else: - api_supported = ', '.join(SupportedAPIs.SUPPORTED_APIS) - error = ('Unsupported provider API version. ' - 'Supported versions are: {}. ' - 'Found: {}.').format(api_supported, api_version) - - logger.error(error) - raise UnsupportedProviderAPI(error) - - def run_provider_select_checks(self, domain, download_if_needed=False): - """ - Populates the check queue. - - :param domain: domain to check - :type domain: str - - :param download_if_needed: if True, makes the checks do not - overwrite already downloaded data - :type download_if_needed: bool - """ - leap_assert(domain and len(domain) > 0, "We need a domain!") - - self._domain = ProviderConfig.sanitize_path_component(domain) - self._download_if_needed = download_if_needed - - cb_chain = [ - (self._check_name_resolution, self.name_resolution), - (self._check_https, self.https_connection), - (self._download_provider_info, self.download_provider_info) - ] - - return self.addCallbackChain(cb_chain) - - def _should_proceed_cert(self): - """ - Returns False if the certificate already exists for the given - provider. True otherwise - - :rtype: bool - """ - leap_assert(self._provider_config, "We need a provider config!") - - if not self._download_if_needed: - return True - - return not os.path.exists(self._provider_config - .get_ca_cert_path(about_to_download=True)) - - def _download_ca_cert(self, *args): - """ - Downloads the CA cert that is going to be used for the api URL - """ - - leap_assert(self._provider_config, "Cannot download the ca cert " - "without a provider config!") - - logger.debug("Downloading ca cert for %s at %s" % - (self._domain, self._provider_config.get_ca_cert_uri())) - - if not self._should_proceed_cert(): - check_and_fix_urw_only( - self._provider_config - .get_ca_cert_path(about_to_download=True)) - return - - res = self._session.get(self._provider_config.get_ca_cert_uri(), - verify=not self._bypass_checks, - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - - cert_path = self._provider_config.get_ca_cert_path( - about_to_download=True) - cert_dir = os.path.dirname(cert_path) - mkdir_p(cert_dir) - with open(cert_path, "w") as f: - f.write(res.content) - - check_and_fix_urw_only(cert_path) - - def _check_ca_fingerprint(self, *args): - """ - Checks the CA cert fingerprint against the one provided in the - json definition - """ - leap_assert(self._provider_config, "Cannot check the ca cert " - "without a provider config!") - - logger.debug("Checking ca fingerprint for %s and cert %s" % - (self._domain, - self._provider_config.get_ca_cert_path())) - - if not self._should_proceed_cert(): - return - - parts = self._provider_config.get_ca_cert_fingerprint().split(":") - - error_msg = "Wrong fingerprint format" - leap_check(len(parts) == 2, error_msg, WrongFingerprint) - - method = parts[0].strip() - fingerprint = parts[1].strip() - cert_data = None - with open(self._provider_config.get_ca_cert_path()) as f: - cert_data = f.read() - - leap_assert(len(cert_data) > 0, "Could not read certificate data") - digest = get_digest(cert_data, method) - - error_msg = "Downloaded certificate has a different fingerprint!" - leap_check(digest == fingerprint, error_msg, WrongFingerprint) - - def _check_api_certificate(self, *args): - """ - Tries to make an API call with the downloaded cert and checks - if it validates against it - """ - leap_assert(self._provider_config, "Cannot check the ca cert " - "without a provider config!") - - logger.debug("Checking api certificate for %s and cert %s" % - (self._provider_config.get_api_uri(), - self._provider_config.get_ca_cert_path())) - - if not self._should_proceed_cert(): - return - - test_uri = "%s/%s/cert" % (self._provider_config.get_api_uri(), - self._provider_config.get_api_version()) - res = self._session.get(test_uri, - verify=self._provider_config - .get_ca_cert_path(), - timeout=REQUEST_TIMEOUT) - res.raise_for_status() - - def run_provider_setup_checks(self, - provider_config, - download_if_needed=False): - """ - Starts the checks needed for a new provider setup. - - :param provider_config: Provider configuration - :type provider_config: ProviderConfig - - :param download_if_needed: if True, makes the checks do not - overwrite already downloaded data. - :type download_if_needed: bool - """ - leap_assert(provider_config, "We need a provider config!") - leap_assert_type(provider_config, ProviderConfig) - - self._provider_config = provider_config - self._download_if_needed = download_if_needed - - cb_chain = [ - (self._download_ca_cert, self.download_ca_cert), - (self._check_ca_fingerprint, self.check_ca_fingerprint), - (self._check_api_certificate, self.check_api_certificate) - ] - - return self.addCallbackChain(cb_chain) diff --git a/src/leap/services/eip/tests/__init__.py b/src/leap/services/eip/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/src/leap/services/eip/tests/__init__.py +++ /dev/null diff --git a/src/leap/services/eip/tests/test_eipbootstrapper.py b/src/leap/services/eip/tests/test_eipbootstrapper.py deleted file mode 100644 index f2331eca..00000000 --- a/src/leap/services/eip/tests/test_eipbootstrapper.py +++ /dev/null @@ -1,347 +0,0 @@ -# -*- coding: utf-8 -*- -# test_eipbootstrapper.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - - -""" -Tests for the EIP Boostrapper checks - -These will be whitebox tests since we want to make sure the private -implementation is checking what we expect. -""" - -import os -import mock -import tempfile -import time -try: - import unittest2 as unittest -except ImportError: - import unittest - -from nose.twistedtools import deferred, reactor -from twisted.internet import threads -from requests.models import Response - -from leap.common.testing.basetest import BaseLeapTest -from leap.services.eip.eipbootstrapper import EIPBootstrapper -from leap.services.eip.eipconfig import EIPConfig -from leap.config.providerconfig import ProviderConfig -from leap.crypto.tests import fake_provider -from leap.common.files import mkdir_p -from leap.crypto.srpauth import SRPAuth - - -class EIPBootstrapperActiveTest(BaseLeapTest): - @classmethod - def setUpClass(cls): - BaseLeapTest.setUpClass() - factory = fake_provider.get_provider_factory() - http = reactor.listenTCP(0, factory) - https = reactor.listenSSL( - 0, factory, - fake_provider.OpenSSLServerContextFactory()) - get_port = lambda p: p.getHost().port - cls.http_port = get_port(http) - cls.https_port = get_port(https) - - def setUp(self): - self.eb = EIPBootstrapper() - self.old_pp = EIPConfig.get_path_prefix - self.old_save = EIPConfig.save - self.old_load = EIPConfig.load - self.old_si = SRPAuth.get_session_id - - def tearDown(self): - EIPConfig.get_path_prefix = self.old_pp - EIPConfig.save = self.old_save - EIPConfig.load = self.old_load - SRPAuth.get_session_id = self.old_si - - def _download_config_test_template(self, ifneeded, new): - """ - All download config tests have the same structure, so this is - a parametrized test for that. - - :param ifneeded: sets _download_if_needed - :type ifneeded: bool - :param new: if True uses time.time() as mtime for the mocked - eip-service file, otherwise it uses 100 (a really - old mtime) - :type new: float or int (will be coersed) - """ - pc = ProviderConfig() - pc.get_domain = mock.MagicMock( - return_value="localhost:%s" % (self.https_port)) - self.eb._provider_config = pc - - pc.get_api_uri = mock.MagicMock( - return_value="https://%s" % (pc.get_domain())) - pc.get_api_version = mock.MagicMock(return_value="1") - - # This is to ignore https checking, since it's not the point - # of this test - pc.get_ca_cert_path = mock.MagicMock(return_value=False) - - path_prefix = tempfile.mkdtemp() - EIPConfig.get_path_prefix = mock.MagicMock(return_value=path_prefix) - EIPConfig.save = mock.MagicMock() - EIPConfig.load = mock.MagicMock() - - self.eb._download_if_needed = ifneeded - - provider_dir = os.path.join(EIPConfig.get_path_prefix(), - "leap", - "providers", - pc.get_domain()) - mkdir_p(provider_dir) - eip_config_path = os.path.join(provider_dir, - "eip-service.json") - - with open(eip_config_path, "w") as ec: - ec.write("A") - - # set mtime to something really new - if new: - os.utime(eip_config_path, (-1, time.time())) - else: - os.utime(eip_config_path, (-1, 100)) - - @deferred() - def test_download_config_not_modified(self): - self._download_config_test_template(True, True) - - d = threads.deferToThread(self.eb._download_config) - - def check(*args): - self.assertFalse(self.eb._eip_config.save.called) - d.addCallback(check) - return d - - @deferred() - def test_download_config_modified(self): - self._download_config_test_template(True, False) - - d = threads.deferToThread(self.eb._download_config) - - def check(*args): - self.assertTrue(self.eb._eip_config.save.called) - d.addCallback(check) - return d - - @deferred() - def test_download_config_ignores_mtime(self): - self._download_config_test_template(False, True) - - d = threads.deferToThread(self.eb._download_config) - - def check(*args): - self.eb._eip_config.save.assert_called_once_with( - ["leap", - "providers", - self.eb._provider_config.get_domain(), - "eip-service.json"]) - d.addCallback(check) - return d - - def _download_certificate_test_template(self, ifneeded, createcert): - """ - All download client certificate tests have the same structure, - so this is a parametrized test for that. - - :param ifneeded: sets _download_if_needed - :type ifneeded: bool - :param createcert: if True it creates a dummy file to play the - part of a downloaded certificate - :type createcert: bool - - :returns: the temp eip cert path and the dummy cert contents - :rtype: tuple of str, str - """ - pc = ProviderConfig() - ec = EIPConfig() - self.eb._provider_config = pc - self.eb._eip_config = ec - - pc.get_domain = mock.MagicMock( - return_value="localhost:%s" % (self.https_port)) - pc.get_api_uri = mock.MagicMock( - return_value="https://%s" % (pc.get_domain())) - pc.get_api_version = mock.MagicMock(return_value="1") - pc.get_ca_cert_path = mock.MagicMock(return_value=False) - - path_prefix = tempfile.mkdtemp() - EIPConfig.get_path_prefix = mock.MagicMock(return_value=path_prefix) - EIPConfig.save = mock.MagicMock() - EIPConfig.load = mock.MagicMock() - - self.eb._download_if_needed = ifneeded - - provider_dir = os.path.join(EIPConfig.get_path_prefix(), - "leap", - "providers", - "somedomain") - mkdir_p(provider_dir) - eip_cert_path = os.path.join(provider_dir, - "cert") - - ec.get_client_cert_path = mock.MagicMock( - return_value=eip_cert_path) - - cert_content = "A" - if createcert: - with open(eip_cert_path, "w") as ec: - ec.write(cert_content) - - return eip_cert_path, cert_content - - def test_download_client_certificate_not_modified(self): - cert_path, old_cert_content = self._download_certificate_test_template( - True, True) - - with mock.patch('leap.common.certs.should_redownload', - new_callable=mock.MagicMock, - return_value=False): - self.eb._download_client_certificates() - with open(cert_path, "r") as c: - self.assertEqual(c.read(), old_cert_content) - - @deferred() - def test_download_client_certificate_old_cert(self): - cert_path, old_cert_content = self._download_certificate_test_template( - True, True) - - def wrapper(*args): - with mock.patch('leap.common.certs.should_redownload', - new_callable=mock.MagicMock, - return_value=True): - with mock.patch('leap.common.certs.is_valid_pemfile', - new_callable=mock.MagicMock, - return_value=True): - self.eb._download_client_certificates() - - def check(*args): - with open(cert_path, "r") as c: - self.assertNotEqual(c.read(), old_cert_content) - d = threads.deferToThread(wrapper) - d.addCallback(check) - - return d - - @deferred() - def test_download_client_certificate_no_cert(self): - cert_path, _ = self._download_certificate_test_template( - True, False) - - def wrapper(*args): - with mock.patch('leap.common.certs.should_redownload', - new_callable=mock.MagicMock, - return_value=False): - with mock.patch('leap.common.certs.is_valid_pemfile', - new_callable=mock.MagicMock, - return_value=True): - self.eb._download_client_certificates() - - def check(*args): - self.assertTrue(os.path.exists(cert_path)) - d = threads.deferToThread(wrapper) - d.addCallback(check) - - return d - - @deferred() - def test_download_client_certificate_force_not_valid(self): - cert_path, old_cert_content = self._download_certificate_test_template( - True, True) - - def wrapper(*args): - with mock.patch('leap.common.certs.should_redownload', - new_callable=mock.MagicMock, - return_value=True): - with mock.patch('leap.common.certs.is_valid_pemfile', - new_callable=mock.MagicMock, - return_value=True): - self.eb._download_client_certificates() - - def check(*args): - with open(cert_path, "r") as c: - self.assertNotEqual(c.read(), old_cert_content) - d = threads.deferToThread(wrapper) - d.addCallback(check) - - return d - - @deferred() - def test_download_client_certificate_invalid_download(self): - cert_path, _ = self._download_certificate_test_template( - False, False) - - def wrapper(*args): - with mock.patch('leap.common.certs.should_redownload', - new_callable=mock.MagicMock, - return_value=True): - with mock.patch('leap.common.certs.is_valid_pemfile', - new_callable=mock.MagicMock, - return_value=False): - with self.assertRaises(Exception): - self.eb._download_client_certificates() - d = threads.deferToThread(wrapper) - - return d - - @deferred() - def test_download_client_certificate_uses_session_id(self): - _, _ = self._download_certificate_test_template( - False, False) - - SRPAuth.get_session_id = mock.MagicMock(return_value="1") - - def check_cookie(*args, **kwargs): - cookies = kwargs.get("cookies", None) - self.assertEqual(cookies, {'_session_id': '1'}) - return Response() - - def wrapper(*args): - with mock.patch('leap.common.certs.should_redownload', - new_callable=mock.MagicMock, - return_value=False): - with mock.patch('leap.common.certs.is_valid_pemfile', - new_callable=mock.MagicMock, - return_value=True): - with mock.patch('requests.sessions.Session.get', - new_callable=mock.MagicMock, - side_effect=check_cookie): - with mock.patch('requests.models.Response.content', - new_callable=mock.PropertyMock, - return_value="A"): - self.eb._download_client_certificates() - - d = threads.deferToThread(wrapper) - - return d - - @deferred() - def test_run_eip_setup_checks(self): - self.eb._download_config = mock.MagicMock() - self.eb._download_client_certificates = mock.MagicMock() - - d = self.eb.run_eip_setup_checks(ProviderConfig()) - - def check(*args): - self.eb._download_config.assert_called_once_with() - self.eb._download_client_certificates.assert_called_once_with(None) - d.addCallback(check) - return d diff --git a/src/leap/services/eip/tests/test_eipconfig.py b/src/leap/services/eip/tests/test_eipconfig.py deleted file mode 100644 index 87ce04c2..00000000 --- a/src/leap/services/eip/tests/test_eipconfig.py +++ /dev/null @@ -1,324 +0,0 @@ -# -*- coding: utf-8 -*- -# test_eipconfig.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -Tests for eipconfig -""" -import copy -import json -import os -import unittest - -from leap.common.testing.basetest import BaseLeapTest -from leap.services.eip.eipconfig import EIPConfig -from leap.config.providerconfig import ProviderConfig - -from mock import Mock - - -sample_config = { - "gateways": [ - { - "capabilities": { - "adblock": False, - "filter_dns": True, - "limited": True, - "ports": [ - "1194", - "443", - "53", - "80"], - "protocols": [ - "tcp", - "udp"], - "transport": ["openvpn"], - "user_ips": False}, - "host": "host.dev.example.org", - "ip_address": "11.22.33.44", - "location": "cyberspace" - }, { - "capabilities": { - "adblock": False, - "filter_dns": True, - "limited": True, - "ports": [ - "1194", - "443", - "53", - "80"], - "protocols": [ - "tcp", - "udp"], - "transport": ["openvpn"], - "user_ips": False}, - "host": "host2.dev.example.org", - "ip_address": "22.33.44.55", - "location": "cyberspace" - } - ], - "locations": { - "ankara": { - "country_code": "XX", - "hemisphere": "S", - "name": "Antarctica", - "timezone": "+2" - }, - "cyberspace": { - "country_code": "XX", - "hemisphere": "X", - "name": "outer space", - "timezone": "" - } - }, - "openvpn_configuration": { - "auth": "SHA1", - "cipher": "AES-128-CBC", - "tls-cipher": "DHE-RSA-AES128-SHA" - }, - "serial": 1, - "version": 1 -} - - -class EIPConfigTest(BaseLeapTest): - - __name__ = "eip_config_tests" - - maxDiff = None - - def setUp(self): - self._old_ospath_exists = os.path.exists - - def tearDown(self): - os.path.exists = self._old_ospath_exists - - def _write_config(self, data): - """ - Helper to write some data to a temp config file. - - :param data: data to be used to save in the config file. - :data type: dict (valid json) - """ - self.configfile = os.path.join(self.tempdir, "eipconfig.json") - conf = open(self.configfile, "w") - conf.write(json.dumps(data)) - conf.close() - - def _get_eipconfig(self, fromfile=True, data=sample_config, api_ver='1'): - """ - Helper that returns an EIPConfig object using the data parameter - or a sample data. - - :param fromfile: sets if we should use a file or a string - :type fromfile: bool - :param data: sets the data to be used to load in the EIPConfig object - :type data: dict (valid json) - :param api_ver: the api_version schema to use. - :type api_ver: str - :rtype: EIPConfig - """ - config = EIPConfig() - config.set_api_version(api_ver) - - loaded = False - if fromfile: - self._write_config(data) - loaded = config.load(self.configfile, relative=False) - else: - json_string = json.dumps(data) - loaded = config.load(data=json_string) - - if not loaded: - return None - - return config - - def test_loads_from_file(self): - config = self._get_eipconfig() - self.assertIsNotNone(config) - - def test_loads_from_data(self): - config = self._get_eipconfig(fromfile=False) - self.assertIsNotNone(config) - - def test_load_valid_config_from_file(self): - config = self._get_eipconfig() - self.assertIsNotNone(config) - - self.assertEqual( - config.get_openvpn_configuration(), - sample_config["openvpn_configuration"]) - - sample_ip = sample_config["gateways"][0]["ip_address"] - self.assertEqual( - config.get_gateway_ip(), - sample_ip) - self.assertEqual(config.get_version(), sample_config["version"]) - self.assertEqual(config.get_serial(), sample_config["serial"]) - self.assertEqual(config.get_gateways(), sample_config["gateways"]) - self.assertEqual(config.get_locations(), sample_config["locations"]) - self.assertEqual(config.get_clusters(), None) - - def test_load_valid_config_from_data(self): - config = self._get_eipconfig(fromfile=False) - self.assertIsNotNone(config) - - self.assertEqual( - config.get_openvpn_configuration(), - sample_config["openvpn_configuration"]) - - sample_ip = sample_config["gateways"][0]["ip_address"] - self.assertEqual( - config.get_gateway_ip(), - sample_ip) - - self.assertEqual(config.get_version(), sample_config["version"]) - self.assertEqual(config.get_serial(), sample_config["serial"]) - self.assertEqual(config.get_gateways(), sample_config["gateways"]) - self.assertEqual(config.get_locations(), sample_config["locations"]) - self.assertEqual(config.get_clusters(), None) - - def test_sanitize_extra_parameters(self): - data = copy.deepcopy(sample_config) - data['openvpn_configuration']["extra_param"] = "FOO" - config = self._get_eipconfig(data=data) - - self.assertEqual( - config.get_openvpn_configuration(), - sample_config["openvpn_configuration"]) - - def test_sanitize_non_allowed_chars(self): - data = copy.deepcopy(sample_config) - data['openvpn_configuration']["auth"] = "SHA1;" - config = self._get_eipconfig(data=data) - - self.assertEqual( - config.get_openvpn_configuration(), - sample_config["openvpn_configuration"]) - - data = copy.deepcopy(sample_config) - data['openvpn_configuration']["auth"] = "SHA1>`&|" - config = self._get_eipconfig(data=data) - - self.assertEqual( - config.get_openvpn_configuration(), - sample_config["openvpn_configuration"]) - - def test_sanitize_lowercase(self): - data = copy.deepcopy(sample_config) - data['openvpn_configuration']["auth"] = "shaSHA1" - config = self._get_eipconfig(data=data) - - self.assertEqual( - config.get_openvpn_configuration(), - sample_config["openvpn_configuration"]) - - def test_all_characters_invalid(self): - data = copy.deepcopy(sample_config) - data['openvpn_configuration']["auth"] = "sha&*!@#;" - config = self._get_eipconfig(data=data) - - self.assertEqual( - config.get_openvpn_configuration(), - {'cipher': 'AES-128-CBC', - 'tls-cipher': 'DHE-RSA-AES128-SHA'}) - - def test_sanitize_bad_ip(self): - data = copy.deepcopy(sample_config) - data['gateways'][0]["ip_address"] = "11.22.33.44;" - config = self._get_eipconfig(data=data) - - self.assertEqual(config.get_gateway_ip(), None) - - data = copy.deepcopy(sample_config) - data['gateways'][0]["ip_address"] = "11.22.33.44`" - config = self._get_eipconfig(data=data) - - self.assertEqual(config.get_gateway_ip(), None) - - def test_default_gateway_on_unknown_index(self): - config = self._get_eipconfig() - sample_ip = sample_config["gateways"][0]["ip_address"] - self.assertEqual(config.get_gateway_ip(999), sample_ip) - - def test_get_gateway_by_index(self): - config = self._get_eipconfig() - sample_ip_0 = sample_config["gateways"][0]["ip_address"] - sample_ip_1 = sample_config["gateways"][1]["ip_address"] - self.assertEqual(config.get_gateway_ip(0), sample_ip_0) - self.assertEqual(config.get_gateway_ip(1), sample_ip_1) - - def test_get_client_cert_path_as_expected(self): - config = self._get_eipconfig() - config.get_path_prefix = Mock(return_value='test') - - provider_config = ProviderConfig() - - # mock 'get_domain' so we don't need to load a config - provider_domain = 'test.provider.com' - provider_config.get_domain = Mock(return_value=provider_domain) - - expected_path = os.path.join('test', 'leap', 'providers', - provider_domain, 'keys', 'client', - 'openvpn.pem') - - # mock 'os.path.exists' so we don't get an error for unexisting file - os.path.exists = Mock(return_value=True) - cert_path = config.get_client_cert_path(provider_config) - - self.assertEqual(cert_path, expected_path) - - def test_get_client_cert_path_about_to_download(self): - config = self._get_eipconfig() - config.get_path_prefix = Mock(return_value='test') - - provider_config = ProviderConfig() - - # mock 'get_domain' so we don't need to load a config - provider_domain = 'test.provider.com' - provider_config.get_domain = Mock(return_value=provider_domain) - - expected_path = os.path.join('test', 'leap', 'providers', - provider_domain, 'keys', 'client', - 'openvpn.pem') - - cert_path = config.get_client_cert_path( - provider_config, about_to_download=True) - - self.assertEqual(cert_path, expected_path) - - def test_get_client_cert_path_fails(self): - config = self._get_eipconfig() - provider_config = ProviderConfig() - - # mock 'get_domain' so we don't need to load a config - provider_domain = 'test.provider.com' - provider_config.get_domain = Mock(return_value=provider_domain) - - with self.assertRaises(AssertionError): - config.get_client_cert_path(provider_config) - - def test_fails_without_api_set(self): - config = EIPConfig() - with self.assertRaises(AssertionError): - config.load('non-relevant-path') - - def test_fails_with_api_without_schema(self): - with self.assertRaises(AssertionError): - self._get_eipconfig(api_ver='123') - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/services/eip/tests/test_providerbootstrapper.py b/src/leap/services/eip/tests/test_providerbootstrapper.py deleted file mode 100644 index b24334a2..00000000 --- a/src/leap/services/eip/tests/test_providerbootstrapper.py +++ /dev/null @@ -1,531 +0,0 @@ -# -*- coding: utf-8 -*- -# test_providerbootstrapper.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - - -""" -Tests for the Provider Boostrapper checks - -These will be whitebox tests since we want to make sure the private -implementation is checking what we expect. -""" - -import os -import mock -import socket -import stat -import tempfile -import time -import requests -try: - import unittest2 as unittest -except ImportError: - import unittest - -from nose.twistedtools import deferred, reactor -from twisted.internet import threads -from requests.models import Response - -from leap.common.testing.https_server import where -from leap.common.testing.basetest import BaseLeapTest -from leap.services.eip.providerbootstrapper import ProviderBootstrapper -from leap.services.eip.providerbootstrapper import UnsupportedProviderAPI -from leap.services.eip.providerbootstrapper import WrongFingerprint -from leap.provider.supportedapis import SupportedAPIs -from leap.config.providerconfig import ProviderConfig -from leap.crypto.tests import fake_provider -from leap.common.files import mkdir_p - - -class ProviderBootstrapperTest(BaseLeapTest): - def setUp(self): - self.pb = ProviderBootstrapper() - - def tearDown(self): - pass - - def test_name_resolution_check(self): - # Something highly likely to success - self.pb._domain = "google.com" - self.pb._check_name_resolution() - # Something highly likely to fail - self.pb._domain = "uquhqweuihowquie.abc.def" - with self.assertRaises(socket.gaierror): - self.pb._check_name_resolution() - - @deferred() - def test_run_provider_select_checks(self): - self.pb._check_name_resolution = mock.MagicMock() - self.pb._check_https = mock.MagicMock() - self.pb._download_provider_info = mock.MagicMock() - - d = self.pb.run_provider_select_checks("somedomain") - - def check(*args): - self.pb._check_name_resolution.assert_called_once_with() - self.pb._check_https.assert_called_once_with(None) - self.pb._download_provider_info.assert_called_once_with(None) - d.addCallback(check) - return d - - @deferred() - def test_run_provider_setup_checks(self): - self.pb._download_ca_cert = mock.MagicMock() - self.pb._check_ca_fingerprint = mock.MagicMock() - self.pb._check_api_certificate = mock.MagicMock() - - d = self.pb.run_provider_setup_checks(ProviderConfig()) - - def check(*args): - self.pb._download_ca_cert.assert_called_once_with() - self.pb._check_ca_fingerprint.assert_called_once_with(None) - self.pb._check_api_certificate.assert_called_once_with(None) - d.addCallback(check) - return d - - def test_should_proceed_cert(self): - self.pb._provider_config = mock.Mock() - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=where("cacert.pem")) - - self.pb._download_if_needed = False - self.assertTrue(self.pb._should_proceed_cert()) - - self.pb._download_if_needed = True - self.assertFalse(self.pb._should_proceed_cert()) - - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=where("somefilethatdoesntexist.pem")) - self.assertTrue(self.pb._should_proceed_cert()) - - def _check_download_ca_cert(self, should_proceed): - """ - Helper to check different paths easily for the download ca - cert check - - :param should_proceed: sets the _should_proceed_cert in the - provider bootstrapper being tested - :type should_proceed: bool - - :returns: The contents of the certificate, the expected - content depending on should_proceed, and the mode of - the file to be checked by the caller - :rtype: tuple of str, str, int - """ - old_content = "NOT THE NEW CERT" - new_content = "NEW CERT" - new_cert_path = os.path.join(tempfile.mkdtemp(), - "mynewcert.pem") - - with open(new_cert_path, "w") as c: - c.write(old_content) - - self.pb._provider_config = mock.Mock() - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=new_cert_path) - self.pb._domain = "somedomain" - - self.pb._should_proceed_cert = mock.MagicMock( - return_value=should_proceed) - - read = None - content_to_check = None - mode = None - - with mock.patch('requests.models.Response.content', - new_callable=mock.PropertyMock) as \ - content: - content.return_value = new_content - response_obj = Response() - response_obj.raise_for_status = mock.MagicMock() - - self.pb._session.get = mock.MagicMock(return_value=response_obj) - self.pb._download_ca_cert() - with open(new_cert_path, "r") as nc: - read = nc.read() - if should_proceed: - content_to_check = new_content - else: - content_to_check = old_content - mode = stat.S_IMODE(os.stat(new_cert_path).st_mode) - - os.unlink(new_cert_path) - return read, content_to_check, mode - - def test_download_ca_cert_no_saving(self): - read, expected_read, mode = self._check_download_ca_cert(False) - self.assertEqual(read, expected_read) - self.assertEqual(mode, int("600", 8)) - - def test_download_ca_cert_saving(self): - read, expected_read, mode = self._check_download_ca_cert(True) - self.assertEqual(read, expected_read) - self.assertEqual(mode, int("600", 8)) - - def test_check_ca_fingerprint_skips(self): - self.pb._provider_config = mock.Mock() - self.pb._provider_config.get_ca_cert_fingerprint = mock.MagicMock( - return_value="") - self.pb._domain = "somedomain" - - self.pb._should_proceed_cert = mock.MagicMock(return_value=False) - - self.pb._check_ca_fingerprint() - self.assertFalse(self.pb._provider_config. - get_ca_cert_fingerprint.called) - - def test_check_ca_cert_fingerprint_raises_bad_format(self): - self.pb._provider_config = mock.Mock() - self.pb._provider_config.get_ca_cert_fingerprint = mock.MagicMock( - return_value="wrongfprformat!!") - self.pb._domain = "somedomain" - - self.pb._should_proceed_cert = mock.MagicMock(return_value=True) - - with self.assertRaises(WrongFingerprint): - self.pb._check_ca_fingerprint() - - # This two hashes different in the last byte, but that's good enough - # for the tests - KNOWN_BAD_HASH = "SHA256: 0f17c033115f6b76ff67871872303ff65034efe" \ - "7dd1b910062ca323eb4da5c7f" - KNOWN_GOOD_HASH = "SHA256: 0f17c033115f6b76ff67871872303ff65034ef" \ - "e7dd1b910062ca323eb4da5c7e" - KNOWN_GOOD_CERT = """ ------BEGIN CERTIFICATE----- -MIIFbzCCA1egAwIBAgIBATANBgkqhkiG9w0BAQ0FADBKMRgwFgYDVQQDDA9CaXRt -YXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNVBAsME2h0dHBzOi8v -Yml0bWFzay5uZXQwHhcNMTIxMTA2MDAwMDAwWhcNMjIxMTA2MDAwMDAwWjBKMRgw -FgYDVQQDDA9CaXRtYXNrIFJvb3QgQ0ExEDAOBgNVBAoMB0JpdG1hc2sxHDAaBgNV -BAsME2h0dHBzOi8vYml0bWFzay5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw -ggIKAoICAQC1eV4YvayaU+maJbWrD4OHo3d7S1BtDlcvkIRS1Fw3iYDjsyDkZxai -dHp4EUasfNQ+EVtXUvtk6170EmLco6Elg8SJBQ27trE6nielPRPCfX3fQzETRfvB -7tNvGw4Jn2YKiYoMD79kkjgyZjkJ2r/bEHUSevmR09BRp86syHZerdNGpXYhcQ84 -CA1+V+603GFIHnrP+uQDdssW93rgDNYu+exT+Wj6STfnUkugyjmPRPjL7wh0tzy+ -znCeLl4xiV3g9sjPnc7r2EQKd5uaTe3j71sDPF92KRk0SSUndREz+B1+Dbe/RGk4 -MEqGFuOzrtsgEhPIX0hplhb0Tgz/rtug+yTT7oJjBa3u20AAOQ38/M99EfdeJvc4 -lPFF1XBBLh6X9UKF72an2NuANiX6XPySnJgZ7nZ09RiYZqVwu/qt3DfvLfhboq+0 -bQvLUPXrVDr70onv5UDjpmEA/cLmaIqqrduuTkFZOym65/PfAPvpGnt7crQj/Ibl -DEDYZQmP7AS+6zBjoOzNjUGE5r40zWAR1RSi7zliXTu+yfsjXUIhUAWmYR6J3KxB -lfsiHBQ+8dn9kC3YrUexWoOqBiqJOAJzZh5Y1tqgzfh+2nmHSB2dsQRs7rDRRlyy -YMbkpzL9ZsOUO2eTP1mmar6YjCN+rggYjRrX71K2SpBG6b1zZxOG+wIDAQABo2Aw -XjAdBgNVHQ4EFgQUuYGDLL2sswnYpHHvProt1JU+D48wDgYDVR0PAQH/BAQDAgIE -MAwGA1UdEwQFMAMBAf8wHwYDVR0jBBgwFoAUuYGDLL2sswnYpHHvProt1JU+D48w -DQYJKoZIhvcNAQENBQADggIBADeG67vaFcbITGpi51264kHPYPEWaXUa5XYbtmBl -cXYyB6hY5hv/YNuVGJ1gWsDmdeXEyj0j2icGQjYdHRfwhrbEri+h1EZOm1cSBDuY -k/P5+ctHyOXx8IE79DBsZ6IL61UKIaKhqZBfLGYcWu17DVV6+LT+AKtHhOrv3TSj -RnAcKnCbKqXLhUPXpK0eTjPYS2zQGQGIhIy9sQXVXJJJsGrPgMxna1Xw2JikBOCG -htD/JKwt6xBmNwktH0GI/LVtVgSp82Clbn9C4eZN9E5YbVYjLkIEDhpByeC71QhX -EIQ0ZR56bFuJA/CwValBqV/G9gscTPQqd+iETp8yrFpAVHOW+YzSFbxjTEkBte1J -aF0vmbqdMAWLk+LEFPQRptZh0B88igtx6tV5oVd+p5IVRM49poLhuPNJGPvMj99l -mlZ4+AeRUnbOOeAEuvpLJbel4rhwFzmUiGoeTVoPZyMevWcVFq6BMkS+jRR2w0jK -G6b0v5XDHlcFYPOgUrtsOBFJVwbutLvxdk6q37kIFnWCd8L3kmES5q4wjyFK47Co -Ja8zlx64jmMZPg/t3wWqkZgXZ14qnbyG5/lGsj5CwVtfDljrhN0oCWK1FZaUmW3d -69db12/g4f6phldhxiWuGC/W6fCW5kre7nmhshcltqAJJuU47iX+DarBFiIj816e -yV8e ------END CERTIFICATE----- -""" - - def _prepare_provider_config_with(self, cert_path, cert_hash): - """ - Mocks the provider config to give the cert_path and cert_hash - specified - - :param cert_path: path for the certificate - :type cert_path: str - :param cert_hash: hash for the certificate as it would appear - in the provider config json - :type cert_hash: str - """ - self.pb._provider_config = mock.Mock() - self.pb._provider_config.get_ca_cert_fingerprint = mock.MagicMock( - return_value=cert_hash) - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=cert_path) - self.pb._domain = "somedomain" - - def test_check_ca_fingerprint_checksout(self): - cert_path = os.path.join(tempfile.mkdtemp(), - "mynewcert.pem") - - with open(cert_path, "w") as c: - c.write(self.KNOWN_GOOD_CERT) - - self._prepare_provider_config_with(cert_path, self.KNOWN_GOOD_HASH) - - self.pb._should_proceed_cert = mock.MagicMock(return_value=True) - - self.pb._check_ca_fingerprint() - - os.unlink(cert_path) - - def test_check_ca_fingerprint_fails(self): - cert_path = os.path.join(tempfile.mkdtemp(), - "mynewcert.pem") - - with open(cert_path, "w") as c: - c.write(self.KNOWN_GOOD_CERT) - - self._prepare_provider_config_with(cert_path, self.KNOWN_BAD_HASH) - - self.pb._should_proceed_cert = mock.MagicMock(return_value=True) - - with self.assertRaises(WrongFingerprint): - self.pb._check_ca_fingerprint() - - os.unlink(cert_path) - - -############################################################################### -# Tests with a fake provider # -############################################################################### - -class ProviderBootstrapperActiveTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - factory = fake_provider.get_provider_factory() - http = reactor.listenTCP(8002, factory) - https = reactor.listenSSL( - 0, factory, - fake_provider.OpenSSLServerContextFactory()) - get_port = lambda p: p.getHost().port - cls.http_port = get_port(http) - cls.https_port = get_port(https) - - def setUp(self): - self.pb = ProviderBootstrapper() - - # At certain points we are going to be replacing these methods - # directly in ProviderConfig to be able to catch calls from - # new ProviderConfig objects inside the methods tested. We - # need to save the old implementation and restore it in - # tearDown so we are sure everything is as expected for each - # test. If we do it inside each specific test, a failure in - # the test will leave the implementation with the mock. - self.old_gpp = ProviderConfig.get_path_prefix - self.old_load = ProviderConfig.load - self.old_save = ProviderConfig.save - self.old_api_version = ProviderConfig.get_api_version - - def tearDown(self): - ProviderConfig.get_path_prefix = self.old_gpp - ProviderConfig.load = self.old_load - ProviderConfig.save = self.old_save - ProviderConfig.get_api_version = self.old_api_version - - def test_check_https_succeeds(self): - # XXX: Need a proper CA signed cert to test this - pass - - @deferred() - def test_check_https_fails(self): - self.pb._domain = "localhost:%s" % (self.https_port,) - - def check(*args): - with self.assertRaises(requests.exceptions.SSLError): - self.pb._check_https() - return threads.deferToThread(check) - - @deferred() - def test_second_check_https_fails(self): - self.pb._domain = "localhost:1234" - - def check(*args): - with self.assertRaises(Exception): - self.pb._check_https() - return threads.deferToThread(check) - - @deferred() - def test_check_https_succeeds_if_danger(self): - self.pb._domain = "localhost:%s" % (self.https_port,) - self.pb._bypass_checks = True - - def check(*args): - self.pb._check_https() - - return threads.deferToThread(check) - - def _setup_provider_config_with(self, api, path_prefix): - """ - Sets up the ProviderConfig with mocks for the path prefix, the - api returned and load/save methods. - It modifies ProviderConfig directly instead of an object - because the object used is created in the method itself and we - cannot control that. - - :param api: API to return - :type api: str - :param path_prefix: path prefix to be used when calculating - paths - :type path_prefix: str - """ - ProviderConfig.get_path_prefix = mock.MagicMock( - return_value=path_prefix) - ProviderConfig.get_api_version = mock.MagicMock( - return_value=api) - ProviderConfig.load = mock.MagicMock() - ProviderConfig.save = mock.MagicMock() - - def _setup_providerbootstrapper(self, ifneeded): - """ - Sets the provider bootstrapper's domain to - localhost:https_port, sets it to bypass https checks and sets - the download if needed based on the ifneeded value. - - :param ifneeded: Value for _download_if_needed - :type ifneeded: bool - """ - self.pb._domain = "localhost:%s" % (self.https_port,) - self.pb._bypass_checks = True - self.pb._download_if_needed = ifneeded - - def _produce_dummy_provider_json(self): - """ - Creates a dummy provider json on disk in order to test - behaviour around it (download if newer online, etc) - - :returns: the provider.json path used - :rtype: str - """ - provider_dir = os.path.join(ProviderConfig() - .get_path_prefix(), - "leap", - "providers", - self.pb._domain) - mkdir_p(provider_dir) - provider_path = os.path.join(provider_dir, - "provider.json") - - with open(provider_path, "w") as p: - p.write("A") - return provider_path - - def test_download_provider_info_new_provider(self): - self._setup_provider_config_with("1", tempfile.mkdtemp()) - self._setup_providerbootstrapper(True) - - self.pb._download_provider_info() - self.assertTrue(ProviderConfig.save.called) - - @mock.patch('leap.config.providerconfig.ProviderConfig.get_ca_cert_path', - lambda x: where('cacert.pem')) - def test_download_provider_info_not_modified(self): - self._setup_provider_config_with("1", tempfile.mkdtemp()) - self._setup_providerbootstrapper(True) - provider_path = self._produce_dummy_provider_json() - - # set mtime to something really new - os.utime(provider_path, (-1, time.time())) - - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - self.pb._download_provider_info() - # we check that it doesn't save the provider - # config, because it's new enough - self.assertFalse(ProviderConfig.save.called) - - @mock.patch('leap.config.providerconfig.ProviderConfig.get_ca_cert_path', - lambda x: where('cacert.pem')) - def test_download_provider_info_modified(self): - self._setup_provider_config_with("1", tempfile.mkdtemp()) - self._setup_providerbootstrapper(True) - provider_path = self._produce_dummy_provider_json() - - # set mtime to something really old - os.utime(provider_path, (-1, 100)) - - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - self.pb._download_provider_info() - self.assertTrue(ProviderConfig.load.called) - self.assertTrue(ProviderConfig.save.called) - - @mock.patch('leap.config.providerconfig.ProviderConfig.get_ca_cert_path', - lambda x: where('cacert.pem')) - def test_download_provider_info_unsupported_api_raises(self): - self._setup_provider_config_with("9999999", tempfile.mkdtemp()) - self._setup_providerbootstrapper(False) - self._produce_dummy_provider_json() - - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - with self.assertRaises(UnsupportedProviderAPI): - self.pb._download_provider_info() - - @mock.patch('leap.config.providerconfig.ProviderConfig.get_ca_cert_path', - lambda x: where('cacert.pem')) - def test_download_provider_info_unsupported_api(self): - self._setup_provider_config_with(SupportedAPIs.SUPPORTED_APIS[0], - tempfile.mkdtemp()) - self._setup_providerbootstrapper(False) - self._produce_dummy_provider_json() - - with mock.patch.object( - ProviderConfig, 'get_api_uri', - return_value="https://localhost:%s" % (self.https_port,)): - self.pb._download_provider_info() - - @mock.patch('leap.config.providerconfig.ProviderConfig.get_api_uri', - lambda x: 'api.uri') - @mock.patch('leap.config.providerconfig.ProviderConfig.get_ca_cert_path', - lambda x: '/cert/path') - def test_check_api_certificate_skips(self): - self.pb._provider_config = ProviderConfig() - self.pb._session.get = mock.MagicMock(return_value=Response()) - - self.pb._should_proceed_cert = mock.MagicMock(return_value=False) - self.pb._check_api_certificate() - self.assertFalse(self.pb._session.get.called) - - @deferred() - def test_check_api_certificate_fails(self): - self.pb._provider_config = ProviderConfig() - self.pb._provider_config.get_api_uri = mock.MagicMock( - return_value="https://localhost:%s" % (self.https_port,)) - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=os.path.join( - os.path.split(__file__)[0], - "wrongcert.pem")) - self.pb._provider_config.get_api_version = mock.MagicMock( - return_value="1") - - self.pb._should_proceed_cert = mock.MagicMock(return_value=True) - - def check(*args): - with self.assertRaises(requests.exceptions.SSLError): - self.pb._check_api_certificate() - d = threads.deferToThread(check) - return d - - @deferred() - def test_check_api_certificate_succeeds(self): - self.pb._provider_config = ProviderConfig() - self.pb._provider_config.get_api_uri = mock.MagicMock( - return_value="https://localhost:%s" % (self.https_port,)) - self.pb._provider_config.get_ca_cert_path = mock.MagicMock( - return_value=where('cacert.pem')) - self.pb._provider_config.get_api_version = mock.MagicMock( - return_value="1") - - self.pb._should_proceed_cert = mock.MagicMock(return_value=True) - - def check(*args): - self.pb._check_api_certificate() - d = threads.deferToThread(check) - return d diff --git a/src/leap/services/eip/tests/test_vpngatewayselector.py b/src/leap/services/eip/tests/test_vpngatewayselector.py deleted file mode 100644 index c90681d7..00000000 --- a/src/leap/services/eip/tests/test_vpngatewayselector.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: utf-8 -*- -# test_vpngatewayselector.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -tests for vpngatewayselector -""" - -import unittest - -from leap.services.eip.eipconfig import EIPConfig, VPNGatewaySelector -from leap.common.testing.basetest import BaseLeapTest -from mock import Mock - - -sample_gateways = [ - {u'host': u'gateway1.com', - u'ip_address': u'1.2.3.4', - u'location': u'location1'}, - {u'host': u'gateway2.com', - u'ip_address': u'2.3.4.5', - u'location': u'location2'}, - {u'host': u'gateway3.com', - u'ip_address': u'3.4.5.6', - u'location': u'location3'}, - {u'host': u'gateway4.com', - u'ip_address': u'4.5.6.7', - u'location': u'location4'} -] - -sample_gateways_no_location = [ - {u'host': u'gateway1.com', - u'ip_address': u'1.2.3.4'}, - {u'host': u'gateway2.com', - u'ip_address': u'2.3.4.5'}, - {u'host': u'gateway3.com', - u'ip_address': u'3.4.5.6'} -] - -sample_locations = { - u'location1': {u'timezone': u'2'}, - u'location2': {u'timezone': u'-7'}, - u'location3': {u'timezone': u'-4'}, - u'location4': {u'timezone': u'+13'} -} - -# 0 is not used, only for indexing from 1 in tests -ips = (0, u'1.2.3.4', u'2.3.4.5', u'3.4.5.6', u'4.5.6.7') - - -class VPNGatewaySelectorTest(BaseLeapTest): - """ - VPNGatewaySelector's tests. - """ - def setUp(self): - self.eipconfig = EIPConfig() - self.eipconfig.get_gateways = Mock(return_value=sample_gateways) - self.eipconfig.get_locations = Mock(return_value=sample_locations) - - def tearDown(self): - pass - - def test_get_no_gateways(self): - gateway_selector = VPNGatewaySelector(self.eipconfig) - self.eipconfig.get_gateways = Mock(return_value=[]) - gateways = gateway_selector.get_gateways() - self.assertEqual(gateways, []) - - def test_get_gateway_with_no_locations(self): - gateway_selector = VPNGatewaySelector(self.eipconfig) - self.eipconfig.get_gateways = Mock( - return_value=sample_gateways_no_location) - self.eipconfig.get_locations = Mock(return_value=[]) - gateways = gateway_selector.get_gateways() - gateways_default_order = [ - sample_gateways[0]['ip_address'], - sample_gateways[1]['ip_address'], - sample_gateways[2]['ip_address'] - ] - self.assertEqual(gateways, gateways_default_order) - - def test_correct_order_gmt(self): - gateway_selector = VPNGatewaySelector(self.eipconfig, 0) - gateways = gateway_selector.get_gateways() - self.assertEqual(gateways, [ips[1], ips[3], ips[2], ips[4]]) - - def test_correct_order_gmt_minus_3(self): - gateway_selector = VPNGatewaySelector(self.eipconfig, -3) - gateways = gateway_selector.get_gateways() - self.assertEqual(gateways, [ips[3], ips[2], ips[1], ips[4]]) - - def test_correct_order_gmt_minus_7(self): - gateway_selector = VPNGatewaySelector(self.eipconfig, -7) - gateways = gateway_selector.get_gateways() - self.assertEqual(gateways, [ips[2], ips[3], ips[4], ips[1]]) - - def test_correct_order_gmt_plus_5(self): - gateway_selector = VPNGatewaySelector(self.eipconfig, 5) - gateways = gateway_selector.get_gateways() - self.assertEqual(gateways, [ips[1], ips[4], ips[3], ips[2]]) - - def test_correct_order_gmt_plus_12(self): - gateway_selector = VPNGatewaySelector(self.eipconfig, 12) - gateways = gateway_selector.get_gateways() - self.assertEqual(gateways, [ips[4], ips[2], ips[3], ips[1]]) - - def test_correct_order_gmt_minus_11(self): - gateway_selector = VPNGatewaySelector(self.eipconfig, -11) - gateways = gateway_selector.get_gateways() - self.assertEqual(gateways, [ips[4], ips[2], ips[3], ips[1]]) - - def test_correct_order_gmt_plus_14(self): - gateway_selector = VPNGatewaySelector(self.eipconfig, 14) - gateways = gateway_selector.get_gateways() - self.assertEqual(gateways, [ips[4], ips[2], ips[3], ips[1]]) - - -if __name__ == "__main__": - unittest.main() diff --git a/src/leap/services/eip/tests/wrongcert.pem b/src/leap/services/eip/tests/wrongcert.pem deleted file mode 100644 index e6cff38a..00000000 --- a/src/leap/services/eip/tests/wrongcert.pem +++ /dev/null @@ -1,33 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFtTCCA52gAwIBAgIJAIWZus5EIXNtMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQwHhcNMTMwNjI1MTc0NjExWhcNMTgwNjI1MTc0NjExWjBF -MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 -ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC -CgKCAgEA2ObM7ESjyuxFZYD/Y68qOPQgjgggW+cdXfBpU2p4n7clsrUeMhWdW40Y -77Phzor9VOeqs3ZpHuyLzsYVp/kFDm8tKyo2ah5fJwzL0VCSLYaZkUQQ7GNUmTCk -furaxl8cQx/fg395V7/EngsS9B3/y5iHbctbA4MnH3jaotO5EGeo6hw7/eyCotQ9 -KbBV9GJMcY94FsXBCmUB+XypKklWTLhSaS6Cu4Fo8YLW6WmcnsyEOGS2F7WVf5at -7CBWFQZHaSgIBLmc818/mDYCnYmCVMFn/6Ndx7V2NTlz+HctWrQn0dmIOnCUeCwS -wXq9PnBR1rSx/WxwyF/WpyjOFkcIo7vm72kS70pfrYsXcZD4BQqkXYj3FyKnPt3O -ibLKtCxL8/83wOtErPcYpG6LgFkgAAlHQ9MkUi5dbmjCJtpqQmlZeK1RALdDPiB3 -K1KZimrGsmcE624dJxUIOJJpuwJDy21F8kh5ZAsAtE1prWETrQYNElNFjQxM83rS -ZR1Ql2MPSB4usEZT57+KvpEzlOnAT3elgCg21XrjSFGi14hCEao4g2OEZH5GAwm5 -frf6UlSRZ/g3tLTfI8Hv1prw15W2qO+7q7SBAplTODCRk+Yb0YoA2mMM/QXBUcXs -vKEDLSSxzNIBi3T62l39RB/ml+gPKo87ZMDivex1ZhrcJc3Yu3sCAwEAAaOBpzCB -pDAdBgNVHQ4EFgQUPjE+4pun+8FreIdpoR8v6N7xKtUwdQYDVR0jBG4wbIAUPjE+ -4pun+8FreIdpoR8v6N7xKtWhSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT -b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQCF -mbrORCFzbTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQCpvCPdtvXJ -muTj379TZuCJs7/l0FhA7AHa1WAlHjsXHaA7N0+3ZWAbdtXDsowal6S+ldgU/kfV -Lq7NrRq+amJWC7SYj6cvVwhrSwSvu01fe/TWuOzHrRv1uTfJ/VXLonVufMDd9opo -bhqYxMaxLdIx6t/MYmZH4Wpiq0yfZuv//M8i7BBl/qvaWbLhg0yVAKRwjFvf59h6 -6tRFCLddELOIhLDQtk8zMbioPEbfAlKdwwP8kYGtDGj6/9/YTd/oTKRdgHuwyup3 -m0L20Y6LddC+tb0WpK5EyrNbCbEqj1L4/U7r6f/FKNA3bx6nfdXbscaMfYonKAKg -1cRrRg45sErmCz0QyTnWzXyvbjR4oQRzyW3kJ1JZudZ+AwOi00J5FYa3NiLuxl1u -gIGKWSrASQWhEdpa1nlCgX7PhdaQgYjEMpQvA0GCA0OF5JDu8en1yZqsOt1hCLIN -lkz/5jKPqrclY5hV99bE3hgCHRmIPNHCZG3wbZv2yJKxJX1YLMmQwAmSh2N7YwGG -yXRvCxQs5ChPHyRairuf/5MZCZnSVb45ppTVuNUijsbflKRUgfj/XvfqQ22f+C9N -Om2dmNvAiS2TOIfuP47CF2OUa5q4plUwmr+nyXQGM0SIoHNCj+MBdFfb3oxxAtI+ -SLhbnzQv5e84Doqz3YF0XW8jyR7q8GFLNA== ------END CERTIFICATE----- diff --git a/src/leap/services/eip/udstelnet.py b/src/leap/services/eip/udstelnet.py deleted file mode 100644 index e6c82350..00000000 --- a/src/leap/services/eip/udstelnet.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# udstelnet.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -import os -import socket -import telnetlib - - -class ConnectionRefusedError(Exception): - pass - - -class MissingSocketError(Exception): - pass - - -class UDSTelnet(telnetlib.Telnet): - """ - A telnet-alike class, that can listen on unix domain sockets - """ - - def open(self, host, port=23, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): - """ - Connect to a host. If port is 'unix', it will open a - connection over unix docmain sockets. - - The optional second argument is the port number, which - defaults to the standard telnet port (23). - Don't try to reopen an already connected instance. - """ - self.eof = 0 - self.host = host - self.port = port - self.timeout = timeout - - if self.port == "unix": - # unix sockets spoken - if not os.path.exists(self.host): - raise MissingSocketError() - self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - try: - self.sock.connect(self.host) - except socket.error: - raise ConnectionRefusedError() - else: - self.sock = socket.create_connection((host, port), timeout) diff --git a/src/leap/services/eip/vpnlaunchers.py b/src/leap/services/eip/vpnlaunchers.py deleted file mode 100644 index 17950a25..00000000 --- a/src/leap/services/eip/vpnlaunchers.py +++ /dev/null @@ -1,927 +0,0 @@ -# -*- coding: utf-8 -*- -# vpnlaunchers.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -""" -Platform dependant VPN launchers -""" -import commands -import logging -import getpass -import os -import platform -import subprocess -import stat -try: - import grp -except ImportError: - pass # ignore, probably windows - -from abc import ABCMeta, abstractmethod -from functools import partial - -from leap.common.check import leap_assert, leap_assert_type -from leap.common.files import which -from leap.config.providerconfig import ProviderConfig -from leap.services.eip.eipconfig import EIPConfig, VPNGatewaySelector -from leap.util import first -from leap.util.privilege_policies import LinuxPolicyChecker -from leap.util import privilege_policies - -logger = logging.getLogger(__name__) - - -class VPNLauncherException(Exception): - pass - - -class OpenVPNNotFoundException(VPNLauncherException): - pass - - -class EIPNoPolkitAuthAgentAvailable(VPNLauncherException): - pass - - -class EIPNoPkexecAvailable(VPNLauncherException): - pass - - -class EIPNoTunKextLoaded(VPNLauncherException): - pass - - -class VPNLauncher(object): - """ - Abstract launcher class - """ - __metaclass__ = ABCMeta - - UPDOWN_FILES = None - OTHER_FILES = None - - @abstractmethod - def get_vpn_command(self, eipconfig=None, providerconfig=None, - socket_host=None, socket_port=None): - """ - Returns the platform dependant vpn launching command - - :param eipconfig: eip configuration object - :type eipconfig: EIPConfig - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - :param socket_host: either socket path (unix) or socket IP - :type socket_host: str - :param socket_port: either string "unix" if it's a unix - socket, or port otherwise - :type socket_port: str - - :return: A VPN command ready to be launched - :rtype: list - """ - return [] - - @abstractmethod - def get_vpn_env(self, providerconfig): - """ - Returns a dictionary with the custom env for the platform. - This is mainly used for setting LD_LIBRARY_PATH to the correct - path when distributing a standalone client - - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - - :rtype: dict - """ - return {} - - @classmethod - def missing_updown_scripts(kls): - """ - Returns what updown scripts are missing. - :rtype: list - """ - leap_assert(kls.UPDOWN_FILES is not None, - "Need to define UPDOWN_FILES for this particular " - "auncher before calling this method") - file_exist = partial(_has_updown_scripts, warn=False) - zipped = zip(kls.UPDOWN_FILES, map(file_exist, kls.UPDOWN_FILES)) - missing = filter(lambda (path, exists): exists is False, zipped) - return [path for path, exists in missing] - - @classmethod - def missing_other_files(kls): - """ - Returns what other important files are missing during startup. - Same as missing_updown_scripts but does not check for exec bit. - :rtype: list - """ - leap_assert(kls.UPDOWN_FILES is not None, - "Need to define OTHER_FILES for this particular " - "auncher before calling this method") - file_exist = partial(_has_other_files, warn=False) - zipped = zip(kls.OTHER_FILES, map(file_exist, kls.OTHER_FILES)) - missing = filter(lambda (path, exists): exists is False, zipped) - return [path for path, exists in missing] - - -def get_platform_launcher(): - launcher = globals()[platform.system() + "VPNLauncher"] - leap_assert(launcher, "Unimplemented platform launcher: %s" % - (platform.system(),)) - return launcher() - - -def _is_pkexec_in_system(): - """ - Checks the existence of the pkexec binary in system. - """ - pkexec_path = which('pkexec') - if len(pkexec_path) == 0: - return False - return True - - -def _has_updown_scripts(path, warn=True): - """ - Checks the existence of the up/down scripts and its - exec bit if applicable. - - :param path: the path to be checked - :type path: str - - :param warn: whether we should log the absence - :type warn: bool - - :rtype: bool - """ - is_file = os.path.isfile(path) - if warn and not is_file: - logger.error("Could not find up/down script %s. " - "Might produce DNS leaks." % (path,)) - - # XXX check if applies in win - is_exe = False - try: - is_exe = (stat.S_IXUSR & os.stat(path)[stat.ST_MODE] != 0) - except OSError as e: - logger.warn("%s" % (e,)) - if warn and not is_exe: - logger.error("Up/down script %s is not executable. " - "Might produce DNS leaks." % (path,)) - return is_file and is_exe - - -def _has_other_files(path, warn=True): - """ - Checks the existence of other important files. - - :param path: the path to be checked - :type path: str - - :param warn: whether we should log the absence - :type warn: bool - - :rtype: bool - """ - is_file = os.path.isfile(path) - if warn and not is_file: - logger.warning("Could not find file during checks: %s. " % ( - path,)) - return is_file - - -def _is_auth_agent_running(): - """ - Checks if a polkit daemon is running. - - :return: True if it's running, False if it's not. - :rtype: boolean - """ - ps = 'ps aux | grep polkit-%s-authentication-agent-1' - opts = (ps % case for case in ['[g]nome', '[k]de']) - is_running = map(lambda l: commands.getoutput(l), opts) - return any(is_running) - - -def _try_to_launch_agent(): - """ - Tries to launch a polkit daemon. - """ - opts = [ - "/usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1", - # XXX add kde thing here - ] - for cmd in opts: - try: - subprocess.Popen([cmd], shell=True) - except: - pass - - -class LinuxVPNLauncher(VPNLauncher): - """ - VPN launcher for the Linux platform - """ - - PKEXEC_BIN = 'pkexec' - OPENVPN_BIN = 'openvpn' - OPENVPN_BIN_PATH = os.path.join( - ProviderConfig().get_path_prefix(), - "..", "apps", "eip", OPENVPN_BIN) - - SYSTEM_CONFIG = "/etc/leap" - UP_DOWN_FILE = "resolv-update" - UP_DOWN_PATH = "%s/%s" % (SYSTEM_CONFIG, UP_DOWN_FILE) - - # We assume this is there by our openvpn dependency, and - # we will put it there on the bundle too. - # TODO adapt to the bundle path. - OPENVPN_DOWN_ROOT_BASE = "/usr/lib/openvpn/" - OPENVPN_DOWN_ROOT_FILE = "openvpn-plugin-down-root.so" - OPENVPN_DOWN_ROOT_PATH = "%s/%s" % ( - OPENVPN_DOWN_ROOT_BASE, - OPENVPN_DOWN_ROOT_FILE) - - UPDOWN_FILES = (UP_DOWN_PATH,) - POLKIT_PATH = LinuxPolicyChecker.get_polkit_path() - OTHER_FILES = (POLKIT_PATH, ) - - def missing_other_files(self): - """ - 'Extend' the VPNLauncher's missing_other_files to check if the polkit - files is outdated. If the polkit file that is in OTHER_FILES exists but - is not up to date, it is added to the missing list. - - :returns: a list of missing files - :rtype: list of str - """ - missing = VPNLauncher.missing_other_files.im_func(self) - polkit_file = LinuxPolicyChecker.get_polkit_path() - if polkit_file not in missing: - if privilege_policies.is_policy_outdated(self.OPENVPN_BIN_PATH): - missing.append(polkit_file) - - return missing - - @classmethod - def cmd_for_missing_scripts(kls, frompath, pol_file): - """ - Returns a sh script that can copy the missing files. - - :param frompath: The path where the up/down scripts live - :type frompath: str - :param pol_file: The path where the dynamically generated - policy file lives - :type pol_file: str - - :rtype: str - """ - to = kls.SYSTEM_CONFIG - - cmd = '#!/bin/sh\n' - cmd += 'mkdir -p "%s"\n' % (to, ) - cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UP_DOWN_FILE, to) - cmd += 'cp "%s" "%s"\n' % (pol_file, kls.POLKIT_PATH) - cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, ) - - return cmd - - @classmethod - def maybe_pkexec(kls): - """ - Checks whether pkexec is available in the system, and - returns the path if found. - - Might raise EIPNoPkexecAvailable or EIPNoPolkitAuthAgentAvailable - - :returns: a list of the paths where pkexec is to be found - :rtype: list - """ - if _is_pkexec_in_system(): - if not _is_auth_agent_running(): - _try_to_launch_agent() - if _is_auth_agent_running(): - pkexec_possibilities = which(kls.PKEXEC_BIN) - leap_assert(len(pkexec_possibilities) > 0, - "We couldn't find pkexec") - return pkexec_possibilities - else: - logger.warning("No polkit auth agent found. pkexec " + - "will use its own auth agent.") - raise EIPNoPolkitAuthAgentAvailable() - else: - logger.warning("System has no pkexec") - raise EIPNoPkexecAvailable() - - @classmethod - def maybe_down_plugin(kls): - """ - Returns the path of the openvpn down-root-plugin, searching first - in the relative path for the standalone bundle, and then in the system - path where the debian package puts it. - - :returns: the path where the plugin was found, or None - :rtype: str or None - """ - cwd = os.getcwd() - rel_path_in_bundle = os.path.join( - 'apps', 'eip', 'files', kls.OPENVPN_DOWN_ROOT_FILE) - abs_path_in_bundle = os.path.join(cwd, rel_path_in_bundle) - if os.path.isfile(abs_path_in_bundle): - return abs_path_in_bundle - abs_path_in_system = kls.OPENVPN_DOWN_ROOT_FILE - if os.path.isfile(abs_path_in_system): - return abs_path_in_system - - logger.warning("We could not find the down-root-plugin, so no updown " - "scripts will be run. DNS leaks are likely!") - return None - - def get_vpn_command(self, eipconfig=None, providerconfig=None, - socket_host=None, socket_port="unix", openvpn_verb=1): - """ - Returns the platform dependant vpn launching command. It will - look for openvpn in the regular paths and algo in - path_prefix/apps/eip/ (in case standalone is set) - - Might raise: - VPNLauncherException, - OpenVPNNotFoundException. - - :param eipconfig: eip configuration object - :type eipconfig: EIPConfig - - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - - :param socket_host: either socket path (unix) or socket IP - :type socket_host: str - - :param socket_port: either string "unix" if it's a unix - socket, or port otherwise - :type socket_port: str - - :param openvpn_verb: openvpn verbosity wanted - :type openvpn_verb: int - - :return: A VPN command ready to be launched - :rtype: list - """ - leap_assert(eipconfig, "We need an eip config") - leap_assert_type(eipconfig, EIPConfig) - leap_assert(providerconfig, "We need a provider config") - leap_assert_type(providerconfig, ProviderConfig) - leap_assert(socket_host, "We need a socket host!") - leap_assert(socket_port, "We need a socket port!") - - kwargs = {} - if ProviderConfig.standalone: - kwargs['path_extension'] = os.path.join( - providerconfig.get_path_prefix(), - "..", "apps", "eip") - - openvpn_possibilities = which(self.OPENVPN_BIN, **kwargs) - - if len(openvpn_possibilities) == 0: - raise OpenVPNNotFoundException() - - openvpn = first(openvpn_possibilities) - args = [] - - pkexec = self.maybe_pkexec() - if pkexec: - args.append(openvpn) - openvpn = first(pkexec) - - if openvpn_verb is not None: - args += ['--verb', '%d' % (openvpn_verb,)] - - gateway_selector = VPNGatewaySelector(eipconfig) - gateways = gateway_selector.get_gateways() - - if not gateways: - logger.error('No gateway was found!') - raise VPNLauncherException(self.tr('No gateway was found!')) - - logger.debug("Using gateways ips: {}".format(', '.join(gateways))) - - for gw in gateways: - args += ['--remote', gw, '1194', 'udp'] - - args += [ - '--client', - '--dev', 'tun', - ############################################################## - # persist-tun makes ping-restart fail because it leaves a - # broken routing table - ############################################################## - # '--persist-tun', - '--persist-key', - '--tls-client', - '--remote-cert-tls', - 'server' - ] - - openvpn_configuration = eipconfig.get_openvpn_configuration() - - for key, value in openvpn_configuration.items(): - args += ['--%s' % (key,), value] - - ############################################################## - # The down-root plugin fails in some situations, so we don't - # drop privs for the time being - ############################################################## - # args += [ - # '--user', getpass.getuser(), - # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name - # ] - - if socket_port == "unix": # that's always the case for linux - args += [ - '--management-client-user', getpass.getuser() - ] - - args += [ - '--management-signal', - '--management', socket_host, socket_port, - '--script-security', '2' - ] - - plugin_path = self.maybe_down_plugin() - # If we do not have the down plugin neither in the bundle - # nor in the system, we do not do updown scripts. The alternative - # is leaving the user without the ability to restore dns and routes - # to its original state. - - if plugin_path and _has_updown_scripts(self.UP_DOWN_PATH): - args += [ - '--up', self.UP_DOWN_PATH, - '--down', self.UP_DOWN_PATH, - ############################################################## - # For the time being we are disabling the usage of the - # down-root plugin, because it doesn't quite work as - # expected (i.e. it doesn't run route -del as root - # when finishing, so it fails to properly - # restart/quit) - ############################################################## - # '--plugin', plugin_path, - # '\'script_type=down %s\'' % self.UP_DOWN_PATH - ] - - args += [ - '--cert', eipconfig.get_client_cert_path(providerconfig), - '--key', eipconfig.get_client_cert_path(providerconfig), - '--ca', providerconfig.get_ca_cert_path() - ] - - logger.debug("Running VPN with command:") - logger.debug("%s %s" % (openvpn, " ".join(args))) - - return [openvpn] + args - - def get_vpn_env(self, providerconfig): - """ - Returns a dictionary with the custom env for the platform. - This is mainly used for setting LD_LIBRARY_PATH to the correct - path when distributing a standalone client - - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - - :rtype: dict - """ - leap_assert(providerconfig, "We need a provider config") - leap_assert_type(providerconfig, ProviderConfig) - - return {"LD_LIBRARY_PATH": os.path.join( - providerconfig.get_path_prefix(), - "..", "lib")} - - -class DarwinVPNLauncher(VPNLauncher): - """ - VPN launcher for the Darwin Platform - """ - - COCOASUDO = "cocoasudo" - # XXX need the good old magic translate for these strings - # (look for magic in 0.2.0 release) - SUDO_MSG = ("Bitmask needs administrative privileges to run " - "Encrypted Internet.") - INSTALL_MSG = ("\"Bitmask needs administrative privileges to install " - "missing scripts and fix permissions.\"") - - INSTALL_PATH = os.path.realpath(os.getcwd() + "/../../") - INSTALL_PATH_ESCAPED = os.path.realpath(os.getcwd() + "/../../") - OPENVPN_BIN = 'openvpn.leap' - OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,) - OPENVPN_PATH_ESCAPED = "%s/Contents/Resources/openvpn" % ( - INSTALL_PATH_ESCAPED,) - - UP_SCRIPT = "%s/client.up.sh" % (OPENVPN_PATH,) - DOWN_SCRIPT = "%s/client.down.sh" % (OPENVPN_PATH,) - OPENVPN_DOWN_PLUGIN = '%s/openvpn-down-root.so' % (OPENVPN_PATH,) - - UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT, OPENVPN_DOWN_PLUGIN) - OTHER_FILES = [] - - @classmethod - def cmd_for_missing_scripts(kls, frompath): - """ - Returns a command that can copy the missing scripts. - :rtype: str - """ - to = kls.OPENVPN_PATH_ESCAPED - cmd = "#!/bin/sh\nmkdir -p %s\ncp \"%s/\"* %s\nchmod 744 %s/*" % ( - to, frompath, to, to) - return cmd - - @classmethod - def maybe_kextloaded(kls): - """ - Checks if the needed kext is loaded before launching openvpn. - """ - return bool(commands.getoutput('kextstat | grep "leap.tun"')) - - def _get_resource_path(self): - """ - Returns the absolute path to the app resources directory - - :rtype: str - """ - return os.path.abspath( - os.path.join( - os.getcwd(), - "../../Contents/Resources")) - - def _get_icon_path(self): - """ - Returns the absolute path to the app icon - - :rtype: str - """ - return os.path.join(self._get_resource_path(), - "leap-client.tiff") - - def get_cocoasudo_ovpn_cmd(self): - """ - Returns a string with the cocoasudo command needed to run openvpn - as admin with a nice password prompt. The actual command needs to be - appended. - - :rtype: (str, list) - """ - iconpath = self._get_icon_path() - has_icon = os.path.isfile(iconpath) - args = ["--icon=%s" % iconpath] if has_icon else [] - args.append("--prompt=%s" % (self.SUDO_MSG,)) - - return self.COCOASUDO, args - - def get_cocoasudo_installmissing_cmd(self): - """ - Returns a string with the cocoasudo command needed to install missing - files as admin with a nice password prompt. The actual command needs to - be appended. - - :rtype: (str, list) - """ - iconpath = self._get_icon_path() - has_icon = os.path.isfile(iconpath) - args = ["--icon=%s" % iconpath] if has_icon else [] - args.append("--prompt=%s" % (self.INSTALL_MSG,)) - - return self.COCOASUDO, args - - def get_vpn_command(self, eipconfig=None, providerconfig=None, - socket_host=None, socket_port="unix", openvpn_verb=1): - """ - Returns the platform dependant vpn launching command - - Might raise VPNException. - - :param eipconfig: eip configuration object - :type eipconfig: EIPConfig - - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - - :param socket_host: either socket path (unix) or socket IP - :type socket_host: str - - :param socket_port: either string "unix" if it's a unix - socket, or port otherwise - :type socket_port: str - - :param openvpn_verb: openvpn verbosity wanted - :type openvpn_verb: int - - :return: A VPN command ready to be launched - :rtype: list - """ - leap_assert(eipconfig, "We need an eip config") - leap_assert_type(eipconfig, EIPConfig) - leap_assert(providerconfig, "We need a provider config") - leap_assert_type(providerconfig, ProviderConfig) - leap_assert(socket_host, "We need a socket host!") - leap_assert(socket_port, "We need a socket port!") - - if not self.maybe_kextloaded(): - raise EIPNoTunKextLoaded - - kwargs = {} - if ProviderConfig.standalone: - kwargs['path_extension'] = os.path.join( - providerconfig.get_path_prefix(), - "..", "apps", "eip") - - openvpn_possibilities = which( - self.OPENVPN_BIN, - **kwargs) - if len(openvpn_possibilities) == 0: - raise OpenVPNNotFoundException() - - openvpn = first(openvpn_possibilities) - args = [openvpn] - - if openvpn_verb is not None: - args += ['--verb', '%d' % (openvpn_verb,)] - - gateway_selector = VPNGatewaySelector(eipconfig) - gateways = gateway_selector.get_gateways() - - logger.debug("Using gateways ips: {gw}".format( - gw=', '.join(gateways))) - - for gw in gateways: - args += ['--remote', gw, '1194', 'udp'] - - args += [ - '--client', - '--dev', 'tun', - ############################################################## - # persist-tun makes ping-restart fail because it leaves a - # broken routing table - ############################################################## - # '--persist-tun', - '--persist-key', - '--tls-client', - '--remote-cert-tls', - 'server' - ] - - openvpn_configuration = eipconfig.get_openvpn_configuration() - for key, value in openvpn_configuration.items(): - args += ['--%s' % (key,), value] - - user = getpass.getuser() - - ############################################################## - # The down-root plugin fails in some situations, so we don't - # drop privs for the time being - ############################################################## - # args += [ - # '--user', user, - # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name - # ] - - if socket_port == "unix": - args += [ - '--management-client-user', user - ] - - args += [ - '--management-signal', - '--management', socket_host, socket_port, - '--script-security', '2' - ] - - if _has_updown_scripts(self.UP_SCRIPT): - args += [ - '--up', '\"%s\"' % (self.UP_SCRIPT,), - ] - - if _has_updown_scripts(self.DOWN_SCRIPT): - args += [ - '--down', '\"%s\"' % (self.DOWN_SCRIPT,) - ] - - # should have the down script too - if _has_updown_scripts(self.OPENVPN_DOWN_PLUGIN): - args += [ - ########################################################### - # For the time being we are disabling the usage of the - # down-root plugin, because it doesn't quite work as - # expected (i.e. it doesn't run route -del as root - # when finishing, so it fails to properly - # restart/quit) - ########################################################### - # '--plugin', self.OPENVPN_DOWN_PLUGIN, - # '\'%s\'' % self.DOWN_SCRIPT - ] - - # we set user to be passed to the up/down scripts - args += [ - '--setenv', "LEAPUSER", "%s" % (user,)] - - args += [ - '--cert', eipconfig.get_client_cert_path(providerconfig), - '--key', eipconfig.get_client_cert_path(providerconfig), - '--ca', providerconfig.get_ca_cert_path() - ] - - command, cargs = self.get_cocoasudo_ovpn_cmd() - cmd_args = cargs + args - - logger.debug("Running VPN with command:") - logger.debug("%s %s" % (command, " ".join(cmd_args))) - - return [command] + cmd_args - - def get_vpn_env(self, providerconfig): - """ - Returns a dictionary with the custom env for the platform. - This is mainly used for setting LD_LIBRARY_PATH to the correct - path when distributing a standalone client - - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - - :rtype: dict - """ - return {"DYLD_LIBRARY_PATH": os.path.join( - providerconfig.get_path_prefix(), - "..", "lib")} - - -class WindowsVPNLauncher(VPNLauncher): - """ - VPN launcher for the Windows platform - """ - - OPENVPN_BIN = 'openvpn_leap.exe' - - # XXX UPDOWN_FILES ... we do not have updown files defined yet! - # (and maybe we won't) - - def get_vpn_command(self, eipconfig=None, providerconfig=None, - socket_host=None, socket_port="9876", openvpn_verb=1): - """ - Returns the platform dependant vpn launching command. It will - look for openvpn in the regular paths and algo in - path_prefix/apps/eip/ (in case standalone is set) - - Might raise VPNException. - - :param eipconfig: eip configuration object - :type eipconfig: EIPConfig - - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - - :param socket_host: either socket path (unix) or socket IP - :type socket_host: str - - :param socket_port: either string "unix" if it's a unix - socket, or port otherwise - :type socket_port: str - - :param openvpn_verb: the openvpn verbosity wanted - :type openvpn_verb: int - - :return: A VPN command ready to be launched - :rtype: list - """ - leap_assert(eipconfig, "We need an eip config") - leap_assert_type(eipconfig, EIPConfig) - leap_assert(providerconfig, "We need a provider config") - leap_assert_type(providerconfig, ProviderConfig) - leap_assert(socket_host, "We need a socket host!") - leap_assert(socket_port, "We need a socket port!") - leap_assert(socket_port != "unix", - "We cannot use unix sockets in windows!") - - openvpn_possibilities = which( - self.OPENVPN_BIN, - path_extension=os.path.join(providerconfig.get_path_prefix(), - "..", "apps", "eip")) - - if len(openvpn_possibilities) == 0: - raise OpenVPNNotFoundException() - - openvpn = first(openvpn_possibilities) - args = [] - if openvpn_verb is not None: - args += ['--verb', '%d' % (openvpn_verb,)] - - gateway_selector = VPNGatewaySelector(eipconfig) - gateways = gateway_selector.get_gateways() - - logger.debug("Using gateways ips: {}".format(', '.join(gateways))) - - for gw in gateways: - args += ['--remote', gw, '1194', 'udp'] - - args += [ - '--client', - '--dev', 'tun', - ############################################################## - # persist-tun makes ping-restart fail because it leaves a - # broken routing table - ############################################################## - # '--persist-tun', - '--persist-key', - '--tls-client', - # We make it log to a file because we cannot attach to the - # openvpn process' stdout since it's a process with more - # privileges than we are - '--log-append', 'eip.log', - '--remote-cert-tls', - 'server' - ] - - openvpn_configuration = eipconfig.get_openvpn_configuration() - for key, value in openvpn_configuration.items(): - args += ['--%s' % (key,), value] - - ############################################################## - # The down-root plugin fails in some situations, so we don't - # drop privs for the time being - ############################################################## - # args += [ - # '--user', getpass.getuser(), - # #'--group', grp.getgrgid(os.getgroups()[-1]).gr_name - # ] - - args += [ - '--management-signal', - '--management', socket_host, socket_port, - '--script-security', '2' - ] - - args += [ - '--cert', eipconfig.get_client_cert_path(providerconfig), - '--key', eipconfig.get_client_cert_path(providerconfig), - '--ca', providerconfig.get_ca_cert_path() - ] - - logger.debug("Running VPN with command:") - logger.debug("%s %s" % (openvpn, " ".join(args))) - - return [openvpn] + args - - def get_vpn_env(self, providerconfig): - """ - Returns a dictionary with the custom env for the platform. - This is mainly used for setting LD_LIBRARY_PATH to the correct - path when distributing a standalone client - - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - - :rtype: dict - """ - return {} - - -if __name__ == "__main__": - logger = logging.getLogger(name='leap') - logger.setLevel(logging.DEBUG) - console = logging.StreamHandler() - console.setLevel(logging.DEBUG) - formatter = logging.Formatter( - '%(asctime)s ' - '- %(name)s - %(levelname)s - %(message)s') - console.setFormatter(formatter) - logger.addHandler(console) - - try: - abs_launcher = VPNLauncher() - except Exception as e: - assert isinstance(e, TypeError), "Something went wrong" - print "Abstract Prefixer class is working as expected" - - vpnlauncher = get_platform_launcher() - - eipconfig = EIPConfig() - eipconfig.set_api_version('1') - if eipconfig.load("leap/providers/bitmask.net/eip-service.json"): - provider = ProviderConfig() - if provider.load("leap/providers/bitmask.net/provider.json"): - vpnlauncher.get_vpn_command(eipconfig=eipconfig, - providerconfig=provider, - socket_host="/blah") diff --git a/src/leap/services/eip/vpnprocess.py b/src/leap/services/eip/vpnprocess.py deleted file mode 100644 index 5b07a3cf..00000000 --- a/src/leap/services/eip/vpnprocess.py +++ /dev/null @@ -1,791 +0,0 @@ -# -*- coding: utf-8 -*- -# vpnprocess.py -# Copyright (C) 2013 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -VPN Manager, spawned in a custom processProtocol. -""" -import logging -import os -import psutil -import psutil.error -import shutil -import socket - -from PySide import QtCore - -from leap.common.check import leap_assert, leap_assert_type -from leap.config.providerconfig import ProviderConfig -from leap.services.eip.vpnlaunchers import get_platform_launcher -from leap.services.eip.eipconfig import EIPConfig -from leap.services.eip.udstelnet import UDSTelnet -from leap.util import first - -logger = logging.getLogger(__name__) -vpnlog = logging.getLogger('leap.openvpn') - -from twisted.internet import protocol -from twisted.internet import defer -from twisted.internet.task import LoopingCall -from twisted.internet import error as internet_error - - -class VPNSignals(QtCore.QObject): - """ - These are the signals that we use to let the UI know - about the events we are polling. - They are instantiated in the VPN object and passed along - till the VPNProcess. - """ - state_changed = QtCore.Signal(dict) - status_changed = QtCore.Signal(dict) - process_finished = QtCore.Signal(int) - - def __init__(self): - QtCore.QObject.__init__(self) - - -class OpenVPNAlreadyRunning(Exception): - message = ("Another openvpn instance is already running, and could " - "not be stopped.") - - -class AlienOpenVPNAlreadyRunning(Exception): - message = ("Another openvpn instance is already running, and could " - "not be stopped because it was not launched by LEAP.") - - -class VPN(object): - """ - This is the high-level object that the GUI is dealing with. - It exposes the start and terminate methods. - - On start, it spawns a VPNProcess instance that will use a vpnlauncher - suited for the running platform and connect to the management interface - opened by the openvpn process, executing commands over that interface on - demand. - """ - TERMINATE_MAXTRIES = 10 - TERMINATE_WAIT = 1 # secs - - OPENVPN_VERB = "openvpn_verb" - - def __init__(self, **kwargs): - """ - Instantiate empty attributes and get a copy - of a QObject containing the QSignals that we will pass along - to the VPNManager. - """ - from twisted.internet import reactor - self._vpnproc = None - self._pollers = [] - self._reactor = reactor - self._qtsigs = VPNSignals() - - self._openvpn_verb = kwargs.get(self.OPENVPN_VERB, None) - - @property - def qtsigs(self): - return self._qtsigs - - def start(self, *args, **kwargs): - """ - Starts the openvpn subprocess. - - :param args: args to be passed to the VPNProcess - :type args: tuple - - :param kwargs: kwargs to be passed to the VPNProcess - :type kwargs: dict - """ - self._stop_pollers() - kwargs['qtsigs'] = self.qtsigs - kwargs['openvpn_verb'] = self._openvpn_verb - - # start the main vpn subprocess - vpnproc = VPNProcess(*args, **kwargs) - #qtsigs=self.qtsigs, - #openvpn_verb=self._openvpn_verb) - - if vpnproc.get_openvpn_process(): - logger.info("Another vpn process is running. Will try to stop it.") - vpnproc.stop_if_already_running() - - cmd = vpnproc.getCommand() - env = os.environ - for key, val in vpnproc.vpn_env.items(): - env[key] = val - - self._reactor.spawnProcess(vpnproc, cmd[0], cmd, env) - self._vpnproc = vpnproc - - # add pollers for status and state - # this could be extended to a collection of - # generic watchers - - poll_list = [LoopingCall(vpnproc.pollStatus), - LoopingCall(vpnproc.pollState)] - self._pollers.extend(poll_list) - self._start_pollers() - - def _kill_if_left_alive(self, tries=0): - """ - Check if the process is still alive, and sends a - SIGKILL after a timeout period. - - :param tries: counter of tries, used in recursion - :type tries: int - """ - from twisted.internet import reactor - while tries < self.TERMINATE_MAXTRIES: - if self._vpnproc.transport.pid is None: - logger.debug("Process has been happily terminated.") - return - else: - logger.debug("Process did not die, waiting...") - tries += 1 - reactor.callLater(self.TERMINATE_WAIT, - self._kill_if_left_alive, tries) - - # after running out of patience, we try a killProcess - logger.debug("Process did not died. Sending a SIGKILL.") - self.killit() - - def killit(self): - """ - Sends a kill signal to the process. - """ - self._stop_pollers() - self._vpnproc.aborted = True - self._vpnproc.killProcess() - - def terminate(self, shutdown=False): - """ - Stops the openvpn subprocess. - - Attempts to send a SIGTERM first, and after a timeout - it sends a SIGKILL. - """ - from twisted.internet import reactor - self._stop_pollers() - - # First we try to be polite and send a SIGTERM... - if self._vpnproc: - self._sentterm = True - self._vpnproc.terminate_openvpn(shutdown=shutdown) - - # ...but we also trigger a countdown to be unpolite - # if strictly needed. - - # XXX Watch out! This will fail NOW since we are running - # openvpn as root as a workaround for some connection issues. - reactor.callLater( - self.TERMINATE_WAIT, self._kill_if_left_alive) - - def _start_pollers(self): - """ - Iterate through the registered observers - and start the looping call for them. - """ - for poller in self._pollers: - poller.start(VPNManager.POLL_TIME) - - def _stop_pollers(self): - """ - Iterate through the registered observers - and stop the looping calls if they are running. - """ - for poller in self._pollers: - if poller.running: - poller.stop() - self._pollers = [] - - -class VPNManager(object): - """ - This is a mixin that we use in the VPNProcess class. - Here we get together all methods related with the openvpn management - interface. - - A copy of a QObject containing signals as attributes is passed along - upon initialization, and we use that object to emit signals to qt-land. - - For more info about management methods:: - - zcat `dpkg -L openvpn | grep management` - """ - - # Timers, in secs - POLL_TIME = 0.5 - CONNECTION_RETRY_TIME = 1 - - TS_KEY = "ts" - STATUS_STEP_KEY = "status_step" - OK_KEY = "ok" - IP_KEY = "ip" - REMOTE_KEY = "remote" - - TUNTAP_READ_KEY = "tun_tap_read" - TUNTAP_WRITE_KEY = "tun_tap_write" - TCPUDP_READ_KEY = "tcp_udp_read" - TCPUDP_WRITE_KEY = "tcp_udp_write" - AUTH_READ_KEY = "auth_read" - - def __init__(self, qtsigs=None): - """ - Initializes the VPNManager. - - :param qtsigs: a QObject containing the Qt signals used by the UI - to give feedback about state changes. - :type qtsigs: QObject - """ - from twisted.internet import reactor - self._reactor = reactor - self._tn = None - self._qtsigs = qtsigs - self._aborted = False - - @property - def qtsigs(self): - return self._qtsigs - - @property - def aborted(self): - return self._aborted - - @aborted.setter - def aborted(self, value): - self._aborted = value - - def _seek_to_eof(self): - """ - Read as much as available. Position seek pointer to end of stream - """ - try: - self._tn.read_eager() - except EOFError: - logger.debug("Could not read from socket. Assuming it died.") - return - - def _send_command(self, command, until=b"END"): - """ - Sends a command to the telnet connection and reads until END - is reached. - - :param command: command to send - :type command: str - - :param until: byte delimiter string for reading command output - :type until: byte str - - :return: response read - :rtype: list - """ - leap_assert(self._tn, "We need a tn connection!") - - try: - self._tn.write("%s\n" % (command,)) - buf = self._tn.read_until(until, 2) - self._seek_to_eof() - blist = buf.split('\r\n') - if blist[-1].startswith(until): - del blist[-1] - return blist - else: - return [] - - except socket.error: - # XXX should get a counter and repeat only - # after mod X times. - logger.warning('socket error (command was: "%s")' % (command,)) - self._close_management_socket(announce=False) - logger.debug('trying to connect to management again') - self.try_to_connect_to_management(max_retries=5) - return [] - - # XXX should move this to a errBack! - except Exception as e: - logger.warning("Error sending command %s: %r" % - (command, e)) - return [] - - def _close_management_socket(self, announce=True): - """ - Close connection to openvpn management interface. - """ - logger.debug('closing socket') - if announce: - self._tn.write("quit\n") - self._tn.read_all() - self._tn.get_socket().close() - self._tn = None - - def _connect_management(self, socket_host, socket_port): - """ - Connects to the management interface on the specified - socket_host socket_port. - - :param socket_host: either socket path (unix) or socket IP - :type socket_host: str - - :param socket_port: either string "unix" if it's a unix - socket, or port otherwise - :type socket_port: str - """ - if self.is_connected(): - self._close_management_socket() - - try: - self._tn = UDSTelnet(socket_host, socket_port) - - # XXX make password optional - # specially for win. we should generate - # the pass on the fly when invoking manager - # from conductor - - # self.tn.read_until('ENTER PASSWORD:', 2) - # self.tn.write(self.password + '\n') - # self.tn.read_until('SUCCESS:', 2) - if self._tn: - self._tn.read_eager() - - # XXX move this to the Errback - except Exception as e: - logger.warning("Could not connect to OpenVPN yet: %r" % (e,)) - self._tn = None - - def _connectCb(self, *args): - """ - Callback for connection. - - :param args: not used - """ - if self._tn: - logger.info('Connected to management') - else: - logger.debug('Cannot connect to management...') - - def _connectErr(self, failure): - """ - Errorback for connection. - - :param failure: Failure - """ - logger.warning(failure) - - def connect_to_management(self, host, port): - """ - Connect to a management interface. - - :param host: the host of the management interface - :type host: str - - :param port: the port of the management interface - :type port: str - - :returns: a deferred - """ - self.connectd = defer.maybeDeferred( - self._connect_management, host, port) - self.connectd.addCallbacks(self._connectCb, self._connectErr) - return self.connectd - - def is_connected(self): - """ - Returns the status of the management interface. - - :returns: True if connected, False otherwise - :rtype: bool - """ - return True if self._tn else False - - def try_to_connect_to_management(self, retry=0, max_retries=None): - """ - Attempts to connect to a management interface, and retries - after CONNECTION_RETRY_TIME if not successful. - - :param retry: number of the retry - :type retry: int - """ - if max_retries and retry > max_retries: - logger.warning("Max retries reached while attempting to connect " - "to management. Aborting.") - self.aborted = True - return - - # _alive flag is set in the VPNProcess class. - if not self._alive: - logger.debug('Tried to connect to management but process is ' - 'not alive.') - return - logger.debug('trying to connect to management') - if not self.aborted and not self.is_connected(): - self.connect_to_management(self._socket_host, self._socket_port) - self._reactor.callLater( - self.CONNECTION_RETRY_TIME, - self.try_to_connect_to_management, retry + 1) - - def _parse_state_and_notify(self, output): - """ - Parses the output of the state command and emits state_changed - signal when the state changes. - - :param output: list of lines that the state command printed as - its output - :type output: list - """ - for line in output: - stripped = line.strip() - if stripped == "END": - continue - parts = stripped.split(",") - if len(parts) < 5: - continue - ts, status_step, ok, ip, remote = parts - - state_dict = { - self.TS_KEY: ts, - self.STATUS_STEP_KEY: status_step, - self.OK_KEY: ok, - self.IP_KEY: ip, - self.REMOTE_KEY: remote - } - - if state_dict != self._last_state: - self.qtsigs.state_changed.emit(state_dict) - self._last_state = state_dict - - def _parse_status_and_notify(self, output): - """ - Parses the output of the status command and emits - status_changed signal when the status changes. - - :param output: list of lines that the status command printed - as its output - :type output: list - """ - tun_tap_read = "" - tun_tap_write = "" - tcp_udp_read = "" - tcp_udp_write = "" - auth_read = "" - for line in output: - stripped = line.strip() - if stripped.endswith("STATISTICS") or stripped == "END": - continue - parts = stripped.split(",") - if len(parts) < 2: - continue - if parts[0].strip() == "TUN/TAP read bytes": - tun_tap_read = parts[1] - elif parts[0].strip() == "TUN/TAP write bytes": - tun_tap_write = parts[1] - elif parts[0].strip() == "TCP/UDP read bytes": - tcp_udp_read = parts[1] - elif parts[0].strip() == "TCP/UDP write bytes": - tcp_udp_write = parts[1] - elif parts[0].strip() == "Auth read bytes": - auth_read = parts[1] - - status_dict = { - self.TUNTAP_READ_KEY: tun_tap_read, - self.TUNTAP_WRITE_KEY: tun_tap_write, - self.TCPUDP_READ_KEY: tcp_udp_read, - self.TCPUDP_WRITE_KEY: tcp_udp_write, - self.AUTH_READ_KEY: auth_read - } - - if status_dict != self._last_status: - self.qtsigs.status_changed.emit(status_dict) - self._last_status = status_dict - - def get_state(self): - """ - Notifies the gui of the output of the state command over - the openvpn management interface. - """ - if self.is_connected(): - return self._parse_state_and_notify(self._send_command("state")) - - def get_status(self): - """ - Notifies the gui of the output of the status command over - the openvpn management interface. - """ - if self.is_connected(): - return self._parse_status_and_notify(self._send_command("status")) - - @property - def vpn_env(self): - """ - Return a dict containing the vpn environment to be used. - """ - return self._launcher.get_vpn_env(self._providerconfig) - - def terminate_openvpn(self, shutdown=False): - """ - Attempts to terminate openvpn by sending a SIGTERM. - """ - if self.is_connected(): - self._send_command("signal SIGTERM") - if shutdown: - self._cleanup_tempfiles() - - def _cleanup_tempfiles(self): - """ - Remove all temporal files we might have left behind. - - Iif self.port is 'unix', we have created a temporal socket path that, - under normal circumstances, we should be able to delete. - """ - if self._socket_port == "unix": - logger.debug('cleaning socket file temp folder') - tempfolder = first(os.path.split(self._socket_host)) - if tempfolder and os.path.isdir(tempfolder): - try: - shutil.rmtree(tempfolder) - except OSError: - logger.error('could not delete tmpfolder %s' % tempfolder) - - def get_openvpn_process(self): - """ - Looks for openvpn instances running. - - :rtype: process - """ - openvpn_process = None - for p in psutil.process_iter(): - try: - # XXX Not exact! - # Will give false positives. - # we should check that cmdline BEGINS - # with openvpn or with our wrapper - # (pkexec / osascript / whatever) - - # This needs more work, see #3268, but for the moment - # we need to be able to filter out arguments in the form - # --openvpn-foo, since otherwise we are shooting ourselves - # in the feet. - if any(map(lambda s: s.startswith("openvpn"), p.cmdline)): - openvpn_process = p - break - except psutil.error.AccessDenied: - pass - return openvpn_process - - def stop_if_already_running(self): - """ - Checks if VPN is already running and tries to stop it. - - Might raise OpenVPNAlreadyRunning. - - :return: True if stopped, False otherwise - - """ - process = self.get_openvpn_process() - if not process: - logger.debug('Could not find openvpn process while ' - 'trying to stop it.') - return - - logger.debug("OpenVPN is already running, trying to stop it...") - cmdline = process.cmdline - - manag_flag = "--management" - if isinstance(cmdline, list) and manag_flag in cmdline: - # we know that our invocation has this distinctive fragment, so - # we use this fingerprint to tell other invocations apart. - # this might break if we change the configuration path in the - # launchers - smellslikeleap = lambda s: "leap" in s and "providers" in s - - if not any(map(smellslikeleap, cmdline)): - logger.debug("We cannot stop this instance since we do not " - "recognise it as a leap invocation.") - raise AlienOpenVPNAlreadyRunning - - try: - index = cmdline.index(manag_flag) - host = cmdline[index + 1] - port = cmdline[index + 2] - logger.debug("Trying to connect to %s:%s" - % (host, port)) - self.connect_to_management(host, port) - - # XXX this has a problem with connections to different - # remotes. So the reconnection will only work when we are - # terminating instances left running for the same provider. - # If we are killing an openvpn instance configured for another - # provider, we will get: - # TLS Error: local/remote TLS keys are out of sync - # However, that should be a rare case right now. - self._send_command("signal SIGTERM") - self._close_management_socket(announce=True) - except Exception as e: - logger.warning("Problem trying to terminate OpenVPN: %r" - % (e,)) - else: - logger.debug("Could not find the expected openvpn command line.") - - process = self.get_openvpn_process() - if process is None: - logger.debug("Successfully finished already running " - "openvpn process.") - return True - else: - logger.warning("Unable to terminate OpenVPN") - raise OpenVPNAlreadyRunning - - -class VPNProcess(protocol.ProcessProtocol, VPNManager): - """ - A ProcessProtocol class that can be used to spawn a process that will - launch openvpn and connect to its management interface to control it - programmatically. - """ - - def __init__(self, eipconfig, providerconfig, socket_host, socket_port, - qtsigs, openvpn_verb): - """ - :param eipconfig: eip configuration object - :type eipconfig: EIPConfig - - :param providerconfig: provider specific configuration - :type providerconfig: ProviderConfig - - :param socket_host: either socket path (unix) or socket IP - :type socket_host: str - - :param socket_port: either string "unix" if it's a unix - socket, or port otherwise - :type socket_port: str - - :param qtsigs: a QObject containing the Qt signals used to notify the - UI. - :type qtsigs: QObject - - :param openvpn_verb: the desired level of verbosity in the - openvpn invocation - :type openvpn_verb: int - """ - VPNManager.__init__(self, qtsigs=qtsigs) - leap_assert_type(eipconfig, EIPConfig) - leap_assert_type(providerconfig, ProviderConfig) - leap_assert_type(qtsigs, QtCore.QObject) - - #leap_assert(not self.isRunning(), "Starting process more than once!") - - self._eipconfig = eipconfig - self._providerconfig = providerconfig - self._socket_host = socket_host - self._socket_port = socket_port - - self._launcher = get_platform_launcher() - - self._last_state = None - self._last_status = None - self._alive = False - - self._openvpn_verb = openvpn_verb - - # processProtocol methods - - def connectionMade(self): - """ - Called when the connection is made. - - .. seeAlso: `http://twistedmatrix.com/documents/13.0.0/api/twisted.internet.protocol.ProcessProtocol.html` # noqa - """ - self._alive = True - self.aborted = False - self.try_to_connect_to_management(max_retries=10) - - def outReceived(self, data): - """ - Called when new data is available on stdout. - - :param data: the data read on stdout - - .. seeAlso: `http://twistedmatrix.com/documents/13.0.0/api/twisted.internet.protocol.ProcessProtocol.html` # noqa - """ - # truncate the newline - # should send this to the logging window - vpnlog.info(data[:-1]) - - def processExited(self, reason): - """ - Called when the child process exits. - - .. seeAlso: `http://twistedmatrix.com/documents/13.0.0/api/twisted.internet.protocol.ProcessProtocol.html` # noqa - """ - exit_code = reason.value.exitCode - if isinstance(exit_code, int): - logger.debug("processExited, status %d" % (exit_code,)) - self.qtsigs.process_finished.emit(exit_code) - self._alive = False - - def processEnded(self, reason): - """ - Called when the child process exits and all file descriptors associated - with it have been closed. - - .. seeAlso: `http://twistedmatrix.com/documents/13.0.0/api/twisted.internet.protocol.ProcessProtocol.html` # noqa - """ - exit_code = reason.value.exitCode - if isinstance(exit_code, int): - logger.debug("processEnded, status %d" % (exit_code,)) - - # polling - - def pollStatus(self): - """ - Polls connection status. - """ - if self._alive: - self.get_status() - - def pollState(self): - """ - Polls connection state. - """ - if self._alive: - self.get_state() - - # launcher - - def getCommand(self): - """ - Gets the vpn command from the aproppriate launcher. - - Might throw: VPNLauncherException, OpenVPNNotFoundException. - """ - cmd = self._launcher.get_vpn_command( - eipconfig=self._eipconfig, - providerconfig=self._providerconfig, - socket_host=self._socket_host, - socket_port=self._socket_port, - openvpn_verb=self._openvpn_verb) - return map(str, cmd) - - # shutdown - - def killProcess(self): - """ - Sends the KILL signal to the running process. - """ - try: - self.transport.signalProcess('KILL') - except internet_error.ProcessExitedAlready: - logger.debug('Process Exited Already') |