[Python-modules-commits] [txwinrm] 01/03: New upstream version 1.1.28

Christopher Hoskin christopher.hoskin at gmail.com
Sun Oct 9 07:49:34 UTC 2016


This is an automated email from the git hooks/post-receive script.

grinorcole-guest pushed a commit to branch master
in repository txwinrm.

commit 6e679d0ea3d9e9250c3535d60d472ff2ff17bc19
Author: Christopher Hoskin <christopher.hoskin at gmail.com>
Date:   Sun Oct 9 08:36:41 2016 +0100

    New upstream version 1.1.28
---
 README.rst                |   7 +
 setup.py                  |   2 +-
 txwinrm/SessionManager.py | 205 +++++++++++++++++++
 txwinrm/WinRMClient.py    | 496 ++++++++++++++++++++++++++++++++++++++++++++++
 txwinrm/app.py            |   7 +-
 txwinrm/shell.py          |   5 -
 txwinrm/util.py           |  42 ++--
 txwinrm/winrm.py          |   4 +-
 txwinrm/winrs.py          |  13 +-
 9 files changed, 752 insertions(+), 29 deletions(-)

diff --git a/README.rst b/README.rst
index aecf117..8bac9f0 100644
--- a/README.rst
+++ b/README.rst
@@ -417,3 +417,10 @@ Run txwinrm/test/precommit before merging to master. This requires that you...
     easy_install flake8
     easy_install coverage
     git clone https://github.com/dgladkov/cyclic_complexity
+
+
+Changes
+-------
+
+1.1.27
+* Add support for running commands/enumerations in a single session
diff --git a/setup.py b/setup.py
old mode 100644
new mode 100755
index 1221d15..c261752
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,7 @@
 
 setup_kwargs = dict(
     name='txwinrm',
-    version='1.1.26',
+    version='1.1.28',
     description='Asynchronous Python WinRM client',
     long_description=open('README.rst').read(),
     license='See LICENSE file',
diff --git a/txwinrm/SessionManager.py b/txwinrm/SessionManager.py
new file mode 100644
index 0000000..e202dcc
--- /dev/null
+++ b/txwinrm/SessionManager.py
@@ -0,0 +1,205 @@
+##############################################################################
+#
+# Copyright (C) Zenoss, Inc. 2016, all rights reserved.
+#
+# This content is made available according to terms specified in
+# License.zenoss under the directory where your Zenoss product is installed.
+#
+##############################################################################
+
+"""txsessionmgr - Python module for a single persistent connection to a device
+for multiple clients.
+
+Useful for situations when multiple connections to a device can be handled
+with one connection through a single login, e.g. txciscoapic, txwinrm
+
+The global SESSION_MANAGER is instantiated one time and is used to manage
+all sessions
+
+Session should be subclassed and implemented to login/logout, send requests,
+and handle responses
+
+A Client should always have a key property.  This will be unique to the types
+of transactions/requests being made through a single Session
+
+"""
+
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+
+
+class Session(object):
+    """
+    Session handler for connection to a device.
+
+    deferred_init will kick off a deferred login to a device from the first
+    client that needs the connection.  Subsequent clients will use the data
+    returned from the first login.
+
+    The Session class is responsible for implementing the login/logout methods
+    """
+    def __init__(self):
+        # Used to keep track of clients using session
+        self._clients = set()
+
+        # The currently valid token.  This can be anything that the client
+        # needs to know the connection is alive
+        self._token = None
+
+        # Deferred waiting for login result.
+        self._login_d = None
+
+        # Error from last login if applicable.
+        self._login_error = None
+
+        # Deferred sending a refresh/keep-alive signal/request
+        self._refresh_d = None
+
+    @inlineCallbacks
+    def deferred_login(self, client):
+        """Return Deferred token
+
+        :param client: Client initiating a connection
+        :client: ZenPack specific client
+        :rtype: Deferred
+        :return: Returns ZenPack unique token to be used for a session.
+        """
+        self._clients.add(client)
+        if self._token:
+            returnValue(self._token)
+
+        # No one already waiting for a token. Login to get a new one.
+        if not self._login_d or self._login_d.called:
+            self._login_d = self._deferred_login(client)
+
+            try:
+                self._token = yield self._login_d
+            except Exception as e:
+                self._login_error = e
+                raise
+
+        # At least one other client is already waiting for a token, and
+        # the login to get it is already in progress. Wait for that
+        # login to finish, then return its token.
+        else:
+            yield self._login_d
+            if self._login_error:
+                raise self._login_error
+
+        returnValue(self._token)
+
+    @inlineCallbacks
+    def deferred_logout(self, client):
+        """Return Deferred None.
+
+        Calls session._deferred_logout() only if all other clients using the same
+        session have also called deferred_logout.
+
+        """
+        if len(self._clients) <= 1:
+            if self._token:
+                try:
+                    yield self._deferred_logout()
+                except Exception:
+                    pass
+
+            self._token = None
+
+        if client in self._clients:
+            self._clients.remove(client)
+        returnValue(None)
+
+    @inlineCallbacks
+    def _deferred_login(self, client):
+        '''login method
+
+        Performs the ZenPack specific login to a device.  This will only be called
+        from the first client to fire off the deferred.  All other clients will
+        use the _token returned from this method
+
+        :param client: Client initiating a connection
+        :type client: ZenPack specific client
+        :rtype: Deferred
+        :return: Returns a Deferred which is logs into the device.
+        '''
+        returnValue(None)
+
+    @inlineCallbacks
+    def _deferred_logout(self):
+        '''logout method
+
+        Performs the ZenPack specific logout from a device.  This will only be called
+        by the last client to logout of the session.
+
+        :rtype: Deferred
+        :return: Returns a Deferred which logs out of the device
+        '''
+        returnValue(None)
+
+
+class SessionManager(object):
+    '''
+    Class to manage open sessions to devices.
+    '''
+    def __init__(self):
+        # Used to keep track of sessions
+        self._sessions = {}
+
+    def get_connection(self, key):
+        '''Return the session for a given key
+        '''
+        if key is None:
+            raise Exception('Client key cannot be empty')
+        return self._sessions.get(key, None)
+
+    def remove_connection(self, key):
+        session = self.get_connection(key)
+        if session:
+            self._sessions.pop(session)
+
+    @inlineCallbacks
+    def init_connection(self, client, session_class=Session):
+        '''Initialize connection to device.
+        If a session is already started return it.
+        Else kick off deferred to initiate session
+
+        The client must contain a key for session storage
+
+        :param client: Client initiating connection
+        :client: ZenPack defined client.
+        '''
+        if not hasattr(client, 'key'):
+            raise Exception('Client must contain a key field')
+
+        session = self.get_connection(client.key)
+        if session:
+            if session._token:
+                if client not in session._clients:
+                    session._clients.add(client)
+                returnValue(session._token)
+
+        if session is None:
+            session = session_class()
+            self._sessions[client.key] = session
+
+        token = yield session.deferred_login(client)
+        returnValue(token)
+
+    @inlineCallbacks
+    def close_connection(self, client):
+        '''Kick off a session's logout
+        If there are no more clients using a session, remove it
+
+        :param client:  Client closing connection
+        :client: ZenPack defined class
+        '''
+        session = self.get_connection(client.key)
+        if not session:
+            returnValue(None)
+        yield session.deferred_logout(client)
+        if not session._clients:
+            # no more clients so we don't need to keep the session
+            self._sessions.pop(client.key)
+        returnValue(None)
+
+
+SESSION_MANAGER = SessionManager()
diff --git a/txwinrm/WinRMClient.py b/txwinrm/WinRMClient.py
new file mode 100755
index 0000000..797008a
--- /dev/null
+++ b/txwinrm/WinRMClient.py
@@ -0,0 +1,496 @@
+##############################################################################
+#
+# Copyright (C) Zenoss, Inc. 2013, all rights reserved.
+#
+# This content is made available according to terms specified in the LICENSE
+# file at the top-level directory of this package.
+#
+##############################################################################
+
+import logging
+from httplib import BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, OK
+
+from twisted.internet.defer import (
+    inlineCallbacks,
+    returnValue,
+    DeferredSemaphore,
+    succeed
+)
+from twisted.internet.error import TimeoutError
+
+try:
+    from twisted.web.client import ResponseFailed
+    ResponseFailed
+except ImportError:
+    class ResponseFailed(Exception):
+        pass
+
+from . import constants as c
+from kerberos import GSSError
+from .util import (
+    _authenticate_with_kerberos,
+    _get_agent,
+    verify_conn_info,
+    _CONTENT_TYPE,
+    _ENCRYPTED_CONTENT_TYPE,
+    Headers,
+    _get_basic_auth_header,
+    _get_request_template,
+    _StringProducer,
+    get_auth_details,
+    UnauthorizedError,
+    ForbiddenError,
+    RequestError,
+    _ErrorReader,
+    _StringProtocol,
+    ET
+)
+from .shell import (
+    _find_shell_id,
+    _build_command_line_elem,
+    _find_command_id,
+    _MAX_REQUESTS_PER_COMMAND,
+    _find_stream,
+    _find_exit_code,
+    CommandResponse,
+    _stripped_lines
+)
+from .enumerate import (
+    DEFAULT_RESOURCE_URI,
+    SaxResponseHandler,
+    _MAX_REQUESTS_PER_ENUMERATION
+)
+from .SessionManager import SESSION_MANAGER, Session
+LOG = logging.getLogger('winrm')
+
+
+class WinRMSession(Session):
+    '''
+    Session class to keep track of single winrm connection
+
+
+    '''
+    def __init__(self):
+        super(WinRMSession, self).__init__()
+
+        # twisted agent to send http/https requests
+        self._agent = _get_agent()
+
+        # our kerberos context for encryption/decryption
+        self._gssclient = None
+
+        # url for session
+        self._url = None
+
+        # headers to use for requests
+        self._headers = None
+
+        # connection info.  see util.ConnectionInfo
+        self._conn_info = None
+
+        # DeferredSemaphore so that we complete one transaction/conversation
+        # at a time.  Windows cannot handle mixed transaction types on one
+        # connection.
+        self.sem = DeferredSemaphore(1)
+
+    def is_kerberos(self):
+        return self._conn_info.auth_type == 'kerberos'
+
+    def decrypt_body(self, body):
+        return self._gssclient.decrypt_body(body)
+
+    def _set_headers(self):
+        if self._headers:
+            return self._headers
+        if self._conn_info.auth_type == 'basic':
+            self._headers = Headers(_CONTENT_TYPE)
+            self._headers.addRawHeader('Connection', self._conn_info.connectiontype)
+            self._headers.addRawHeader(
+                'Authorization', _get_basic_auth_header(self._conn_info))
+        elif self.is_kerberos():
+            self._headers = Headers(_ENCRYPTED_CONTENT_TYPE)
+            self._headers.addRawHeader('Connection', self._conn_info.connectiontype)
+        return self._headers
+
+    @inlineCallbacks
+    def _deferred_login(self, client=None):
+        if client:
+            self._conn_info = client._conn_info
+        self._url = "{c.scheme}://{c.ipaddress}:{c.port}/wsman".format(c=self._conn_info)
+        if self.is_kerberos():
+            self._gssclient = yield _authenticate_with_kerberos(self._conn_info, self._url, self._agent)
+            returnValue(self._gssclient)
+        else:
+            returnValue('basic_auth_token')
+
+    @inlineCallbacks
+    def _deferred_logout(self):
+        # close connections so we do not end up with orphans
+        # return a Deferred()
+        self.loggedout = True
+        if self._agent and hasattr(self._agent, 'closeCachedConnections'):
+            # twisted 11 has no return and is part of the Agent
+            return succeed(self._agent.closeCachedConnections())
+        elif self._agent:
+            # twisted 12 returns a Deferred
+            return self._agent._pool.closeCachedConnections()
+        else:
+            # no agent
+            return succeed(None)
+
+    @inlineCallbacks
+    def handle_response(self, request, response, client):
+        if response.code == UNAUTHORIZED or response.code == BAD_REQUEST:
+            # check to see if we need to re-authorize due to lost connection or bad request error
+            if self._gssclient is not None:
+                self._gssclient.cleanup()
+                self._gssclient = None
+                self._token = None
+                self._agent = _get_agent()
+                self._login_d = None
+                yield SESSION_MANAGER.init_connection(client, WinRMSession)
+                try:
+                    yield self._set_headers()
+                    encrypted_request = self._gssclient.encrypt_body(request)
+                    if not encrypted_request.startswith("--Encrypted Boundary"):
+                        self._headers.setRawHeaders('Content-Type', _CONTENT_TYPE['Content-Type'])
+                    body_producer = _StringProducer(encrypted_request)
+                    response = yield self._agent.request(
+                        'POST', self._url, self._headers, body_producer)
+                except Exception as e:
+                    raise e
+            if response.code == UNAUTHORIZED:
+                if self.is_kerberos():
+                    auth_header = response.headers.getRawHeaders('WWW-Authenticate')[0]
+                    auth_details = get_auth_details(auth_header)
+                    try:
+                        if auth_details:
+                            self._gssclient._step(auth_details)
+                    except GSSError as e:
+                        msg = "HTTP Unauthorized received.  "
+                        "Kerberos error code {0}: {1}.".format(e.args[1][1], e.args[1][0])
+                        raise Exception(msg)
+                raise UnauthorizedError(
+                    "HTTP Unauthorized received: Check username and password")
+        if response.code == FORBIDDEN:
+            raise ForbiddenError(
+                "Forbidden: Check WinRM port and version")
+        elif response.code != OK:
+            if self.is_kerberos():
+                reader = _ErrorReader(self._gssclient)
+            else:
+                reader = _ErrorReader()
+            response.deliverBody(reader)
+            message = yield reader.d
+            raise RequestError("HTTP status: {}. {}".format(
+                response.code, message))
+        returnValue(response)
+
+    @inlineCallbacks
+    def send_request(self, request_template_name, client, **kwargs):
+        response = yield self._send_request(
+            request_template_name, client, **kwargs)
+        proto = _StringProtocol()
+        response.deliverBody(proto)
+        body = yield proto.d
+        if self.is_kerberos():
+            xml_str = self._gssclient.decrypt_body(body)
+        else:
+            xml_str = yield body
+        if LOG.isEnabledFor(logging.DEBUG):
+            try:
+                import xml.dom.minidom
+                xml = xml.dom.minidom.parseString(xml_str)
+                LOG.debug(xml.toprettyxml())
+            except:
+                LOG.debug('Could not prettify response XML: "{0}"'.format(xml_str))
+        returnValue(ET.fromstring(xml_str))
+
+    @inlineCallbacks
+    def _send_request(self, request_template_name, client, **kwargs):
+        if self._login_d and not self._login_d.called:
+            # check for a reconnection attempt so we do not send any requests
+            # to a dead connection
+            yield self._login_d
+        LOG.debug('sending request: {0} {1}'.format(
+            request_template_name, kwargs))
+        request = _get_request_template(request_template_name).format(**kwargs)
+        self._headers = self._set_headers()
+        if self.is_kerberos():
+            encrypted_request = self._gssclient.encrypt_body(request)
+            if not encrypted_request.startswith("--Encrypted Boundary"):
+                self._headers.setRawHeaders('Content-Type', _CONTENT_TYPE['Content-Type'])
+            body_producer = _StringProducer(encrypted_request)
+        else:
+            body_producer = _StringProducer(request)
+        try:
+            response = yield self._agent.request(
+                'POST', self._url, self._headers, body_producer)
+        except Exception as e:
+            raise e
+        LOG.debug('received response {0} {1}'.format(
+            response.code, request_template_name))
+        response = yield self.handle_response(request, response, client)
+        returnValue(response)
+
+
+class WinRMClient(object):
+    def __init__(self, conn_info):
+        verify_conn_info(conn_info)
+        self.key = None
+        self._conn_info = conn_info
+        self.session_manager = SESSION_MANAGER
+        self._session = None
+
+    @inlineCallbacks
+    def init_connection(self):
+        '''Initialize a connection through the session_manager'''
+        yield self.session_manager.init_connection(self, WinRMSession)
+        self._session = self.session_manager.get_connection(self.key)
+        returnValue(None)
+
+    def is_kerberos(self):
+        return self._conn_info.auth_type == 'kerberos'
+
+    def decrypt_body(self, body):
+        return self._session.decrypt_body(body)
+
+    @inlineCallbacks
+    def _create_shell(self):
+        elem = yield self._session.send_request('create', self)
+        returnValue(_find_shell_id(elem))
+
+    @inlineCallbacks
+    def _delete_shell(self, shell_id):
+        yield self._session.send_request('delete', self, shell_id=shell_id)
+        returnValue(None)
+
+    @inlineCallbacks
+    def _signal_terminate(self, shell_id, command_id):
+        yield self._session.send_request('signal',
+                                         self,
+                                         shell_id=shell_id,
+                                         command_id=command_id,
+                                         signal_code=c.SHELL_SIGNAL_TERMINATE)
+        returnValue(None)
+
+    @inlineCallbacks
+    def _signal_ctrl_c(self, shell_id, command_id):
+        yield self._session.send_request('signal',
+                                         self,
+                                         shell_id=shell_id,
+                                         command_id=command_id,
+                                         signal_code=c.SHELL_SIGNAL_CTRL_C)
+        returnValue(None)
+
+    @inlineCallbacks
+    def _send_command(self, shell_id, command_line):
+        command_line_elem = _build_command_line_elem(command_line)
+        command_elem = yield self._session.send_request(
+            'command', self, shell_id=shell_id, command_line_elem=command_line_elem,
+            timeout=self._conn_info.timeout)
+        returnValue(command_elem)
+
+    @inlineCallbacks
+    def _send_receive(self, shell_id, command_id):
+        receive_elem = yield self._session.send_request(
+            'receive', self, shell_id=shell_id, command_id=command_id)
+        returnValue(receive_elem)
+
+    @inlineCallbacks
+    def close_connection(self):
+        yield self.session_manager.close_connection(self)
+        returnValue(None)
+
+
+class SingleCommandClient(WinRMClient):
+
+    def __init__(self, conn_info):
+        super(SingleCommandClient, self).__init__(conn_info)
+        self.key = (self._conn_info.ipaddress, 'short')
+
+    @inlineCallbacks
+    def run_command(self, command_line):
+        '''
+        Run a single command in the session's semaphore.  Windows must finish
+        a command conversation before a new command or enumeration can start
+        '''
+        yield self.init_connection()
+        try:
+            cmd_response = yield self._session.sem.run(self.run_single_command,
+                                                       command_line)
+        except Exception:
+            yield self.close_connection()
+        returnValue(cmd_response)
+
+    @inlineCallbacks
+    def run_single_command(self, command_line):
+        """
+        Run a single command line in a remote shell like the winrs application
+        on Windows. Returns a dictionary with the following
+        structure:
+            CommandResponse
+                .stdout = [<non-empty, stripped line>, ...]
+                .stderr = [<non-empty, stripped line>, ...]
+                .exit_code = <int>
+        """
+        shell_id = yield self._create_shell()
+
+        try:
+            cmd_response = yield self._run_command(shell_id, command_line)
+        except TimeoutError:
+            yield self.close_connection()
+        yield self._delete_shell(shell_id)
+        yield self.close_connection()
+        returnValue(cmd_response)
+
+    @inlineCallbacks
+    def _run_command(self, shell_id, command_line):
+        command_elem = yield self._send_command(shell_id, command_line)
+        command_id = _find_command_id(command_elem)
+        stdout_parts = []
+        stderr_parts = []
+        for i in xrange(_MAX_REQUESTS_PER_COMMAND):
+            receive_elem = yield self._send_receive(shell_id, command_id)
+            stdout_parts.extend(
+                _find_stream(receive_elem, command_id, 'stdout'))
+            stderr_parts.extend(
+                _find_stream(receive_elem, command_id, 'stderr'))
+            exit_code = _find_exit_code(receive_elem, command_id)
+            if exit_code is not None:
+                break
+        else:
+            raise Exception("Reached max requests per command.")
+        yield self._signal_terminate(shell_id, command_id)
+        stdout = _stripped_lines(stdout_parts)
+        stderr = _stripped_lines(stderr_parts)
+        returnValue(CommandResponse(stdout, stderr, exit_code))
+
+
+class LongCommandClient(WinRMClient):
+    def __init__(self, conn_info):
+        super(LongCommandClient, self).__init__(conn_info)
+        self._shell_id = None
+        self._command_id = None
+        self._exit_code = None
+
+    @inlineCallbacks
+    def start(self, command_line):
+        LOG.debug("LongRunningCommand run_command: {0}".format(command_line))
+        self.key = (self._conn_info.ipaddress, command_line)
+        yield self.init_connection()
+        self._shell_id = yield self._create_shell()
+        command_line_elem = _build_command_line_elem(command_line)
+        LOG.debug('LongRunningCommand run_command: sending command request '
+                  '(shell_id={0}, command_line_elem={1})'.format(
+                      self._shell_id, command_line_elem))
+        try:
+            command_elem = yield self._send_command(self._shell_id,
+                                                    command_line)
+        except TimeoutError:
+            yield self.close_connection()
+            raise
+        self._command_id = _find_command_id(command_elem)
+        returnValue(None)
+
+    @inlineCallbacks
+    def receive(self):
+        try:
+            receive_elem = yield self._send_receive(self._shell_id, self._command_id)
+        except TimeoutError:
+            yield self.close_connection()
+            raise
+        stdout_parts = _find_stream(receive_elem, self._command_id, 'stdout')
+        stderr_parts = _find_stream(receive_elem, self._command_id, 'stderr')
+        self._exit_code = _find_exit_code(receive_elem, self._command_id)
+        stdout = _stripped_lines(stdout_parts)
+        stderr = _stripped_lines(stderr_parts)
+        returnValue((stdout, stderr))
+
+    @inlineCallbacks
+    def stop(self, close=False):
+        yield self._signal_ctrl_c(self._shell_id, self._command_id)
+        try:
+            stdout, stderr = yield self.receive()
+        except TimeoutError:
+            pass
+        yield self._signal_terminate(self._shell_id, self._command_id)
+        yield self._delete_shell(self._shell_id)
+        if close:
+            yield self.close_connection()
+        returnValue(CommandResponse(stdout, stderr, self._exit_code))
+
+
+class EnumerateClient(WinRMClient):
+    """
+    Sends enumerate requests to a host running the WinRM service and returns
+    a list of items.
+    """
+
+    def __init__(self, conn_info):
+        super(EnumerateClient, self).__init__(conn_info)
+        self._handler = SaxResponseHandler(self)
+        self._hostname = conn_info.ipaddress
+        self.key = (conn_info.ipaddress, 'short')
+
+    @inlineCallbacks
+    def enumerate(self, wql, resource_uri=DEFAULT_RESOURCE_URI):
+        """
+        Runs a remote WQL query.
+        """
+        self.init_connection()
+        request_template_name = 'enumerate'
+        enumeration_context = None
+        items = []
+        try:
+            for i in xrange(_MAX_REQUESTS_PER_ENUMERATION):
+                LOG.info('{0} "{1}" {2}'.format(
+                    self._hostname, wql, request_template_name))
+                response = yield self._session._send_request(
+                    request_template_name,
+                    self,
+                    resource_uri=resource_uri,
+                    wql=wql,
+                    enumeration_context=enumeration_context)
+                LOG.info("{0} {1} HTTP status: {2}".format(
+                    self._hostname, wql, response.code))
+                enumeration_context, new_items = \
+                    yield self._handler.handle_response(response)
+                items.extend(new_items)
+                if not enumeration_context:
+                    break
+                request_template_name = 'pull'
+            else:
+                raise Exception("Reached max requests per enumeration.")
+        except (ResponseFailed, RequestError, Exception) as e:
+            if isinstance(e, ResponseFailed):
+                for reason in e.reasons:
+                    LOG.error('{0} {1}'.format(self._hostname, reason.value))
+            else:
+                LOG.info('{0} {1}'.format(self._hostname, e))
+            raise
+        returnValue(items)
+
+    @inlineCallbacks
+    def do_collect(self, enum_infos):
+        '''
+        Run enumerations in the session's semaphore.  Windows must finish
+        an enumeration before a new command or enumeration can start
+        '''
+        items = {}
+        yield self.init_connection()
+        self._session = self.session_manager.get_connection(self.key)
+        for enum_info in enum_infos:
+            try:
+                items[enum_info] = yield self._session.sem.run(self.enumerate,
+                                                               enum_info.wql,
+                                                               enum_info.resource_uri)
+            except (UnauthorizedError, ForbiddenError):
+                # Fail the collection for general errors.
+                raise
+            except RequestError:
+                # Store empty results for other query-specific errors.
+                continue
+
+        yield self.close_connection()
+        returnValue(items)
diff --git a/txwinrm/app.py b/txwinrm/app.py
index 1c7f426..4f92834 100644
--- a/txwinrm/app.py
+++ b/txwinrm/app.py
@@ -219,6 +219,7 @@ def _parse_args(utility):
     parser.add_argument("--keytab", "-k")
     parser.add_argument("--password", "-p")
     parser.add_argument("--ipaddress", "-s")
+    parser.add_argument("--service", "-e", help='http/https/wsman', default='http')
     utility.add_args(parser)
     args = parser.parse_args()
     if not args.config:
@@ -235,8 +236,10 @@ def _parse_args(utility):
                 password = getpass()
             connectiontype = 'Keep-Alive'
             args.conn_info = ConnectionInfo(
-                hostname, args.authentication, args.username, password, scheme,
-                port, connectiontype, args.keytab, args.dcip, ipaddress=args.ipaddress)
+                hostname, args.authentication,
+                args.username, password, scheme,
+                port, connectiontype, args.keytab,
+                args.dcip, ipaddress=args.ipaddress, service=args.service)
             try:
                 verify_conn_info(args.conn_info)
             except Exception as e:
diff --git a/txwinrm/shell.py b/txwinrm/shell.py
index 3d5e2ed..18086f5 100644
--- a/txwinrm/shell.py
+++ b/txwinrm/shell.py
@@ -181,10 +181,6 @@ class LongRunningCommand(object):
         self._command_id = None
         self._exit_code = None
 
-    def __del__(self):
-        # in case of network errors
-        yield self._sender.close_connections()
-
     @defer.inlineCallbacks
     def start(self, command_line):
         log.debug("LongRunningCommand run_command: {0}".format(command_line))
@@ -239,7 +235,6 @@ class LongRunningCommand(object):
             command_id=self._command_id,
             signal_code=c.SHELL_SIGNAL_TERMINATE)
         yield self._sender.send_request('delete', shell_id=self._shell_id)
-        yield self._sender.close_connections()
         defer.returnValue(CommandResponse(stdout, stderr, self._exit_code))
 
 
diff --git a/txwinrm/util.py b/txwinrm/util.py
index 295a727..4e337fb 100644
--- a/txwinrm/util.py
+++ b/txwinrm/util.py
@@ -21,6 +21,7 @@ from twisted.internet.protocol import Protocol
 from twisted.web.client import Agent
 from twisted.internet.ssl import ClientContextFactory
 from twisted.web.http_headers import Headers
+from twisted.internet.threads import deferToThread
 from . import constants as c
 
 from .krb5 import kinit, ccname, add_trusted_realm
@@ -237,7 +238,7 @@ class AuthGSSClient(object):
         @return:          a result code
         """
         log.debug('GSSAPI step challenge="{0}"'.format(challenge))
-        return kerberos.authGSSClientStep(self._context, challenge)
+        return deferToThread(kerberos.authGSSClientStep, self._context, challenge)
 
     @defer.inlineCallbacks
     def get_base64_client_data(self, challenge=''):
@@ -248,7 +249,7 @@ class AuthGSSClient(object):
         result_code = None
         for i in xrange(_MAX_KERBEROS_RETRIES):
             try:
-                result_code = self._step(challenge)
+                result_code = yield self._step(challenge)
                 break
             except kerberos.GSSError as e:
                 msg = e.args[1][0]
@@ -270,6 +271,7 @@ class AuthGSSClient(object):
         base64_client_data = kerberos.authGSSClientResponse(self._context)
         defer.returnValue(base64_client_data)
 
+    @defer.inlineCallbacks
     def get_username(self, challenge):
         """
         Get the user name of the principal authenticated via the now complete
@@ -278,12 +280,12 @@ class AuthGSSClient(object):
         @param challenge: a string containing the base64-encoded server data
         @return:          a string containing the user name.
         """
-        result_code = self._step(challenge)
+        result_code = yield self._step(challenge)
         if result_code != kerberos.AUTH_GSS_COMPLETE:
             raise Exception('kerberos authGSSClientStep failed ({0}). '
                             'challenge={1}'
                             .format(result_code, challenge))
-        return kerberos.authGSSClientUserName(self._context)
+        defer.returnValue(kerberos.authGSSClientUserName(self._context))
 
     def encrypt_body(self, body):
         # get original length of body. wrap will encrypt in place
@@ -309,13 +311,13 @@ class AuthGSSClient(object):
         payload = bytes(base64.b64decode(ewrap))
         # add carriage returns to body
         body = _BODY.replace('\n', '\r\n')
-        body = bytes(body.format(original_length=orig_len+pad_len, emsg=payload))
+        body = bytes(body.format(original_length=orig_len + pad_len, emsg=payload))
         return body
 
     def decrypt_body(self, body):
         try:
             b_start = body.index("Content-Type: application/octet-stream") + \
-                      len("Content-Type: application/octet-stream\r\n")
+                len("Content-Type: application/octet-stream\r\n")
         except ValueError:
             # Unencrypted data, return body
             return body
@@ -354,7 +356,7 @@ def get_auth_details(auth_header=''):
 
 @defer.inlineCallbacks
 def _authenticate_with_kerberos(conn_info, url, agent, gss_client=None):
-    service = '{0}@{1}'.format(conn_info.scheme.upper(), conn_info.hostname)
+    service = '{0}@{1}'.format(conn_info.service.upper(), conn_info.hostname)
     if gss_client is None:
         gss_client = AuthGSSClient(
             service,
@@ -372,7 +374,7 @@ def _authenticate_with_kerberos(conn_info, url, agent, gss_client=None):
     if response.code == httplib.UNAUTHORIZED:
         try:
             if auth_details:
-                gss_client._step(auth_details)
+                yield gss_client._step(auth_details)
         except kerberos.GSSError as e:
             msg = "HTTP Unauthorized received on kerberos initialization.  "\
                 "Kerberos error code {0}: {1}.".format(e.args[1][1], e.args[1][0])
@@ -394,7 +396,7 @@ def _authenticate_with_kerberos(conn_info, url, agent, gss_client=None):
         raise Exception(
             'negotiate not found in WWW-Authenticate header: {0}'
             .format(auth_header))
-    k_username = gss_client.get_username(auth_details)
+    k_username = yield gss_client.get_username(auth_details)
     log.debug('kerberos auth successful for user: {0} / {1} '
               .format(conn_info.username, k_username))
     defer.returnValue(gss_client)
@@ -414,16 +416,19 @@ class ConnectionInfo(namedtuple(
         'timeout',
         'trusted_realm',
         'trusted_kdc',
-        'ipaddress'])):
+        'ipaddress',
+        'service'])):
     def __new__(cls, hostname, auth_type, username, password, scheme, port,
-                connectiontype, keytab, dcip, timeout=60, trusted_realm='', trusted_kdc='', ipaddress=''):
+                connectiontype, keytab, dcip, timeout=60, trusted_realm='', trusted_kdc='', ipaddress='', service=''):
         if not ipaddress:
             ipaddress = hostname
+        if not service:
+            service = scheme
         return super(ConnectionInfo, cls).__new__(cls, hostname, auth_type,
                                                   username, password, scheme,
                                                   port, connectiontype, keytab,
                                                   dcip, timeout,
-                                                  trusted_realm, trusted_kdc, ipaddress)
+                                                  trusted_realm, trusted_kdc, ipaddress, service)
 
 
 def verify_hostname(conn_info):
@@ -465,6 +470,17 @@ def verify_scheme(conn_info):
             .format(scheme))
 
 
+def verify_service(conn_info):
+    has_service, service = _has_get_attr(conn_info, 'service')
+    if not has_service:
+        # if not supplied, default to scheme
+        has_service, service = _has_get_attr(conn_info, 'scheme')
+    if not has_service or service not in ['http', 'https', 'wsman']:
+        raise Exception(
+            "service must be http, https, or wsman: {0}"
+            .format(service))
+
+
 def verify_port(conn_info):
     has_port, port = _has_get_attr(conn_info, 'port')
     if not has_port or not port or not isinstance(port, int):
@@ -585,7 +601,7 @@ class RequestSender(object):
                     auth_details = get_auth_details(auth_header)
                     try:
                         if auth_details:
-                            self.gssclient._step(auth_details)
+                            yield self.gssclient._step(auth_details)
                     except kerberos.GSSError as e:
                         msg ="HTTP Unauthorized received.  "\
                         "Kerberos error code {0}: {1}.".format(e.args[1][1],e.args[1][0])
diff --git a/txwinrm/winrm.py b/txwinrm/winrm.py
index e7a4335..ca80b98 100644
--- a/txwinrm/winrm.py
+++ b/txwinrm/winrm.py
@@ -14,7 +14,7 @@ Use twisted web client to enumerate/pull WQL query.
 import sys
 from twisted.internet import defer
 from . import app
-from .enumerate import create_winrm_client
+from .WinRMClient import EnumerateClient
 
 
 class WinrmStrategy(object):
@@ -49,7 +49,7 @@ class WinrmStrategy(object):
         include_header = len(config.conn_infos) > 1
         ds = []
         for conn_info in good_conn_infos:
-            client = create_winrm_client(conn_info)
+            client = EnumerateClient(conn_info)
             for wql in config.wqls:
                 d = client.enumerate(wql)
                 d.addCallback(
diff --git a/txwinrm/winrs.py b/txwinrm/winrs.py
index d2d0bdb..18276ae 100644
--- a/txwinrm/winrs.py
+++ b/txwinrm/winrs.py
@@ -12,8 +12,8 @@ import cmd
 from pprint import pprint
 from twisted.internet import reactor, defer, task, threads
 from . import app
-from .shell import create_long_running_command, create_single_shot_command, \
-    create_remote_shell
+from .shell import create_remote_shell
+from .WinRMClient import SingleCommandClient, LongCommandClient
 
 
 def print_output(stdout, stderr):
@@ -57,7 +57,7 @@ class WinrsCmd(cmd.Cmd):
 @defer.inlineCallbacks
 def long_running_main(args):
     try:
-        client = create_long_running_command(args.conn_info)
+        client = LongCommandClient(args.conn_info)
         yield client.start(args.command)
         for i in xrange(5):
             stdout, stderr = yield task.deferLater(
... 30 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/txwinrm.git



More information about the Python-modules-commits mailing list