summaryrefslogtreecommitdiff
path: root/src/leap/bitmask/services
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/bitmask/services')
-rw-r--r--src/leap/bitmask/services/eip/linuxvpnlauncher.py113
-rw-r--r--src/leap/bitmask/services/eip/vpnlauncher.py124
-rw-r--r--src/leap/bitmask/services/eip/vpnprocess.py88
3 files changed, 198 insertions, 127 deletions
diff --git a/src/leap/bitmask/services/eip/linuxvpnlauncher.py b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
index 8747daa6..1f0813e0 100644
--- a/src/leap/bitmask/services/eip/linuxvpnlauncher.py
+++ b/src/leap/bitmask/services/eip/linuxvpnlauncher.py
@@ -25,7 +25,6 @@ import sys
import time
from leap.bitmask.config import flags
-from leap.bitmask.util import privilege_policies
from leap.bitmask.util.privilege_policies import LinuxPolicyChecker
from leap.common.files import which
from leap.bitmask.services.eip.vpnlauncher import VPNLauncher
@@ -36,6 +35,8 @@ from leap.bitmask.util import first
logger = logging.getLogger(__name__)
+COM = commands
+
class EIPNoPolkitAuthAgentAvailable(VPNLauncherException):
pass
@@ -64,10 +65,10 @@ def _is_auth_agent_running():
"""
# the [x] thing is to avoid grep match itself
polkit_options = [
- 'ps aux | grep polkit-[g]nome-authentication-agent-1',
- 'ps aux | grep polkit-[k]de-authentication-agent-1',
- 'ps aux | grep polkit-[m]ate-authentication-agent-1',
- 'ps aux | grep [l]xpolkit'
+ 'ps aux | grep "polkit-[g]nome-authentication-agent-1"',
+ 'ps aux | grep "polkit-[k]de-authentication-agent-1"',
+ 'ps aux | grep "polkit-[m]ate-authentication-agent-1"',
+ 'ps aux | grep "[l]xpolkit"'
]
is_running = [commands.getoutput(cmd) for cmd in polkit_options]
return any(is_running)
@@ -85,35 +86,39 @@ def _try_to_launch_agent():
# will do "sh -c 'foo'", so if we do not quoute it we'll end
# up with a invocation to the python interpreter. And that
# is bad.
+ logger.debug("Trying to launch polkit agent")
subprocess.call(["python -m leap.bitmask.util.polkit_agent"],
shell=True, env=env)
except Exception as exc:
logger.exception(exc)
+SYSTEM_CONFIG = "/etc/leap"
+leapfile = lambda f: "%s/%s" % (SYSTEM_CONFIG, f)
+
+
class LinuxVPNLauncher(VPNLauncher):
PKEXEC_BIN = 'pkexec'
- OPENVPN_BIN = 'openvpn'
- OPENVPN_BIN_PATH = os.path.join(
- get_path_prefix(), "..", "apps", "eip", OPENVPN_BIN)
-
- SYSTEM_CONFIG = "/etc/leap"
- UP_DOWN_FILE = "resolv-update"
- UP_DOWN_PATH = "%s/%s" % (SYSTEM_CONFIG, UP_DOWN_FILE)
+ BITMASK_ROOT = "/usr/sbin/bitmask-root"
# 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_BASE = "/usr/lib/openvpn/"
- OPENVPN_DOWN_ROOT_FILE = "openvpn-plugin-down-root.so"
- OPENVPN_DOWN_ROOT_PATH = "%s/%s" % (
- OPENVPN_DOWN_ROOT_BASE,
- OPENVPN_DOWN_ROOT_FILE)
-
- UP_SCRIPT = DOWN_SCRIPT = UP_DOWN_PATH
- UPDOWN_FILES = (UP_DOWN_PATH,)
+ if flags.STANDALONE:
+ OPENVPN_BIN_PATH = "/usr/sbin/leap-openvpn"
+ else:
+ OPENVPN_BIN_PATH = "/usr/sbin/openvpn"
+
POLKIT_PATH = LinuxPolicyChecker.get_polkit_path()
- OTHER_FILES = (POLKIT_PATH, )
+
+ if flags.STANDALONE:
+ RESOLVCONF_BIN_PATH = "/usr/local/sbin/leap-resolvconf"
+ else:
+ # this only will work with debian/ubuntu distros.
+ RESOLVCONF_BIN_PATH = "/sbin/resolvconf"
+
+ # XXX openvpn binary TOO
+ OTHER_FILES = (POLKIT_PATH, BITMASK_ROOT, OPENVPN_BIN_PATH,
+ RESOLVCONF_BIN_PATH)
@classmethod
def maybe_pkexec(kls):
@@ -131,7 +136,7 @@ class LinuxVPNLauncher(VPNLauncher):
if _is_pkexec_in_system():
if not _is_auth_agent_running():
_try_to_launch_agent()
- time.sleep(0.5)
+ time.sleep(2)
if _is_auth_agent_running():
pkexec_possibilities = which(kls.PKEXEC_BIN)
leap_assert(len(pkexec_possibilities) > 0,
@@ -146,28 +151,6 @@ class LinuxVPNLauncher(VPNLauncher):
raise EIPNoPkexecAvailable()
@classmethod
- def missing_other_files(kls):
- """
- 'Extend' the VPNLauncher's missing_other_files to check if the polkit
- files is outdated, in the case of an standalone bundle.
- If the polkit file that is in OTHER_FILES exists but is not up to date,
- it is added to the missing list.
-
- :returns: a list of missing files
- :rtype: list of str
- """
- # we use `super` in order to send the class to use
- missing = super(LinuxVPNLauncher, kls).missing_other_files()
-
- if flags.STANDALONE:
- polkit_file = LinuxPolicyChecker.get_polkit_path()
- if polkit_file not in missing:
- if privilege_policies.is_policy_outdated(kls.OPENVPN_BIN_PATH):
- missing.append(polkit_file)
-
- return missing
-
- @classmethod
def get_vpn_command(kls, eipconfig, providerconfig, socket_host,
socket_port="unix", openvpn_verb=1):
"""
@@ -198,6 +181,10 @@ class LinuxVPNLauncher(VPNLauncher):
command = super(LinuxVPNLauncher, kls).get_vpn_command(
eipconfig, providerconfig, socket_host, socket_port, openvpn_verb)
+ command.insert(0, kls.BITMASK_ROOT)
+ command.insert(1, "openvpn")
+ command.insert(2, "start")
+
pkexec = kls.maybe_pkexec()
if pkexec:
command.insert(0, first(pkexec))
@@ -205,26 +192,44 @@ class LinuxVPNLauncher(VPNLauncher):
return command
@classmethod
- def cmd_for_missing_scripts(kls, frompath, pol_file):
+ def cmd_for_missing_scripts(kls, frompath):
"""
Returns a sh script that can copy the missing files.
- :param frompath: The path where the up/down scripts live
+ :param frompath: The path where the helper files live
:type frompath: str
- :param pol_file: The path where the dynamically generated
- policy file lives
- :type pol_file: str
:rtype: str
"""
- to = kls.SYSTEM_CONFIG
+ # no system config for now
+ # sys_config = kls.SYSTEM_CONFIG
+ (polkit_file, openvpn_bin_file,
+ bitmask_root_file, resolvconf_bin_file) = map(
+ lambda p: os.path.split(p)[-1],
+ (kls.POLKIT_PATH, kls.OPENVPN_BIN_PATH,
+ kls.BITMASK_ROOT, kls.RESOLVCONF_BIN_PATH))
cmd = '#!/bin/sh\n'
- cmd += 'mkdir -p "%s"\n' % (to, )
- cmd += 'cp "%s/%s" "%s"\n' % (frompath, kls.UP_DOWN_FILE, to)
- cmd += 'cp "%s" "%s"\n' % (pol_file, kls.POLKIT_PATH)
+ cmd += 'mkdir -p /usr/local/sbin\n'
+
+ cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, polkit_file),
+ kls.POLKIT_PATH)
cmd += 'chmod 644 "%s"\n' % (kls.POLKIT_PATH, )
+ cmd += 'cp "%s" "%s"\n' % (os.path.join(frompath, bitmask_root_file),
+ kls.BITMASK_ROOT)
+ cmd += 'chmod 744 "%s"\n' % (kls.BITMASK_ROOT, )
+
+ if flags.STANDALONE:
+ cmd += 'cp "%s" "%s"\n' % (
+ os.path.join(frompath, openvpn_bin_file),
+ kls.OPENVPN_BIN_PATH)
+ cmd += 'chmod 744 "%s"\n' % (kls.POLKIT_PATH, )
+
+ cmd += 'cp "%s" "%s"\n' % (
+ os.path.join(frompath, resolvconf_bin_file),
+ kls.RESOLVCONF_BIN_PATH)
+ cmd += 'chmod 744 "%s"\n' % (kls.POLKIT_PATH, )
return cmd
@classmethod
diff --git a/src/leap/bitmask/services/eip/vpnlauncher.py b/src/leap/bitmask/services/eip/vpnlauncher.py
index 99cae7f9..dcb48e8a 100644
--- a/src/leap/bitmask/services/eip/vpnlauncher.py
+++ b/src/leap/bitmask/services/eip/vpnlauncher.py
@@ -25,14 +25,12 @@ import stat
from abc import ABCMeta, abstractmethod
from functools import partial
-from leap.bitmask.config import flags
from leap.bitmask.config.leapsettings import LeapSettings
from leap.bitmask.config.providerconfig import ProviderConfig
+from leap.bitmask.platform_init import IS_LINUX
from leap.bitmask.services.eip.eipconfig import EIPConfig, VPNGatewaySelector
-from leap.bitmask.util import first
-from leap.bitmask.util import get_path_prefix
from leap.common.check import leap_assert, leap_assert_type
-from leap.common.files import which
+
logger = logging.getLogger(__name__)
@@ -107,10 +105,43 @@ class VPNLauncher(object):
@classmethod
@abstractmethod
+ def get_gateways(kls, eipconfig, providerconfig):
+ """
+ Return the selected gateways for a given provider, looking at the EIP
+ config file.
+
+ :param eipconfig: eip configuration object
+ :type eipconfig: EIPConfig
+
+ :param providerconfig: provider specific configuration
+ :type providerconfig: ProviderConfig
+
+ :rtype: list
+ """
+ gateways = []
+ leap_settings = LeapSettings()
+ domain = providerconfig.get_domain()
+ gateway_conf = leap_settings.get_selected_gateway(domain)
+
+ if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
+ gateway_selector = VPNGatewaySelector(eipconfig)
+ gateways = gateway_selector.get_gateways()
+ else:
+ gateways = [gateway_conf]
+
+ if not gateways:
+ logger.error('No gateway was found!')
+ raise VPNLauncherException('No gateway was found!')
+
+ logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
+ return gateways
+
+ @classmethod
+ @abstractmethod
def get_vpn_command(kls, eipconfig, providerconfig,
socket_host, socket_port, openvpn_verb=1):
"""
- Returns the platform dependant vpn launching command
+ Return the platform-dependant vpn command for launching openvpn.
Might raise:
OpenVPNNotFoundException,
@@ -134,16 +165,19 @@ class VPNLauncher(object):
leap_assert_type(eipconfig, EIPConfig)
leap_assert_type(providerconfig, ProviderConfig)
- kwargs = {}
- if flags.STANDALONE:
- kwargs['path_extension'] = os.path.join(
- get_path_prefix(), "..", "apps", "eip")
-
- openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs)
- if len(openvpn_possibilities) == 0:
+ # XXX this still has to be changed on osx and windows accordingly
+ #kwargs = {}
+ #openvpn_possibilities = which(kls.OPENVPN_BIN, **kwargs)
+ #if not openvpn_possibilities:
+ #raise OpenVPNNotFoundException()
+ #openvpn = first(openvpn_possibilities)
+ # -----------------------------------------
+ if not os.path.isfile(kls.OPENVPN_BIN_PATH):
+ logger.warning("Could not find openvpn bin in path %s" % (
+ kls.OPENVPN_BIN_PATH))
raise OpenVPNNotFoundException()
- openvpn = first(openvpn_possibilities)
+ openvpn = kls.OPENVPN_BIN_PATH
args = []
args += [
@@ -154,22 +188,7 @@ class VPNLauncher(object):
if openvpn_verb is not None:
args += ['--verb', '%d' % (openvpn_verb,)]
- gateways = []
- leap_settings = LeapSettings()
- domain = providerconfig.get_domain()
- gateway_conf = leap_settings.get_selected_gateway(domain)
-
- if gateway_conf == leap_settings.GATEWAY_AUTOMATIC:
- gateway_selector = VPNGatewaySelector(eipconfig)
- gateways = gateway_selector.get_gateways()
- else:
- gateways = [gateway_conf]
-
- if not gateways:
- logger.error('No gateway was found!')
- raise VPNLauncherException('No gateway was found!')
-
- logger.debug("Using gateways ips: {0}".format(', '.join(gateways)))
+ gateways = kls.get_gateways(eipconfig, providerconfig)
for gw in gateways:
args += ['--remote', gw, '1194', 'udp']
@@ -177,11 +196,6 @@ class VPNLauncher(object):
args += [
'--client',
'--dev', 'tun',
- ##############################################################
- # persist-tun makes ping-restart fail because it leaves a
- # broken routing table
- ##############################################################
- # '--persist-tun',
'--persist-key',
'--tls-client',
'--remote-cert-tls',
@@ -194,15 +208,6 @@ class VPNLauncher(object):
user = getpass.getuser()
- ##############################################################
- # The down-root plugin fails in some situations, so we don't
- # drop privs for the time being
- ##############################################################
- # args += [
- # '--user', user,
- # '--group', grp.getgrgid(os.getgroups()[-1]).gr_name
- # ]
-
if socket_port == "unix": # that's always the case for linux
args += [
'--management-client-user', user
@@ -226,20 +231,6 @@ class VPNLauncher(object):
'--down', '\"%s\"' % (kls.DOWN_SCRIPT,)
]
- ###########################################################
- # For the time being we are disabling the usage of the
- # down-root plugin, because it doesn't quite work as
- # expected (i.e. it doesn't run route -del as root
- # when finishing, so it fails to properly
- # restart/quit)
- ###########################################################
- # if _has_updown_scripts(kls.OPENVPN_DOWN_PLUGIN):
- # args += [
- # '--plugin', kls.OPENVPN_DOWN_ROOT,
- # '\'%s\'' % kls.DOWN_SCRIPT # for OSX
- # '\'script_type=down %s\'' % kls.DOWN_SCRIPT # for Linux
- # ]
-
args += [
'--cert', eipconfig.get_client_cert_path(providerconfig),
'--key', eipconfig.get_client_cert_path(providerconfig),
@@ -271,13 +262,18 @@ class VPNLauncher(object):
:rtype: list
"""
- leap_assert(kls.UPDOWN_FILES is not None,
- "Need to define UPDOWN_FILES for this particular "
- "launcher 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]
+ # FIXME
+ # XXX remove method when we ditch UPDOWN in osx and win too
+ if IS_LINUX:
+ return []
+ else:
+ leap_assert(kls.UPDOWN_FILES is not None,
+ "Need to define UPDOWN_FILES for this particular "
+ "launcher 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):
diff --git a/src/leap/bitmask/services/eip/vpnprocess.py b/src/leap/bitmask/services/eip/vpnprocess.py
index c7b8071c..1559ea8b 100644
--- a/src/leap/bitmask/services/eip/vpnprocess.py
+++ b/src/leap/bitmask/services/eip/vpnprocess.py
@@ -21,6 +21,7 @@ import logging
import os
import shutil
import socket
+import subprocess
import sys
from itertools import chain, repeat
@@ -36,10 +37,11 @@ except ImportError:
from leap.bitmask.config import flags
from leap.bitmask.config.providerconfig import ProviderConfig
from leap.bitmask.services.eip import get_vpn_launcher
+from leap.bitmask.services.eip import linuxvpnlauncher
from leap.bitmask.services.eip.eipconfig import EIPConfig
from leap.bitmask.services.eip.udstelnet import UDSTelnet
from leap.bitmask.util import first
-from leap.bitmask.platform_init import IS_MAC
+from leap.bitmask.platform_init import IS_MAC, IS_LINUX
from leap.common.check import leap_assert, leap_assert_type
logger = logging.getLogger(__name__)
@@ -66,9 +68,8 @@ class VPNObserver(object):
'Network is unreachable (code=101)',),
'PROCESS_RESTART_TLS': (
"SIGUSR1[soft,tls-error]",),
- # Let ping-restart work as it should
- # 'PROCESS_RESTART_PING': (
- # "SIGUSR1[soft,ping-restart]",),
+ 'PROCESS_RESTART_PING': (
+ "SIGTERM[soft,ping-restart]",),
'INITIALIZATION_COMPLETED': (
"Initialization Sequence Completed",),
}
@@ -159,6 +160,8 @@ class VPN(object):
self._signaler = kwargs['signaler']
self._openvpn_verb = flags.OPENVPN_VERBOSITY
+ self._user_stopped = False
+
def start(self, *args, **kwargs):
"""
Starts the openvpn subprocess.
@@ -170,6 +173,7 @@ class VPN(object):
:type kwargs: dict
"""
logger.debug('VPN: start')
+ self._user_stopped = False
self._stop_pollers()
kwargs['openvpn_verb'] = self._openvpn_verb
kwargs['signaler'] = self._signaler
@@ -181,6 +185,15 @@ class VPN(object):
logger.info("Another vpn process is running. Will try to stop it.")
vpnproc.stop_if_already_running()
+ # we try to bring the firewall up
+ if IS_LINUX:
+ gateways = vpnproc.getGateways()
+ firewall_up = self._launch_firewall(gateways)
+ if not firewall_up:
+ logger.error("Could not bring firewall up, "
+ "aborting openvpn launch.")
+ return
+
cmd = vpnproc.getCommand()
env = os.environ
for key, val in vpnproc.vpn_env.items():
@@ -198,9 +211,37 @@ class VPN(object):
self._pollers.extend(poll_list)
self._start_pollers()
+ def _launch_firewall(self, gateways):
+ """
+ Launch the firewall using the privileged wrapper.
+
+ :param gateways:
+ :type gateways: list
+
+ :returns: True if the exitcode of calling the root helper in a
+ subprocess is 0.
+ :rtype: bool
+ """
+ # XXX could check for wrapper existence, check it's root owned etc.
+ # XXX could check that the iptables rules are in place.
+
+ BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT
+ exitCode = subprocess.call(["pkexec",
+ BM_ROOT, "firewall", "start"] + gateways)
+ return True if exitCode is 0 else False
+
+ def _tear_down_firewall(self):
+ """
+ Tear the firewall down using the privileged wrapper.
+ """
+ BM_ROOT = linuxvpnlauncher.LinuxVPNLauncher.BITMASK_ROOT
+ exitCode = subprocess.call(["pkexec",
+ BM_ROOT, "firewall", "stop"])
+ return True if exitCode is 0 else False
+
def _kill_if_left_alive(self, tries=0):
"""
- Check if the process is still alive, and sends a
+ Check if the process is still alive, and send a
SIGKILL after a timeout period.
:param tries: counter of tries, used in recursion
@@ -210,6 +251,15 @@ class VPN(object):
while tries < self.TERMINATE_MAXTRIES:
if self._vpnproc.transport.pid is None:
logger.debug("Process has been happily terminated.")
+
+ # we try to tear the firewall down
+ if IS_LINUX and self._user_stopped:
+ firewall_down = self._tear_down_firewall()
+ if firewall_down:
+ logger.debug("Firewall down")
+ else:
+ logger.warning("Could not tear firewall down")
+
return
else:
logger.debug("Process did not die, waiting...")
@@ -246,6 +296,10 @@ class VPN(object):
from twisted.internet import reactor
self._stop_pollers()
+ # We assume that the only valid shutodowns are initiated
+ # by an user action.
+ self._user_stopped = shutdown
+
# First we try to be polite and send a SIGTERM...
if self._vpnproc:
self._sentterm = True
@@ -253,12 +307,17 @@ class VPN(object):
# ...but we also trigger a countdown to be unpolite
# if strictly needed.
-
- # XXX Watch out! This will fail NOW since we are running
- # openvpn as root as a workaround for some connection issues.
reactor.callLater(
self.TERMINATE_WAIT, self._kill_if_left_alive)
+ if shutdown:
+ if IS_LINUX and self._user_stopped:
+ firewall_down = self._tear_down_firewall()
+ if firewall_down:
+ logger.debug("Firewall down")
+ else:
+ logger.warning("Could not tear firewall down")
+
def _start_pollers(self):
"""
Iterate through the registered observers
@@ -830,9 +889,20 @@ class VPNProcess(protocol.ProcessProtocol, VPNManager):
if not isinstance(c, str):
command[i] = c.encode(encoding)
- logger.debug("Running VPN with command: {0}".format(command))
+ logger.debug("Running VPN with command: ")
+ logger.debug("{0}".format(" ".join(command)))
return command
+ def getGateways(self):
+ """
+ Get the gateways from the appropiate launcher.
+
+ :rtype: list
+ """
+ gateways = self._launcher.get_gateways(
+ self._eipconfig, self._providerconfig)
+ return gateways
+
# shutdown
def killProcess(self):