summaryrefslogtreecommitdiff
path: root/src/leap
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-06-13 10:52:26 -0300
committerTomás Touceda <chiiph@leap.se>2013-06-13 10:52:26 -0300
commit35291c130f749850753e89d889a121ec098c0dc9 (patch)
tree59305982261bdcca659b45bafdd4c97597ac17ee /src/leap
parentf35506e9b93cf0182af37ccdcc36e343b44b882c (diff)
parentcd11784b8fdf0cb45783e8d6a8e9b5288f34820d (diff)
Merge remote-tracking branch 'kali/feature/osx-eip-scripts_rev1' into develop
Conflicts: src/leap/gui/mainwindow.py
Diffstat (limited to 'src/leap')
-rw-r--r--src/leap/config/leapsettings.py19
-rw-r--r--src/leap/gui/mainwindow.py3
-rw-r--r--src/leap/platform_init/initializers.py121
-rw-r--r--src/leap/services/abstractbootstrapper.py18
-rw-r--r--src/leap/services/eip/eipbootstrapper.py4
-rw-r--r--src/leap/services/eip/vpnlaunchers.py96
-rw-r--r--src/leap/services/mail/smtpbootstrapper.py9
-rw-r--r--src/leap/util/leap_log_handler.py81
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