summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkali <kali@leap.se>2012-12-12 04:26:16 +0900
committerkali <kali@leap.se>2012-12-12 04:26:16 +0900
commitb262ac8bae66c391aa249e93268db9935f1c475f (patch)
tree3d39b48495fbcfbfeab776af07558c345f4161cb
parentcd78d9d552977e8f8fb12b6a2ff56fda9c37bf35 (diff)
parent04d423e2a89034dfb86fe305108162fd2a696079 (diff)
Merge branch 'feature/openvpn-ciphers' into develop
-rw-r--r--pkg/requirements.pip1
-rw-r--r--src/leap/base/auth.py16
-rw-r--r--src/leap/base/config.py94
-rw-r--r--src/leap/base/network.py20
-rw-r--r--src/leap/base/pluggableconfig.py17
-rw-r--r--src/leap/base/tests/test_providers.py8
-rw-r--r--src/leap/baseapp/leap_app.py2
-rw-r--r--src/leap/baseapp/mainwindow.py9
-rw-r--r--src/leap/baseapp/network.py7
-rw-r--r--src/leap/baseapp/systray.py2
-rw-r--r--src/leap/eip/checks.py9
-rw-r--r--src/leap/eip/config.py69
-rw-r--r--src/leap/eip/eipconnection.py238
-rw-r--r--src/leap/eip/openvpnconnection.py472
-rw-r--r--src/leap/eip/specs.py7
-rw-r--r--src/leap/eip/tests/test_checks.py6
-rw-r--r--src/leap/eip/tests/test_config.py93
-rw-r--r--src/leap/eip/tests/test_eipconnection.py9
-rw-r--r--src/leap/util/fileutil.py5
-rw-r--r--src/leap/util/misc.py16
20 files changed, 665 insertions, 435 deletions
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
index e5338744..5664aa5e 100644
--- a/pkg/requirements.pip
+++ b/pkg/requirements.pip
@@ -11,3 +11,4 @@ jsonschema
srp
pycrypto
keyring
+python-dateutil
diff --git a/src/leap/base/auth.py b/src/leap/base/auth.py
index 50533278..73856bb0 100644
--- a/src/leap/base/auth.py
+++ b/src/leap/base/auth.py
@@ -10,6 +10,7 @@ from PyQt4 import QtCore
from leap.base import constants as baseconstants
from leap.crypto import leapkeyring
+from leap.util.misc import null_check
from leap.util.web import get_https_domain_and_port
logger = logging.getLogger(__name__)
@@ -26,11 +27,6 @@ one if not.
"""
-class ImproperlyConfigured(Exception):
- """
- """
-
-
class SRPAuthenticationError(Exception):
"""
exception raised
@@ -38,14 +34,6 @@ class SRPAuthenticationError(Exception):
"""
-def null_check(value, value_name):
- try:
- assert value is not None
- except AssertionError:
- raise ImproperlyConfigured(
- "%s parameter cannot be None" % value_name)
-
-
safe_unhexlify = lambda x: binascii.unhexlify(x) \
if (len(x) % 2 == 0) else binascii.unhexlify('0' + x)
@@ -64,7 +52,7 @@ class LeapSRPRegister(object):
hashfun=srp.SHA256,
ng_constant=srp.NG_1024):
- null_check(provider, provider)
+ null_check(provider, "provider")
self.schema = schema
diff --git a/src/leap/base/config.py b/src/leap/base/config.py
index 0255fbab..b307ad05 100644
--- a/src/leap/base/config.py
+++ b/src/leap/base/config.py
@@ -5,11 +5,12 @@ import grp
import json
import logging
import socket
-import tempfile
+import time
import os
logger = logging.getLogger(name=__name__)
+from dateutil import parser as dateparser
import requests
from leap.base import exceptions
@@ -125,17 +126,43 @@ class JSONLeapConfig(BaseLeapConfig):
# mandatory baseconfig interface
- def save(self, to=None):
- if to is None:
- to = self.filename
- folder, filename = os.path.split(to)
- if folder and not os.path.isdir(folder):
- mkdir_p(folder)
- self._config.serialize(to)
+ def save(self, to=None, force=False):
+ """
+ force param will skip the dirty check.
+ :type force: bool
+ """
+ # XXX this force=True does not feel to right
+ # but still have to look for a better way
+ # of dealing with dirtiness and the
+ # trick of loading remote config only
+ # when newer.
+
+ if force:
+ do_save = True
+ else:
+ do_save = self._config.is_dirty()
+
+ if do_save:
+ if to is None:
+ to = self.filename
+ folder, filename = os.path.split(to)
+ if folder and not os.path.isdir(folder):
+ mkdir_p(folder)
+ self._config.serialize(to)
+ return True
+
+ else:
+ return False
+
+ def load(self, fromfile=None, from_uri=None, fetcher=None,
+ force_download=False, verify=False):
- def load(self, fromfile=None, from_uri=None, fetcher=None, verify=False):
if from_uri is not None:
- fetched = self.fetch(from_uri, fetcher=fetcher, verify=verify)
+ fetched = self.fetch(
+ from_uri,
+ fetcher=fetcher,
+ verify=verify,
+ force_dl=force_download)
if fetched:
return
if fromfile is None:
@@ -146,33 +173,64 @@ class JSONLeapConfig(BaseLeapConfig):
logger.error('tried to load config from non-existent path')
logger.error('Not Found: %s', fromfile)
- def fetch(self, uri, fetcher=None, verify=True):
+ def fetch(self, uri, fetcher=None, verify=True, force_dl=False):
if not fetcher:
fetcher = self.fetcher
+
logger.debug('verify: %s', verify)
logger.debug('uri: %s', uri)
- request = fetcher.get(uri, verify=verify)
- # XXX should send a if-modified-since header
- # XXX get 404, ...
- # and raise a UnableToFetch...
+ rargs = (uri, )
+ rkwargs = {'verify': verify}
+ headers = {}
+
+ curmtime = self.get_mtime() if not force_dl else None
+ if curmtime:
+ logger.debug('requesting with if-modified-since %s' % curmtime)
+ headers['if-modified-since'] = curmtime
+ rkwargs['headers'] = headers
+
+ #request = fetcher.get(uri, verify=verify)
+ request = fetcher.get(*rargs, **rkwargs)
request.raise_for_status()
- fd, fname = tempfile.mkstemp(suffix=".json")
- if request.json:
- self._config.load(json.dumps(request.json))
+ if request.status_code == 304:
+ logger.debug('...304 Not Changed')
+ # On this point, we have to assume that
+ # we HAD the filename. If that filename is corruct,
+ # we should enforce a force_download in the load
+ # method above.
+ self._config.load(fromfile=self.filename)
+ return True
+ if request.json:
+ mtime = None
+ last_modified = request.headers.get('last-modified', None)
+ if last_modified:
+ _mtime = dateparser.parse(last_modified)
+ mtime = int(_mtime.strftime("%s"))
+ self._config.load(json.dumps(request.json), mtime=mtime)
+ self._config.set_dirty()
else:
# not request.json
# might be server did not announce content properly,
# let's try deserializing all the same.
try:
self._config.load(request.content)
+ self._config.set_dirty()
except ValueError:
raise eipexceptions.LeapBadConfigFetchedError
return True
+ def get_mtime(self):
+ try:
+ _mtime = os.stat(self.filename)[8]
+ mtime = time.strftime("%c GMT", time.gmtime(_mtime))
+ return mtime
+ except OSError:
+ return None
+
def get_config(self):
return self._config.config
diff --git a/src/leap/base/network.py b/src/leap/base/network.py
index 3aba3f61..765d8ea0 100644
--- a/src/leap/base/network.py
+++ b/src/leap/base/network.py
@@ -3,10 +3,11 @@ from __future__ import (print_function)
import logging
import threading
-from leap.eip.config import get_eip_gateway
+from leap.eip import config as eipconfig
from leap.base.checks import LeapNetworkChecker
from leap.base.constants import ROUTE_CHECK_INTERVAL
from leap.base.exceptions import TunnelNotDefaultRouteError
+from leap.util.misc import null_check
from leap.util.coroutines import (launch_thread, process_events)
from time import sleep
@@ -27,11 +28,20 @@ class NetworkCheckerThread(object):
lambda exc: logger.error("%s", exc.message))
self.shutdown = threading.Event()
- # XXX get provider_gateway and pass it to checker
- # see in eip.config for function
- # #718
+ # XXX get provider passed here
+ provider = kwargs.pop('provider', None)
+ null_check(provider, 'provider')
+
+ eipconf = eipconfig.EIPConfig(domain=provider)
+ eipconf.load()
+ eipserviceconf = eipconfig.EIPServiceConfig(domain=provider)
+ eipserviceconf.load()
+
+ gw = eipconfig.get_eip_gateway(
+ eipconfig=eipconf,
+ eipserviceconfig=eipserviceconf)
self.checker = LeapNetworkChecker(
- provider_gw=get_eip_gateway())
+ provider_gw=gw)
def start(self):
self.process_handle = self._launch_recurrent_network_checks(
diff --git a/src/leap/base/pluggableconfig.py b/src/leap/base/pluggableconfig.py
index b8615ad8..34c1e060 100644
--- a/src/leap/base/pluggableconfig.py
+++ b/src/leap/base/pluggableconfig.py
@@ -180,6 +180,8 @@ class PluggableConfig(object):
self.adaptors = adaptors
self.types = types
self._format = format
+ self.mtime = None
+ self.dirty = False
@property
def option_dict(self):
@@ -319,6 +321,13 @@ class PluggableConfig(object):
serializable = self.prep_value(config)
adaptor.write(serializable, filename)
+ if self.mtime:
+ self.touch_mtime(filename)
+
+ def touch_mtime(self, filename):
+ mtime = self.mtime
+ os.utime(filename, (mtime, mtime))
+
def deserialize(self, string=None, fromfile=None, format=None):
"""
load configuration from a file or string
@@ -364,6 +373,12 @@ class PluggableConfig(object):
content = _try_deserialize()
return content
+ def set_dirty(self):
+ self.dirty = True
+
+ def is_dirty(self):
+ return self.dirty
+
def load(self, *args, **kwargs):
"""
load from string or file
@@ -373,6 +388,8 @@ class PluggableConfig(object):
"""
string = args[0] if args else None
fromfile = kwargs.get("fromfile", None)
+ mtime = kwargs.pop("mtime", None)
+ self.mtime = mtime
content = None
# start with defaults, so we can
diff --git a/src/leap/base/tests/test_providers.py b/src/leap/base/tests/test_providers.py
index 15c4ed58..d9604fab 100644
--- a/src/leap/base/tests/test_providers.py
+++ b/src/leap/base/tests/test_providers.py
@@ -8,7 +8,7 @@ import os
import jsonschema
-from leap import __branding as BRANDING
+#from leap import __branding as BRANDING
from leap.testing.basetest import BaseLeapTest
from leap.base import providers
@@ -33,8 +33,8 @@ class TestLeapProviderDefinition(BaseLeapTest):
self.domain = "testprovider.example.org"
self.definition = providers.LeapProviderDefinition(
domain=self.domain)
- self.definition.save()
- self.definition.load()
+ self.definition.save(force=True)
+ self.definition.load() # why have to load after save??
self.config = self.definition.config
def tearDown(self):
@@ -61,7 +61,7 @@ class TestLeapProviderDefinition(BaseLeapTest):
def test_provider_dump(self):
# check a good provider definition is dumped to disk
self.testfile = self.get_tempfile('test.json')
- self.definition.save(to=self.testfile)
+ self.definition.save(to=self.testfile, force=True)
deserialized = json.load(open(self.testfile, 'rb'))
self.maxDiff = None
self.assertEqual(deserialized, EXPECTED_DEFAULT_CONFIG)
diff --git a/src/leap/baseapp/leap_app.py b/src/leap/baseapp/leap_app.py
index e41cff40..4d3aebd6 100644
--- a/src/leap/baseapp/leap_app.py
+++ b/src/leap/baseapp/leap_app.py
@@ -148,6 +148,6 @@ class MainWindowMixin(object):
# in conductor
# XXX send signal instead?
logger.info('Shutting down')
- self.conductor.cleanup(shutdown=True)
+ self.conductor.disconnect(shutdown=True)
logger.info('Exiting. Bye.')
QtGui.qApp.quit()
diff --git a/src/leap/baseapp/mainwindow.py b/src/leap/baseapp/mainwindow.py
index 8d61bf5c..65c30bff 100644
--- a/src/leap/baseapp/mainwindow.py
+++ b/src/leap/baseapp/mainwindow.py
@@ -61,10 +61,15 @@ class LeapWindow(QtGui.QMainWindow,
logger.debug('provider: %s', self.provider_domain)
logger.debug('eip_username: %s', self.eip_username)
+ provider = self.provider_domain
EIPConductorAppMixin.__init__(
- self, opts=opts, provider=self.provider_domain)
+ self, opts=opts, provider=provider)
StatusAwareTrayIconMixin.__init__(self)
- NetworkCheckerAppMixin.__init__(self)
+
+ # XXX network checker should probably not
+ # trigger run_checks on init... but wait
+ # for ready signal instead...
+ NetworkCheckerAppMixin.__init__(self, provider=provider)
MainWindowMixin.__init__(self)
geom_key = "DebugGeometry" if self.debugmode else "Geometry"
diff --git a/src/leap/baseapp/network.py b/src/leap/baseapp/network.py
index 077d5164..3e57490d 100644
--- a/src/leap/baseapp/network.py
+++ b/src/leap/baseapp/network.py
@@ -17,11 +17,14 @@ class NetworkCheckerAppMixin(object):
"""
def __init__(self, *args, **kwargs):
+ provider = kwargs.pop('provider', None)
self.network_checker = NetworkCheckerThread(
error_cb=self.networkError.emit,
- debug=self.debugmode)
+ debug=self.debugmode,
+ provider=provider)
- # XXX move run_checks to slot
+ # XXX move run_checks to slot -- this definitely
+ # cannot start on init!!!
self.network_checker.run_checks()
@QtCore.pyqtSlot(object)
diff --git a/src/leap/baseapp/systray.py b/src/leap/baseapp/systray.py
index 49f044aa..52060ae2 100644
--- a/src/leap/baseapp/systray.py
+++ b/src/leap/baseapp/systray.py
@@ -217,6 +217,8 @@ class StatusAwareTrayIconMixin(object):
updates icon, according to the openvpn status change.
"""
icon_name = self.conductor.get_icon_name()
+ if not icon_name:
+ return
# XXX refactor. Use QStateMachine
diff --git a/src/leap/eip/checks.py b/src/leap/eip/checks.py
index 116c535e..8d615b94 100644
--- a/src/leap/eip/checks.py
+++ b/src/leap/eip/checks.py
@@ -427,6 +427,7 @@ class EIPConfigChecker(object):
return True
def fetch_definition(self, skip_download=False,
+ force_download=False,
config=None, uri=None,
domain=None):
"""
@@ -459,6 +460,7 @@ class EIPConfigChecker(object):
self.defaultprovider.save()
def fetch_eip_service_config(self, skip_download=False,
+ force_download=False,
config=None, uri=None, domain=None):
if skip_download:
return True
@@ -469,7 +471,10 @@ class EIPConfigChecker(object):
domain = self.domain or config.get('provider', None)
uri = self._get_eip_service_uri(domain=domain)
- self.eipserviceconfig.load(from_uri=uri, fetcher=self.fetcher)
+ self.eipserviceconfig.load(
+ from_uri=uri,
+ fetcher=self.fetcher,
+ force_download=force_download)
self.eipserviceconfig.save()
def check_complete_eip_config(self, config=None):
@@ -497,7 +502,7 @@ class EIPConfigChecker(object):
return self.eipconfig.exists()
def _dump_default_eipconfig(self):
- self.eipconfig.save()
+ self.eipconfig.save(force=True)
def _get_provider_definition_uri(self, domain=None, path=None):
if domain is None:
diff --git a/src/leap/eip/config.py b/src/leap/eip/config.py
index 42c00380..e40d2785 100644
--- a/src/leap/eip/config.py
+++ b/src/leap/eip/config.py
@@ -1,10 +1,12 @@
import logging
import os
import platform
+import re
import tempfile
from leap import __branding as BRANDING
from leap import certs
+from leap.util.misc import null_check
from leap.util.fileutil import (which, mkdir_p, check_and_fix_urw_only)
from leap.base import config as baseconfig
@@ -53,34 +55,34 @@ def get_socket_path():
socket_path = os.path.join(
tempfile.mkdtemp(prefix="leap-tmp"),
'openvpn.socket')
- logger.debug('socket path: %s', socket_path)
+ #logger.debug('socket path: %s', socket_path)
return socket_path
-def get_eip_gateway(provider=None):
+def get_eip_gateway(eipconfig=None, eipserviceconfig=None):
"""
return the first host in eip service config
that matches the name defined in the eip.json config
file.
"""
- placeholder = "testprovider.example.org"
- # XXX check for null on provider??
+ null_check(eipconfig, "eipconfig")
+ null_check(eipserviceconfig, "eipserviceconfig")
+
+ PLACEHOLDER = "testprovider.example.org"
- eipconfig = EIPConfig(domain=provider)
- eipconfig.load()
conf = eipconfig.config
+ eipsconf = eipserviceconfig.config
primary_gateway = conf.get('primary_gateway', None)
if not primary_gateway:
- return placeholder
+ return PLACEHOLDER
- eipserviceconfig = EIPServiceConfig(domain=provider)
- eipserviceconfig.load()
- eipsconf = eipserviceconfig.get_config()
gateways = eipsconf.get('gateways', None)
+
if not gateways:
logger.error('missing gateways in eip service config')
- return placeholder
+ return PLACEHOLDER
+
if len(gateways) > 0:
for gw in gateways:
name = gw.get('name', None)
@@ -100,6 +102,30 @@ def get_eip_gateway(provider=None):
'gateway list')
+def get_cipher_options(eipserviceconfig=None):
+ """
+ gathers optional cipher options from eip-service config.
+ :param eipserviceconfig: EIPServiceConfig instance
+ """
+ null_check(eipserviceconfig, 'eipserviceconfig')
+ eipsconf = eipserviceconfig.get_config()
+
+ ALLOWED_KEYS = ("auth", "cipher", "tls-cipher")
+ CIPHERS_REGEX = re.compile("[A-Z0-9\-]+")
+ opts = []
+ if 'openvpn_configuration' in eipsconf:
+ config = eipserviceconfig.config.get(
+ "openvpn_configuration", {})
+ for key, value in config.items():
+ if key in ALLOWED_KEYS and value is not None:
+ sanitized_val = CIPHERS_REGEX.findall(value)
+ if len(sanitized_val) != 0:
+ _val = sanitized_val[0]
+ opts.append('--%s' % key)
+ opts.append('%s' % _val)
+ return opts
+
+
def build_ovpn_options(daemon=False, socket_path=None, **kwargs):
"""
build a list of options
@@ -116,6 +142,10 @@ def build_ovpn_options(daemon=False, socket_path=None, **kwargs):
# things from there if present.
provider = kwargs.pop('provider', None)
+ eipconfig = EIPConfig(domain=provider)
+ eipconfig.load()
+ eipserviceconfig = EIPServiceConfig(domain=provider)
+ eipserviceconfig.load()
# get user/group name
# also from config.
@@ -137,11 +167,17 @@ def build_ovpn_options(daemon=False, socket_path=None, **kwargs):
opts.append('--verb')
opts.append("%s" % verbosity)
- # remote
+ # remote ##############################
+ # (server, port, protocol)
+
opts.append('--remote')
- gw = get_eip_gateway(provider=provider)
+
+ gw = get_eip_gateway(eipconfig=eipconfig,
+ eipserviceconfig=eipserviceconfig)
logger.debug('setting eip gateway to %s', gw)
opts.append(str(gw))
+
+ # get port/protocol from eipservice too
opts.append('1194')
#opts.append('80')
opts.append('udp')
@@ -150,6 +186,13 @@ def build_ovpn_options(daemon=False, socket_path=None, **kwargs):
opts.append('--remote-cert-tls')
opts.append('server')
+ # get ciphers #######################
+
+ ciphers = get_cipher_options(
+ eipserviceconfig=eipserviceconfig)
+ for cipheropt in ciphers:
+ opts.append(str(cipheropt))
+
# set user and group
opts.append('--user')
opts.append('%s' % user)
diff --git a/src/leap/eip/eipconnection.py b/src/leap/eip/eipconnection.py
index 7828c864..8751f643 100644
--- a/src/leap/eip/eipconnection.py
+++ b/src/leap/eip/eipconnection.py
@@ -5,6 +5,7 @@ from __future__ import (absolute_import,)
import logging
import Queue
import sys
+import time
from leap.eip.checks import ProviderCertChecker
from leap.eip.checks import EIPConfigChecker
@@ -15,20 +16,143 @@ from leap.eip.openvpnconnection import OpenVPNConnection
logger = logging.getLogger(name=__name__)
-class EIPConnection(OpenVPNConnection):
+class StatusMixIn(object):
+
+ # a bunch of methods related with querying the connection
+ # state/status and displaying useful info.
+ # Needs to get clear on what is what, and
+ # separate functions.
+ # Should separate EIPConnectionStatus (self.status)
+ # from the OpenVPN state/status command and parsing.
+
+ def connection_state(self):
+ """
+ returns the current connection state
+ """
+ return self.status.current
+
+ def get_icon_name(self):
+ """
+ get icon name from status object
+ """
+ return self.status.get_state_icon()
+
+ def get_leap_status(self):
+ return self.status.get_leap_status()
+
+ def poll_connection_state(self):
+ """
+ """
+ try:
+ state = self.get_connection_state()
+ except eip_exceptions.ConnectionRefusedError:
+ # connection refused. might be not ready yet.
+ logger.warning('connection refused')
+ return
+ if not state:
+ logger.debug('no state')
+ return
+ (ts, status_step,
+ ok, ip, remote) = state
+ self.status.set_vpn_state(status_step)
+ status_step = self.status.get_readable_status()
+ return (ts, status_step, ok, ip, remote)
+
+ def make_error(self):
+ """
+ capture error and wrap it in an
+ understandable format
+ """
+ # mostly a hack to display errors in the debug UI
+ # w/o breaking the polling.
+ #XXX get helpful error codes
+ self.with_errors = True
+ now = int(time.time())
+ return '%s,LAUNCHER ERROR,ERROR,-,-' % now
+
+ def state(self):
+ """
+ Sends OpenVPN command: state
+ """
+ state = self._send_command("state")
+ if not state:
+ return None
+ if isinstance(state, str):
+ return state
+ if isinstance(state, list):
+ if len(state) == 1:
+ return state[0]
+ else:
+ return state[-1]
+
+ def vpn_status(self):
+ """
+ OpenVPN command: status
+ """
+ status = self._send_command("status")
+ return status
+
+ def vpn_status2(self):
+ """
+ OpenVPN command: last 2 statuses
+ """
+ return self._send_command("status 2")
+
+ #
+ # parse info as the UI expects
+ #
+
+ def get_status_io(self):
+ status = self.vpn_status()
+ if isinstance(status, str):
+ lines = status.split('\n')
+ if isinstance(status, list):
+ lines = status
+ try:
+ (header, when, tun_read, tun_write,
+ tcp_read, tcp_write, auth_read) = tuple(lines)
+ except ValueError:
+ return None
+
+ # XXX this will break with different locales I assume...
+ when_ts = time.strptime(when.split(',')[1], "%a %b %d %H:%M:%S %Y")
+ sep = ','
+ # XXX clean up this!
+ tun_read = tun_read.split(sep)[1]
+ tun_write = tun_write.split(sep)[1]
+ tcp_read = tcp_read.split(sep)[1]
+ tcp_write = tcp_write.split(sep)[1]
+ auth_read = auth_read.split(sep)[1]
+
+ # XXX this could be a named tuple. prettier.
+ return when_ts, (tun_read, tun_write, tcp_read, tcp_write, auth_read)
+
+ def get_connection_state(self):
+ state = self.state()
+ if state is not None:
+ ts, status_step, ok, ip, remote = state.split(',')
+ ts = time.gmtime(float(ts))
+ # XXX this could be a named tuple. prettier.
+ return ts, status_step, ok, ip, remote
+
+
+class EIPConnection(OpenVPNConnection, StatusMixIn):
"""
+ Aka conductor.
Manages the execution of the OpenVPN process, auto starts, monitors the
network connection, handles configuration, fixes leaky hosts, handles
errors, etc.
Status updates (connected, bandwidth, etc) are signaled to the GUI.
"""
+ # XXX change name to EIPConductor ??
+
def __init__(self,
provider_cert_checker=ProviderCertChecker,
config_checker=EIPConfigChecker,
*args, **kwargs):
- self.settingsfile = kwargs.get('settingsfile', None)
- self.logfile = kwargs.get('logfile', None)
+ #self.settingsfile = kwargs.get('settingsfile', None)
+ #self.logfile = kwargs.get('logfile', None)
self.provider = kwargs.pop('provider', None)
self._providercertchecker = provider_cert_checker
self._configchecker = config_checker
@@ -48,11 +172,27 @@ class EIPConnection(OpenVPNConnection):
super(EIPConnection, self).__init__(*args, **kwargs)
+ def connect(self):
+ """
+ entry point for connection process
+ """
+ # in OpenVPNConnection
+ self.try_openvpn_connection()
+
+ def disconnect(self, shutdown=False):
+ """
+ disconnects client
+ """
+ self.terminate_openvpn_connection(shutdown=shutdown)
+ self.status.change_to(self.status.DISCONNECTED)
+
def has_errors(self):
return True if self.error_queue.qsize() != 0 else False
def init_checkers(self):
- # initialize checkers
+ """
+ initialize checkers
+ """
self.provider_cert_checker = self._providercertchecker(
domain=self.provider)
self.config_checker = self._configchecker(domain=self.provider)
@@ -101,96 +241,6 @@ class EIPConnection(OpenVPNConnection):
except Exception as exc:
push_err(exc)
- def connect(self):
- """
- entry point for connection process
- """
- #self.forget_errors()
- self._try_connection()
-
- def disconnect(self):
- """
- disconnects client
- """
- self.cleanup()
- logger.debug("disconnect: clicked.")
- self.status.change_to(self.status.DISCONNECTED)
-
- #def shutdown(self):
- #"""
- #shutdown and quit
- #"""
- #self.desired_con_state = self.status.DISCONNECTED
-
- def connection_state(self):
- """
- returns the current connection state
- """
- return self.status.current
-
- def poll_connection_state(self):
- """
- """
- try:
- state = self.get_connection_state()
- except eip_exceptions.ConnectionRefusedError:
- # connection refused. might be not ready yet.
- logger.warning('connection refused')
- return
- if not state:
- logger.debug('no state')
- return
- (ts, status_step,
- ok, ip, remote) = state
- self.status.set_vpn_state(status_step)
- status_step = self.status.get_readable_status()
- return (ts, status_step, ok, ip, remote)
-
- def get_icon_name(self):
- """
- get icon name from status object
- """
- return self.status.get_state_icon()
-
- def get_leap_status(self):
- return self.status.get_leap_status()
-
- #
- # private methods
- #
-
- #def _disconnect(self):
- # """
- # private method for disconnecting
- # """
- # if self.subp is not None:
- # logger.debug('disconnecting...')
- # self.subp.terminate()
- # self.subp = None
-
- #def _is_alive(self):
- #"""
- #don't know yet
- #"""
- #pass
-
- def _connect(self):
- """
- entry point for connection cascade methods.
- """
- try:
- conn_result = self._try_connection()
- except eip_exceptions.UnrecoverableError as except_msg:
- logger.error("FATAL: %s" % unicode(except_msg))
- conn_result = self.status.UNRECOVERABLE
-
- # XXX enqueue exceptions themselves instead?
- except Exception as except_msg:
- self.error_queue.append(except_msg)
- logger.error("Failed Connection: %s" %
- unicode(except_msg))
- return conn_result
-
class EIPConnectionStatus(object):
"""
diff --git a/src/leap/eip/openvpnconnection.py b/src/leap/eip/openvpnconnection.py
index 07bc628a..253f5056 100644
--- a/src/leap/eip/openvpnconnection.py
+++ b/src/leap/eip/openvpnconnection.py
@@ -7,7 +7,6 @@ import os
import psutil
import shutil
import socket
-import time
from functools import partial
logger = logging.getLogger(name=__name__)
@@ -20,12 +19,123 @@ from leap.eip import config as eip_config
from leap.eip import exceptions as eip_exceptions
-class OpenVPNConnection(Connection):
+class OpenVPNManagement(object):
+
+ # TODO explain a little bit how management interface works
+ # and our telnet interface with support for unix sockets.
+
+ """
+ for more information, read openvpn management notes.
+ zcat `dpkg -L openvpn | grep management`
+ """
+
+ def _connect_to_management(self):
+ """
+ Connect to openvpn management interface
+ """
+ if hasattr(self, 'tn'):
+ self._close_management_socket()
+ self.tn = UDSTelnet(self.host, self.port)
+
+ # XXX make password optional
+ # specially for win. we should generate
+ # the pass on the fly when invoking manager
+ # from conductor
+
+ #self.tn.read_until('ENTER PASSWORD:', 2)
+ #self.tn.write(self.password + '\n')
+ #self.tn.read_until('SUCCESS:', 2)
+ if self.tn:
+ self._seek_to_eof()
+ return True
+
+ def _close_management_socket(self, announce=True):
+ """
+ Close connection to openvpn management interface
+ """
+ logger.debug('closing socket')
+ if announce:
+ self.tn.write("quit\n")
+ self.tn.read_all()
+ self.tn.get_socket().close()
+ del self.tn
+
+ def _seek_to_eof(self):
+ """
+ Read as much as available. Position seek pointer to end of stream
+ """
+ try:
+ b = self.tn.read_eager()
+ except EOFError:
+ logger.debug("Could not read from socket. Assuming it died.")
+ return
+ while b:
+ try:
+ b = self.tn.read_eager()
+ except EOFError:
+ logger.debug("Could not read from socket. Assuming it died.")
+
+ def _send_command(self, cmd):
+ """
+ Send a command to openvpn and return response as list
+ """
+ if not self.connected():
+ try:
+ self._connect_to_management()
+ except eip_exceptions.MissingSocketError:
+ logger.warning('missing management socket')
+ return []
+ try:
+ if hasattr(self, 'tn'):
+ self.tn.write(cmd + "\n")
+ except socket.error:
+ logger.error('socket error')
+ self._close_management_socket(announce=False)
+ return []
+ buf = self.tn.read_until(b"END", 2)
+ self._seek_to_eof()
+ blist = buf.split('\r\n')
+ if blist[-1].startswith('END'):
+ del blist[-1]
+ return blist
+ else:
+ return []
+
+ def _send_short_command(self, cmd):
+ """
+ parse output from commands that are
+ delimited by "success" instead
+ """
+ if not self.connected():
+ self.connect()
+ self.tn.write(cmd + "\n")
+ # XXX not working?
+ buf = self.tn.read_until(b"SUCCESS", 2)
+ self._seek_to_eof()
+ blist = buf.split('\r\n')
+ return blist
+
+ #
+ # random maybe useful vpn commands
+ #
+
+ def pid(self):
+ #XXX broken
+ return self._send_short_command("pid")
+
+
+class OpenVPNConnection(Connection, OpenVPNManagement):
"""
All related to invocation
- of the openvpn binary
+ of the openvpn binary.
+ It's extended by EIPConnection.
"""
+ # XXX Inheriting from Connection was an early design idea
+ # but currently that's an empty class.
+ # We can get rid of that if we don't use it for sharing
+ # state with other leap modules.
+
def __init__(self,
watcher_cb=None,
debug=False,
@@ -34,24 +144,21 @@ class OpenVPNConnection(Connection):
password=None,
*args, **kwargs):
"""
- :param config_file: configuration file to read from
:param watcher_cb: callback to be \
called for each line in watched stdout
:param signal_map: dictionary of signal names and callables \
to be triggered for each one of them.
- :type config_file: str
:type watcher_cb: function
:type signal_map: dict
"""
#XXX FIXME
#change watcher_cb to line_observer
+ # XXX if not host: raise ImproperlyConfigured
logger.debug('init openvpn connection')
self.debug = debug
- # XXX if not host: raise ImproperlyConfigured
self.ovpn_verbosity = kwargs.get('ovpn_verbosity', None)
- #self.config_file = config_file
self.watcher_cb = watcher_cb
#self.signal_maps = signal_maps
@@ -62,21 +169,13 @@ to be triggered for each one of them.
self.port = None
self.proto = None
- #XXX workaround for signaling
- #the ui that we don't know how to
- #manage a connection error
- #self.with_errors = False
-
self.command = None
self.args = None
# XXX get autostart from config
self.autostart = True
- #
- # management init methods
- #
-
+ # management interface init
self.host = host
if isinstance(port, str) and port.isdigit():
port = int(port)
@@ -88,101 +187,47 @@ to be triggered for each one of them.
self.password = password
def run_openvpn_checks(self):
+ """
+ runs check needed before launching
+ openvpn subprocess. will raise if errors found.
+ """
logger.debug('running openvpn checks')
+ # XXX I think that "check_if_running" should be called
+ # from try openvpn connection instead. -- kali.
+ # let's prepare tests for that before changing it...
self._check_if_running_instance()
self._set_ovpn_command()
self._check_vpn_keys()
- def _set_ovpn_command(self):
- # XXX check also for command-line --command flag
- try:
- command, args = eip_config.build_ovpn_command(
- provider=self.provider,
- debug=self.debug,
- socket_path=self.host,
- ovpn_verbosity=self.ovpn_verbosity)
- except eip_exceptions.EIPNoPolkitAuthAgentAvailable:
- command = args = None
- raise
- except eip_exceptions.EIPNoPkexecAvailable:
- command = args = None
- raise
-
- # XXX if not command, signal error.
- self.command = command
- self.args = args
-
- def _check_vpn_keys(self):
- """
- checks for correct permissions on vpn keys
- """
- try:
- eip_config.check_vpn_keys(provider=self.provider)
- except eip_exceptions.EIPInitBadKeyFilePermError:
- logger.error('Bad VPN Keys permission!')
- # do nothing now
- # and raise the rest ...
-
- def _launch_openvpn(self):
- """
- invocation of openvpn binaries in a subprocess.
- """
- #XXX TODO:
- #deprecate watcher_cb,
- #use _only_ signal_maps instead
-
- logger.debug('_launch_openvpn called')
- if self.watcher_cb is not None:
- linewrite_callback = self.watcher_cb
- else:
- #XXX get logger instead
- linewrite_callback = lambda line: print('watcher: %s' % line)
-
- # the partial is not
- # being applied now because we're not observing the process
- # stdout like we did in the early stages. but I leave it
- # here since it will be handy for observing patterns in the
- # thru-the-manager updates (with regex)
- observers = (linewrite_callback,
- partial(lambda con_status, line: None, self.status))
- subp, watcher = spawn_and_watch_process(
- self.command,
- self.args,
- observers=observers)
- self.subp = subp
- self.watcher = watcher
-
- def _try_connection(self):
+ def try_openvpn_connection(self):
"""
attempts to connect
"""
+ # XXX should make public method
if self.command is None:
raise eip_exceptions.EIPNoCommandError
if self.subp is not None:
logger.debug('cowardly refusing to launch subprocess again')
+ # XXX this is not returning ???!!
+ # FIXME -- so it's calling it all the same!!
self._launch_openvpn()
- def _check_if_running_instance(self):
+ def connected(self):
"""
- check if openvpn is already running
+ Returns True if connected
+ rtype: bool
"""
- for process in psutil.get_process_list():
- if process.name == "openvpn":
- logger.debug('an openvpn instance is already running.')
- logger.debug('attempting to stop openvpn instance.')
- if not self._stop():
- raise eip_exceptions.OpenVPNAlreadyRunning
-
- logger.debug('no openvpn instance found.')
+ # XXX make a property
+ return hasattr(self, 'tn')
- def cleanup(self, shutdown=False):
+ def terminate_openvpn_connection(self, shutdown=False):
"""
terminates openvpn child subprocess
"""
if self.subp:
try:
- self._stop()
+ self._stop_openvpn()
except eip_exceptions.ConnectionRefusedError:
logger.warning(
'unable to send sigterm signal to openvpn: '
@@ -202,9 +247,9 @@ to be triggered for each one of them.
'(We might have left openvpn running)' % RETCODE)
if shutdown:
- self.cleanup_tempfiles()
+ self._cleanup_tempfiles()
- def cleanup_tempfiles(self):
+ def _cleanup_tempfiles(self):
"""
remove all temporal files
we might have left behind
@@ -224,172 +269,89 @@ to be triggered for each one of them.
except OSError:
logger.error('could not delete tmpfolder %s' % tempfolder)
- def _get_openvpn_process(self):
- # plist = [p for p in psutil.get_process_list() if p.name == "openvpn"]
- # return plist[0] if plist else None
+ # checks
+
+ def _check_if_running_instance(self):
+ """
+ check if openvpn is already running
+ """
for process in psutil.get_process_list():
if process.name == "openvpn":
- return process
- return None
-
- # management methods
- #
- # XXX REVIEW-ME
- # REFACTOR INFO: (former "manager".
- # Can we move to another
- # base class to test independently?)
- #
-
- #def forget_errors(self):
- #logger.debug('forgetting errors')
- #self.with_errors = False
-
- def connect_to_management(self):
- """Connect to openvpn management interface"""
- #logger.debug('connecting socket')
- if hasattr(self, 'tn'):
- self.close()
- self.tn = UDSTelnet(self.host, self.port)
-
- # XXX make password optional
- # specially for win. we should generate
- # the pass on the fly when invoking manager
- # from conductor
+ logger.debug('an openvpn instance is already running.')
+ logger.debug('attempting to stop openvpn instance.')
+ if not self._stop_openvpn():
+ raise eip_exceptions.OpenVPNAlreadyRunning
- #self.tn.read_until('ENTER PASSWORD:', 2)
- #self.tn.write(self.password + '\n')
- #self.tn.read_until('SUCCESS:', 2)
- if self.tn:
- self._seek_to_eof()
- return True
+ logger.debug('no openvpn instance found.')
- def _seek_to_eof(self):
- """
- Read as much as available. Position seek pointer to end of stream
- """
+ def _set_ovpn_command(self):
try:
- b = self.tn.read_eager()
- except EOFError:
- logger.debug("Could not read from socket. Assuming it died.")
- return
- while b:
- try:
- b = self.tn.read_eager()
- except EOFError:
- logger.debug("Could not read from socket. Assuming it died.")
-
- def connected(self):
- """
- Returns True if connected
- rtype: bool
- """
- return hasattr(self, 'tn')
+ command, args = eip_config.build_ovpn_command(
+ provider=self.provider,
+ debug=self.debug,
+ socket_path=self.host,
+ ovpn_verbosity=self.ovpn_verbosity)
+ except eip_exceptions.EIPNoPolkitAuthAgentAvailable:
+ command = args = None
+ raise
+ except eip_exceptions.EIPNoPkexecAvailable:
+ command = args = None
+ raise
- def close(self, announce=True):
- """
- Close connection to openvpn management interface
- """
- logger.debug('closing socket')
- if announce:
- self.tn.write("quit\n")
- self.tn.read_all()
- self.tn.get_socket().close()
- del self.tn
+ # XXX if not command, signal error.
+ self.command = command
+ self.args = args
- def _send_command(self, cmd):
+ def _check_vpn_keys(self):
"""
- Send a command to openvpn and return response as list
+ checks for correct permissions on vpn keys
"""
- if not self.connected():
- try:
- self.connect_to_management()
- except eip_exceptions.MissingSocketError:
- logger.warning('missing management socket')
- return []
try:
- if hasattr(self, 'tn'):
- self.tn.write(cmd + "\n")
- except socket.error:
- logger.error('socket error')
- self.close(announce=False)
- return []
- buf = self.tn.read_until(b"END", 2)
- self._seek_to_eof()
- blist = buf.split('\r\n')
- if blist[-1].startswith('END'):
- del blist[-1]
- return blist
- else:
- return []
-
- def _send_short_command(self, cmd):
- """
- parse output from commands that are
- delimited by "success" instead
- """
- if not self.connected():
- self.connect()
- self.tn.write(cmd + "\n")
- # XXX not working?
- buf = self.tn.read_until(b"SUCCESS", 2)
- self._seek_to_eof()
- blist = buf.split('\r\n')
- return blist
-
- #
- # useful vpn commands
- #
-
- def pid(self):
- #XXX broken
- return self._send_short_command("pid")
+ eip_config.check_vpn_keys(provider=self.provider)
+ except eip_exceptions.EIPInitBadKeyFilePermError:
+ logger.error('Bad VPN Keys permission!')
+ # do nothing now
+ # and raise the rest ...
- def make_error(self):
- """
- capture error and wrap it in an
- understandable format
- """
- #XXX get helpful error codes
- self.with_errors = True
- now = int(time.time())
- return '%s,LAUNCHER ERROR,ERROR,-,-' % now
+ # starting and stopping openvpn subprocess
- def state(self):
+ def _launch_openvpn(self):
"""
- OpenVPN command: state
+ invocation of openvpn binaries in a subprocess.
"""
- state = self._send_command("state")
- if not state:
- return None
- if isinstance(state, str):
- return state
- if isinstance(state, list):
- if len(state) == 1:
- return state[0]
- else:
- return state[-1]
+ #XXX TODO:
+ #deprecate watcher_cb,
+ #use _only_ signal_maps instead
- def vpn_status(self):
- """
- OpenVPN command: status
- """
- #logger.debug('status called')
- status = self._send_command("status")
- return status
+ logger.debug('_launch_openvpn called')
+ if self.watcher_cb is not None:
+ linewrite_callback = self.watcher_cb
+ else:
+ #XXX get logger instead
+ linewrite_callback = lambda line: print('watcher: %s' % line)
- def vpn_status2(self):
- """
- OpenVPN command: last 2 statuses
- """
- return self._send_command("status 2")
+ # the partial is not
+ # being applied now because we're not observing the process
+ # stdout like we did in the early stages. but I leave it
+ # here since it will be handy for observing patterns in the
+ # thru-the-manager updates (with regex)
+ observers = (linewrite_callback,
+ partial(lambda con_status, line: None, self.status))
+ subp, watcher = spawn_and_watch_process(
+ self.command,
+ self.args,
+ observers=observers)
+ self.subp = subp
+ self.watcher = watcher
- def _stop(self):
+ def _stop_openvpn(self):
"""
stop openvpn process
by sending SIGTERM to the management
interface
"""
- logger.debug("disconnecting...")
+ # XXX method a bit too long, split
+ logger.debug("terminating openvpn process...")
if self.connected():
try:
self._send_command("signal SIGTERM\n")
@@ -424,38 +386,10 @@ to be triggered for each one of them.
return True
- #
- # parse info
- #
-
- def get_status_io(self):
- status = self.vpn_status()
- if isinstance(status, str):
- lines = status.split('\n')
- if isinstance(status, list):
- lines = status
- try:
- (header, when, tun_read, tun_write,
- tcp_read, tcp_write, auth_read) = tuple(lines)
- except ValueError:
- return None
-
- when_ts = time.strptime(when.split(',')[1], "%a %b %d %H:%M:%S %Y")
- sep = ','
- # XXX cleanup!
- tun_read = tun_read.split(sep)[1]
- tun_write = tun_write.split(sep)[1]
- tcp_read = tcp_read.split(sep)[1]
- tcp_write = tcp_write.split(sep)[1]
- auth_read = auth_read.split(sep)[1]
-
- # XXX this could be a named tuple. prettier.
- return when_ts, (tun_read, tun_write, tcp_read, tcp_write, auth_read)
-
- def get_connection_state(self):
- state = self.state()
- if state is not None:
- ts, status_step, ok, ip, remote = state.split(',')
- ts = time.gmtime(float(ts))
- # XXX this could be a named tuple. prettier.
- return ts, status_step, ok, ip, remote
+ def _get_openvpn_process(self):
+ # plist = [p for p in psutil.get_process_list() if p.name == "openvpn"]
+ # return plist[0] if plist else None
+ for process in psutil.get_process_list():
+ if process.name == "openvpn":
+ return process
+ return None
diff --git a/src/leap/eip/specs.py b/src/leap/eip/specs.py
index 57e7537b..cf5d5359 100644
--- a/src/leap/eip/specs.py
+++ b/src/leap/eip/specs.py
@@ -119,6 +119,13 @@ eipservice_config_spec = {
"label": {"en":"west"},
"capabilities": {},
"hosts": ["1.2.3.4", "1.2.3.5"]}]
+ },
+ 'openvpn_configuration': {
+ 'type': dict,
+ 'default': {
+ "auth": None,
+ "cipher": None,
+ "tls-cipher": None}
}
}
}
diff --git a/src/leap/eip/tests/test_checks.py b/src/leap/eip/tests/test_checks.py
index 1d7bfc17..ab11037a 100644
--- a/src/leap/eip/tests/test_checks.py
+++ b/src/leap/eip/tests/test_checks.py
@@ -25,6 +25,7 @@ from leap.eip.tests import data as testdata
from leap.testing.basetest import BaseLeapTest
from leap.testing.https_server import BaseHTTPSServerTestCase
from leap.testing.https_server import where as where_cert
+from leap.util.fileutil import mkdir_f
class NoLogRequestHandler:
@@ -118,6 +119,7 @@ class EIPCheckTest(BaseLeapTest):
sampleconfig = copy.copy(testdata.EIP_SAMPLE_CONFIG)
sampleconfig['provider'] = None
eipcfg_path = checker.eipconfig.filename
+ mkdir_f(eipcfg_path)
with open(eipcfg_path, 'w') as fp:
json.dump(sampleconfig, fp)
#with self.assertRaises(eipexceptions.EIPMissingDefaultProvider):
@@ -138,6 +140,8 @@ class EIPCheckTest(BaseLeapTest):
def test_fetch_definition(self):
with patch.object(requests, "get") as mocked_get:
mocked_get.return_value.status_code = 200
+ mocked_get.return_value.headers = {
+ 'last-modified': "Wed Dec 12 12:12:12 GMT 2012"}
mocked_get.return_value.json = DEFAULT_PROVIDER_DEFINITION
checker = eipchecks.EIPConfigChecker(fetcher=requests)
sampleconfig = testdata.EIP_SAMPLE_CONFIG
@@ -156,6 +160,8 @@ class EIPCheckTest(BaseLeapTest):
def test_fetch_eip_service_config(self):
with patch.object(requests, "get") as mocked_get:
mocked_get.return_value.status_code = 200
+ mocked_get.return_value.headers = {
+ 'last-modified': "Wed Dec 12 12:12:12 GMT 2012"}
mocked_get.return_value.json = testdata.EIP_SAMPLE_SERVICE
checker = eipchecks.EIPConfigChecker(fetcher=requests)
sampleconfig = testdata.EIP_SAMPLE_CONFIG
diff --git a/src/leap/eip/tests/test_config.py b/src/leap/eip/tests/test_config.py
index 50538240..404d543f 100644
--- a/src/leap/eip/tests/test_config.py
+++ b/src/leap/eip/tests/test_config.py
@@ -1,3 +1,4 @@
+from collections import OrderedDict
import json
import os
import platform
@@ -10,7 +11,7 @@ except ImportError:
#from leap.base import constants
#from leap.eip import config as eip_config
-from leap import __branding as BRANDING
+#from leap import __branding as BRANDING
from leap.eip import config as eipconfig
from leap.eip.tests.data import EIP_SAMPLE_CONFIG, EIP_SAMPLE_SERVICE
from leap.testing.basetest import BaseLeapTest
@@ -47,11 +48,21 @@ class EIPConfigTest(BaseLeapTest):
open(tfile, 'wb').close()
os.chmod(tfile, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
- def write_sample_eipservice(self):
+ def write_sample_eipservice(self, vpnciphers=False, extra_vpnopts=None):
conf = eipconfig.EIPServiceConfig()
folder, f = os.path.split(conf.filename)
if not os.path.isdir(folder):
mkdir_p(folder)
+ if vpnciphers:
+ openvpnconfig = OrderedDict({
+ "auth": "SHA1",
+ "cipher": "AES-128-CBC",
+ "tls-cipher": "DHE-RSA-AES128-SHA"})
+ if extra_vpnopts:
+ for k, v in extra_vpnopts.items():
+ openvpnconfig[k] = v
+ EIP_SAMPLE_SERVICE['openvpn_configuration'] = openvpnconfig
+
with open(conf.filename, 'w') as fd:
fd.write(json.dumps(EIP_SAMPLE_SERVICE))
@@ -63,8 +74,13 @@ class EIPConfigTest(BaseLeapTest):
with open(conf.filename, 'w') as fd:
fd.write(json.dumps(EIP_SAMPLE_CONFIG))
- def get_expected_openvpn_args(self):
+ def get_expected_openvpn_args(self, with_openvpn_ciphers=False):
args = []
+ eipconf = eipconfig.EIPConfig(domain=self.provider)
+ eipconf.load()
+ eipsconf = eipconfig.EIPServiceConfig(domain=self.provider)
+ eipsconf.load()
+
username = self.get_username()
groupname = self.get_groupname()
@@ -75,8 +91,10 @@ class EIPConfigTest(BaseLeapTest):
args.append('--persist-tun')
args.append('--persist-key')
args.append('--remote')
+
args.append('%s' % eipconfig.get_eip_gateway(
- provider=self.provider))
+ eipconfig=eipconf,
+ eipserviceconfig=eipsconf))
# XXX get port!?
args.append('1194')
# XXX get proto
@@ -85,6 +103,14 @@ class EIPConfigTest(BaseLeapTest):
args.append('--remote-cert-tls')
args.append('server')
+ if with_openvpn_ciphers:
+ CIPHERS = [
+ "--tls-cipher", "DHE-RSA-AES128-SHA",
+ "--cipher", "AES-128-CBC",
+ "--auth", "SHA1"]
+ for opt in CIPHERS:
+ args.append(opt)
+
args.append('--user')
args.append(username)
args.append('--group')
@@ -139,14 +165,63 @@ class EIPConfigTest(BaseLeapTest):
from leap.util.fileutil import which
path = os.environ['PATH']
vpnbin = which('openvpn', path=path)
- print 'path =', path
- print 'vpnbin = ', vpnbin
- command, args = eipconfig.build_ovpn_command(
+ #print 'path =', path
+ #print 'vpnbin = ', vpnbin
+ vpncommand, vpnargs = eipconfig.build_ovpn_command(
+ do_pkexec_check=False, vpnbin=vpnbin,
+ socket_path="/tmp/test.socket",
+ provider=self.provider)
+ self.assertEqual(vpncommand, self.home + '/bin/openvpn')
+ self.assertEqual(vpnargs, self.get_expected_openvpn_args())
+
+ def test_build_ovpn_command_openvpnoptions(self):
+ self.touch_exec()
+
+ from leap.eip import config as eipconfig
+ from leap.util.fileutil import which
+ path = os.environ['PATH']
+ vpnbin = which('openvpn', path=path)
+
+ self.write_sample_eipconfig()
+
+ # regular run, everything normal
+ self.write_sample_eipservice(vpnciphers=True)
+ vpncommand, vpnargs = eipconfig.build_ovpn_command(
+ do_pkexec_check=False, vpnbin=vpnbin,
+ socket_path="/tmp/test.socket",
+ provider=self.provider)
+ self.assertEqual(vpncommand, self.home + '/bin/openvpn')
+ expected = self.get_expected_openvpn_args(
+ with_openvpn_ciphers=True)
+ self.assertEqual(vpnargs, expected)
+
+ # bad options -- illegal options
+ self.write_sample_eipservice(
+ vpnciphers=True,
+ # WE ONLY ALLOW vpn options in auth, cipher, tls-cipher
+ extra_vpnopts={"notallowedconfig": "badvalue"})
+ vpncommand, vpnargs = eipconfig.build_ovpn_command(
+ do_pkexec_check=False, vpnbin=vpnbin,
+ socket_path="/tmp/test.socket",
+ provider=self.provider)
+ self.assertEqual(vpncommand, self.home + '/bin/openvpn')
+ expected = self.get_expected_openvpn_args(
+ with_openvpn_ciphers=True)
+ self.assertEqual(vpnargs, expected)
+
+ # bad options -- illegal chars
+ self.write_sample_eipservice(
+ vpnciphers=True,
+ # WE ONLY ALLOW A-Z09\-
+ extra_vpnopts={"cipher": "AES-128-CBC;FOOTHING"})
+ vpncommand, vpnargs = eipconfig.build_ovpn_command(
do_pkexec_check=False, vpnbin=vpnbin,
socket_path="/tmp/test.socket",
provider=self.provider)
- self.assertEqual(command, self.home + '/bin/openvpn')
- self.assertEqual(args, self.get_expected_openvpn_args())
+ self.assertEqual(vpncommand, self.home + '/bin/openvpn')
+ expected = self.get_expected_openvpn_args(
+ with_openvpn_ciphers=True)
+ self.assertEqual(vpnargs, expected)
if __name__ == "__main__":
diff --git a/src/leap/eip/tests/test_eipconnection.py b/src/leap/eip/tests/test_eipconnection.py
index aefca36f..4ee5ae30 100644
--- a/src/leap/eip/tests/test_eipconnection.py
+++ b/src/leap/eip/tests/test_eipconnection.py
@@ -123,9 +123,14 @@ class EIPConductorTest(BaseLeapTest):
self.con.status.CONNECTED)
# disconnect
- self.con.cleanup = Mock()
+ self.con.terminate_openvpn_connection = Mock()
self.con.disconnect()
- self.con.cleanup.assert_called_once_with()
+ self.con.terminate_openvpn_connection.assert_called_once_with(
+ shutdown=False)
+ self.con.terminate_openvpn_connection = Mock()
+ self.con.disconnect(shutdown=True)
+ self.con.terminate_openvpn_connection.assert_called_once_with(
+ shutdown=True)
# new status should be disconnected
# XXX this should evolve and check no errors
diff --git a/src/leap/util/fileutil.py b/src/leap/util/fileutil.py
index aef4cfe0..820ffe46 100644
--- a/src/leap/util/fileutil.py
+++ b/src/leap/util/fileutil.py
@@ -93,6 +93,11 @@ def mkdir_p(path):
raise
+def mkdir_f(path):
+ folder, fname = os.path.split(path)
+ mkdir_p(folder)
+
+
def check_and_fix_urw_only(_file):
"""
test for 600 mode and try
diff --git a/src/leap/util/misc.py b/src/leap/util/misc.py
new file mode 100644
index 00000000..3c26892b
--- /dev/null
+++ b/src/leap/util/misc.py
@@ -0,0 +1,16 @@
+"""
+misc utils
+"""
+
+
+class ImproperlyConfigured(Exception):
+ """
+ """
+
+
+def null_check(value, value_name):
+ try:
+ assert value is not None
+ except AssertionError:
+ raise ImproperlyConfigured(
+ "%s parameter cannot be None" % value_name)