diff options
| -rw-r--r-- | README.txt | 21 | ||||
| -rw-r--r-- | src/leap/app.py | 6 | ||||
| -rw-r--r-- | src/leap/baseapp/config.py | 40 | ||||
| -rw-r--r-- | src/leap/baseapp/dialogs.py | 33 | ||||
| -rw-r--r-- | src/leap/baseapp/mainwindow.py | 87 | ||||
| -rw-r--r-- | src/leap/baseapp/permcheck.py | 17 | ||||
| -rw-r--r-- | src/leap/eip/conductor.py | 166 | ||||
| -rw-r--r-- | src/leap/eip/config.py | 426 | ||||
| -rw-r--r-- | src/leap/eip/vpnmanager.py | 3 | ||||
| -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.py | 111 | ||||
| -rw-r--r-- | src/leap/util/leap_argparse.py (renamed from src/leap/utils/leap_argparse.py) | 0 | 
13 files changed, 808 insertions, 102 deletions
@@ -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 e7ccfac..e7ccfac 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  | 
