diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/leap/config/leapsettings.py | 19 | ||||
| -rw-r--r-- | src/leap/gui/mainwindow.py | 3 | ||||
| -rw-r--r-- | src/leap/platform_init/initializers.py | 121 | ||||
| -rw-r--r-- | src/leap/services/abstractbootstrapper.py | 18 | ||||
| -rw-r--r-- | src/leap/services/eip/eipbootstrapper.py | 4 | ||||
| -rw-r--r-- | src/leap/services/eip/vpnlaunchers.py | 96 | ||||
| -rw-r--r-- | src/leap/services/mail/smtpbootstrapper.py | 9 | ||||
| -rw-r--r-- | src/leap/util/leap_log_handler.py | 81 | 
8 files changed, 289 insertions, 62 deletions
| diff --git a/src/leap/config/leapsettings.py b/src/leap/config/leapsettings.py index 006be851..ab0c1860 100644 --- a/src/leap/config/leapsettings.py +++ b/src/leap/config/leapsettings.py @@ -66,6 +66,7 @@ class LeapSettings(object):      PROPERPROVIDER_KEY = "ProperProvider"      REMEMBER_KEY = "RememberUserAndPass"      DEFAULTPROVIDER_KEY = "DefaultProvider" +    ALERTMISSING_KEY = "AlertMissingScripts"      def __init__(self, standalone=False):          """ @@ -249,3 +250,21 @@ class LeapSettings(object):          """          leap_assert(len(provider) > 0, "We cannot save an empty provider")          self._settings.setValue(self.DEFAULTPROVIDER_KEY, provider) + +    def get_alert_missing_scripts(self): +        """ +        Returns the setting for alerting of missing up/down scripts. + +        :rtype: bool +        """ +        return to_bool(self._settings.value(self.ALERTMISSING_KEY, True)) + +    def set_alert_missing_scripts(self, value): +        """ +        Sets the setting for alerting of missing up/down scripts. + +        :param value: the value to set +        :type value: bool +        """ +        leap_assert_type(value, bool) +        self._settings.setValue(self.ALERTMISSING_KEY, value) diff --git a/src/leap/gui/mainwindow.py b/src/leap/gui/mainwindow.py index 65f5fe3d..0e388e64 100644 --- a/src/leap/gui/mainwindow.py +++ b/src/leap/gui/mainwindow.py @@ -224,7 +224,8 @@ class MainWindow(QtGui.QMainWindow):          self._systray = None          self._action_eip_status = QtGui.QAction( -            self.tr("Encrypted internet is OFF"), self) +            self.tr("Encrypted internet is OFF"), +            self)          self._action_eip_status.setEnabled(False)          self._action_eip_startstop = QtGui.QAction(              self.tr("Turn encryption ON"), self) diff --git a/src/leap/platform_init/initializers.py b/src/leap/platform_init/initializers.py index 2e8cbe95..d72dc61f 100644 --- a/src/leap/platform_init/initializers.py +++ b/src/leap/platform_init/initializers.py @@ -22,10 +22,15 @@ Platform dependant initializing code  import logging  import os  import platform +import stat  import subprocess +import tempfile  from PySide import QtGui +from leap.config.leapsettings import LeapSettings +from leap.services.eip import vpnlaunchers +  logger = logging.getLogger(__name__)  # NOTE we could use a deferToThread here, but should @@ -75,6 +80,30 @@ 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 @@ -128,15 +157,73 @@ def _darwin_has_tun_kext():      return has_tun_and_startup +def _darwin_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 +    """ +    # We expect to execute this from some way of bundle, since +    # the up/down scripts should be put in place by the installer. +    installer_path = os.path.join( +        os.getcwd(), +        "..", +        "Resources", +        "openvpn") +    launcher = vpnlaunchers.DarwinVPNLauncher +    if os.path.isdir(installer_path): +        tempscript = tempfile.mktemp() +        try: +            cmd = launcher.OSASCRIPT_BIN +            scriptlines = launcher.cmd_for_missing_scripts(installer_path) +            with open(tempscript, '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) + +            osascript = launcher.OSX_ASADMIN % ("/bin/sh %s" % (tempscript,),) +            cmdline = ["%s -e '%s'" % (cmd, osascript)] +            ret = subprocess.call( +                cmdline, stdout=subprocess.PIPE, +                shell=True) +            assert(ret) +        except Exception as exc: +            logger.error(badexec) +            logger.error("Error was: %r" % (exc,)) +            f.close() +        finally: +            # XXX remove file +            pass +    else: +        logger.error(notfound) +        logger.debug('path searched: %s' % (installer_path,)) + +  def DarwinInitializer():      """      Raises a dialog in case that the osx tuntap driver has not been found      in the registry, asking the user for permission to install the driver      """ -    NOTFOUND_MSG = ("Tried to install tuntaposx kext, but the installer " -                    "is not found inside this bundle.") -    BADEXEC_MSG = ("Tried to install tuntaposx kext, but the installer " -                   "failed to be launched.") +    # 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. @@ -170,6 +257,28 @@ def DarwinInitializer():                          cmd, stdout=subprocess.PIPE,                          shell=True)                  except: -                    logger.error(BADEXEC_MSG) +                    logger.error(TUNTAP_BADEXEC_MSG)              else: -                logger.error(NOTFOUND_MSG) +                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_() + +        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.") + +        elif ret == QtGui.QMessageBox.Rejected: +            logger.debug( +                "Setting alert_missing_scripts to False, we will not " +                "ask again") +            config.set_alert_missing_scripts(False) diff --git a/src/leap/services/abstractbootstrapper.py b/src/leap/services/abstractbootstrapper.py index 7bebdc15..2cbd56bc 100644 --- a/src/leap/services/abstractbootstrapper.py +++ b/src/leap/services/abstractbootstrapper.py @@ -48,14 +48,14 @@ class AbstractBootstrapper(QtCore.QObject):          """          QtCore.QObject.__init__(self) -        leap_assert(self._gui_errback.im_func == \ -                        AbstractBootstrapper._gui_errback.im_func, +        leap_assert(self._gui_errback.im_func == +                    AbstractBootstrapper._gui_errback.im_func,                      "Cannot redefine _gui_errback") -        leap_assert(self._errback.im_func == \ -                        AbstractBootstrapper._errback.im_func, +        leap_assert(self._errback.im_func == +                    AbstractBootstrapper._errback.im_func,                      "Cannot redefine _errback") -        leap_assert(self._gui_notify.im_func == \ -                        AbstractBootstrapper._gui_notify.im_func, +        leap_assert(self._gui_notify.im_func == +                    AbstractBootstrapper._gui_notify.im_func,                      "Cannot redefine _gui_notify")          # **************************************************** # @@ -87,9 +87,9 @@ class AbstractBootstrapper(QtCore.QObject):                  if self._err_msg is not None \                  else str(failure.value)              self._signal_to_emit.emit({ -                    self.PASSED_KEY: False, -                    self.ERROR_KEY: err_msg -                    }) +                self.PASSED_KEY: False, +                self.ERROR_KEY: err_msg +            })              failure.trap(Exception)      def _errback(self, failure, signal=None): diff --git a/src/leap/services/eip/eipbootstrapper.py b/src/leap/services/eip/eipbootstrapper.py index 51c3dab4..4da8f90f 100644 --- a/src/leap/services/eip/eipbootstrapper.py +++ b/src/leap/services/eip/eipbootstrapper.py @@ -173,8 +173,8 @@ class EIPBootstrapper(AbstractBootstrapper):          cb_chain = [              (self._download_config, self.download_config), -            (self._download_client_certificates, \ -                 self.download_client_certificate) +            (self._download_client_certificates, +             self.download_client_certificate)          ]          self.addCallbackChain(cb_chain) diff --git a/src/leap/services/eip/vpnlaunchers.py b/src/leap/services/eip/vpnlaunchers.py index 952d3618..6c2ff006 100644 --- a/src/leap/services/eip/vpnlaunchers.py +++ b/src/leap/services/eip/vpnlaunchers.py @@ -29,6 +29,7 @@ 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 @@ -105,22 +106,37 @@ def get_platform_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): +def _has_updown_scripts(path, warn=True):      """ -    Checks the existence of the up/down scripts +    Checks the existence of the up/down scripts. + +    :param path: the path to be checked +    :type path: str + +    :param warn: whether we should log the absence +    :type warn: bool + +    :rtype: bool      """ -    # XXX should check permissions too      is_file = os.path.isfile(path) -    if not is_file: -        logger.error("Could not find up/down scripts. " + -                     "Might produce DNS leaks.") -    return is_file +    if warn and not is_file: +        logger.error("Could not find up/down script %s. " +                     "Might produce DNS leaks." % (path,)) + +    is_exe = os.access(path, os.X_OK) +    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 _is_auth_agent_running(): @@ -229,7 +245,6 @@ class LinuxVPNLauncher(VPNLauncher):          openvpn_configuration = eipconfig.get_openvpn_configuration() -        # FIXME: sanitize this! --          for key, value in openvpn_configuration.items():              args += ['--%s' % (key,), value] @@ -294,15 +309,40 @@ class DarwinVPNLauncher(VPNLauncher):      OSASCRIPT_BIN = '/usr/bin/osascript'      OSX_ASADMIN = "do shell script \"%s\" with administrator privileges" -    OPENVPN_BIN = 'openvpn.leap' -    INSTALL_PATH = "/Applications/LEAPClient.app/" + +    INSTALL_PATH = "/Applications/LEAP\ Client.app"      # OPENVPN_BIN = "/%s/Contents/Resources/openvpn.leap" % (      #   self.INSTALL_PATH,) -    UP_SCRIPT = "/%s/client.up.sh" % (INSTALL_PATH,) -    DOWN_SCRIPT = "/%s/client.down.sh" % (INSTALL_PATH,) +    OPENVPN_BIN = 'openvpn.leap' +    OPENVPN_PATH = "%s/Contents/Resources/openvpn" % (INSTALL_PATH,) + +    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) + +    @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] -    # TODO: Add -    # OPENVPN_DOWN_ROOT = "/usr/lib/openvpn/openvpn-down-root.so" +    @classmethod +    def cmd_for_missing_scripts(kls, frompath): +        """ +        Returns a command that can copy the missing scripts. +        :rtype: str +        """ +        to = kls.OPENVPN_PATH +        cmd = "#!/bin/sh\nmkdir -p %s\ncp \"%s/\"* %s" % (to, frompath, to) +        #return kls.OSX_ASADMIN % cmd +        return cmd      def get_vpn_command(self, eipconfig=None, providerconfig=None,                          socket_host=None, socket_port="unix"): @@ -365,20 +405,19 @@ class DarwinVPNLauncher(VPNLauncher):              'server'          ] -        # FIXME: sanitize this! -- -          openvpn_configuration = eipconfig.get_openvpn_configuration()          for key, value in openvpn_configuration.items():              args += ['--%s' % (key,), value] +        user = getpass.getuser()          args += [ -            '--user', getpass.getuser(), +            '--user', user,              '--group', grp.getgrgid(os.getgroups()[-1]).gr_name          ]          if socket_port == "unix":              args += [ -                '--management-client-user', getpass.getuser() +                '--management-client-user', user              ]          args += [ @@ -391,13 +430,21 @@ class DarwinVPNLauncher(VPNLauncher):              args += [                  '--up', self.UP_SCRIPT,              ] +          if _has_updown_scripts(self.DOWN_SCRIPT):              args += [ -                '--down', self.DOWN_SCRIPT, -                # FIXME add down-plugin -                # '--plugin', self.OPENVPN_DOWN_ROOT, -                # '\'script_type=down %s\'' % self.DOWN_SCRIPT -            ] +                '--down', self.DOWN_SCRIPT] + +            # should have the down script too +            if _has_updown_scripts(self.OPENVPN_DOWN_PLUGIN): +                args += [ +                    '--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), @@ -405,6 +452,9 @@ class DarwinVPNLauncher(VPNLauncher):              '--ca', providerconfig.get_ca_cert_path()          ] +        # We are using osascript until we can write a proper wrapper +        # for privilege escalation. +          command = self.OSASCRIPT_BIN          cmd_args = ["-e", self.OSX_ASADMIN % (' '.join(args),)] diff --git a/src/leap/services/mail/smtpbootstrapper.py b/src/leap/services/mail/smtpbootstrapper.py index ea480c6d..e8af5349 100644 --- a/src/leap/services/mail/smtpbootstrapper.py +++ b/src/leap/services/mail/smtpbootstrapper.py @@ -94,11 +94,10 @@ class SMTPBootstrapper(AbstractBootstrapper):          # Not modified          if res.status_code == 304:              logger.debug("SMTP definition has not been modified") -            self._smtp_config.load(os.path.join("leap", -                                                "providers", -                                                self._provider_config.\ -                                                    get_domain(), -                                                "smtp-service.json")) +            self._smtp_config.load(os.path.join( +                "leap", "providers", +                self._provider_config.get_domain(), +                "smtp-service.json"))          else:              smtp_definition, mtime = get_content(res) diff --git a/src/leap/util/leap_log_handler.py b/src/leap/util/leap_log_handler.py index 0e598032..5b8ae789 100644 --- a/src/leap/util/leap_log_handler.py +++ b/src/leap/util/leap_log_handler.py @@ -14,7 +14,6 @@  #  # You should have received a copy of the GNU General Public License  # along with this program.  If not, see <http://www.gnu.org/licenses/>. -  """  Custom handler for the logger window.  """ @@ -23,23 +22,27 @@ import logging  from PySide import QtCore -class LeapLogHandler(logging.Handler, QtCore.QObject): +class LogHandler(logging.Handler):      """ -    Custom logging handler. It emits Qt signals so it can be plugged to a gui. -    Also stores an history of logs that can be fetched after connect to a gui. +    This is the custom handler that implements our desired formatting +    and also keeps a history of all the logged events.      """ -    # All dicts returned are of the form -    # {'record': LogRecord, 'message': str} -    new_log = QtCore.Signal(dict)      MESSAGE_KEY = 'message'      RECORD_KEY = 'record' -    def __init__(self): -        logging.Handler.__init__(self) -        QtCore.QObject.__init__(self) +    # TODO This is going to eat lots of memory after some time. +    # Should be pruned at some moment. +    _log_history = [] -        self._log_history = [] +    def __init__(self, qtsignal): +        """ +        LogHander initialization. +        Calls parent method and keeps a reference to the qtsignal +        that will be used to fire the gui update. +        """ +        logging.Handler.__init__(self) +        self._qtsignal = qtsignal      def _set_format(self, logging_level):          """ @@ -66,6 +69,7 @@ class LeapLogHandler(logging.Handler, QtCore.QObject):          format_attrs = [time, name, level, message]          log_format = ' - '.join(format_attrs)          formatter = logging.Formatter(log_format) +          self.setFormatter(formatter)      def emit(self, logRecord): @@ -74,8 +78,6 @@ class LeapLogHandler(logging.Handler, QtCore.QObject):          logging module.          This method reimplements logging.Handler.emit that is fired          in every logged message. -        QObject.emit gets in the way on the PySide signal model but we -        workarouded that issue.          :param logRecord: the record emitted by the logging module.          :type logRecord: logging.LogRecord. @@ -83,17 +85,64 @@ class LeapLogHandler(logging.Handler, QtCore.QObject):          self._set_format(logRecord.levelname)          log = self.format(logRecord)          log_item = {self.RECORD_KEY: logRecord, self.MESSAGE_KEY: log} -        self._log_history.append(log_item) +        self._qtsignal(log_item) + + +class HandlerAdapter(object): +    """ +    New style class that accesses all attributes from the LogHandler. + +    Used as a workaround for a problem with multiple inheritance with Pyside +    that surfaced under OSX with pyside 1.1.0. +    """ +    MESSAGE_KEY = 'message' +    RECORD_KEY = 'record' + +    def __init__(self, qtsignal): +        self._handler = LogHandler(qtsignal=qtsignal) + +    def setLevel(self, *args, **kwargs): +        return self._handler.setLevel(*args, **kwargs) + +    def handle(self, *args, **kwargs): +        return self._handler.handle(*args, **kwargs) + +    @property +    def level(self): +        return self._handler.level + + +class LeapLogHandler(QtCore.QObject, HandlerAdapter): +    """ +    Custom logging handler. It emits Qt signals so it can be plugged to a gui. + +    Its inner handler also stores an history of logs that can be fetched after +    having been connected to a gui. +    """ +    # All dicts returned are of the form +    # {'record': LogRecord, 'message': str} +    new_log = QtCore.Signal(dict) + +    def __init__(self): +        """ +        LeapLogHandler initialization. +        Initializes parent classes. +        """ +        QtCore.QObject.__init__(self) +        HandlerAdapter.__init__(self, qtsignal=self.qtsignal) +    def qtsignal(self, log_item):          # WARNING: the new-style connection does NOT work because PySide          # translates the emit method to self.emit, and that collides with          # the emit method for logging.Handler          # self.new_log.emit(log_item) -        QtCore.QObject.emit(self, QtCore.SIGNAL('new_log(PyObject)'), log_item) +        QtCore.QObject.emit( +            self, +            QtCore.SIGNAL('new_log(PyObject)'), log_item)      @property      def log_history(self):          """          Returns the history of the logged messages.          """ -        return self._log_history +        return self._handler._log_history | 
