# -*- coding: utf-8 -*- # initializers.py # Copyright (C) 2013 LEAP # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ 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 # be aware of this bug: http://www.themacaque.com/?p=1067 def init_platform(): """ Returns the right initializer for the platform we are running in, or None if no proper initializer is found """ initializer = None try: initializer = globals()[platform.system() + "Initializer"] except: pass if initializer: logger.debug("Running initializer for %s" % (platform.system(),)) initializer() else: logger.debug("Initializer not found for %s" % (platform.system(),)) def _windows_has_tap_device(): """ Loops over the windows registry trying to find if the tap0901 tap driver has been installed on this machine. """ import _winreg as reg adapter_key = 'SYSTEM\CurrentControlSet\Control\Class' \ '\{4D36E972-E325-11CE-BFC1-08002BE10318}' with reg.OpenKey(reg.HKEY_LOCAL_MACHINE, adapter_key) as adapters: try: for i in xrange(10000): key_name = reg.EnumKey(adapters, i) with reg.OpenKey(adapters, key_name) as adapter: try: component_id = reg.QueryValueEx(adapter, 'ComponentId')[0] if component_id.startswith("tap0901"): return True except WindowsError: pass except WindowsError: pass 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 in the registry, asking the user for permission to install the driver """ if not _windows_has_tap_device(): msg = QtGui.QMessageBox() msg.setWindowTitle(msg.tr("TAP Driver")) msg.setText(msg.tr("LEAPClient needs to install the necessary drivers " "for Encrypted Internet to work. Would you like to " "proceed?")) msg.setInformativeText(msg.tr("Encrypted Internet uses VPN, which " "needs a TAP device installed and none " "has been found. This will ask for " "administrative privileges.")) msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) msg.setDefaultButton(QtGui.QMessageBox.Yes) ret = msg.exec_() if ret == QtGui.QMessageBox.Yes: driver_path = os.path.join(os.getcwd(), "apps", "eip", "tap_driver") dev_installer = os.path.join(driver_path, "devcon.exe") if os.path.isfile(dev_installer) and \ os.access(dev_installer, os.X_OK): inf_path = os.path.join(driver_path, "OemWin2k.inf") cmd = [dev_installer, "install", inf_path, "tap0901"] ret = subprocess.call(cmd, stdout=subprocess.PIPE, shell=True) else: logger.error("Tried to install TAP driver, but the installer " "is not found or not executable") def _darwin_has_tun_kext(): """ Returns True only if we found a directory under the system kext folder containing a kext named tun.kext, AND we found a startup item named 'tun' """ # XXX we should be smarter here and use kextstats output. has_kext = os.path.isdir("/System/Library/Extensions/tun.kext") has_startup = os.path.isdir("/System/Library/StartupItems/tun") has_tun_and_startup = has_kext and has_startup logger.debug( 'platform initializer check: has tun_and_startup = %s' % (has_tun_and_startup,)) 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 """ # 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. if not _darwin_has_tun_kext(): msg = QtGui.QMessageBox() msg.setWindowTitle(msg.tr("TUN Driver")) msg.setText(msg.tr("LEAPClient needs to install the necessary drivers " "for Encrypted Internet to work. Would you like to " "proceed?")) msg.setInformativeText(msg.tr("Encrypted Internet uses VPN, which " "needs a kernel extension for a TUN " "device installed, and none " "has been found. This will ask for " "administrative privileges.")) msg.setStandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) msg.setDefaultButton(QtGui.QMessageBox.Yes) ret = msg.exec_() if ret == QtGui.QMessageBox.Yes: installer_path = os.path.join( os.getcwd(), "..", "Resources", "tuntap-installer.app") if os.path.isdir(installer_path): cmd = ["open %s" % (installer_path,)] try: ret = subprocess.call( cmd, stdout=subprocess.PIPE, shell=True) except: logger.error(TUNTAP_BADEXEC_MSG) 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_() 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)