summaryrefslogtreecommitdiff
path: root/src/leap/services/eip
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/services/eip')
-rw-r--r--src/leap/services/eip/eipconfig.py33
-rw-r--r--src/leap/services/eip/vpnlaunchers.py188
-rw-r--r--src/leap/services/eip/vpnprocess.py28
3 files changed, 175 insertions, 74 deletions
diff --git a/src/leap/services/eip/eipconfig.py b/src/leap/services/eip/eipconfig.py
index f7d03963..a85fe64a 100644
--- a/src/leap/services/eip/eipconfig.py
+++ b/src/leap/services/eip/eipconfig.py
@@ -52,33 +52,32 @@ class VPNGatewaySelector(object):
self._set_local_offset()
self._eipconfig = eipconfig
- def _get_best_gateway(self):
+ def get_gateways(self):
"""
- Returns index of the closest gateway, using timezones offsets.
+ Returns the 4 best gateways, sorted by timezone proximity.
- :rtype: int
+ :rtype: list of IPv4Address or IPv6Address object.
"""
- best_gateway = (-1, 99) # gateway, distance
+ gateways_timezones = []
locations = self._eipconfig.get_locations()
gateways = self._eipconfig.get_gateways()
+
for idx, gateway in enumerate(gateways):
- gateway_offset = int(locations[gateway['location']]['timezone'])
- gateway_distance = self._get_timezone_distance(gateway_offset)
- if gateway_distance < best_gateway[1]:
- best_gateway = (idx, gateway_distance)
+ gateway_location = gateway.get('location')
+ gateway_distance = 99 # if hasn't location -> should go last
- return best_gateway[0]
+ if gateway_location is not None:
+ gw_offset = int(locations[gateway['location']]['timezone'])
+ gateway_distance = self._get_timezone_distance(gw_offset)
- def get_best_gateway_ip(self):
- """
- Returns the ip of the best possible gateway.
+ ip = self._eipconfig.get_gateway_ip(idx)
+ gateways_timezones.append((ip, gateway_distance))
- :rtype: An IPv4Address or IPv6Address object.
- """
- best_gateway = self._get_best_gateway()
- gateway_ip = self._eipconfig.get_gateway_ip(best_gateway)
+ gateways_timezones = sorted(gateways_timezones,
+ key=lambda gw: gw[1])[:4]
- return gateway_ip
+ gateways = [ip for ip, dist in gateways_timezones]
+ return gateways
def _get_timezone_distance(self, offset):
'''
diff --git a/src/leap/services/eip/vpnlaunchers.py b/src/leap/services/eip/vpnlaunchers.py
index fa2989bc..af77c146 100644
--- a/src/leap/services/eip/vpnlaunchers.py
+++ b/src/leap/services/eip/vpnlaunchers.py
@@ -35,6 +35,7 @@ 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
logger = logging.getLogger(__name__)
@@ -59,9 +60,11 @@ class VPNLauncher:
"""
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):
@@ -97,6 +100,35 @@ class VPNLauncher:
"""
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"]
@@ -117,7 +149,8 @@ def _is_pkexec_in_system():
def _has_updown_scripts(path, warn=True):
"""
- Checks the existence of the up/down scripts.
+ Checks the existence of the up/down scripts and its
+ exec bit if applicable.
:param path: the path to be checked
:type path: str
@@ -132,6 +165,7 @@ def _has_updown_scripts(path, warn=True):
logger.error("Could not find up/down script %s. "
"Might produce DNS leaks." % (path,))
+ # XXX check if applies in win
is_exe = os.access(path, os.X_OK)
if warn and not is_exe:
logger.error("Up/down script %s is not executable. "
@@ -139,6 +173,25 @@ def _has_updown_scripts(path, warn=True):
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.
@@ -160,8 +213,59 @@ class LinuxVPNLauncher(VPNLauncher):
PKEXEC_BIN = 'pkexec'
OPENVPN_BIN = 'openvpn'
- UP_DOWN_SCRIPT = "/etc/leap/resolv-update"
- OPENVPN_DOWN_ROOT = "/usr/lib/openvpn/openvpn-down-root.so"
+ 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 = "/usr/lib/openvpn/openvpn-plugin-down-root.so"
+
+ POLKIT_BASE = "/usr/share/polkit-1/actions"
+ POLKIT_FILE = "net.openvpn.gui.leap.policy"
+ POLKIT_PATH = "%s/%s" % (POLKIT_BASE, POLKIT_FILE)
+
+ UPDOWN_FILES = (UP_DOWN_PATH,)
+ OTHER_FILES = (POLKIT_PATH,)
+
+ @classmethod
+ def cmd_for_missing_scripts(kls, frompath):
+ """
+ Returns a command that can copy the missing scripts.
+ :rtype: str
+ """
+ to = kls.SYSTEM_CONFIG
+ cmd = "#!/bin/sh\nset -e\nmkdir -p %s\ncp %s/%s %s\ncp %s/%s %s" % (
+ to,
+ frompath, kls.UP_DOWN_FILE, to,
+ frompath, kls.POLKIT_FILE, 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 _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()
def get_vpn_command(self, eipconfig=None, providerconfig=None,
socket_host=None, socket_port="unix"):
@@ -201,44 +305,34 @@ class LinuxVPNLauncher(VPNLauncher):
providerconfig.get_path_prefix(),
"..", "apps", "eip")
- openvpn_possibilities = which(
- self.OPENVPN_BIN,
- **kwargs)
+ openvpn_possibilities = which(self.OPENVPN_BIN, **kwargs)
if len(openvpn_possibilities) == 0:
raise OpenVPNNotFoundException()
- openvpn = openvpn_possibilities[0]
+ openvpn = first(openvpn_possibilities)
args = []
- if _is_pkexec_in_system():
- if _is_auth_agent_running():
- pkexec_possibilities = which(self.PKEXEC_BIN)
- leap_assert(len(pkexec_possibilities) > 0,
- "We couldn't find pkexec")
- args.append(openvpn)
- openvpn = pkexec_possibilities[0]
- 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()
+ pkexec = self.maybe_pkexec()
+ if pkexec:
+ args.append(openvpn)
+ openvpn = first(pkexec)
# TODO: handle verbosity
gateway_selector = VPNGatewaySelector(eipconfig)
- gateway_ip = gateway_selector.get_best_gateway_ip()
+ gateways = gateway_selector.get_gateways()
- logger.debug("Using gateway ip %s" % (gateway_ip,))
+ logger.debug("Using gateways ips: {}".format(', '.join(gateways)))
+
+ for gw in gateways:
+ args += ['--remote', gw, '1194', 'udp']
args += [
'--client',
'--dev', 'tun',
'--persist-tun',
'--persist-key',
- '--remote', gateway_ip, '1194', 'udp',
'--tls-client',
'--remote-cert-tls',
'server'
@@ -265,12 +359,12 @@ class LinuxVPNLauncher(VPNLauncher):
'--script-security', '2'
]
- if _has_updown_scripts(self.UP_DOWN_SCRIPT):
+ if _has_updown_scripts(self.UP_DOWN_PATH):
args += [
- '--up', self.UP_DOWN_SCRIPT,
- '--down', self.UP_DOWN_SCRIPT,
+ '--up', self.UP_DOWN_PATH,
+ '--down', self.UP_DOWN_PATH,
'--plugin', self.OPENVPN_DOWN_ROOT,
- '\'script_type=down %s\'' % self.UP_DOWN_SCRIPT
+ '\'script_type=down %s\'' % self.UP_DOWN_PATH
]
args += [
@@ -324,17 +418,6 @@ class DarwinVPNLauncher(VPNLauncher):
UPDOWN_FILES = (UP_SCRIPT, DOWN_SCRIPT, OPENVPN_DOWN_PLUGIN)
@classmethod
- def missing_updown_scripts(kls):
- """
- Returns what updown scripts are missing.
- :rtype: list
- """
- 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 cmd_for_missing_scripts(kls, frompath):
"""
Returns a command that can copy the missing scripts.
@@ -387,22 +470,24 @@ class DarwinVPNLauncher(VPNLauncher):
if len(openvpn_possibilities) == 0:
raise OpenVPNNotFoundException()
- openvpn = openvpn_possibilities[0]
+ openvpn = first(openvpn_possibilities)
args = [openvpn]
# TODO: handle verbosity
gateway_selector = VPNGatewaySelector(eipconfig)
- gateway_ip = gateway_selector.get_best_gateway_ip()
+ gateways = gateway_selector.get_gateways()
- logger.debug("Using gateway ip %s" % (gateway_ip,))
+ logger.debug("Using gateways ips: {}".format(', '.join(gateways)))
+
+ for gw in gateways:
+ args += ['--remote', gw, '1194', 'udp']
args += [
'--client',
'--dev', 'tun',
'--persist-tun',
'--persist-key',
- '--remote', gateway_ip, '1194', 'udp',
'--tls-client',
'--remote-cert-tls',
'server'
@@ -489,6 +574,8 @@ class WindowsVPNLauncher(VPNLauncher):
OPENVPN_BIN = 'openvpn_leap.exe'
+ # XXX UPDOWN_FILES ... we do not have updown files defined yet!
+
def get_vpn_command(self, eipconfig=None, providerconfig=None,
socket_host=None, socket_port="9876"):
"""
@@ -528,29 +615,30 @@ class WindowsVPNLauncher(VPNLauncher):
if len(openvpn_possibilities) == 0:
raise OpenVPNNotFoundException()
- openvpn = openvpn_possibilities[0]
+ openvpn = first(openvpn_possibilities)
args = []
# TODO: handle verbosity
gateway_selector = VPNGatewaySelector(eipconfig)
- gateway_ip = gateway_selector.get_best_gateway_ip()
+ gateways = gateway_selector.get_gateways()
+
+ logger.debug("Using gateways ips: {}".format(', '.join(gateways)))
- logger.debug("Using gateway ip %s" % (gateway_ip,))
+ for gw in gateways:
+ args += ['--remote', gw, '1194', 'udp']
args += [
'--client',
'--dev', 'tun',
'--persist-tun',
'--persist-key',
- '--remote', gateway_ip, '1194', 'udp',
'--tls-client',
'--remote-cert-tls',
'server'
]
openvpn_configuration = eipconfig.get_openvpn_configuration()
- # XXX sanitize this
for key, value in openvpn_configuration.items():
args += ['--%s' % (key,), value]
@@ -558,13 +646,11 @@ class WindowsVPNLauncher(VPNLauncher):
'--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),
diff --git a/src/leap/services/eip/vpnprocess.py b/src/leap/services/eip/vpnprocess.py
index 162dc7f0..f3443533 100644
--- a/src/leap/services/eip/vpnprocess.py
+++ b/src/leap/services/eip/vpnprocess.py
@@ -20,8 +20,8 @@ VPN Manager, spawned in a custom processProtocol.
import logging
import os
import psutil
+import shutil
import socket
-import time
from PySide import QtCore
@@ -143,7 +143,7 @@ class VPN(object):
logger.debug("Process did not died. Sending a SIGKILL.")
self._vpnproc.killProcess()
- def terminate(self):
+ def terminate(self, shutdown=False):
"""
Stops the openvpn subprocess.
@@ -156,15 +156,13 @@ class VPN(object):
# First we try to be polite and send a SIGTERM...
if self._vpnproc:
self._sentterm = True
- self._vpnproc.terminate_openvpn()
+ self._vpnproc.terminate_openvpn(shutdown=shutdown)
# ...but we also trigger a countdown to be unpolite
# if strictly needed.
reactor.callLater(
self.TERMINATE_WAIT, self._kill_if_left_alive)
- # TODO: should also cleanup tempfiles!!!
-
def _start_pollers(self):
"""
Iterate through the registered observers
@@ -482,12 +480,30 @@ class VPNManager(object):
"""
return self._launcher.get_vpn_env(self._providerconfig)
- def terminate_openvpn(self):
+ 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 = os.path.split(self._socket_host)[0] # XXX use `first`
+ if os.path.isdir(tempfolder):
+ try:
+ shutil.rmtree(tempfolder)
+ except OSError:
+ logger.error('could not delete tmpfolder %s' % tempfolder)
# ---------------------------------------------------
# XXX old methods, not adapted to twisted process yet