diff options
Diffstat (limited to 'src/leap/eip/openvpnconnection.py')
-rw-r--r-- | src/leap/eip/openvpnconnection.py | 410 |
1 files changed, 0 insertions, 410 deletions
diff --git a/src/leap/eip/openvpnconnection.py b/src/leap/eip/openvpnconnection.py deleted file mode 100644 index bee8c010..00000000 --- a/src/leap/eip/openvpnconnection.py +++ /dev/null @@ -1,410 +0,0 @@ -""" -OpenVPN Connection -""" -from __future__ import (print_function) -from functools import partial -import logging -import os -import psutil -import shutil -import select -import socket -from time import sleep - -logger = logging.getLogger(name=__name__) - -from leap.base.connection import Connection -from leap.base.constants import OPENVPN_BIN -from leap.util.coroutines import spawn_and_watch_process -from leap.util.misc import get_openvpn_pids - -from leap.eip.udstelnet import UDSTelnet -from leap.eip import config as eip_config -from leap.eip import exceptions as eip_exceptions - - -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 [] - try: - 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 [] - except socket.error as exc: - logger.debug('socket error: %s' % exc.message) - except select.error as exc: - logger.debug('select error: %s' % exc.message) - - 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. - 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, - host=None, - port="unix", - password=None, - *args, **kwargs): - """ - :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 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 - self.ovpn_verbosity = kwargs.get('ovpn_verbosity', None) - - self.watcher_cb = watcher_cb - #self.signal_maps = signal_maps - - self.subp = None - self.watcher = None - - self.server = None - self.port = None - self.proto = None - - self.command = None - self.args = None - - # XXX get autostart from config - self.autostart = True - - # management interface init - self.host = host - if isinstance(port, str) and port.isdigit(): - port = int(port) - elif port == "unix": - port = "unix" - else: - port = None - self.port = port - 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 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 connected(self): - """ - Returns True if connected - rtype: bool - """ - # XXX make a property - return hasattr(self, 'tn') - - def terminate_openvpn_connection(self, shutdown=False): - """ - terminates openvpn child subprocess - """ - if self.subp: - try: - self._stop_openvpn() - except eip_exceptions.ConnectionRefusedError: - logger.warning( - 'unable to send sigterm signal to openvpn: ' - 'connection refused.') - - # XXX kali -- - # XXX review-me - # I think this will block if child process - # does not return. - # Maybe we can .poll() for a given - # interval and exit in any case. - - RETCODE = self.subp.wait() - if RETCODE: - logger.error( - 'cannot terminate subprocess! Retcode %s' - '(We might have left openvpn running)' % RETCODE) - - if shutdown: - self._cleanup_tempfiles() - - def _cleanup_tempfiles(self): - """ - remove all temporal files - we might have left behind - """ - # if self.port is 'unix', we have - # created a temporal socket path that, under - # normal circumstances, we should be able to - # delete - - if self.port == "unix": - logger.debug('cleaning socket file temp folder') - - tempfolder = os.path.split(self.host)[0] - if os.path.isdir(tempfolder): - try: - shutil.rmtree(tempfolder) - except OSError: - logger.error('could not delete tmpfolder %s' % tempfolder) - - # checks - - def _check_if_running_instance(self): - """ - check if openvpn is already running - """ - openvpn_pids = get_openvpn_pids() - if openvpn_pids: - logger.debug('an openvpn instance is already running.') - logger.debug('attempting to stop openvpn instance.') - if not self._stop_openvpn(): - raise eip_exceptions.OpenVPNAlreadyRunning - return - else: - logger.debug('no openvpn instance found.') - - def _set_ovpn_command(self): - 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 ... - - # starting and stopping openvpn subprocess - - 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: logger.debug( - '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: linewrite_callback, self.status)) - subp, watcher = spawn_and_watch_process( - self.command, - self.args, - observers=observers) - self.subp = subp - self.watcher = watcher - - def _stop_openvpn(self): - """ - stop openvpn process - by sending SIGTERM to the management - interface - """ - # XXX method a bit too long, split - logger.debug("atempting to terminate openvpn process...") - if self.connected(): - try: - self._send_command("signal SIGTERM\n") - sleep(1) - if not self.subp: # XXX ??? - return True - except socket.error: - logger.warning('management socket died') - return - - #shutting openvpn failured - #try patching in old openvpn host and trying again - # XXX could be more than one! - process = self._get_openvpn_process() - if process: - logger.debug('process: %s' % process.name) - cmdline = process.cmdline - - manag_flag = "--management" - if isinstance(cmdline, list) and manag_flag in cmdline: - _index = cmdline.index(manag_flag) - self.host = cmdline[_index + 1] - self._send_command("signal SIGTERM\n") - - #make sure the process was terminated - process = self._get_openvpn_process() - if not process: - logger.debug("Existing OpenVPN Process Terminated") - return True - else: - logger.error("Unable to terminate existing OpenVPN Process.") - return False - - return True - - def _get_openvpn_process(self): - for process in psutil.process_iter(): - if OPENVPN_BIN in process.name: - return process - return None - - def get_log(self, lines=1): - log = self._send_command("log %s" % lines) - return log |