diff options
Diffstat (limited to 'src/leap/platform_init')
-rw-r--r-- | src/leap/platform_init/__init__.py | 28 | ||||
-rw-r--r-- | src/leap/platform_init/initializers.py | 409 | ||||
-rw-r--r-- | src/leap/platform_init/locks.py | 405 |
3 files changed, 0 insertions, 842 deletions
diff --git a/src/leap/platform_init/__init__.py b/src/leap/platform_init/__init__.py deleted file mode 100644 index 2a262a30..00000000 --- a/src/leap/platform_init/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# __init__.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 <http://www.gnu.org/licenses/>. - -""" -System constants -""" -import platform - -_system = platform.system() - -IS_WIN = True if _system == "Windows" else False -IS_MAC = True if _system == "Darwin" else False -IS_LINUX = True if _system == "Linux" else False -IS_UNIX = IS_MAC or IS_LINUX diff --git a/src/leap/platform_init/initializers.py b/src/leap/platform_init/initializers.py deleted file mode 100644 index 46488250..00000000 --- a/src/leap/platform_init/initializers.py +++ /dev/null @@ -1,409 +0,0 @@ -# -*- 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 <http://www.gnu.org/licenses/>. - -""" -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 -from leap.util import first -from leap.util import privilege_policies - - -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(): - """ - Returns the right initializer for the platform we are running in, or - None if no proper initializer is found - """ - initializer = None - try: - initializer = globals()[_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(),)) - - -# -# 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, " - "Bitmask 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 - - # XXX maybe move constants to fun - ok = install_missing_fun(UPDOWN_BADEXEC_MSG, UPDOWN_NOTFOUND_MSG) - if not ok: - msg = QtGui.QMessageBox() - msg.setWindowTitle(msg.tr("Problem installing files")) - msg.setText(msg.tr('Some of the files could not be copied.')) - msg.setIcon(QtGui.QMessageBox.Warning) - msg.exec_() - - 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 - 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 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("Bitmask 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: - # 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", - "tap_driver") - dev_installer = os.path.join(driver_path, - "devcon.exe") - if os.path.isfile(dev_installer) and \ - stat.S_IXUSR & os.stat(dev_installer)[stat.ST_MODE] != 0: - 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") - -# -# Darwin initializer functions -# - - -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 - :returns: True if the files could be copied successfully. - :rtype: bool - """ - # We expect to execute this from some way of bundle, since - # the up/down scripts should be put in place by the installer. - success = False - installer_path = os.path.join( - os.getcwd(), - "..", - "Resources", - "openvpn") - launcher = vpnlaunchers.DarwinVPNLauncher - - if os.path.isdir(installer_path): - fd, tempscript = tempfile.mkstemp(prefix="leap_installer-") - try: - 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) - - cmd, args = launcher().get_cocoasudo_installmissing_cmd() - args.append(tempscript) - cmdline = " ".join([cmd] + args) - ret = subprocess.call( - cmdline, stdout=subprocess.PIPE, - shell=True) - success = ret == 0 - if not success: - logger.error("Install missing scripts failed.") - 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,)) - - return success - - -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 - - TUNTAP_NOTFOUND_MSG = NOTFOUND_MSG % ( - "tuntaposx kext", "the installer") - TUNTAP_BADEXEC_MSG = BADEXEC_MSG % ( - "tuntaposx kext", "the installer", "be launched") - - # 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("Bitmask 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.abspath( - 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) - - # Second check, for missing scripts. - check_missing() - - -# -# Linux initializers -# -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 - :returns: True if the files could be copied successfully. - :rtype: bool - """ - success = False - 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-") - polfd, pol_tempfile = tempfile.mkstemp(prefix="leap_installer-") - try: - path = launcher.OPENVPN_BIN_PATH - policy_contents = privilege_policies.get_policy_contents(path) - - with os.fdopen(polfd, 'w') as f: - f.write(policy_contents) - - pkexec = first(launcher.maybe_pkexec()) - scriptlines = launcher.cmd_for_missing_scripts(installer_path, - pol_tempfile) - 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) - success = ret == 0 - if not success: - logger.error("Install missing scripts failed.") - 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,)) - - return success - - -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/platform_init/locks.py b/src/leap/platform_init/locks.py deleted file mode 100644 index 39b18648..00000000 --- a/src/leap/platform_init/locks.py +++ /dev/null @@ -1,405 +0,0 @@ -# -*- coding: utf-8 -*- -# locks.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 <http://www.gnu.org/licenses/>. -""" -Utilities for handling multi-platform file locking mechanisms -""" -import logging -import errno -import os -import platform - -from leap.common.events import signal as signal_event -from leap.common.events import events_pb2 as proto -from leap import platform_init - -if platform_init.IS_UNIX: - from fcntl import flock, LOCK_EX, LOCK_NB -else: # WINDOWS - import datetime - import glob - import shutil - import time - - from tempfile import gettempdir - - from leap.util import get_modification_ts, update_modification_ts - -logger = logging.getLogger(__name__) - -if platform_init.IS_UNIX: - - class UnixLock(object): - """ - Uses flock to get an exclusive lock over a file. - See man 2 flock - """ - - def __init__(self, path): - """ - iniializes t he UnixLock with the path of the - desired lockfile - """ - - self._fd = None - self.path = path - - def get_lock(self): - """ - Tries to get a lock, and writes the running pid there if successful - """ - gotit, pid = self._get_lock_and_pid() - return gotit - - def get_pid(self): - """ - Returns the pid of the locking process - """ - gotit, pid = self._get_lock_and_pid() - return pid - - def _get_lock(self): - """ - Tries to get a lock, returning True if successful - - :rtype: bool - """ - self._fd = os.open(self.path, os.O_CREAT | os.O_RDWR) - - try: - flock(self._fd, LOCK_EX | LOCK_NB) - except IOError as exc: - # could not get the lock - #import ipdb; ipdb.set_trace() - - if exc.args[0] in (errno.EDEADLK, errno.EAGAIN): - # errno 11 or 35 - # Resource temporarily unavailable - return False - else: - raise - return True - - @property - def locked_by_us(self): - """ - Returns True if the pid in the pidfile - is ours. - - :rtype: bool - """ - gotit, pid = self._get_lock_and_pid() - return pid == os.getpid() - - def _get_lock_and_pid(self): - """ - Tries to get a lock over the file. - Returns (locked, pid) tuple. - - :rtype: tuple - """ - - if self._get_lock(): - self._write_to_pidfile() - return True, None - - return False, self._read_from_pidfile() - - def _read_from_pidfile(self): - """ - Tries to read pid from the pidfile, - returns False if no content found. - """ - - pidfile = os.read( - self._fd, 16) - if not pidfile: - return False - - try: - return int(pidfile.strip()) - except Exception as exc: - exc.args += (pidfile, self.lock_file) - raise - - def _write_to_pidfile(self): - """ - Writes the pid of the running process - to the pidfile - """ - fd = self._fd - os.ftruncate(fd, 0) - os.write(fd, '%d\n' % os.getpid()) - os.fsync(fd) - - -if platform_init.IS_WIN: - - # Time to wait (in secs) before assuming a raise window signal has not been - # ack-ed. - - RAISE_WINDOW_TIMEOUT = 2 - - # How many steps to do while checking lockfile ts update. - - RAISE_WINDOW_WAIT_STEPS = 10 - - def _release_lock(name): - """ - Tries to remove a folder path. - - :param name: folder lock to remove - :type name: str - """ - try: - shutil.rmtree(name) - return True - except WindowsError as exc: - if exc.errno in (errno.EPIPE, errno.ENOENT, - errno.ESRCH, errno.EACCES): - logger.warning( - 'exception while trying to remove the lockfile dir') - logger.warning('errno %s: %s' % (exc.errno, exc.args[1])) - # path does not exist - return False - else: - logger.debug('errno = %s' % (exc.errno,)) - # we did not foresee this error, better add it explicitely - raise - - class WindowsLock(object): - """ - Creates a lock based on the atomic nature of mkdir on Windows - system calls. - """ - LOCKBASE = os.path.join(gettempdir(), "leap-client-lock") - - def __init__(self): - """ - Initializes the lock. - Sets the lock name to basename plus the process pid. - """ - self._fd = None - pid = os.getpid() - self.name = "%s-%s" % (self.LOCKBASE, pid) - self.pid = pid - - def get_lock(self): - """ - Tries to get a lock, and writes the running pid there if successful - """ - gotit = self._get_lock() - return gotit - - def _get_lock(self): - """ - Tries to write to a file with the current pid as part of the name - """ - try: - self._fd = os.makedirs(self.name) - except OSError as exc: - # could not create the dir - if exc.args[0] == 183: - logger.debug('cannot create dir') - # cannot create dir with existing name - return False - else: - raise - return self._is_one_pidfile()[0] - - def _is_one_pidfile(self): - """ - Returns True, pid if there is only one pidfile with the expected - base path - - :rtype: tuple - """ - pidfiles = glob.glob(self.LOCKBASE + '-*') - if len(pidfiles) == 1: - pid = pidfiles[0].split('-')[-1] - return True, int(pid) - else: - return False, None - - def get_pid(self): - """ - Returns the pid of the locking process. - - :rtype: int - """ - # XXX assert there is only one? - _, pid = self._is_one_pidfile() - return pid - - def get_locking_path(self): - """ - Returns the pid path of the locking process. - - :rtype: str - """ - pid = self.get_pid() - if pid: - return "%s-%s" % (self.LOCKBASE, pid) - - def release_lock(self, name=None): - """ - Releases the pidfile dir for this process, by removing it. - """ - if not name: - name = self.name - _release_lock(name) - - @classmethod - def release_all_locks(self): - """ - Releases all locks. Used for clean shutdown. - """ - for lockdir in glob.glob("%s-%s" % (self.LOCKBASE, '*')): - _release_lock(lockdir) - - @property - def locked_by_us(self): - """ - Returns True if the pid in the pidfile - is ours. - - :rtype: bool - """ - _, pid = self._is_one_pidfile() - return pid == self.pid - - def update_ts(self): - """ - Updates the timestamp of the lock. - """ - if self.locked_by_us: - update_modification_ts(self.name) - - def write_port(self, port): - """ - Writes the port for windows control to the pidfile folder - Returns True if successful. - - :rtype: bool - """ - if not self.locked_by_us: - logger.warning("Tried to write control port to a " - "non-unique pidfile folder") - return False - port_file = os.path.join(self.name, "port") - with open(port_file, 'w') as f: - f.write("%s" % port) - return True - - def get_control_port(self): - """ - Reads control port of the main instance from the port file - in the pidfile dir - - :rtype: int - """ - pid = self.get_pid() - port_file = os.path.join(self.LOCKBASE + "-%s" % pid, "port") - port = None - try: - with open(port_file) as f: - port_str = f.read() - port = int(port_str.strip()) - except IOError as exc: - if exc.errno == errno.ENOENT: - logger.error("Tried to read port from non-existent file") - else: - # we did not know explicitely about this error - raise - return port - - def raise_window_ack(): - """ - This function is called from the windows callback that is registered - with the raise_window event. It just updates the modification time - of the lock file so we can signal an ack to the instance that tried - to raise the window. - """ - lock = WindowsLock() - lock.update_ts() - - -def we_are_the_one_and_only(): - """ - Returns True if we are the only instance running, False otherwise. - If we came later, send a raise signal to the main instance of the - application. - - Under windows we are not using flock magic, so we wait during - RAISE_WINDOW_TIMEOUT time, if not ack is - received, we assume it was a stalled lock, so we remove it and continue - with initialization. - - :rtype: bool - """ - _sys = platform.system() - - if _sys in ("Linux", "Darwin"): - locker = UnixLock('/tmp/leap-client.lock') - locker.get_lock() - we_are_the_one = locker.locked_by_us - if not we_are_the_one: - signal_event(proto.RAISE_WINDOW) - return we_are_the_one - - elif _sys == "Windows": - locker = WindowsLock() - locker.get_lock() - we_are_the_one = locker.locked_by_us - - if not we_are_the_one: - locker.release_lock() - lock_path = locker.get_locking_path() - ts = get_modification_ts(lock_path) - - nowfun = datetime.datetime.now - t0 = nowfun() - pause = RAISE_WINDOW_TIMEOUT / float(RAISE_WINDOW_WAIT_STEPS) - timeout_delta = datetime.timedelta(0, RAISE_WINDOW_TIMEOUT) - check_interval = lambda: nowfun() - t0 < timeout_delta - - # let's assume it's a stalled lock - we_are_the_one = True - signal_event(proto.RAISE_WINDOW) - - while check_interval(): - if get_modification_ts(lock_path) > ts: - # yay! someone claimed their control over the lock. - # so the lock is alive - logger.debug('Raise window ACK-ed') - we_are_the_one = False - break - else: - time.sleep(pause) - - if we_are_the_one: - # ok, it really was a stalled lock. let's remove all - # that is left, and put only ours there. - WindowsLock.release_all_locks() - WindowsLock().get_lock() - - return we_are_the_one - - else: - logger.warning("Multi-instance checker " - "not implemented for %s" % (_sys)) - # lies, lies, lies... - return True |