summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkali <kali@leap.se>2012-08-08 09:00:02 +0900
committerkali <kali@leap.se>2012-08-08 09:00:02 +0900
commit01561795251a4eb2255548da22036660cfbf1dda (patch)
tree285d105ec659a2c18f0d544ca9d624283239ed44
parent359adbca9dbde07f4054c775650adcd20823f29c (diff)
parentc217bd1f1456cf10ceabf698ea6f4dd8f636f454 (diff)
Merge branch 'ovpn-invocation' into tests-cleanup
Conflicts: .gitignore
-rw-r--r--.gitignore2
-rw-r--r--README.txt21
-rw-r--r--src/leap/app.py6
-rw-r--r--src/leap/baseapp/config.py40
-rw-r--r--src/leap/baseapp/dialogs.py33
-rw-r--r--src/leap/baseapp/mainwindow.py87
-rw-r--r--src/leap/baseapp/permcheck.py17
-rw-r--r--src/leap/eip/conductor.py166
-rw-r--r--src/leap/eip/config.py426
-rw-r--r--src/leap/eip/vpnmanager.py3
-rw-r--r--src/leap/util/__init__.py (renamed from src/leap/utils/__init__.py)0
-rw-r--r--src/leap/util/coroutines.py (renamed from src/leap/utils/coroutines.py)0
-rw-r--r--src/leap/util/fileutil.py111
-rw-r--r--src/leap/util/leap_argparse.py (renamed from src/leap/utils/leap_argparse.py)0
14 files changed, 809 insertions, 103 deletions
diff --git a/.gitignore b/.gitignore
index d711102..3a0afe7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,4 @@ include/
lib/
local/
share/
-src/leap_client.egg-info/
+src/leap.egg-info/
diff --git a/README.txt b/README.txt
index 14ac253..4a4ae52 100644
--- a/README.txt
+++ b/README.txt
@@ -1,15 +1,34 @@
========================================
= LEAP =
-= The Internet Encryption Toolkit =
+= The LEAP Encryption Access Project =
+= your internet encryption toolkit =
========================================
Install
=======
python setup.py install
+Running
+=======
+
+You need to set up a provider in your eip.cfg file:
+
+cd ~/.config/leap
+vim eip.cfg
+
+[provider]
+remote_ip = XXX.XXX.XXX.XXX
+
+and then run:
+
+leap --debug
+
+(or python app.py --debug if you run it from the src/leap folder).
+
Running tests
=============
nosetests -v
+[ currently broken ]
Deps
====
diff --git a/src/leap/app.py b/src/leap/app.py
index 0a61fd4..db48701 100644
--- a/src/leap/app.py
+++ b/src/leap/app.py
@@ -6,6 +6,7 @@ from PyQt4.QtGui import (QApplication, QSystemTrayIcon, QMessageBox)
from leap.baseapp.mainwindow import LeapWindow
+logging.basicConfig()
logger = logging.getLogger(name=__name__)
@@ -15,13 +16,14 @@ def main():
long live to the (hidden) leap window!
"""
import sys
- from leap.utils import leap_argparse
+ from leap.util import leap_argparse
parser, opts = leap_argparse.init_leapc_args()
debug = getattr(opts, 'debug', False)
#XXX get debug level and set logger accordingly
if debug:
- logger.debug('args: ', opts)
+ logger.setLevel('DEBUG')
+ logger.debug('args: %s' % opts)
app = QApplication(sys.argv)
diff --git a/src/leap/baseapp/config.py b/src/leap/baseapp/config.py
deleted file mode 100644
index efdb472..0000000
--- a/src/leap/baseapp/config.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import ConfigParser
-import os
-
-
-def get_config(config_file=None):
- """
- temporary method for getting configs,
- mainly for early stage development process.
- in the future we will get preferences
- from the storage api
- """
- config = ConfigParser.ConfigParser()
- #config.readfp(open('defaults.cfg'))
- #XXX does this work on win / mac also???
- conf_path_list = ['eip.cfg', # XXX build a
- # proper path with platform-specific places
- # XXX make .config/foo
- os.path.expanduser('~/.eip.cfg')]
- if config_file:
- config.readfp(config_file)
- else:
- config.read(conf_path_list)
- return config
-
-
-# XXX wrapper around config? to get default values
-
-def get_with_defaults(config, section, option):
- if config.has_option(section, option):
- return config.get(section, option)
- else:
- # XXX lookup in defaults dict???
- pass
-
-
-def get_vpn_stdout_mockup():
- command = "python"
- args = ["-u", "-c", "from eip_client import fakeclient;\
-fakeclient.write_output()"]
- return command, args
diff --git a/src/leap/baseapp/dialogs.py b/src/leap/baseapp/dialogs.py
new file mode 100644
index 0000000..4b1b5b6
--- /dev/null
+++ b/src/leap/baseapp/dialogs.py
@@ -0,0 +1,33 @@
+from PyQt4.QtGui import (QDialog, QFrame, QPushButton, QLabel, QMessageBox)
+
+
+class ErrorDialog(QDialog):
+ def __init__(self, parent=None):
+ super(ErrorDialog, self).__init__(parent)
+
+ frameStyle = QFrame.Sunken | QFrame.Panel
+ self.warningLabel = QLabel()
+ self.warningLabel.setFrameStyle(frameStyle)
+ self.warningButton = QPushButton("QMessageBox.&warning()")
+
+ def warningMessage(self, msg, label):
+ msgBox = QMessageBox(QMessageBox.Warning,
+ "QMessageBox.warning()", msg,
+ QMessageBox.NoButton, self)
+ msgBox.addButton("&Ok", QMessageBox.AcceptRole)
+ msgBox.addButton("&Cancel", QMessageBox.RejectRole)
+ if msgBox.exec_() == QMessageBox.AcceptRole:
+ self.warningLabel.setText("Save Again")
+ else:
+ self.warningLabel.setText("Continue")
+
+ def criticalMessage(self, msg, label):
+ msgBox = QMessageBox(QMessageBox.Critical,
+ "QMessageBox.critical()", msg,
+ QMessageBox.NoButton, self)
+ msgBox.addButton("&Ok", QMessageBox.AcceptRole)
+ msgBox.addButton("&Cancel", QMessageBox.RejectRole)
+ if msgBox.exec_() == QMessageBox.AcceptRole:
+ self.warningLabel.setText("Save Again")
+ else:
+ self.warningLabel.setText("Continue")
diff --git a/src/leap/baseapp/mainwindow.py b/src/leap/baseapp/mainwindow.py
index 68b6de8..85129a9 100644
--- a/src/leap/baseapp/mainwindow.py
+++ b/src/leap/baseapp/mainwindow.py
@@ -11,8 +11,14 @@ from PyQt4.QtGui import (QMainWindow, QWidget, QVBoxLayout, QMessageBox,
QTextBrowser, qApp)
from PyQt4.QtCore import (pyqtSlot, pyqtSignal, QTimer)
+from leap.baseapp.dialogs import ErrorDialog
+from leap.eip.conductor import (EIPConductor,
+ EIPNoCommandError)
+
+from leap.eip.config import (EIPInitBadKeyFilePermError)
+# from leap.eip import exceptions as eip_exceptions
+
from leap.gui import mainwindow_rc
-from leap.eip.conductor import EIPConductor
class LeapWindow(QMainWindow):
@@ -58,21 +64,76 @@ class LeapWindow(QMainWindow):
mainLayout.addWidget(self.loggerBox)
widget.setLayout(mainLayout)
+ self.trayIcon.show()
+ config_file = getattr(opts, 'config_file', None)
+
#
# conductor is in charge of all
# vpn-related configuration / monitoring.
# we pass a tuple of signals that will be
# triggered when status changes.
#
- config_file = getattr(opts, 'config_file', None)
self.conductor = EIPConductor(
watcher_cb=self.newLogLine.emit,
config_file=config_file,
- status_signals=(self.statusChange.emit, ))
+ status_signals=(self.statusChange.emit, ),
+ debug=self.debugmode)
- self.trayIcon.show()
+ #
+ # bunch of self checks.
+ # XXX move somewhere else alltogether.
+ #
- self.setWindowTitle("Leap")
+ if self.conductor.missing_provider is True:
+ dialog = ErrorDialog()
+ dialog.criticalMessage(
+ 'Missing provider. Add a remote_ip entry '
+ 'under section [provider] in eip.cfg',
+ 'error')
+
+ if self.conductor.missing_vpn_keyfile is True:
+ dialog = ErrorDialog()
+ dialog.criticalMessage(
+ 'Could not find the vpn keys file',
+ 'error')
+
+ # ... btw, review pending.
+ # os.kill of subprocess fails if we have
+ # some of this errors.
+
+ if self.conductor.bad_provider is True:
+ dialog = ErrorDialog()
+ dialog.criticalMessage(
+ 'Bad provider entry. Check that remote_ip entry '
+ 'has an IP under section [provider] in eip.cfg',
+ 'error')
+
+ if self.conductor.bad_keyfile_perms is True:
+ dialog = ErrorDialog()
+ dialog.criticalMessage(
+ 'The vpn keys file has bad permissions',
+ 'error')
+
+ if self.conductor.missing_auth_agent is True:
+ dialog = ErrorDialog()
+ dialog.warningMessage(
+ 'We could not find any authentication '
+ 'agent in your system.<br/>'
+ 'Make sure you have '
+ '<b>polkit-gnome-authentication-agent-1</b> '
+ 'running and try again.',
+ 'error')
+
+ if self.conductor.missing_pkexec is True:
+ dialog = ErrorDialog()
+ dialog.warningMessage(
+ 'We could not find <b>pkexec</b> in your '
+ 'system.<br/> Do you want to try '
+ '<b>setuid workaround</b>? '
+ '(<i>DOES NOTHING YET</i>)',
+ 'error')
+
+ self.setWindowTitle("LEAP Client")
self.resize(400, 300)
self.set_statusbarMessage('ready')
@@ -222,6 +283,7 @@ technolust</i>")
self.trayIconMenu.addAction(self.quitAction)
self.trayIcon = QSystemTrayIcon(self)
+ self.setIcon('disconnected')
self.trayIcon.setContextMenu(self.trayIconMenu)
def createLogBrowser(self):
@@ -285,11 +347,11 @@ technolust</i>")
updating icon, status bar, etc.
"""
- print('STATUS CHANGED! (on Qt-land)')
- print('%s -> %s' % (status.previous, status.current))
+ #print('STATUS CHANGED! (on Qt-land)')
+ #print('%s -> %s' % (status.previous, status.current))
icon_name = self.conductor.get_icon_name()
self.setIcon(icon_name)
- print 'icon = ', icon_name
+ #print 'icon = ', icon_name
# change connection pixmap widget
self.setConnWidget(icon_name)
@@ -315,7 +377,14 @@ technolust</i>")
stub for running child process with vpn
"""
if self.vpn_service_started is False:
- self.conductor.connect()
+ try:
+ self.conductor.connect()
+ except EIPNoCommandError:
+ dialog = ErrorDialog()
+ dialog.warningMessage(
+ 'No suitable openvpn command found. '
+ '<br/>(Might be a permissions problem)',
+ 'error')
if self.debugmode:
self.startStopButton.setText('&Disconnect')
self.vpn_service_started = True
diff --git a/src/leap/baseapp/permcheck.py b/src/leap/baseapp/permcheck.py
new file mode 100644
index 0000000..6b74cb6
--- /dev/null
+++ b/src/leap/baseapp/permcheck.py
@@ -0,0 +1,17 @@
+import commands
+import os
+
+from leap.util.fileutil import which
+
+
+def is_pkexec_in_system():
+ pkexec_path = which('pkexec')
+ if not pkexec_path:
+ return False
+ return os.access(pkexec_path, os.X_OK)
+
+
+def is_auth_agent_running():
+ return bool(
+ commands.getoutput(
+ 'ps aux | grep polkit-[g]nome-authentication-agent-1'))
diff --git a/src/leap/eip/conductor.py b/src/leap/eip/conductor.py
index e3adadc..8f9d605 100644
--- a/src/leap/eip/conductor.py
+++ b/src/leap/eip/conductor.py
@@ -6,8 +6,20 @@ from __future__ import (division, unicode_literals, print_function)
from functools import partial
import logging
-from leap.utils.coroutines import spawn_and_watch_process
-from leap.baseapp.config import get_config, get_vpn_stdout_mockup
+from leap.util.coroutines import spawn_and_watch_process
+
+# XXX from leap.eip import config as eipconfig
+# from leap.eip import exceptions as eip_exceptions
+
+from leap.eip.config import (get_config, build_ovpn_command,
+ check_or_create_default_vpnconf,
+ check_vpn_keys,
+ EIPNoPkexecAvailable,
+ EIPNoPolkitAuthAgentAvailable,
+ EIPInitNoProviderError,
+ EIPInitBadProviderError,
+ EIPInitNoKeyFileError,
+ EIPInitBadKeyFilePermError)
from leap.eip.vpnwatcher import EIPConnectionStatus, status_watcher
from leap.eip.vpnmanager import OpenVPNManager, ConnectionRefusedError
@@ -15,6 +27,10 @@ logger = logging.getLogger(name=__name__)
# TODO Move exceptions to their own module
+# eip.exceptions
+
+class EIPNoCommandError(Exception):
+ pass
class ConnectionError(Exception):
@@ -39,8 +55,16 @@ class UnrecoverableError(EIPClientError):
"""
we cannot do anything about it, sorry
"""
+ # XXX we should catch this and raise
+ # to qtland, so we emit signal
+ # to translate whatever kind of error
+ # to user-friendly msg in dialog.
pass
+#
+# Openvpn related classes
+#
+
class OpenVPNConnection(object):
"""
@@ -49,7 +73,8 @@ class OpenVPNConnection(object):
"""
# Connection Methods
- def __init__(self, config_file=None, watcher_cb=None):
+ def __init__(self, config_file=None,
+ watcher_cb=None, debug=False):
#XXX FIXME
#change watcher_cb to line_observer
"""
@@ -64,6 +89,8 @@ to be triggered for each one of them.
"""
# XXX get host/port from config
self.manager = OpenVPNManager()
+ self.debug = debug
+ #print('conductor:%s' % debug)
self.config_file = config_file
self.watcher_cb = watcher_cb
@@ -76,46 +103,104 @@ to be triggered for each one of them.
self.port = None
self.proto = None
- self.autostart = True
+ self.missing_pkexec = False
+ self.missing_auth_agent = False
+ self.bad_keyfile_perms = False
+ self.missing_vpn_keyfile = False
+ self.missing_provider = False
+ self.bad_provider = False
- self._get_config()
+ self.command = None
+ self.args = None
- def _set_command_mockup(self):
- """
- sets command and args for a command mockup
- that just mimics the output from the real thing
- """
- command, args = get_vpn_stdout_mockup()
- self.command, self.args = command, args
+ self.autostart = True
+ self._get_or_create_config()
+ self._check_vpn_keys()
- def _get_config(self):
- """
- retrieves the config options from defaults or
- home file, or config file passed in command line.
- """
- config = get_config(config_file=self.config_file)
- self.config = config
+ def _set_autostart(self):
+ config = self.config
+ if config.has_option('openvpn', 'autostart'):
+ autostart = config.getboolean('openvpn',
+ 'autostart')
+ self.autostart = autostart
+ else:
+ if config.has_option('DEFAULT', 'autostart'):
+ autostart = config.getboolean('DEFAULT',
+ 'autostart')
+ self.autostart = autostart
+ def _set_ovpn_command(self):
+ config = self.config
if config.has_option('openvpn', 'command'):
commandline = config.get('openvpn', 'command')
- if commandline == "mockup":
- self._set_command_mockup()
- return
+
command_split = commandline.split(' ')
command = command_split[0]
if len(command_split) > 1:
args = command_split[1:]
else:
args = []
+
self.command = command
- #print("debug: command = %s" % command)
self.args = args
else:
- self._set_command_mockup()
+ # no command in config, we build it up.
+ # XXX check also for command-line --command flag
+ try:
+ command, args = build_ovpn_command(config,
+ debug=self.debug)
+ except EIPNoPolkitAuthAgentAvailable:
+ command = args = None
+ self.missing_auth_agent = True
+ except EIPNoPkexecAvailable:
+ command = args = None
+ self.missing_pkexec = True
+
+ # XXX if not command, signal error.
+ self.command = command
+ self.args = args
- if config.has_option('openvpn', 'autostart'):
- autostart = config.get('openvpn', 'autostart')
- self.autostart = autostart
+ def _check_ovpn_config(self):
+ """
+ checks if there is a default openvpn config.
+ if not, it writes one with info from the provider
+ definition file
+ """
+ # TODO
+ # - get --with-openvpn-config from opts
+ try:
+ check_or_create_default_vpnconf(self.config)
+ except EIPInitNoProviderError:
+ logger.error('missing default provider definition')
+ self.missing_provider = True
+ except EIPInitBadProviderError:
+ logger.error('bad provider definition')
+ self.bad_provider = True
+
+ def _get_or_create_config(self):
+ """
+ retrieves the config options from defaults or
+ home file, or config file passed in command line.
+ populates command and args to be passed to subprocess.
+ """
+ config = get_config(config_file=self.config_file)
+ self.config = config
+
+ self._set_autostart()
+ self._set_ovpn_command()
+ self._check_ovpn_config()
+
+ def _check_vpn_keys(self):
+ """
+ checks for correct permissions on vpn keys
+ """
+ try:
+ check_vpn_keys(self.config)
+ except EIPInitNoKeyFileError:
+ self.missing_vpn_keyfile = True
+ except EIPInitBadKeyFilePermError:
+ logger.error('error while checking vpn keys')
+ self.bad_keyfile_perms = True
def _launch_openvpn(self):
"""
@@ -140,13 +225,15 @@ to be triggered for each one of them.
self.subp = subp
self.watcher = watcher
- conn_result = self.status.CONNECTED
- return conn_result
+ #conn_result = self.status.CONNECTED
+ #return conn_result
def _try_connection(self):
"""
attempts to connect
"""
+ if self.command is None:
+ raise EIPNoCommandError
if self.subp is not None:
print('cowardly refusing to launch subprocess again')
return
@@ -186,7 +273,7 @@ class EIPConductor(OpenVPNConnection):
"""
self.manager.forget_errors()
self._try_connection()
- # XXX should capture errors?
+ # XXX should capture errors here?
def disconnect(self):
"""
@@ -194,25 +281,6 @@ class EIPConductor(OpenVPNConnection):
"""
self._disconnect()
self.status.change_to(self.status.DISCONNECTED)
- pass
-
- 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 desired_connection_state(self):
- """
- returns the desired_connection state
- """
- return self.desired_con_state
def poll_connection_state(self):
"""
diff --git a/src/leap/eip/config.py b/src/leap/eip/config.py
new file mode 100644
index 0000000..6118c9d
--- /dev/null
+++ b/src/leap/eip/config.py
@@ -0,0 +1,426 @@
+import ConfigParser
+import grp
+import logging
+import os
+import platform
+import socket
+
+from leap.util.fileutil import (which, mkdir_p,
+ check_and_fix_urw_only)
+from leap.baseapp.permcheck import (is_pkexec_in_system,
+ is_auth_agent_running)
+
+logger = logging.getLogger(name=__name__)
+logger.setLevel('DEBUG')
+
+# XXX move exceptions to
+# from leap.eip import exceptions as eip_exceptions
+
+
+class EIPNoPkexecAvailable(Exception):
+ pass
+
+
+class EIPNoPolkitAuthAgentAvailable(Exception):
+ pass
+
+
+class EIPInitNoProviderError(Exception):
+ pass
+
+
+class EIPInitBadProviderError(Exception):
+ pass
+
+
+class EIPInitNoKeyFileError(Exception):
+ pass
+
+
+class EIPInitBadKeyFilePermError(Exception):
+ pass
+
+
+OPENVPN_CONFIG_TEMPLATE = """#Autogenerated by eip-client wizard
+remote {VPN_REMOTE_HOST} {VPN_REMOTE_PORT}
+
+client
+dev tun
+persist-tun
+persist-key
+proto udp
+tls-client
+remote-cert-tls server
+
+cert {LEAP_EIP_KEYS}
+key {LEAP_EIP_KEYS}
+ca {LEAP_EIP_KEYS}
+"""
+
+
+def get_config_dir():
+ """
+ get the base dir for all leap config
+ @rparam: config path
+ @rtype: string
+ """
+ # TODO
+ # check for $XDG_CONFIG_HOME var?
+ # get a more sensible path for win/mac
+ # kclair: opinion? ^^
+ return os.path.expanduser(
+ os.path.join('~',
+ '.config',
+ 'leap'))
+
+
+def get_config_file(filename, folder=None):
+ """
+ concatenates the given filename
+ with leap config dir.
+ @param filename: name of the file
+ @type filename: string
+ @rparam: full path to config file
+ """
+ path = []
+ path.append(get_config_dir())
+ if folder is not None:
+ path.append(folder)
+ path.append(filename)
+ return os.path.join(*path)
+
+
+def get_default_provider_path():
+ default_subpath = os.path.join("providers",
+ "default")
+ default_provider_path = get_config_file(
+ '',
+ folder=default_subpath)
+ return default_provider_path
+
+
+def validate_ip(ip_str):
+ """
+ raises exception if the ip_str is
+ not a valid representation of an ip
+ """
+ socket.inet_aton(ip_str)
+
+
+def check_or_create_default_vpnconf(config):
+ """
+ checks that a vpn config file
+ exists for a default provider,
+ or creates one if it does not.
+ ATM REQURES A [provider] section in
+ eip.cfg with _at least_ a remote_ip value
+ """
+ default_provider_path = get_default_provider_path()
+
+ if not os.path.isdir(default_provider_path):
+ mkdir_p(default_provider_path)
+
+ conf_file = get_config_file(
+ 'openvpn.conf',
+ folder=default_provider_path)
+
+ if os.path.isfile(conf_file):
+ return
+ else:
+ logger.debug(
+ 'missing default openvpn config\n'
+ 'creating one...')
+
+ # We're getting provider from eip.cfg
+ # by now. Get it from a list of gateways
+ # instead.
+
+ try:
+ remote_ip = config.get('provider',
+ 'remote_ip')
+ validate_ip(remote_ip)
+
+ except ConfigParser.NoOptionError:
+ raise EIPInitNoProviderError
+
+ except socket.error:
+ # this does not look like an ip, dave
+ raise EIPInitBadProviderError
+
+ if config.has_option('provider', 'remote_port'):
+ remote_port = config.get('provider',
+ 'remote_port')
+ else:
+ remote_port = 1194
+
+ default_subpath = os.path.join("providers",
+ "default")
+ default_provider_path = get_config_file(
+ '',
+ folder=default_subpath)
+
+ if not os.path.isdir(default_provider_path):
+ mkdir_p(default_provider_path)
+
+ conf_file = get_config_file(
+ 'openvpn.conf',
+ folder=default_provider_path)
+
+ # XXX keys have to be manually placed by now
+ keys_file = get_config_file(
+ 'openvpn.keys',
+ folder=default_provider_path)
+
+ ovpn_config = OPENVPN_CONFIG_TEMPLATE.format(
+ VPN_REMOTE_HOST=remote_ip,
+ VPN_REMOTE_PORT=remote_port,
+ LEAP_EIP_KEYS=keys_file)
+
+ with open(conf_file, 'wb') as f:
+ f.write(ovpn_config)
+
+
+def build_ovpn_options(daemon=False):
+ """
+ build a list of options
+ to be passed in the
+ openvpn invocation
+ @rtype: list
+ @rparam: options
+ """
+ # XXX review which of the
+ # options we don't need.
+
+ # TODO pass also the config file,
+ # since we will need to take some
+ # things from there if present.
+
+ # get user/group name
+ # also from config.
+ user = os.getlogin()
+ gid = os.getgroups()[-1]
+ group = grp.getgrgid(gid).gr_name
+
+ opts = []
+
+ #moved to config files
+ #opts.append('--persist-tun')
+ #opts.append('--persist-key')
+
+ # set user and group
+ opts.append('--user')
+ opts.append('%s' % user)
+ opts.append('--group')
+ opts.append('%s' % group)
+
+ opts.append('--management-client-user')
+ opts.append('%s' % user)
+ opts.append('--management-signal')
+
+ # set default options for management
+ # interface. unix sockets or telnet interface for win.
+ # XXX take them from the config object.
+
+ ourplatform = platform.system()
+ if ourplatform in ("Linux", "Mac"):
+ opts.append('--management')
+ opts.append('/tmp/.eip.sock')
+ opts.append('unix')
+ if ourplatform == "Windows":
+ opts.append('--management')
+ opts.append('localhost')
+ # XXX which is a good choice?
+ opts.append('7777')
+
+ # remaining config options will go in a file
+
+ # NOTE: we will build this file from
+ # the service definition file.
+ # XXX override from --with-openvpn-config
+
+ opts.append('--config')
+
+ default_provider_path = get_default_provider_path()
+ ovpncnf = get_config_file(
+ 'openvpn.conf',
+ folder=default_provider_path)
+ opts.append(ovpncnf)
+
+ # we cannot run in daemon mode
+ # with the current subp setting.
+ # see: https://leap.se/code/issues/383
+ #if daemon is True:
+ #opts.append('--daemon')
+
+ return opts
+
+
+def build_ovpn_command(config, debug=False):
+ """
+ build a string with the
+ complete openvpn invocation
+
+ @param config: config object
+ @type config: ConfigParser instance
+
+ @rtype [string, [list of strings]]
+ @rparam: a list containing the command string
+ and a list of options.
+ """
+ command = []
+ use_pkexec = True
+ ovpn = None
+
+ if config.has_option('openvpn', 'use_pkexec'):
+ use_pkexec = config.get('openvpn', 'use_pkexec')
+ if platform.system() == "Linux" and use_pkexec:
+
+ # XXX check for both pkexec (done)
+ # AND a suitable authentication
+ # agent running.
+ logger.info('use_pkexec set to True')
+
+ if not is_pkexec_in_system():
+ logger.error('no pkexec in system')
+ raise EIPNoPkexecAvailable
+
+ if not is_auth_agent_running():
+ logger.warning(
+ "no polkit auth agent found. "
+ "pkexec will use its own text "
+ "based authentication agent. "
+ "that's probably a bad idea")
+ raise EIPNoPolkitAuthAgentAvailable
+
+ command.append('pkexec')
+
+ if config.has_option('openvpn',
+ 'openvpn_binary'):
+ ovpn = config.get('openvpn',
+ 'openvpn_binary')
+ if not ovpn and config.has_option('DEFAULT',
+ 'openvpn_binary'):
+ ovpn = config.get('DEFAULT',
+ 'openvpn_binary')
+
+ if ovpn:
+ command.append(ovpn)
+
+ daemon_mode = not debug
+
+ for opt in build_ovpn_options(daemon=daemon_mode):
+ command.append(opt)
+
+ # XXX check len and raise proper error
+
+ return [command[0], command[1:]]
+
+
+def get_sensible_defaults():
+ """
+ gathers a dict of sensible defaults,
+ platform sensitive,
+ to be used to initialize the config parser
+ @rtype: dict
+ @rparam: default options.
+ """
+
+ # this way we're passing a simple dict
+ # that will initialize the configparser
+ # and will get written to "DEFAULTS" section,
+ # which is fine for now.
+ # if we want to write to a particular section
+ # we can better pass a tuple of triples
+ # (('section1', 'foo', '23'),)
+ # and config.set them
+
+ defaults = dict()
+ defaults['openvpn_binary'] = which('openvpn')
+ defaults['autostart'] = 'true'
+
+ # TODO
+ # - management.
+ return defaults
+
+
+def get_config(config_file=None):
+ """
+ temporary method for getting configs,
+ mainly for early stage development process.
+ in the future we will get preferences
+ from the storage api
+
+ @rtype: ConfigParser instance
+ @rparam: a config object
+ """
+ # TODO
+ # - refactor out common things and get
+ # them to util/ or baseapp/
+
+ defaults = get_sensible_defaults()
+ config = ConfigParser.ConfigParser(defaults)
+
+ if not config_file:
+ fpath = get_config_file('eip.cfg')
+ if not os.path.isfile(fpath):
+ dpath, cfile = os.path.split(fpath)
+ if not os.path.isdir(dpath):
+ mkdir_p(dpath)
+ with open(fpath, 'wb') as configfile:
+ config.write(configfile)
+ config_file = open(fpath)
+
+ #TODO
+ # - convert config_file to list;
+ # look in places like /etc/leap/eip.cfg
+ # for global settings.
+ # - raise warnings/error if bad options.
+
+ # at this point, the file should exist.
+ # errors would have been raised above.
+
+ config.readfp(config_file)
+
+ return config
+
+
+def check_vpn_keys(config):
+ """
+ performs an existance and permission check
+ over the openvpn keys file.
+ Currently we're expecting a single file
+ per provider, containing the CA cert,
+ the provider key, and our client certificate
+ """
+
+ keyopt = ('provider', 'keyfile')
+
+ # XXX at some point,
+ # should separate between CA, provider cert
+ # and our certificate.
+ # make changes in the default provider template
+ # accordingly.
+
+ # get vpn keys
+ if config.has_option(*keyopt):
+ keyfile = config.get(*keyopt)
+ else:
+ keyfile = get_config_file(
+ 'openvpn.keys',
+ folder=get_default_provider_path())
+ logger.debug('keyfile = %s', keyfile)
+
+ # if no keys, raise error.
+ # should be catched by the ui and signal user.
+
+ if not os.path.isfile(keyfile):
+ logger.error('key file %s not found. aborting.',
+ keyfile)
+ raise EIPInitNoKeyFileError
+
+ # check proper permission on keys
+ # bad perms? try to fix them
+ try:
+ check_and_fix_urw_only(keyfile)
+ except OSError:
+ raise EIPInitBadKeyFilePermError
diff --git a/src/leap/eip/vpnmanager.py b/src/leap/eip/vpnmanager.py
index 78777cf..caf7ab7 100644
--- a/src/leap/eip/vpnmanager.py
+++ b/src/leap/eip/vpnmanager.py
@@ -6,6 +6,7 @@ import telnetlib
import time
logger = logging.getLogger(name=__name__)
+logger.setLevel('DEBUG')
TELNET_PORT = 23
@@ -74,7 +75,7 @@ class OpenVPNManager(object):
self.with_errors = False
def forget_errors(self):
- print('forgetting errors')
+ logger.debug('forgetting errors')
self.with_errors = False
def connect(self):
diff --git a/src/leap/utils/__init__.py b/src/leap/util/__init__.py
index e69de29..e69de29 100644
--- a/src/leap/utils/__init__.py
+++ b/src/leap/util/__init__.py
diff --git a/src/leap/utils/coroutines.py b/src/leap/util/coroutines.py
index 5e25eb6..5e25eb6 100644
--- a/src/leap/utils/coroutines.py
+++ b/src/leap/util/coroutines.py
diff --git a/src/leap/util/fileutil.py b/src/leap/util/fileutil.py
new file mode 100644
index 0000000..cc3bf34
--- /dev/null
+++ b/src/leap/util/fileutil.py
@@ -0,0 +1,111 @@
+import errno
+from itertools import chain
+import logging
+import os
+import platform
+import stat
+
+
+logger = logging.getLogger()
+
+
+def is_user_executable(fpath):
+ st = os.stat(fpath)
+ return bool(st.st_mode & stat.S_IXUSR)
+
+
+def extend_path():
+ ourplatform = platform.system()
+ if ourplatform == "Linux":
+ return "/usr/local/sbin:/usr/sbin"
+ # XXX add mac / win extended search paths?
+
+
+def which(program):
+ """
+ an implementation of which
+ that extends the path with
+ other locations, like sbin
+ (f.i., openvpn binary is likely to be there)
+ @param program: a string representing the binary we're looking for.
+ """
+ def is_exe(fpath):
+ """
+ check that path exists,
+ it's a file,
+ and is executable by the owner
+ """
+ # we would check for access,
+ # but it's likely that we're
+ # using uid 0 + polkitd
+
+ return os.path.isfile(fpath)\
+ and is_user_executable(fpath)
+
+ def ext_candidates(fpath):
+ yield fpath
+ for ext in os.environ.get("PATHEXT", "").split(os.pathsep):
+ yield fpath + ext
+
+ def iter_path(pathset):
+ """
+ returns iterator with
+ full path for a given path list
+ and the current target bin.
+ """
+ for path in pathset.split(os.pathsep):
+ exe_file = os.path.join(path, program)
+ #print 'file=%s' % exe_file
+ for candidate in ext_candidates(exe_file):
+ if is_exe(candidate):
+ yield candidate
+
+ fpath, fname = os.path.split(program)
+ if fpath:
+ if is_exe(program):
+ return program
+ else:
+ # extended iterator
+ # with extra path
+ extended_path = chain(
+ iter_path(os.environ["PATH"]),
+ iter_path(extend_path()))
+ for candidate in extended_path:
+ if candidate is not None:
+ return candidate
+
+ # sorry bro.
+ return None
+
+
+def mkdir_p(path):
+ """
+ implements mkdir -p functionality
+ """
+ try:
+ os.makedirs(path)
+ except OSError as exc:
+ if exc.errno == errno.EEXIST:
+ pass
+ else:
+ raise
+
+
+def check_and_fix_urw_only(_file):
+ """
+ test for 600 mode and try
+ to set it if anything different found
+ """
+ mode = os.stat(_file).st_mode
+ if mode != int('600', 8):
+ try:
+ logger.warning(
+ 'bad permission on %s '
+ 'attempting to set 600',
+ _file)
+ os.chmod(_file, stat.S_IRUSR | stat.S_IWUSR)
+ except OSError:
+ logger.error(
+ 'error while trying to chmod 600 %s',
+ _file)
+ raise
diff --git a/src/leap/utils/leap_argparse.py b/src/leap/util/leap_argparse.py
index 9c35513..9c35513 100644
--- a/src/leap/utils/leap_argparse.py
+++ b/src/leap/util/leap_argparse.py