# -*- coding: utf-8 -*- # launcher.py # Copyright (C) 2013-2017 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-independent VPN launcher interface. """ import getpass import os import stat from twisted.logger import Logger from abc import ABCMeta, abstractmethod from leap.bitmask.system import IS_MAC, IS_SNAP from leap.bitmask.vpn.utils import force_eval log = Logger() class VPNLauncherException(Exception): pass class OpenVPNNotFoundException(VPNLauncherException): pass def _has_updown_scripts(path, warn=True): """ Checks the existence of the up/down scripts and its exec bit if applicable. :param path: the path to be checked :type path: str :param warn: whether we should log the absence :type warn: bool :rtype: bool """ is_file = os.path.isfile(path) if warn and not is_file: log.error('Could not find up/down script %s. ' 'Might produce DNS leaks.' % (path,)) # XXX check if applies in win is_exe = False try: is_exe = (stat.S_IXUSR & os.stat(path)[stat.ST_MODE] != 0) except OSError as e: log.warn("%s" % (e,)) if warn and not is_exe: log.error('Up/down script %s is not executable. ' 'Might produce DNS leaks.' % (path,)) return is_file and is_exe def _has_other_files(path, warn=True): """ Check the existence of other important files. :param path: the path to be checked :type path: str :param warn: whether we should log the absence :type warn: bool :rtype: bool """ is_file = os.path.isfile(path) if warn and not is_file: log.warn('Could not find file during checks: %s. ' % ( path,)) return is_file class VPNLauncher(object): """ Abstract launcher class """ __metaclass__ = ABCMeta UPDOWN_FILES = None OTHER_FILES = None UP_SCRIPT = None DOWN_SCRIPT = None PREFERRED_PORTS = ("443", "80", "53", "1194") @classmethod @abstractmethod def get_vpn_command(kls, vpnconfig, providerconfig, socket_host, socket_port, remotes, openvpn_verb=1): """ Return the platform-dependant vpn command for launching openvpn. Might raise: OpenVPNNotFoundException, VPNLauncherException. :param vpnconfig: vpn configuration object :type vpnconfig: VPNConfig :param providerconfig: provider specific configuration :type providerconfig: ProviderConfig :param socket_host: either socket path (unix) or socket IP :type socket_host: str :param socket_port: either string "unix" if it's a unix socket, or port otherwise :type socket_port: str :param openvpn_verb: the openvpn verbosity wanted :type openvpn_verb: int :return: A VPN command ready to be launched. :rtype: list """ openvpn_path = force_eval(kls.OPENVPN_BIN_PATH) if not os.path.isfile(openvpn_path): log.warn('Could not find openvpn bin in path %s' % ( openvpn_path)) err = OpenVPNNotFoundException() raise err args = [] args += [ '--nobind' ] if openvpn_verb is not None: args += ['--verb', '%d' % (openvpn_verb,)] gateways = remotes for ip, port in gateways: # we're hardcoding tcpv4 for now, but that seems to break in # the version of openvpn shipped with the snaps. if IS_SNAP: args += ['--remote', ip, port, 'tcp'] else: args += ['--remote', ip, port, 'tcp4'] args += [ '--client', '--tls-client', '--remote-cert-tls', 'server', ] openvpn_configuration = vpnconfig.get_openvpn_configuration() for key, value in openvpn_configuration.items(): if type(value) is bool: if value: args += ['--%s' % (key,)] else: args += ['--%s' % (key,), value] user = getpass.getuser() if socket_port == "unix": # that's always the case for linux args += [ '--management-client-user', user ] args += [ '--management-signal', '--management', socket_host, socket_port, '--ca', providerconfig.get_ca_cert_path(), '--cert', vpnconfig.get_client_cert_path(providerconfig), '--key', vpnconfig.get_client_cert_path(providerconfig) ] # TODO -- get platform specific launcher args if IS_MAC: args += [ '--script-security', '2'] command_and_args = [openvpn_path] + args return command_and_args