diff options
-rw-r--r-- | changes/bug-2905_close-app-on-rejected-wizard | 1 | ||||
-rw-r--r-- | changes/bug_2643-almost-invisible-icons | 1 | ||||
-rw-r--r-- | changes/feature_2877-improve_gateway_selector | 1 | ||||
-rw-r--r-- | changes/feature_install-scripts | 2 | ||||
-rw-r--r-- | src/leap/gui/mainwindow.py | 7 | ||||
-rw-r--r-- | src/leap/gui/statuspanel.py | 47 | ||||
-rw-r--r-- | src/leap/gui/wizard.py | 5 | ||||
-rw-r--r-- | src/leap/platform_init/initializers.py | 217 | ||||
-rw-r--r-- | src/leap/services/eip/eipconfig.py | 33 | ||||
-rw-r--r-- | src/leap/services/eip/vpnlaunchers.py | 188 | ||||
-rw-r--r-- | src/leap/services/eip/vpnprocess.py | 28 | ||||
-rw-r--r-- | src/leap/util/__init__.py | 10 |
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 |