summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes/bug-2905_close-app-on-rejected-wizard1
-rw-r--r--changes/bug_2643-almost-invisible-icons1
-rw-r--r--changes/feature_2877-improve_gateway_selector1
-rw-r--r--changes/feature_install-scripts2
-rw-r--r--src/leap/gui/mainwindow.py7
-rw-r--r--src/leap/gui/statuspanel.py47
-rw-r--r--src/leap/gui/wizard.py5
-rw-r--r--src/leap/platform_init/initializers.py217
-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
-rw-r--r--src/leap/util/__init__.py10
12 files changed, 396 insertions, 144 deletions
diff --git a/changes/bug-2905_close-app-on-rejected-wizard b/changes/bug-2905_close-app-on-rejected-wizard
new file mode 100644
index 00000000..cf3b8e3d
--- /dev/null
+++ b/changes/bug-2905_close-app-on-rejected-wizard
@@ -0,0 +1 @@
+ o Close the app on rejected wizard. Closes bug #2905.
diff --git a/changes/bug_2643-almost-invisible-icons b/changes/bug_2643-almost-invisible-icons
new file mode 100644
index 00000000..57427bce
--- /dev/null
+++ b/changes/bug_2643-almost-invisible-icons
@@ -0,0 +1 @@
+ o Set EIP different status icons depending on OS. Closes #2643.
diff --git a/changes/feature_2877-improve_gateway_selector b/changes/feature_2877-improve_gateway_selector
new file mode 100644
index 00000000..5162a09e
--- /dev/null
+++ b/changes/feature_2877-improve_gateway_selector
@@ -0,0 +1 @@
+ o Improve gateway selector based on timezone. It allows to use multiple gateways in openvpn for redundancy. Closes #2894.
diff --git a/changes/feature_install-scripts b/changes/feature_install-scripts
new file mode 100644
index 00000000..f20f1fd0
--- /dev/null
+++ b/changes/feature_install-scripts
@@ -0,0 +1,2 @@
+ o linux: check for up/down scripts and policy files and ask user for permission to install them
+ in a root-writeable location. Used from within bundle or for broken installations.
diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py
index d5979395..cf20c6eb 100644
--- a/src/leap/gui/mainwindow.py
+++ b/src/leap/gui/mainwindow.py
@@ -299,9 +299,12 @@ class MainWindow(QtGui.QMainWindow):
"""
if self._wizard is None:
self._wizard = Wizard(bypass_checks=self._bypass_checks)
- self._wizard.accepted.connect(self._finish_init)
+ self._wizard.accepted.connect(self._finish_init)
+
self.setVisible(False)
self._wizard.exec_()
+ # We need this to process any wizard related event
+ QtCore.QCoreApplication.processEvents()
self._wizard = None
self.setVisible(True)
@@ -1209,7 +1212,7 @@ class MainWindow(QtGui.QMainWindow):
self._cleanup_pidfiles()
logger.debug('Terminating vpn')
- self._vpn.terminate()
+ self._vpn.terminate(shutdown=True)
def quit(self):
"""
diff --git a/src/leap/gui/statuspanel.py b/src/leap/gui/statuspanel.py
index 554903d8..19f84d4c 100644
--- a/src/leap/gui/statuspanel.py
+++ b/src/leap/gui/statuspanel.py
@@ -25,7 +25,7 @@ from PySide import QtCore, QtGui
from ui_statuspanel import Ui_StatusPanel
from leap.services.eip.vpnprocess import VPNManager
-from leap.platform_init import IS_MAC
+from leap.platform_init import IS_WIN, IS_LINUX
from leap.common.check import leap_assert_type
logger = logging.getLogger(__name__)
@@ -52,13 +52,35 @@ class StatusPanelWidget(QtGui.QWidget):
self.ui.btnEipStartStop.clicked.connect(
self.start_eip)
- if IS_MAC:
- EIP_ICONS = (
- ":/images/conn_connecting-light.png",
- ":/images/conn_connected-light.png",
- ":/images/conn_error-light.png")
- else:
- EIP_ICONS = (
+ # Set the EIP status icons
+ self.CONNECTING_ICON = None
+ self.CONNECTED_ICON = None
+ self.ERROR_ICON = None
+ self.CONNECTING_ICON_TRAY = None
+ self.CONNECTED_ICON_TRAY = None
+ self.ERROR_ICON_TRAY = None
+ self._set_eip_icons()
+
+ def _set_eip_icons(self):
+ """
+ Sets the EIP status icons for the main window and for the tray
+
+ MAC : dark icons
+ LINUX : dark icons in window, light icons in tray
+ WIN : light icons
+ """
+ EIP_ICONS = EIP_ICONS_TRAY = (
+ ":/images/conn_connecting-light.png",
+ ":/images/conn_connected-light.png",
+ ":/images/conn_error-light.png")
+
+ if IS_LINUX:
+ EIP_ICONS_TRAY = (
+ ":/images/conn_connecting.png",
+ ":/images/conn_connected.png",
+ ":/images/conn_error.png")
+ elif IS_WIN:
+ EIP_ICONS = EIP_ICONS_TRAY = (
":/images/conn_connecting.png",
":/images/conn_connected.png",
":/images/conn_error.png")
@@ -67,6 +89,10 @@ class StatusPanelWidget(QtGui.QWidget):
self.CONNECTED_ICON = QtGui.QPixmap(EIP_ICONS[1])
self.ERROR_ICON = QtGui.QPixmap(EIP_ICONS[2])
+ self.CONNECTING_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[0])
+ self.CONNECTED_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[1])
+ self.ERROR_ICON_TRAY = QtGui.QPixmap(EIP_ICONS_TRAY[2])
+
def set_systray(self, systray):
"""
Sets the systray object to use
@@ -199,17 +225,20 @@ class StatusPanelWidget(QtGui.QWidget):
:type status: str
"""
selected_pixmap = self.ERROR_ICON
+ selected_pixmap_tray = self.ERROR_ICON_TRAY
tray_message = self.tr("Encryption is OFF")
if status in ("WAIT", "AUTH", "GET_CONFIG",
"RECONNECTING", "ASSIGN_IP"):
selected_pixmap = self.CONNECTING_ICON
+ selected_pixmap_tray = self.CONNECTING_ICON_TRAY
tray_message = self.tr("Turning ON")
elif status in ("CONNECTED"):
tray_message = self.tr("Encryption is ON")
selected_pixmap = self.CONNECTED_ICON
+ selected_pixmap_tray = self.CONNECTED_ICON_TRAY
self.set_icon(selected_pixmap)
- self._systray.setIcon(QtGui.QIcon(selected_pixmap))
+ self._systray.setIcon(QtGui.QIcon(selected_pixmap_tray))
self._action_eip_status.setText(tray_message)
def set_provider(self, provider):
diff --git a/src/leap/gui/wizard.py b/src/leap/gui/wizard.py
index d03427db..897bf94f 100644
--- a/src/leap/gui/wizard.py
+++ b/src/leap/gui/wizard.py
@@ -154,6 +154,11 @@ class Wizard(QtGui.QWizard):
self.page(self.FINISH_PAGE).setButtonText(
QtGui.QWizard.FinishButton, self.tr("Connect"))
+ # XXX: Temporary removal for enrollment policy
+ # https://leap.se/code/issues/2922
+ self.ui.label_12.setVisible(False)
+ self.ui.lblProviderPolicy.setVisible(False)
+
def get_domain(self):
return self._domain
diff --git a/src/leap/platform_init/initializers.py b/src/leap/platform_init/initializers.py
index d72dc61f..3374e32e 100644
--- a/src/leap/platform_init/initializers.py
+++ b/src/leap/platform_init/initializers.py
@@ -30,12 +30,17 @@ from PySide import QtGui
from leap.config.leapsettings import LeapSettings
from leap.services.eip import vpnlaunchers
+from leap.util import first
logger = logging.getLogger(__name__)
# NOTE we could use a deferToThread here, but should
# be aware of this bug: http://www.themacaque.com/?p=1067
+__all__ = ["init_platform"]
+
+_system = platform.system()
+
def init_platform():
"""
@@ -44,7 +49,7 @@ def init_platform():
"""
initializer = None
try:
- initializer = globals()[platform.system() + "Initializer"]
+ initializer = globals()[_system + "Initializer"]
except:
pass
if initializer:
@@ -54,6 +59,86 @@ def init_platform():
logger.debug("Initializer not found for %s" % (platform.system(),))
+#
+# common utils
+#
+
+NOTFOUND_MSG = ("Tried to install %s, but %s "
+ "not found inside this bundle.")
+BADEXEC_MSG = ("Tried to install %s, but %s "
+ "failed to %s.")
+
+UPDOWN_NOTFOUND_MSG = NOTFOUND_MSG % (
+ "updown scripts", "those were")
+UPDOWN_BADEXEC_MSG = BADEXEC_MSG % (
+ "updown scripts", "they", "be copied")
+
+
+def get_missing_updown_dialog():
+ """
+ Creates a dialog for notifying of missing updown scripts.
+ Returns that dialog.
+
+ :rtype: QtGui.QMessageBox instance
+ """
+ WE_NEED_POWERS = ("To better protect your privacy, "
+ "LEAP needs administrative privileges "
+ "to install helper files. "
+ "Do you want to proceed?")
+ msg = QtGui.QMessageBox()
+ msg.setWindowTitle(msg.tr("Missing up/down scripts"))
+ msg.setText(msg.tr(WE_NEED_POWERS))
+ # but maybe the user really deserve to know more
+ #msg.setInformativeText(msg.tr(BECAUSE))
+ msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
+ msg.addButton("No, don't ask again", QtGui.QMessageBox.RejectRole)
+ msg.setDefaultButton(QtGui.QMessageBox.Yes)
+ return msg
+
+
+def check_missing():
+ """
+ Checks for the need of installing missing scripts, and
+ raises a dialog to ask user for permission to do it.
+ """
+ config = LeapSettings()
+ alert_missing = config.get_alert_missing_scripts()
+
+ launcher = vpnlaunchers.get_platform_launcher()
+ missing_scripts = launcher.missing_updown_scripts
+ missing_other = launcher.missing_other_files
+
+ if alert_missing and (missing_scripts() or missing_other()):
+ msg = get_missing_updown_dialog()
+ ret = msg.exec_()
+
+ if ret == QtGui.QMessageBox.Yes:
+ install_missing_fun = globals().get(
+ "_%s_install_missing_scripts" % (_system.lower(),),
+ None)
+ if not install_missing_fun:
+ logger.warning(
+ "Installer not found for platform %s." % (_system,))
+ return
+ install_missing_fun(
+ # XXX maybe move constants to fun
+ UPDOWN_BADEXEC_MSG,
+ UPDOWN_NOTFOUND_MSG)
+
+ elif ret == QtGui.QMessageBox.No:
+ logger.debug("Not installing missing scripts, "
+ "user decided to ignore our warning.")
+
+ elif ret == QtGui.QMessageBox.Rejected:
+ logger.debug(
+ "Setting alert_missing_scripts to False, we will not "
+ "ask again")
+ config.set_alert_missing_scripts(False)
+#
+# windows initializers
+#
+
+
def _windows_has_tap_device():
"""
Loops over the windows registry trying to find if the tap0901 tap driver
@@ -80,30 +165,6 @@ def _windows_has_tap_device():
return False
-def _get_missing_updown_dialog():
- """
- Creates a dialog for notifying of missing updown scripts.
- Returns that dialog.
-
- :rtype: QtGui.QMessageBox instance
- """
- msg = QtGui.QMessageBox()
- msg.setWindowTitle(msg.tr("Missing up/down scripts"))
- msg.setText(msg.tr(
- "LEAPClient needs to install up/down scripts "
- "for Encrypted Internet to work properly. "
- "Would you like to proceed?"))
- msg.setInformativeText(msg.tr(
- "It looks like either you have not installed "
- "LEAP Client in a permanent location or you have an "
- "incomplete installation. This will ask for "
- "administrative privileges."))
- msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
- msg.addButton("No, don't ask again", QtGui.QMessageBox.RejectRole)
- msg.setDefaultButton(QtGui.QMessageBox.Yes)
- return msg
-
-
def WindowsInitializer():
"""
Raises a dialog in case that the windows tap driver has not been found
@@ -124,6 +185,9 @@ def WindowsInitializer():
ret = msg.exec_()
if ret == QtGui.QMessageBox.Yes:
+ # XXX should do this only if executed inside bundle.
+ # Let's assume it's the only way it's gonna be executed under win
+ # by now.
driver_path = os.path.join(os.getcwd(),
"apps",
"eip",
@@ -140,6 +204,10 @@ def WindowsInitializer():
logger.error("Tried to install TAP driver, but the installer "
"is not found or not executable")
+#
+# Darwin initializer functions
+#
+
def _darwin_has_tun_kext():
"""
@@ -174,12 +242,15 @@ def _darwin_install_missing_scripts(badexec, notfound):
"Resources",
"openvpn")
launcher = vpnlaunchers.DarwinVPNLauncher
+
+ # TODO should change osascript by use of the proper
+ # os authorization api.
if os.path.isdir(installer_path):
- tempscript = tempfile.mktemp()
+ fd, tempscript = tempfile.mkstemp(prefix="leap_installer-")
try:
cmd = launcher.OSASCRIPT_BIN
scriptlines = launcher.cmd_for_missing_scripts(installer_path)
- with open(tempscript, 'w') as f:
+ with os.fdopen(fd, 'w') as f:
f.write(scriptlines)
st = os.stat(tempscript)
os.chmod(tempscript, st.st_mode | stat.S_IEXEC | stat.S_IXUSR |
@@ -190,14 +261,15 @@ def _darwin_install_missing_scripts(badexec, notfound):
ret = subprocess.call(
cmdline, stdout=subprocess.PIPE,
shell=True)
- assert(ret)
+ assert([ret]) # happy flakes
except Exception as exc:
logger.error(badexec)
logger.error("Error was: %r" % (exc,))
- f.close()
finally:
- # XXX remove file
- pass
+ try:
+ os.remove(tempscript)
+ except OSError as exc:
+ logger.error("%r" % (exc,))
else:
logger.error(notfound)
logger.debug('path searched: %s' % (installer_path,))
@@ -210,21 +282,11 @@ def DarwinInitializer():
"""
# XXX split this function into several
- NOTFOUND_MSG = ("Tried to install %s, but %s "
- "not found inside this bundle.")
- BADEXEC_MSG = ("Tried to install %s, but %s "
- "failed to %s.")
-
TUNTAP_NOTFOUND_MSG = NOTFOUND_MSG % (
"tuntaposx kext", "the installer")
TUNTAP_BADEXEC_MSG = BADEXEC_MSG % (
"tuntaposx kext", "the installer", "be launched")
- UPDOWN_NOTFOUND_MSG = NOTFOUND_MSG % (
- "updown scripts", "those were")
- UPDOWN_BADEXEC_MSG = BADEXEC_MSG % (
- "updown scripts", "they", "be copied")
-
# TODO DRY this with other cases, and
# factor out to _should_install() function.
# Leave the dialog as a more generic thing.
@@ -261,24 +323,61 @@ def DarwinInitializer():
else:
logger.error(TUNTAP_NOTFOUND_MSG)
- config = LeapSettings()
- alert_missing = config.get_alert_missing_scripts()
- missing_scripts = vpnlaunchers.DarwinVPNLauncher.missing_updown_scripts
- if alert_missing and missing_scripts():
- msg = _get_missing_updown_dialog()
- ret = msg.exec_()
+ # Second check, for missing scripts.
+ check_missing()
- if ret == QtGui.QMessageBox.Yes:
- _darwin_install_missing_scripts(
- UPDOWN_BADEXEC_MSG,
- UPDOWN_NOTFOUND_MSG)
- elif ret == QtGui.QMessageBox.No:
- logger.debug("Not installing missing scripts, "
- "user decided to ignore our warning.")
+#
+# Linux initializers
+#
- elif ret == QtGui.QMessageBox.Rejected:
- logger.debug(
- "Setting alert_missing_scripts to False, we will not "
- "ask again")
- config.set_alert_missing_scripts(False)
+def _linux_install_missing_scripts(badexec, notfound):
+ """
+ Tries to install the missing up/down scripts.
+
+ :param badexec: error for notifying execution error during command.
+ :type badexec: str
+ :param notfound: error for notifying missing path.
+ :type notfound: str
+ """
+ installer_path = os.path.join(
+ os.getcwd(),
+ "apps", "eip", "files")
+ launcher = vpnlaunchers.LinuxVPNLauncher
+
+ # XXX refactor with darwin, same block.
+
+ if os.path.isdir(installer_path):
+ fd, tempscript = tempfile.mkstemp(prefix="leap_installer-")
+ try:
+ pkexec = first(launcher.maybe_pkexec())
+ scriptlines = launcher.cmd_for_missing_scripts(installer_path)
+ with os.fdopen(fd, 'w') as f:
+ f.write(scriptlines)
+ st = os.stat(tempscript)
+ os.chmod(tempscript, st.st_mode | stat.S_IEXEC | stat.S_IXUSR |
+ stat.S_IXGRP | stat.S_IXOTH)
+ cmdline = ["%s %s" % (pkexec, tempscript)]
+ ret = subprocess.call(
+ cmdline, stdout=subprocess.PIPE,
+ shell=True)
+ assert([ret]) # happy flakes
+ except Exception as exc:
+ logger.error(badexec)
+ logger.error("Error was: %r" % (exc,))
+ finally:
+ try:
+ os.remove(tempscript)
+ except OSError as exc:
+ logger.error("%r" % (exc,))
+ else:
+ logger.error(notfound)
+ logger.debug('path searched: %s' % (installer_path,))
+
+
+def LinuxInitializer():
+ """
+ Raises a dialog in case that either updown scripts or policykit file
+ are missing or they have incorrect permissions.
+ """
+ check_missing()
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
diff --git a/src/leap/util/__init__.py b/src/leap/util/__init__.py
index 41358d38..5ceaede5 100644
--- a/src/leap/util/__init__.py
+++ b/src/leap/util/__init__.py
@@ -37,3 +37,13 @@ except ImportError:
pass
__full_version__ = __appname__ + '/' + str(__version__)
+
+
+def first(things):
+ """
+ Return the head of a collection.
+ """
+ try:
+ return things[0]
+ except TypeError:
+ return None